├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── CNAME ├── LICENSE ├── book.toml ├── src ├── README.md ├── SUMMARY.md ├── acknowledgements.md ├── additional-resources.md ├── architecture.md ├── concurrency.md ├── faq.md ├── first-steps.md ├── layout.md ├── renderers.md ├── resources │ ├── counter-interface-annotated.svg │ ├── counter-interface.svg │ ├── diagrams.excalidraw │ ├── gui-displays-user-interacts.svg │ ├── logo.svg │ ├── the-elm-architecture.svg │ ├── the-gui-trinity-focused.svg │ ├── the-gui-trinity.svg │ └── the-runtime.svg ├── shells.md ├── styling.md ├── subscriptions.md ├── the-runtime.md ├── themes.md └── widgets.md └── theme └── favicon.svg /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | book: 8 | runs-on: ubuntu-20.04 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: hecrj/setup-rust-action@v2 14 | - name: Install `mdbook`, `mdbook-iced`, and `wasm-bindgen-cli` 15 | run: cargo install mdbook mdbook-iced wasm-bindgen-cli 16 | - name: Add `wasm32-unknown-unknown` target to Rust toolchain 17 | run: rustup target add wasm32-unknown-unknown 18 | - name: Build book 19 | run: mdbook build 20 | - name: Copy CNAME 21 | run: cp CNAME ./book/. 22 | - name: Publish book 23 | uses: peaceiris/actions-gh-pages@v3 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_dir: ./book 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches-ignore: 5 | - gh-pages 6 | pull_request: 7 | jobs: 8 | book: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Install mdBook 13 | uses: peaceiris/actions-mdbook@v1 14 | with: 15 | mdbook-version: 'latest' 16 | - name: Build book 17 | run: mdbook build 18 | - name: Test book 19 | run: mdbook test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | target 3 | .icebergs 4 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | book.iced.rs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Héctor Ramón Jiménez 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Héctor Ramón Jiménez"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "iced — A Cross-Platform GUI Library for Rust" 7 | 8 | [output.html] 9 | mathjax-support = false 10 | git-repository-url = "https://github.com/iced-rs/book/tree/master" 11 | edit-url-template = "https://github.com/iced-rs/book/edit/master/{path}" 12 | 13 | [output.html.playground] 14 | runnable = false 15 | 16 | [preprocessor.iced] 17 | tag = "0.13.0" 18 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 |
2 | The iced logo 3 |
4 | 5 | # Introduction 6 | [iced] is a cross-platform GUI library for [Rust]. It is inspired by [Elm], a delightful functional language for building web applications. 7 | 8 | As a GUI library, iced helps you build *[graphical user interfaces]* for your Rust applications. 9 | 10 | iced is strongly focused on **simplicity** and **type-safety**. As a result, iced tries to provide simple building blocks that can be put together with strong typing to reduce the chance of **runtime errors**. 11 | 12 | This book will: 13 | 14 | - Introduce you to the fundamental ideas of iced. 15 | - Teach you how to build interactive applications with iced. 16 | - Emphasize principles to scale and grow iced applications. 17 | 18 | Before proceeding, you should have some basic familiarity with Rust. If you are new to Rust or feel lost at some point, I recommend you to read [the official Rust book]. 19 | 20 | [iced]: https://iced.rs 21 | [Rust]: https://rust-lang.org 22 | [Elm]: https://elm-lang.org 23 | [graphical user interfaces]: https://en.wikipedia.org/wiki/Graphical_user_interface 24 | [the official Rust book]: https://doc.rust-lang.org/book/ 25 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | [Introduction](README.md) 3 | 4 | # Learning the Basics 5 | - [Architecture](architecture.md) 6 | - [First Steps](first-steps.md) 7 | - [The Runtime](the-runtime.md) 8 | 9 | - [More to come!]() 10 | 11 | 28 | 29 | # Appendix 30 | - [Additional Resources](additional-resources.md) 31 | - [Frequently Asked Questions](faq.md) 32 | -------------------------------------------------------------------------------- /src/acknowledgements.md: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | -------------------------------------------------------------------------------- /src/additional-resources.md: -------------------------------------------------------------------------------- 1 | # Additional Resources 2 | Here are some further resources you can use to learn more about iced while I am still working on this book: 3 | 4 | > Keep in mind that some of these resources may be using an older version of iced. However, while the specifics 5 | > of the APIs used may change, the fundamental ideas of iced tend to be quite stable. 6 | 7 | - A [step-by-step video guide to building a simple text editor](https://www.youtube.com/watch?v=gcBJ7cPSALo) 8 | - The [official examples](https://github.com/iced-rs/iced/tree/master/examples) 9 | - The [API Reference](https://docs.iced.rs/iced/) 10 | - The [official list of awesome iced projects](https://github.com/iced-rs/awesome-iced) 11 | - The [unofficial guides](https://github.com/iced-rs/awesome-iced#Resources) 12 | 13 | We also have a very welcoming and active community! Feel free to ask any questions in [our Discord server] or [our Discourse forum]. 14 | 15 | [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) 16 | [![Discourse](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscourse.iced.rs%2Fsite%2Fstatistics.json&query=%24.users_count&suffix=%20users&label=discourse&color=5e7ce2)](https://discourse.iced.rs/) 17 | 18 | [our Discord server]: https://discord.gg/3xZJ65GAhd 19 | [our Discourse forum]: https://discourse.iced.rs/ 20 | -------------------------------------------------------------------------------- /src/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | Let's start from the basics! You are probably very familiar with graphical user interfaces already. 3 | You can find them on your phone, computer, and most interactive electronic devices. In fact, you are 4 | most likely reading this book using one! 5 | 6 | At their essence, graphical user interfaces are applications that __display__ some information graphically 7 | to a user. This user can then choose to __interact__ with the application—normally using some kind of device; 8 | like a keyboard, mouse, or touchscreen. 9 | 10 |
11 | Interface displays, user interacts 12 |
13 | 14 | The user interactions may cause the application to update and display new information as a result, which in turn 15 | may cause further user interactions, which in turn cause further updates... And so on. This quick feedback loop 16 | is what causes the feeling of _interactivity_. 17 | 18 | > Note: In this book, we will refer to graphical user interfaces as __GUIs__, __UIs__, __user interfaces__, or simply 19 | __interfaces__. Technically, not all interfaces are graphical nor user-oriented; but, given the context of this 20 | book, we will use all of these terms interchangeably. 21 | 22 | ## Dissecting an Interface 23 | Since we are interested in creating user interfaces, let's take a closer look at them. We will start with a very 24 | simple one: the classical counter interface. What is it made of? 25 | 26 |
27 | A classical counter interface 28 |
29 | 30 | As we can clearly see, this interface has three visibly distinct elements: two buttons with a number in between. 31 | We refer to these visibly distinct elements of a user interface as __widgets__ or __elements__. 32 | 33 | Some __widgets__ may be interactive, like a button. In the counter interface, the buttons can be used to trigger 34 | certain __interactions__. Specifically, the button at the top can be used to increment the counter value, while the 35 | button at the bottom can be used to decrement it. 36 | 37 | We can also say that user interfaces are _stateful_—there is some __state__ that persists between interactions. 38 | The counter interface displays a number representing the counter value. The number displayed will change depending on 39 | the amount of times we press the buttons. Pressing the increment button once will result in a different displayed value 40 | compared to pressing it twice. 41 | 42 |
43 | A dissected counter interface 44 |
45 | 46 | ## The GUI Trinity 47 | Our quick dissection has successfully identified three foundational ideas in a user interface: 48 | 49 | - __Widgets__ — the distinct visual elements of an interface. 50 | - __Interactions__ — the actions that may be triggered by some widgets. 51 | - __State__ — the underlying condition or information of an interface. 52 | 53 | These ideas are connected to each other, forming another feedback loop! 54 | 55 | __Widgets__ produce __interactions__ when a user interacts with them. These __interactions__ then change the __state__ 56 | of the interface. The changed __state__ propagates and dictates the new __widgets__ that must be displayed. These new 57 | __widgets__ may then produce new __interactions__, which can change the __state__ again... And so on. 58 | 59 |
60 | The GUI trinity 61 |
62 | 63 | These ideas and their connections make up the fundamental architecture of a user interface. Therefore, creating a user 64 | interface must inevitably consist in defining these __widgets__, __interactions__, and __state__; as well as the connections 65 | between them. 66 | 67 | ## Different Ideas, Different Nature 68 | The three foundational ideas of an interface differ quite a bit when it comes to reusability. 69 | 70 | The state and the interactions of an interface are very specific to the application and its purpose. If I tell you that 71 | I have an interface with a numeric value and increment and decrement interactions, you will very easily 72 | guess I am talking about a counter interface. 73 | 74 | However, if I tell you I have an interface with two buttons and a number... It's quite trickier for you to guess the kind 75 | of interface I am talking about. It could be anything! 76 | 77 | This is because widgets are generally very generic and, therefore, more reusable. Most interfaces display a combination of 78 | familiar widgets—like buttons and numbers. In fact, users expect familiar widgets to always behave a certain way. If they 79 | don't behave properly, the interface will be unintuitive and have poor [user experience]. 80 | 81 | While widgets are generally very reusable; the specific widget configuration dictated by the application state and its 82 | interactions is very application-specific. A button is generic; but a button that has a "+" label and causes a value 83 | increment when pressed is very specific. 84 | 85 | All of this means that, when we are creating a specific user interface, we don't want to focus on implementing every 86 | familiar widget and its behavior. Instead, we want to leverage widgets as reusable building blocks—independent of our 87 | application and provided by some library—while placing our focus on the application-specific parts of the fundamental 88 | architecture: state, interactions, how the interactions change the state, and how the state dictates the widgets. 89 | 90 |
91 | The application-specific parts of a GUI 92 |
93 | 94 | [user experience]: https://en.wikipedia.org/wiki/User_experience 95 | 96 | ## The Elm Architecture 97 | It turns out that the four application-specific parts of the architecture of an interface are also the four foundational 98 | ideas of [The Elm Architecture]. 99 | 100 | > The Elm Architecture is a pattern for architecting interactive programs that emerges naturally in [Elm], a delightful 101 | > purely functional programming language for reliable web applications. 102 | > 103 | > Patterns and ideas that emerge in purely functional programming languages tend to work very well in Rust 104 | > because they leverage immutability and [referential transparency]—both very desirable properties that not only 105 | > make code easy to reason about, but also play nicely with the borrow checker. 106 | > 107 | > Furthermore, The Elm Architecture not only emerges naturally in Elm, but also when simply dissecting user 108 | > interfaces and formalizing their inner workings; like we just did in this chapter. 109 | 110 | The Elm Architecture uses a different—if not more precise—nomenclature for its fundamental parts: 111 | 112 | - __Model__ — the state of the application. 113 | - __Messages__ — the interactions of the application. 114 | - __Update logic__ — how the messages change the state. 115 | - __View logic__ — how the state dictates the widgets. 116 | 117 | These are different names, but they point to the same exact fundamental ideas we have already discovered and, 118 | therefore, can be used interchangeably. 119 | 120 |
121 | The Elm Architecture 122 |
123 | 124 | 125 | > Note: In iced, the names __state__ and __messages__ are used more often than __model__ and 126 | __interactions__, respectively. 127 | 128 | 129 | [The Elm Architecture]: https://guide.elm-lang.org/architecture/ 130 | [Elm]: https://elm-lang.org/ 131 | [referential transparency]: https://en.wikipedia.org/wiki/Referential_transparency -------------------------------------------------------------------------------- /src/concurrency.md: -------------------------------------------------------------------------------- 1 | # Concurrency 2 | -------------------------------------------------------------------------------- /src/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## When Will the Book Be Finished? 4 | Soon™. Open source is a gift; so whenever I feel like it. 5 | 6 | ## How Do I Scale a Large Application? 7 | You split your application into multiple screens, and then use simple composition. 8 | 9 | [The Pocket Guide] has [a specific section that showcases this approach](https://docs.rs/iced/0.13.1/iced/#scaling-applications). 10 | 11 | ## How Can My Application Receive Updates From a Channel? 12 | You can use [`Task::run`] to generate messages from an asynchronous [`Stream`]. 13 | 14 | Alternatively, if you control the creation of the channel; you can use [`Subscription::run`]. 15 | 16 | [The Pocket Guide]: https://docs.rs/iced/0.13.1/iced/index.html#the-pocket-guide 17 | [`Task::run`]: https://docs.rs/iced/0.13.1/iced/task/struct.Task.html#method.run 18 | [`Subscription::run`]: https://docs.rs/iced/0.13.1/iced/struct.Subscription.html#method.run 19 | [`Stream`]: https://docs.rs/futures/latest/futures/stream/trait.Stream.html 20 | 21 | ## Does Iced Support Right-To-Left Text and/or CJK scripts? 22 | Not very well yet! 23 | 24 | You may be able to render some scripts using [`Text::shaping`] with [`Shaping::Advanced`], 25 | but text editing for these scripts is not yet supported; and neither are [Input Method Editors]. 26 | 27 | These features are in the [`ROADMAP`], however! 28 | 29 | [`Text::shaping`]: https://docs.rs/iced/0.13.1/iced/widget/text/type.Text.html#method.shaping 30 | [`Shaping::Advanced`]: https://docs.rs/iced/0.13.1/iced/widget/text/enum.Shaping.html#variant.Advanced 31 | [Input Method Editors]: https://en.wikipedia.org/wiki/Input_method 32 | [`ROADMAP`]: https://whimsical.com/roadmap-iced-7vhq6R35Lp3TmYH4WeYwLM 33 | 34 | ## When Are the `view` and `subscription` Functions Called? 35 | After every batch of messages and `update` calls. But this is an implementation detail; 36 | and should never rely on this. 37 | 38 | Try to treat these functions as declarative, stateless functions. 39 | 40 | ## Does Iced Redraw All the Time?! 41 | Yes! iced currently redraws after every runtime event; including tiny mouse movements. 42 | 43 | There are plans to redraw less frequently by detecting widget state changes, but performance has not 44 | been a priority so far. 45 | 46 | The renderers do perform quite a lot of caching; so redrawing is quite cheap. As a result, 47 | this is rarely an issue for most use cases! 48 | 49 | ## I Am Getting A Panic Saying There Is No Reactor Running. What Is Going On? 50 | You are probably using `Task` to execute a `Future` that needs the `tokio` executor: 51 | 52 | ```text 53 | there is no reactor running, must be called from the context of a Tokio 1.x runtime 54 | ``` 55 | 56 | You should be able to fix this issue by enabling [the `tokio` feature flag] in the `iced` crate: 57 | 58 | ```toml 59 | iced = { version = "0.13", features = ["tokio"] } 60 | ``` 61 | 62 | [the `tokio` feature flag]: https://docs.rs/crate/iced/latest/features#tokio 63 | -------------------------------------------------------------------------------- /src/first-steps.md: -------------------------------------------------------------------------------- 1 | # First Steps 2 | But enough with the theory. It's about time we start writing some code! 3 | 4 | iced embraces The Elm Architecture as the most natural approach for architecting interactive applications. 5 | Therefore, when using iced, we will be dealing with the four main ideas we introduced in the previous chapter: 6 | __state__, __messages__, __update logic__, and __view logic__. 7 | 8 | In the previous chapter, we dissected and studied the classical counter interface. Let's try to 9 | build it in Rust while leveraging The Elm Architecture. 10 | 11 |
12 | A classical counter interface 13 |
14 | 15 | ## State 16 | Let's start with the __state__—the underlying data of the application. 17 | 18 | In Rust, given the ownership and borrowing rules, it is extremely important to think carefully about the data model 19 | of your application. 20 | 21 | > I encourage you to always start by pondering about the data of your application and 22 | its different states—not only those that are possible, but also those that must be impossible. Then try to leverage 23 | the type system as much as you can to _[Make Impossible States Impossible]_. 24 | 25 | For our counter interface, all we need is a counter value. Since we have both increment and decrement interactions, 26 | the number could potentially be negative. This means we need a signed integer. 27 | 28 | Also, we know some users are crazy and they may want to count a lot of things. Let's give them 64 bits to play with: 29 | 30 | ```rust 31 | struct Counter { 32 | value: i64, 33 | } 34 | ``` 35 | 36 | If a crazy user counted 1000 things every second, it would take them ~300 million years to run out of numbers. 37 | Let's hope that's enough. 38 | 39 | [Make Impossible States Impossible]: https://www.youtube.com/watch?v=IcgmSRJHu_8 40 | 41 | ## Messages 42 | Next, we need to define our __messages__—the interactions of the application. 43 | 44 | Our counter interface has two interactions: __increment__ and __decrement__. Technically, we could use a simple boolean to 45 | encode these interactions: `true` for increment and `false` for decrement, for instance. 46 | 47 | But... we can do better in Rust! Interactions are mutually exclusive—when we have an interaction, what we really have is one 48 | value of a possible set of values. It turns out that Rust has the perfect data type for modeling this kind of idea: the _enum_. 49 | 50 | Thus, we can define our messages like this: 51 | 52 | ```rust 53 | enum Message { 54 | Increment, 55 | Decrement, 56 | } 57 | ``` 58 | 59 | Simple enough! This also sets us up for the long-term. If we ever wanted to add additional interactions to our application—like a 60 | `Reset` interaction, for instance—we could just introduce additional variants to this type. Enums are very powerful and convenient. 61 | 62 | ## Update Logic 63 | Now, it's time for our __update logic__—how messages change the state of the application. 64 | 65 | Basically, we need to write some logic that given any message can update any state of the application accordingly. The simplest 66 | and most idiomatic way to express this logic in Rust is by defining a method named `update` in our application state. 67 | 68 | For our counter interface, we only need to properly increment or decrement the `value` of our `Counter` struct based on the `Message` 69 | we just defined: 70 | 71 | ```rust,ignore 72 | impl Counter { 73 | fn update(&mut self, message: Message) { 74 | match message { 75 | Message::Increment => { 76 | self.value += 1; 77 | } 78 | Message::Decrement => { 79 | self.value -= 1; 80 | } 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | Great! Now we are ready to process user interactions. For instance, imagine we initialized our counter like this: 87 | 88 | ```rust,ignore 89 | let mut counter = Counter { value: 0 }; 90 | ``` 91 | 92 | And let's say we wanted to simulate a user playing with our interface for a bit—pressing the increment button twice 93 | and then the decrement button once. We could easily compute the final state of our counter with our __update logic__: 94 | 95 | ```rust,ignore 96 | counter.update(Message::Increment); 97 | counter.update(Message::Increment); 98 | counter.update(Message::Decrement); 99 | ``` 100 | 101 | This would cause our `Counter` to end up with a `value` of `1`: 102 | 103 | ```rust,ignore 104 | assert_eq!(counter.value, 1); 105 | ``` 106 | 107 | In fact, we have just written a simple test for our application logic: 108 | 109 | ```rust,ignore 110 | #[test] 111 | fn it_counts_properly() { 112 | let mut counter = Counter { value: 0 }; 113 | 114 | counter.update(Message::Increment); 115 | counter.update(Message::Increment); 116 | counter.update(Message::Decrement); 117 | 118 | assert_eq!(counter.value, 1); 119 | } 120 | ``` 121 | 122 | Notice how easy this was to write! So far, we are just leveraging very simple Rust concepts. No dependencies in sight! 123 | You may even be wondering... "Where is the GUI code?!" 124 | 125 | This is one of the main advantages of The Elm Architecture. As we discovered in the previous chapter, widgets are the 126 | only fundamental idea of an interface that is reusable in nature. All the parts we have defined so far are application-specific 127 | and, therefore, do not need to know about the UI library at all! 128 | 129 | The Elm Architecture properly embraces the different nature of each part of a user interface—decoupling __state__, 130 | __messages__, and __update logic__ from __widgets__ and __view logic__. 131 | 132 | ## View Logic 133 | Finally, the only part left for us to define is our __view logic__—how state dictates the widgets of the application. 134 | 135 | Here is where the magic happens! In view logic, we bring together the state of the application and its possible interactions 136 | to produce a visual representation of the user interface that must be displayed to the user. 137 | 138 |
139 | A classical counter interface 140 |
141 | 142 | As we have already learned, this visual representation is made of widgets—the visibly distinct units of an interface. Most 143 | widgets are not application-specific and they can be abstracted and packaged into reusable libraries. These libraries are 144 | normally called _widget toolkits_, _GUI frameworks_, or simply _GUI libraries_. 145 | 146 | And this is where __iced__ comes in—finally! iced is a cross-platform GUI library for Rust. It packages a fair collection of 147 | ready-to-use widgets; buttons and numbers included. Exactly what we need for our counter. 148 | 149 | ### The Buttons 150 | Our counter interface has two __buttons__. Let's see how we can define them using iced. 151 | 152 | In iced, widgets are independent values. The same way you can have an integer in a variable, you can have a widget as well. 153 | These values are normally created using a _helper function_ from the `widget` module. 154 | 155 | For our buttons, we can use the `button` helper: 156 | 157 | ```rust,ignore 158 | use iced::widget::button; 159 | 160 | let increment = button("+"); 161 | let decrement = button("-"); 162 | ``` 163 | 164 | That's quite simple, isn't it? For now, we have just defined a couple of variables for our buttons. 165 | 166 | As we can see, widget helpers may take arguments for configuring parts of the widgets to our liking. 167 | In this case, the `button` function takes a single argument used to describe the contents of the button. 168 | 169 | 170 | ### The Number 171 | We have our buttons sitting nicely in our `increment` and `decrement` variables. How about we do the same 172 | for our counter value? 173 | 174 | While iced does not really have a `number` widget, it does have a more generic `text` widget that can be used 175 | to display any kind of text—numbers included: 176 | 177 | ```rust,ignore 178 | use iced::widget::text; 179 | 180 | let counter = text(15); 181 | ``` 182 | 183 | Sweet! Like `button`, `text` also takes an argument used to describe its contents. Since we are just getting started, let's 184 | simply hardcode `15` for now. 185 | 186 | ### The Layout 187 | Alright! We have our two buttons in `increment` and `decrement`, and our counter value in `counter`. That should be everything, right? 188 | 189 | Not so fast! The widgets in our counter interface are displayed in a specific __order__. Given our three widgets, there is a total of 190 | __six__ different ways to order them. However, the order we want is: `increment`, `counter`, and `decrement`. 191 | 192 | A very simple way of describing this order is to create a list with our widgets: 193 | 194 | ```rust,ignore 195 | let interface = vec![increment, counter, decrement]; 196 | ``` 197 | 198 | But we are still missing something! It's not only the order that is specific, our interface also has a specific visual __layout__. 199 | 200 | The widgets are positioned on top of each other, but they could very well be positioned from left to right instead. There is nothing 201 | in our description so far that talks about the __layout__ of our widgets. 202 | 203 | In iced, layout is described using... well, more widgets! That's right. Not all widgets produce visual results directly; some may simply 204 | manage the position of existing widgets. And since widgets are just values, they can be nested and composed nicely. 205 | 206 | The kind of vertical layout that we need for our counter can be achieved with the `column` widget: 207 | 208 | ```rust,ignore 209 | use iced::widget::column; 210 | 211 | let interface = column![increment, counter, decrement]; 212 | ``` 213 | 214 | This is very similar to our previous snippet. iced provides a `column!` macro for creating a `column` out of some widgets in a particular 215 | __order__—analogous to `vec!`. 216 | 217 | ### The Interactions 218 | At this point, we have in our `interface` variable a `column` representing our counter interface. But if we actually tried to run it, 219 | we would quickly find out that something is wrong. 220 | 221 | Our buttons would be completely disabled. Of course! We have not defined any __interactions__ for them. Notice that we have yet 222 | to use our `Message` enum in our view logic. How is our user interface supposed to produce __messages__ if we don't specify 223 | them? Let's do that now. 224 | 225 | In iced, every widget has a specific type that enables further configuration using simple builder methods. The `button` 226 | helper returns an instance of [the `Button` type], which has an `on_press` method we can use to define the message it must 227 | __produce__ when a user presses the button: 228 | 229 | ```rust,ignore 230 | use iced::widget::button; 231 | 232 | let increment = button("+").on_press(Message::Increment); 233 | let decrement = button("-").on_press(Message::Decrement); 234 | ``` 235 | 236 | Awesome! Our interactions are wired up. But there is still a small detail left. A button can be pressed multiple times. Therefore, 237 | the same button may need to produce multiple instances of the same `Message`. As a result, we need our `Message` type to be cloneable. 238 | 239 | We can easily _derive_ the `Clone` trait—as well as `Debug` and `Copy` for good measure: 240 | 241 | ```rust 242 | #[derive(Debug, Clone, Copy)] 243 | enum Message { 244 | Increment, 245 | Decrement, 246 | } 247 | ``` 248 | 249 | In The Elm Architecture, messages represent __events__ that have occurred—made of pure data. As a consequence, it should always be easy 250 | to derive `Debug` and `Clone` for our `Message` type. 251 | 252 | 253 | [the `Button` type]: https://docs.rs/iced/0.12.1/iced/widget/struct.Button.html 254 | 255 | ### The View 256 | We are almost there! There is only one thing left to do: connecting our application __state__ to the view logic. 257 | 258 | Let's bring together all the view logic we have written so far: 259 | 260 | ```rust,ignore 261 | use iced::widget::{button, column, text}; 262 | 263 | // The buttons 264 | let increment = button("+").on_press(Message::Increment); 265 | let decrement = button("-").on_press(Message::Decrement); 266 | 267 | // The number 268 | let counter = text(15); 269 | 270 | // The layout 271 | let interface = column![increment, counter, decrement]; 272 | ``` 273 | 274 | If we ran this view logic, we would now be able to press the buttons. However, nothing would happen as a result. The 275 | counter would be stuck—always showing the number `15`. Our interface is completely stateless! 276 | 277 | Obviously, the issue here is that our `counter` variable contains a text widget with a hardcoded `15`. Instead, what 278 | we want is to actually display the `value` field of our `Counter` state. This way, when a button is pressed and 279 | our update logic is triggered, the text widget will display the new `value`. 280 | 281 | We can easily do this by running our view logic in a method of our `Counter`—just like we did with our update logic: 282 | 283 | ```rust,ignore 284 | use iced::widget::{button, column, text}; 285 | 286 | impl Counter { 287 | fn view(&self) { 288 | // The buttons 289 | let increment = button("+").on_press(Message::Increment); 290 | let decrement = button("-").on_press(Message::Decrement); 291 | 292 | // The number 293 | let counter = text(self.value); 294 | 295 | // The layout 296 | let interface = column![increment, counter, decrement]; 297 | } 298 | } 299 | ``` 300 | 301 | Our `counter` variable now will always have a `text` widget with the current `value` of our `Counter`. Great! 302 | 303 | However, and as you may have noticed, this `view` method is completely useless—it constructs an 304 | `interface`, but then... It does nothing with it and throws it away! 305 | 306 | > In iced, constructing and configuring widgets has no side effects. There is no "global context" you need to 307 | worry about in your view code. 308 | 309 | Instead of throwing the `interface` away, we need to return it. Remember, the purpose of our __view logic__ is 310 | to dictate the widgets of our user interface; and the content of the `interface` variable is precisely the 311 | description of the interface we want: 312 | 313 | ```rust,ignore 314 | use iced::widget::{button, column, text, Column}; 315 | 316 | impl Counter { 317 | fn view(&self) -> Column { 318 | // The buttons 319 | let increment = button("+").on_press(Message::Increment); 320 | let decrement = button("-").on_press(Message::Decrement); 321 | 322 | // The number 323 | let counter = text(self.value); 324 | 325 | // The layout 326 | let interface = column![increment, counter, decrement]; 327 | 328 | interface 329 | } 330 | } 331 | ``` 332 | 333 | Tada! Notice how the `view` method needs a return type now. The returned type is `Column` because the `column!` macro produces 334 | a widget of this type—just like `button` produces a widget of the `Button` type. 335 | 336 | You may also have noticed that this `Column` type has a generic type parameter. This type parameter simply specifies the type 337 | of messages the widget may produce. In this case, it takes our `Message` because the `increment` and `decrement` buttons inside 338 | the column produce messages of this type. 339 | 340 | > iced has a strong focus on type safety—leveraging the type system and compile-time guarantees to minimize runtime errors 341 | as much as possible. 342 | 343 | And well... That's it! Our view logic is done! But wait... It's a bit verbose right now. Since it's such a simple interface, 344 | let's just inline everything: 345 | 346 |
347 | A classical counter interface 348 |
349 | 350 | ```rust,ignore 351 | use iced::widget::{button, column, text, Column}; 352 | 353 | impl Counter { 354 | fn view(&self) -> Column { 355 | column![ 356 | button("+").on_press(Message::Increment), 357 | text(self.value), 358 | button("-").on_press(Message::Decrement), 359 | ] 360 | } 361 | } 362 | ``` 363 | 364 | That's much more concise. It even resembles the actual interface! Since creating widgets just yields values with no 365 | side effects; we can move things around in our view logic without worrying about breaking other stuff. No spooky 366 | action at a distance! 367 | 368 | And that's all there is to our counter interface. I am sure you can't wait to __run__ it. Shall we? 369 | -------------------------------------------------------------------------------- /src/layout.md: -------------------------------------------------------------------------------- 1 | # Layout 2 | -------------------------------------------------------------------------------- /src/renderers.md: -------------------------------------------------------------------------------- 1 | # Renderers 2 | -------------------------------------------------------------------------------- /src/resources/counter-interface-annotated.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | +-15buttonwidgetbuttonwidgetnumberwidgetincrementinteractiondecrementinteractioncounterstate -------------------------------------------------------------------------------- /src/resources/counter-interface.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | +-15 -------------------------------------------------------------------------------- /src/resources/gui-displays-user-interacts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | displaysinteractsuserinterface+-15 -------------------------------------------------------------------------------- /src/resources/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/resources/the-elm-architecture.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | messageswidgetsstateproducechangeupdate logicdictatesview logic -------------------------------------------------------------------------------- /src/resources/the-gui-trinity-focused.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | interactionswidgetsstateproducechangedictates -------------------------------------------------------------------------------- /src/resources/the-gui-trinity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | interactionswidgetsstateproducechangedictates -------------------------------------------------------------------------------- /src/resources/the-runtime.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | messageswidgetsstateproducechangeupdate logicdictatesview logic -------------------------------------------------------------------------------- /src/shells.md: -------------------------------------------------------------------------------- 1 | # Shells 2 | -------------------------------------------------------------------------------- /src/styling.md: -------------------------------------------------------------------------------- 1 | # Styling 2 | -------------------------------------------------------------------------------- /src/subscriptions.md: -------------------------------------------------------------------------------- 1 | # Subscriptions 2 | -------------------------------------------------------------------------------- /src/the-runtime.md: -------------------------------------------------------------------------------- 1 | # The Runtime 2 | In the previous chapter we built the classical counter interface using iced and The Elm Architecture. We focused on each 3 | fundamental part—one at a time: __state__, __messages__, __update logic__, and __view logic__. 4 | 5 | But now what? Yes, we have all the fundamental parts of a user interface—as we learned during 6 | [our dissection](architecture.md)—but it is unclear how we are supposed to bring it to life. 7 | 8 | It seems we are missing _something_ that can put all the parts together and _run_ them in unison. _Something_ that 9 | creates and runs the fundamental loop of a user interface—displaying widgets to a user and reacting to any interactions. 10 | 11 | This _something_ is called the __runtime__. You can think of it as the environment where the feedback loop of a user 12 | interface takes place. The runtime is in charge of every part of the loop: initializing the __state__, 13 | producing __messages__, executing the __update logic__, and running our __view logic__. 14 | 15 |
16 | The Runtime 17 |
18 | 19 | > Another way to picture the runtime is by imagining a huge engine with four fundamental parts missing. Our job is 20 | > to fill in these parts—and then the engine can run! 21 | 22 | ## A Magical Runtime 23 | Let's try to get a better understanding of the lifetime of an interface by exploring the internals of a basic (although very magical!) runtime. 24 | 25 | In fact, we have actually started writing a runtime already! When [we implemented the update logic of our counter](first-steps.md#update-logic), 26 | we wrote a very small test that simulated a user: 27 | 28 | ```rust,ignore 29 | #[test] 30 | fn it_counts_properly() { 31 | let mut counter = Counter { value: 0 }; 32 | 33 | counter.update(Message::Increment); 34 | counter.update(Message::Increment); 35 | counter.update(Message::Decrement); 36 | 37 | assert_eq!(counter.value, 1); 38 | } 39 | ``` 40 | 41 | This is technically a very bare-bones runtime. It initializes the __state__, produces some __interactions__, 42 | and executes the __update logic__. 43 | 44 | Of course, the interactions are made up, it is very short-lived, and there is no __view logic__ 45 | involved—far from what we actually want. Still, it's a great start! Let's try to extend it, step by step. 46 | 47 | ### Initializing the State 48 | Our small runtime is already initializing the application state properly: 49 | 50 | ```rust,ignore 51 | // Initialize the state 52 | let mut counter = Counter { value: 0 }; 53 | ``` 54 | 55 | However, we can avoid hardcoding the initial state by leveraging the `Default` trait. Let's just derive it: 56 | 57 | ```rust 58 | #[derive(Default)] 59 | struct Counter { 60 | value: i64 61 | } 62 | ``` 63 | 64 | And then, we simply use `Counter::default` in our runtime: 65 | 66 | ```rust,ignore 67 | // Initialize the state 68 | let mut counter = Counter::default(); 69 | ``` 70 | 71 | The difference may be subtle, but we are separating concerns—we keep the initial state of our application close to 72 | the state definition and separated from the runtime. This way, we may eventually be able to make our runtime work with 73 | _any_ application! 74 | 75 | ### Displaying the Interface 76 | Alright! We have our __state__ initialized. What's next? Well, before a user can __interact__ with our interface, we 77 | need to __display__ it to them. 78 | 79 | That's easy! We just need to open a window in whatever OS the user is running, initialize a proper graphics backend, 80 | and then render the widgets returned by our __view logic__—properly laid out, of course! 81 | 82 | What? You have no clue of how to do that? Don't worry, I have this magical function: `display`. It takes a reference to 83 | any interface and displays it to the user. It totally works! 84 | 85 | ```rust,ignore 86 | use magic::display; 87 | 88 | # // Initialize the state 89 | # let mut counter = Counter::default(); 90 | # 91 | // Run our view logic to obtain our interface 92 | let interface = counter.view(); 93 | 94 | // Display the interface to the user 95 | display(&interface); 96 | ``` 97 | 98 | See? Easy! Jokes aside, the purpose of this chapter is not for us to learn graphics programming; but for us 99 | to get a better understanding of how a runtime works. A little bit of magic doesn't hurt! 100 | 101 | ### Gathering the Interactions 102 | The user is seeing our interface and is now interacting with it. We need to pay very good attention to all 103 | the interactions and produce all the relevant __messages__ that our widgets specify. 104 | 105 | How? With some more magic, of course! I just found this `interact` function inside of my top hat—it takes an 106 | interface and produces the __messages__ that correspond to the latest interactions of the user. 107 | 108 | ```rust,ignore 109 | use magic::{display, interact}; 110 | 111 | # // Initialize the state 112 | # let mut counter = Counter::default(); 113 | # 114 | # // Run our view logic to obtain our interface 115 | # let interface = counter.view(); 116 | # 117 | # // Display the interface to the user 118 | # display(&interface); 119 | # 120 | // Process the user interactions and obtain our messages 121 | let messages = interact(&interface); 122 | ``` 123 | 124 | Great! `interact` returns a list of __messages__ for us—ready to be iterated. 125 | 126 | 127 | ### Reacting to the Interactions 128 | At this point, we have gathered the user interactions and we have turned them into a bunch of __messages__. In order to 129 | react properly to the user, we need to update our __state__ accordingly for each message. 130 | 131 | Luckily, there are no more magic tricks involved in this step—we can just use our __update logic__: 132 | 133 | ```rust,ignore 134 | # use magic::{display, interact}; 135 | # 136 | # // Initialize the state 137 | # let mut counter = Counter::default(); 138 | # 139 | # // Run our view logic to obtain our interface 140 | # let interface = counter.view(); 141 | # 142 | # // Display the interface to the user 143 | # display(&interface); 144 | # 145 | # // Process the user interactions and obtain our messages 146 | # let messages = interact(&interface); 147 | # 148 | // Update our state by processing each message 149 | for message in messages { 150 | counter.update(message); 151 | } 152 | ``` 153 | 154 | That should keep our state completely up-to-date with the latest user interactions. 155 | 156 | ### Looping Around 157 | Okay! Our state has been updated to reflect the user interactions. Now, we need to display the resulting interface again 158 | to the user. And after that, we must process any further interactions... And then, update our state once more. 159 | And then... Do it all over once again! 160 | 161 | This is a loop! And no, loops aren't very magical—not when we write Rust, at least: 162 | 163 | ```rust,ignore 164 | use magic::{display, interact}; 165 | 166 | // Initialize the state 167 | let mut counter = Counter::default(); 168 | 169 | // Be interactive. All the time! 170 | loop { 171 | // Run our view logic to obtain our interface 172 | let interface = counter.view(); 173 | 174 | // Display the interface to the user 175 | display(&interface); 176 | 177 | // Process the user interactions and obtain our messages 178 | let messages = interact(&interface); 179 | 180 | // Update our state by processing each message 181 | for message in messages { 182 | counter.update(message); 183 | } 184 | } 185 | ``` 186 | 187 | Congratulations! We just wrote a perfectly functional runtime—magical properties aside. We can clearly understand here how 188 | each fundamental part of The Elm Architecture fits in the lifetime of an application. 189 | 190 | Specifically, 191 | 192 | - __state__ is initialized once, 193 | - __view logic__ runs once at startup and then after every batch of interactions, 194 | - and __update logic__ runs for every interaction that created a __message__. 195 | 196 | ## The Ice Wizard 197 | "That's cool and all", you say, "but I am not a wizard and I still have no clue of how to run the counter interface I wrote. 198 | I have things to count!" 199 | 200 | Fair enough! iced implements a very similar runtime to the one we just built. It comes bundled with 201 | its own magic[^magic]—so you don't need to worry about learning the dark arts yourself. 202 | 203 | If we want to run our `Counter`, all we have to do is call [`run`]: 204 | 205 | ```rust,ignore,iced(height=100px) 206 | # use iced::widget::{button, column, text, Column}; 207 | # 208 | pub fn main() -> iced::Result { 209 | iced::run("A cool counter", Counter::update, Counter::view) 210 | } 211 | # 212 | # #[derive(Default)] 213 | # struct Counter { 214 | # value: i64, 215 | # } 216 | # 217 | # #[derive(Debug, Clone, Copy)] 218 | # enum Message { 219 | # Increment, 220 | # Decrement, 221 | # } 222 | # 223 | # impl Counter { 224 | # fn update(&mut self, message: Message) { 225 | # match message { 226 | # Message::Increment => { 227 | # self.value += 1; 228 | # } 229 | # Message::Decrement => { 230 | # self.value -= 1; 231 | # } 232 | # } 233 | # } 234 | # 235 | # fn view(&self) -> Column { 236 | # column![ 237 | # button("+").on_press(Message::Increment), 238 | # text(self.value), 239 | # button("-").on_press(Message::Decrement), 240 | # ] 241 | # } 242 | # } 243 | ``` 244 | 245 | We just give our application a _cool_ title and then provide the __update logic__ and __view logic__ to 246 | the __runtime__—which then figures out the rest! 247 | 248 | The runtime is capable of inferring the types for the __state__ and __messages__ out of the type signatures of 249 | our __update logic__ and __view logic__. The __state__ is initialized leveraging `Default`, as we described earlier. 250 | 251 | Notice also that [`run`] can fail and, therefore, it returns an [`iced::Result`]. If all we are doing is run the 252 | application, we can return this result directly in `main`. 253 | 254 | And that should be it! Have fun counting things for 300 million years—at least! 255 | 256 | [`run`]: https://docs.iced.rs/iced/fn.run.html 257 | [`iced::Result`]: https://docs.iced.rs/iced/type.Result.html 258 | [^magic]: Mainly [`winit`], [`softbuffer`], [`wgpu`], [`tiny-skia`], and [`cosmic-text`]. 259 | 260 | [`winit`]: https://github.com/rust-windowing/winit 261 | [`softbuffer`]: https://github.com/rust-windowing/softbuffer 262 | [`wgpu`]: https://github.com/gfx-rs/wgpu 263 | [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia 264 | [`cosmic-text`]: https://github.com/pop-os/cosmic-text 265 | 266 | > #### Note From the Author 267 | > You reached the end of the book, for now! 268 | > 269 | > I think it should already serve as a quick introduction to the basics of the library. 270 | > There is a lot more to unravel—but hopefully you are now at a point where you can start 271 | > playing around, having fun, and experimenting further. 272 | > 273 | > The book is far from finished—there are a lot more topics I want to cover here, namely: 274 | > 275 | > - Layout 276 | > - Styling 277 | > - Concurrency 278 | > - Scaling Applications 279 | > - Extending the Runtime 280 | > - And More! 281 | > 282 | > Until I get to write them, check out the [Additional Resources](additional-resources.md) 283 | > chapter if you want to explore and learn further. 284 | > 285 | > I hope that you enjoyed the read so far. Stay tuned! 286 | > 287 | > — Héctor 288 | -------------------------------------------------------------------------------- /src/themes.md: -------------------------------------------------------------------------------- 1 | # Themes 2 | -------------------------------------------------------------------------------- /src/widgets.md: -------------------------------------------------------------------------------- 1 | # Widgets 2 | -------------------------------------------------------------------------------- /theme/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | --------------------------------------------------------------------------------