├── 0000-template.md ├── LICENSE ├── README.md └── text ├── 0006-theming.md ├── 0007-widget-operations.md ├── 0013-client-side-controls.md ├── 0015-svg-theming.md ├── 23-custom-pipelines.md ├── designs ├── 23-01-pointer.md ├── 23-02-widget.md └── 23-03-multi-backend.md ├── diagram.png └── silvia.jpeg /0000-template.md: -------------------------------------------------------------------------------- 1 | # Feature Name 2 | 3 | ## Summary 4 | 5 | One paragraph explanation of the feature. 6 | 7 | 8 | ## Motivation 9 | 10 | Why are we doing this? What use cases does it support? What is the expected outcome? 11 | 12 | 13 | ## Guide-level explanation 14 | 15 | Explain the proposal as if it was already included in the library and you were teaching it to another iced programmer. That generally means: 16 | 17 | - Introducing new named concepts. 18 | - Explaining the feature largely in terms of examples. 19 | - Explaining how iced programmers should *think* about the feature, and how it should impact the way they use iced. It should explain the impact as concretely as possible. 20 | 21 | For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. 22 | 23 | 24 | ## Implementation strategy 25 | 26 | This is the technical portion of the RFC. Explain the design in sufficient detail that: 27 | 28 | - Its interaction with other features is clear. 29 | - It is reasonably clear how the feature would be implemented. 30 | - Corner cases are dissected by example. 31 | 32 | The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. 33 | 34 | 35 | ## Drawbacks 36 | 37 | Why should we *not* do this? 38 | 39 | 40 | ## Rationale and alternatives 41 | 42 | - Why is this design the best in the space of possible designs? 43 | - What other designs have been considered and what is the rationale for not choosing them? 44 | - What is the impact of not doing this? 45 | 46 | 47 | ## [Optional] Prior art 48 | 49 | Discuss prior art, both the good and the bad, in relation to this proposal. 50 | A few examples of what this can include are: 51 | 52 | - Does this feature exist in other GUI toolkits and what experience have their community had? 53 | - Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. 54 | 55 | This section is intended to encourage you as an author to think about the lessons from other toolkits, provide readers of your RFC with a fuller picture. 56 | If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. 57 | 58 | Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. 59 | Please also take into consideration that iced sometimes intentionally diverges from common toolkit features. 60 | 61 | 62 | ## Unresolved questions 63 | 64 | - What parts of the design do you expect to resolve through the RFC process before this gets merged? 65 | - What parts of the design do you expect to resolve through the implementation of this feature before stabilization? 66 | - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? 67 | 68 | 69 | ## [Optional] Future possibilities 70 | 71 | Think about what the natural extension and evolution of your proposal would be and how it would affect the toolkit and project as a whole in a holistic way. Try to use this section as a tool to more fully consider all possible interactions with the project and language in your proposal. Also consider how this all fits into the roadmap for the project. 72 | 73 | This is also a good place to "dump ideas", if they are out of scope for the RFC you are writing but otherwise related. 74 | 75 | If you have tried and cannot think of any future possibilities, you may simply state that you cannot think of anything. 76 | 77 | Note that having something written down in the future-possibilities section is not a reason to accept the current or a future RFC; such notes should be in the section on motivation or rationale in this or subsequent RFCs. The section merely provides additional information. 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iced RFCs 2 | 3 | If you want to contribute a significant change to [`iced`], you may be asked to write an RFC. 4 | 5 | 6 | ## What is an RFC? 7 | 8 | In short, an RFC (Request For Comments) is a written document explaining the design and rationale of a specific feature or a set of changes. 9 | 10 | RFCs are intended to provide a consistent and controlled path for new features to be added to the library while understanding their impact in the evolution of the library. 11 | 12 | 13 | ## When do I need to write an RFC? 14 | 15 | You need to write an RFC if you intend to make significant changes to [`iced`], the website, the book, or the RFC process itself. In general, a change is considered significant when it creates, removes, or impacts established ideas or APIs in the library. 16 | 17 | On the other hand, small changes (bugfixes, small tweaks, documentation, etc.) do not generally require an RFC. 18 | 19 | If you are unsure whether your changes are small or require an RFC, please create a discussion in the [`iced`] repository. 20 | 21 | If you submit a pull request to implement a new feature without going through the RFC process, it may be closed with a polite request to submit an RFC first. 22 | 23 | [`iced`]: https://github.com/iced-rs/iced 24 | 25 | 26 | ## How do I create an RFC? 27 | 28 | Let's say you want to write an RFC about a new feature called: `my_feature`. The process is quite straightforward. 29 | 30 | 1. Fork this repository. 31 | 1. Create a new `my_feature` branch for your new RFC. 32 | 1. Copy the `0000-template.md` to `text/0000-my_feature.md`. 33 | 1. Fill in the RFC. 34 | 1. Submit a pull request in this repository. 35 | 1. Replace the `0000` in the filename `0000-my_feature.md` with the number of your PR. 36 | 1. Wait for a review of the core team and iterate the design until consensus is reached. 37 | 1. If the PR is... 38 | - __merged__, then contributors can start working on the implementation and create a PR in the [`iced`] repository. 39 | - __closed__, then the design was dismissed in its current state because consensus was not reached. 40 | 41 | -------------------------------------------------------------------------------- /text/0006-theming.md: -------------------------------------------------------------------------------- 1 | # Theming 2 | 3 | ## Summary 4 | 5 | This proposal introduces the first-class concept of a __theme__ in iced. A __theme__ is defined as some data type capable of changing the default look of the widgets of an application. 6 | 7 | 8 | ## Motivation 9 | 10 | Applications tend to use a single, consistent look for all of its widgets. 11 | 12 | However, styling in iced currently works in a case-by-case basis. In other words, the style of every widget in an application needs to be explicitly set with the `style` methods of the specific widget. For instance: 13 | 14 | ```rust 15 | let text_input = TextInput::new(/* ... */).style(theme); 16 | let button = Button::new(/* ... */).style(theme); 17 | let slider = Slider::new(/* ... */).style(theme); 18 | let progress_bar = ProgressBar::new(/* ... */).style(theme); 19 | 20 | let scrollable = Scrollable::new(/* ... */) 21 | .style(theme) 22 | .push(text_input) 23 | .push(button); 24 | .push(slider); 25 | ``` 26 | 27 | In the snippet above, all of the widgets use the same `theme` as their `style`. The only way to achieve a consistent look in an application is by passing the same `theme` to every widget. Obviously, this is cumbersome as well as error-prone. 28 | 29 | Given how common and basic this use case is, iced should introduce new ideas to support a single, centralized, consistent theme for an application. 30 | 31 | ## Guide-level explanation 32 | 33 | ### First-class themes 34 | 35 | An `Application` has a new `Theme` associated type alongside a new `theme` method: 36 | 37 | ```rust 38 | struct Example; 39 | 40 | impl Application for Example { 41 | type Theme = /* ... */; 42 | 43 | // ... 44 | 45 | fn theme(&self) -> Self::Theme { 46 | // ... 47 | } 48 | } 49 | ``` 50 | 51 | The `theme` method works analogously to other methods in the trait like `view`, `title`, `mode`, etc. The theme returned in this method will be used to draw the `Application`. 52 | 53 | 54 | Widgets may require the `Application::Theme` to implement their own specific style sheets. For instance, a `Button` requires `Application::Theme` to implement `button::StyleSheet` before it can be used. 55 | 56 | Furthermore, the `StyleSheet` trait of a widget can introduce associated types to offer additional styling flexibility. For instance, `button::StyleSheet` introduces a `Style` associated type which can be used to set the specific style of a `Button` instance with the `Button::style` method. 57 | 58 | Widgets and `Element` in `iced` now have a `Theme` generic type, alongside the potentially present `Message`, that needs to be specified in the type signatures. The compiler will be able to infer the type in other instances, so the overall impact on the API should be relatively low. 59 | 60 | Let's see an example of how all of this works together! Let's say we want to use our own custom theme type in our `Application`. We start by defining our custom `Theme` type: 61 | 62 | ```rust 63 | struct Theme { 64 | text: Color, 65 | action: Color, 66 | positive: Color, 67 | negative: Color, 68 | } 69 | ``` 70 | 71 | Then, we define the styles of a `Button` we will have in our application: 72 | 73 | ```rust 74 | #[derive(Debug, Clone, Copy)] 75 | enum Button { 76 | Primary, 77 | Positive, 78 | Destructive, 79 | } 80 | 81 | // The `Button` widget demands that the `Style` of a `button::StyleSheet` 82 | // implements `Default` 83 | impl Default for Button { 84 | fn default() -> Self { 85 | Self::Primary 86 | } 87 | } 88 | ``` 89 | 90 | Now, we can implement `button::StyleSheet` for our `Theme`: 91 | 92 | ```rust 93 | use iced::button; 94 | 95 | impl button::StyleSheet for Theme { 96 | type Style = Button; 97 | 98 | fn active(&self, style: Button) -> button::Style { 99 | // We can use the `Theme` colors in `self` here and produce a different 100 | // `button::Style` for each `Button` style 101 | match style { 102 | Button::Primary => /* ... */, 103 | Button::Positive => /* ... */, 104 | Button::Destructive => /* ... */, 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | Then, we can use our brand new custom `Theme` in our `Application`: 111 | 112 | ```rust 113 | use iced::pure::{Application, Element}; 114 | use iced::pure::{button, column}; 115 | 116 | use theme::{self, Theme}; 117 | 118 | struct Example; 119 | 120 | impl Application for Example { 121 | type Theme = Theme; 122 | 123 | // ... 124 | 125 | // Notice that `Element` needs to know about the custom `Theme` now! 126 | fn view(&self) -> Element { 127 | // Let's show all of our button styles 128 | column() 129 | .push(button("Primary")) // The default `Button` style is used! 130 | .push(button("Positive").style(theme::Button::Positive)) 131 | .push(button("Destructive").style(theme::Button::Destructive)) 132 | .into() 133 | } 134 | 135 | fn theme(&self) -> Theme { 136 | // We generate the `Theme` on the fly here, but we could also store it 137 | // in our application state and return a copy 138 | Theme { 139 | /* ... */ 140 | } 141 | } 142 | } 143 | ``` 144 | 145 | As you can see, the new `Theme` associated type ties everything up together in a type-safe way. If we were to change our `Theme` to a different type, our `view` code would stop compiling unless the new type supported the same button styles. 146 | 147 | 148 | ### Built-in themes 149 | Overall, custom themes are considered an advanced use case. 150 | 151 | Users that are getting started with iced do not want to be forced to build a custom theme and manually implement the `StyleSheet` trait of every widget they need. Additionally, they may not be interested in fine-tuning the looks of the application, but instead they may just want to choose an existing good-looking theme. 152 | 153 | For these reasons, `iced` provides a built-in `Theme` type and `theme` module with styles for some widgets that can be used out of the box: 154 | 155 | ```rust 156 | use iced::pure::{Application, Element}; 157 | use iced::pure::button; 158 | use iced::theme::{self, Theme}; 159 | 160 | struct Example; 161 | 162 | impl Application for Example { 163 | type Theme = Theme; 164 | 165 | fn view(&self) -> Element { 166 | button("Hello!").style(theme::Button::Secondary) 167 | } 168 | 169 | fn theme(&self) -> Theme { 170 | Theme::Light 171 | } 172 | } 173 | ``` 174 | 175 | Notice how, in this case, we didn't specify the `Theme` in the `Element`. All of the built-in widgets (and `Element`) will use the built-in `Theme` type as the default type for their new `Theme` generic type. As a result, when using the built-in `Theme`, all of the type signatures can stay simple! 176 | 177 | For now, the built-in `Theme` will be a simple enum with three variants: 178 | 179 | ```rust 180 | pub enum Theme { 181 | Light, 182 | Dark, 183 | Custom(Palette), 184 | } 185 | ``` 186 | 187 | The built-in `Theme`, as well as the supported widget variants, can be thoroughly extended in the future! See "[Future possibilities](#future-possibilities)". 188 | 189 | The `Custom` variant can be used to define a custom color `Palette`, which is defined as follows: 190 | 191 | ```rust 192 | pub struct Palette { 193 | background: Color, 194 | text: Color, 195 | primary: Color, 196 | success: Color, 197 | danger: Color, 198 | } 199 | ``` 200 | 201 | Internally, all of the `Theme` variants will define its own `Palette`. In other words, both `Light` and `Dark` themes are just built-in custom color palettes: 202 | 203 | ```rust 204 | assert_eq!(Theme::Light.palette(), Theme::Custom(Theme::Light.palette()).palette()); 205 | ``` 206 | 207 | ### Extending the built-in themes 208 | 209 | The built-in `Theme` can be supported by custom widgets defined in other crates of the ecosystem. 210 | 211 | A custom widget can define its own `StyleSheet` trait: 212 | 213 | ```rust 214 | pub trait StyleSheet { 215 | type Style: Default + Copy; 216 | 217 | // ... 218 | } 219 | ``` 220 | 221 | And then, implement the trait for the built-in `Theme` in the same crate: 222 | 223 | ```rust 224 | use iced::Theme; 225 | 226 | impl StyleSheet for Theme { 227 | type Style = /* ... */; 228 | 229 | // ... 230 | } 231 | ``` 232 | 233 | `Theme` exposes an `extended_palette` method that can be leveraged in the `StyleSheet` implementation to choose the proper colors. `extended_palette` returns a `palette::Extended` type that contains different shades generated from the original `Palette` of a `Theme`. 234 | 235 | These internal details are likely to change as we fine-tune the specific styling for `iced`, which falls out of scope of this RFC. 236 | 237 | ### `Sandbox` and simplicity 238 | 239 | For simplicity, the `Sandbox` trait does not have the `Theme` associated type and, as a result, will always use the default built-in `Theme` type. 240 | 241 | As a consequence, users will need to migrate to the `Application` trait if they decide to leverage a custom `Theme` type. However, `Sandbox` does have a `theme` method that can be used to change the `Theme` variant. 242 | 243 | 244 | ## Implementation strategy 245 | 246 | We introduce a `Theme` associated type to the `Renderer` trait in `iced_native`. 247 | 248 | As a consequence, widgets can easily add bounds on this `Theme` as needed. For instance, the `Button` described above can be implemented as follows: 249 | 250 | ```rust 251 | pub use iced_style::button::{Style, StyleSheet}; 252 | 253 | pub struct Button<'a, Message, Renderer> 254 | where 255 | Renderer: iced_native::Renderer, 256 | // The `Theme` needs to implement `StyleSheet` 257 | Renderer::Theme: StyleSheet, 258 | { 259 | // ... 260 | // A `Button` stores the `Style` defined by the `StyleSheet` 261 | style: ::Style, 262 | } 263 | 264 | impl<'a, Message, Renderer> Button<'a, Message, Renderer> 265 | where 266 | Renderer: iced_native::Renderer, 267 | Renderer::Theme: StyleSheet, 268 | { 269 | pub fn new(content: impl Into>) -> Self { 270 | Button { 271 | // ... 272 | style: Default::default(), 273 | } 274 | } 275 | 276 | // ... 277 | 278 | /// Sets the style of this [`Button`]. 279 | pub fn style( 280 | mut self, 281 | style: impl Into<::Style>, 282 | ) -> Self { 283 | self.style = style.into(); 284 | self 285 | } 286 | } 287 | ``` 288 | 289 | Additionally, we need to provide the current `Renderer::Theme` to the widgets during `draw`. Therefore, we need to introduce the `Renderer::Theme` as an argument to the `draw` method in the `Widget`: 290 | 291 | ```rust 292 | pub trait Widget 293 | where 294 | Renderer: crate::Renderer, 295 | { 296 | /// Draws the [`Widget`] using the associated `Renderer`. 297 | fn draw( 298 | &self, 299 | renderer: &mut Renderer, 300 | theme: &Renderer::Theme, 301 | style: &renderer::Style, 302 | layout: Layout<'_>, 303 | cursor_position: Point, 304 | viewport: &Rectangle, 305 | ); 306 | } 307 | ``` 308 | 309 | Notice that we need to add the `crate::Renderer` bound to the `Renderer` generic type in order to properly reference `Renderer::Theme` in the trait body. 310 | 311 | Besides the `Widget` trait, the `Overlay` trait needs to change analogously, as well as their `pure` counterparts. 312 | 313 | Now that widgets need a `Renderer::Theme`, we will need to provide it to `UserInterface::draw` as well: 314 | 315 | ```rust 316 | impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> 317 | where 318 | Renderer: crate::Renderer, 319 | { 320 | pub fn draw( 321 | &mut self, 322 | renderer: &mut Renderer, 323 | theme: &Renderer::Theme, 324 | cursor_position: Point, 325 | ) -> mouse::Interaction { 326 | // ... 327 | } 328 | } 329 | ``` 330 | 331 | The `update` method in `program::State` in `iced_native` will need it too: 332 | 333 | ```rust 334 | impl

State

335 | where 336 | P: Program + 'static, 337 | { 338 | pub fn update( 339 | &mut self, 340 | bounds: Size, 341 | cursor_position: Point, 342 | renderer: &mut P::Renderer, 343 | theme: &::Theme, 344 | clipboard: &mut dyn Clipboard, 345 | debug: &mut Debug, 346 | ) -> Option> { 347 | // ... 348 | } 349 | } 350 | ``` 351 | 352 | Since we want `Application` to choose the `Theme`, we will need to make the `Renderer` in `iced_graphics` generic over a `Theme` generic type: 353 | 354 | ```rust 355 | #[derive(Debug)] 356 | pub struct Renderer { 357 | backend: B, 358 | primitives: Vec, 359 | theme: PhantomData, 360 | } 361 | ``` 362 | 363 | And then implement `iced_native::Renderer` as follows: 364 | 365 | ```rust 366 | impl iced_native::Renderer for Renderer 367 | where 368 | B: Backend, 369 | { 370 | type Theme = T; 371 | 372 | // ... 373 | } 374 | ``` 375 | 376 | These changes prompt the `Renderer` in `iced_wgpu` and `iced_glow` to change analogously: 377 | 378 | ```rust 379 | pub type Renderer = 380 | iced_graphics::Renderer; 381 | ``` 382 | 383 | Notice that we set the default type of the `Theme` generic type as `iced_native::Theme`, which should reduce verbosity when choosing the built-in `Theme`. 384 | 385 | The `Compositor` in `iced_wgpu` and `iced_glow` need to also be aware of the `Theme` too: 386 | 387 | ```rust 388 | pub struct Compositor { 389 | // ... 390 | theme: PhantomData, 391 | } 392 | ``` 393 | 394 | So they can implement the `Compositor` traits from `iced_graphics`: 395 | 396 | ```rust 397 | impl iced_graphics::window::Compositor for Compositor { 398 | type Renderer = Renderer; 399 | 400 | // ... 401 | } 402 | ``` 403 | 404 | Now, we can introduce the new `theme` method to the `Application` trait in `iced_winit`: 405 | 406 | 407 | ```rust 408 | trait Application: Program { 409 | // ... 410 | 411 | /// Returns the current [`Theme`] of the [`Application`]. 412 | fn theme(&self) -> ::Theme; 413 | 414 | // ... 415 | } 416 | ``` 417 | 418 | We initialize the `theme` in `run_instance`: 419 | 420 | ```rust 421 | // ... 422 | 423 | let mut state = State::new(&application, &window); 424 | let mut viewport_version = state.viewport_version(); 425 | let mut theme = application.theme(); 426 | 427 | // ... 428 | ``` 429 | 430 | Then, we properly update it after an `update`: 431 | 432 | ```rust 433 | // ... 434 | 435 | theme = application.theme(); 436 | user_interface = ManuallyDrop::new(build_user_interface( 437 | &mut application, 438 | cache, 439 | &mut renderer, 440 | state.logical_size(), 441 | &mut debug, 442 | )); 443 | 444 | // ... 445 | ``` 446 | 447 | And we provide it to `UserInterface::draw`: 448 | 449 | ```rust 450 | debug.draw_started(); 451 | let new_mouse_interaction = user_interface.draw( 452 | &mut renderer, 453 | &theme, 454 | state.cursor_position(), 455 | ); 456 | debug.draw_finished(); 457 | ``` 458 | 459 | `iced_glutin` needs to change analogously. 460 | 461 | Finally, we can update the `Application` trait in the `iced` crate. We need to introduce the `Theme` associated type and the `theme` method: 462 | 463 | ```rust 464 | pub trait Application: Sized { 465 | // ... 466 | 467 | /// The theme of your [`Application`]. 468 | type Theme; 469 | 470 | // ... 471 | 472 | /// Returns the current [`Theme`] of the [`Application`]. 473 | fn theme(&self) -> Self::Theme; 474 | 475 | // ... 476 | } 477 | ``` 478 | 479 | Then, we complete its implementation of both `iced_winit::Program` and `iced_winit::Application` traits: 480 | 481 | ```rust 482 | 483 | impl iced_winit::Program for Instance 484 | where 485 | A: Application, 486 | { 487 | type Renderer = crate::renderer::Renderer; 488 | 489 | // ... 490 | } 491 | 492 | impl crate::runtime::Application for Instance 493 | where 494 | A: Application, 495 | { 496 | fn theme(&self) -> A::Theme { 497 | self.0.theme() 498 | } 499 | } 500 | ``` 501 | 502 | We also need to introduce the `Theme` generic type to all the type aliases for widget (and `Element`): 503 | 504 | ```rust 505 | pub type Column<'a, Message, Theme = crate::Theme> = 506 | iced_native::widget::Column<'a, Message, crate::Renderer>; 507 | 508 | pub type Row<'a, Message, Theme = crate::Theme> = 509 | iced_native::widget::Row<'a, Message, crate::Renderer>; 510 | 511 | // ... 512 | 513 | pub type Element<'a, Message, Theme = crate::Theme> = 514 | crate::runtime::Element<'a, Message, crate::Renderer>; 515 | ``` 516 | 517 | As before, we set the default type of to the built-in `Theme`. 518 | 519 | And that should be it! 520 | 521 | 522 | ## Drawbacks 523 | 524 | The design introduces some complexity on the type signature of the widgets (and `Element`) when using custom themes. However, this issue isn't particularly worrying because users can always define their own type aliases. For instance: 525 | 526 | ```rust 527 | pub type Element<'a, Message> = iced::Element<'a, Message, MyCustomTheme>; 528 | ``` 529 | 530 | This could be a nice side-effect, since encouraging users to build their own `view` helpers on top of `iced` is great. 531 | 532 | An important drawback is that the current design does not allow for widget customization outside of a `Theme`. For instance, if a user is using an existing `Theme`, they will not be able to easily fine-tune the specific properties of the styling of a `Button` to their liking. They will be forced to create a new custom theme. 533 | 534 | However, I believe this is a good thing since consistency is the main motivation for this feature. Furthermore, users can always leverage composition to reuse existing themes. 535 | 536 | 537 | ## Rationale and alternatives 538 | This design is quite elegant, simple, and type-safe. 539 | 540 | Using the existing `Renderer` generic type in the widgets to introduce bounds for the `Theme` is quite straightforward and powerful. 541 | 542 | Furthermore, the design leverages the type system and generics to monomorphize the supported variants of a `Theme`. As a result, it removes the `Box` present everywhere in the current widgets, which should in turn help reduce allocations in `view` code. 543 | 544 | The design also allows for easy customization and extensibility. Supporting custom themes is the main satisfied use case and should allow the community to create and maintain their own custom theme crates. The built-in `Theme` can also be easily extended as well. 545 | 546 | 547 | ## Unresolved questions 548 | 549 | - All questions resolved for now! 550 | 551 | 552 | ## Future possibilities 553 | 554 | - In the future, the built-in `Theme` can be extended with additional variants for supporting different color schemes. 555 | - An `Autodetect` variant could be added to the built-in `Theme` to let `iced` choose the best fitting variant of the `Theme` based on the environment (e.g. OS settings). 556 | -------------------------------------------------------------------------------- /text/0007-widget-operations.md: -------------------------------------------------------------------------------- 1 | # Widget Operations 2 | 3 | ## Summary 4 | 5 | This proposal introduces the idea of __widget operations__ in iced. A __widget operation__ is defined as some logic that can traverse (and operate on) the widget tree of an iced application in order to query or update some widget state. 6 | 7 | 8 | ## Motivation 9 | 10 | The non-pure widget API in iced forces users to explicitly store widget state in their application state. This approach, while cumbersome, has a nice benefit. Given that all of the widget state is explicitly defined in the application state, it's possible for users to easily modify the internal state of a specific widget during an `update`. 11 | 12 | For instance, if we have a `TextInput` and a `Button` in our application: 13 | 14 | ```rust 15 | struct Example { 16 | input: text_input::State, 17 | button: button::State, 18 | } 19 | ``` 20 | 21 | We can easily change the state of the `input` in `update`. For example, let's say we want to focus the `input` after the `button` is pressed. We can just write: 22 | 23 | ```rust 24 | fn update(&mut self, message: Message) { 25 | match message { 26 | Message::ButtonPressed => { 27 | self.input = text_input::State::focused(); 28 | } 29 | // ... 30 | } 31 | } 32 | ``` 33 | 34 | Since we cannot expect users to manage and synchronize all of the widget internal state through The Elm Architecture, this is a very common and perfectly valid use case. 35 | 36 | In [#1284](https://github.com/iced-rs/iced/pull/1284), a new widget API was introduced with the goal to replace the current non-pure approach. And precisely, the main difference is the lack of explicit widget state management. 37 | 38 | In practice, this means that users do not have to store widget state in their application state anymore. But, as a consequence, users cannot change the internal state of a widget during an `update`, since there is no widget state to refer to. 39 | 40 | For instance, when using the pure widget API, the previous `Example` struct would not hold any widget state: 41 | 42 | ```rust 43 | struct Example; 44 | ``` 45 | 46 | Thus, it would not be possible to refer to the state of the `TextInput`: 47 | 48 | ```rust 49 | fn update(&mut self, message: Message) { 50 | match message { 51 | Message::ButtonPressed => { 52 | // How do we focus the `TextInput` here? 53 | } 54 | // ... 55 | } 56 | } 57 | ``` 58 | 59 | If we want the new pure API to replace the current one, we need to introduce new ideas in iced that can be used to query and update internal widget state. 60 | 61 | 62 | ## Guide-level explanation 63 | 64 | There are only two new ideas that end-users need to get familiarized with: **operations** and **identifiers**. 65 | 66 | ### Operations 67 | Widget modules can export **widget operations** in their public interface. A widget operation is a `Command` and, as a result, it can be run by any implementor of the `Application` trait. 68 | 69 | For instance, we could satisfy the previous use case if the `text_input` module exposed a `focus` operation: 70 | 71 | ```rust 72 | fn update(&mut self, message: Message) -> Command { 73 | match message { 74 | Message::ButtonPressed => { 75 | // We build the `focus` operation and return the resulting `Command` 76 | text_input::focus(MY_TEXT_INPUT_ID) 77 | } 78 | // ... 79 | } 80 | } 81 | ``` 82 | 83 | In the example above, `focus` is a function exported by the `text_input` module that can be used to build a widget operation that focuses a specific `TextInput`. Its output is a `Command` that any `Application` can run. 84 | 85 | Since we may produce many different text inputs in our `Application::view`, we need some way to **identify** the specific text input. As a consequence, some widget operations may need **identifiers**. 86 | 87 | 88 | ### Identifiers 89 | 90 | A **widget identifier** is an instance of some opaque type `Id` exported by a widget module. A widget will only support identifiers if it exposes an operation that needs them. 91 | 92 | For instance, the `MY_TEXT_INPUT_ID` static in the previous example could be defined as follows: 93 | 94 | ```rust 95 | lazy_static! { 96 | static MY_TEXT_INPUT_ID: text_input::Id = text_input::Id::unique(); 97 | } 98 | ``` 99 | 100 | For any built-in widget, the `unique` constructor of an `Id` type always produces a different instance every time it's called: 101 | 102 | ```rust 103 | use some_widget::Id; 104 | 105 | let some_id = Id::unique(); 106 | let another_id = Id::unique(); 107 | 108 | assert_ne!(some_id, another_id); 109 | ``` 110 | 111 | Any built-in `Id` type also generally offers a simple `new` constructor that takes a `String`, which can be used in `view` code to generate identifiers from an external source of data at runtime (as opposed to using `unique` at compile-time). 112 | 113 | A widget that supports identifiers has an `id` method that can be used to set the `Id` in `view` code, effectively connecting `view` logic with any operation produced in `update`. 114 | 115 | For instance, if we include the following `TextInput` in our `view`: 116 | 117 | ```rust 118 | text_input("Some placeholder", some_value, Message::InputChanged) 119 | .id(MY_TEXT_INPUT_ID) 120 | ``` 121 | 122 | Then, returning the `Command` produced by `text_input::focus(MY_TEXT_INPUT_ID)` in `Application::update` will cause this specific `TextInput` to gain focus. 123 | 124 | 125 | ## Implementation strategy 126 | 127 | ### A new kind of `Command` 128 | A new `widget` method will be introduced to `Command` in `iced_native`: 129 | 130 | ```rust 131 | impl Command { 132 | pub fn widget(operation: impl widget::Operation) -> Self { 133 | Self::single(Action::Widget(Box::new(operation))) 134 | } 135 | } 136 | ``` 137 | 138 | As shown above, the implementation of `Command::widget` will use a new `command::Action`: 139 | 140 | ```rust 141 | pub enum Action { 142 | // ... 143 | /// Run a widget operation. 144 | Widget(Box>) 145 | } 146 | ``` 147 | 148 | The new `Command::widget` method will allow widget developers to expose a widget operation as a `Command`. For instance, we could write `text_input::focus` as follows: 149 | 150 | ```rust 151 | use iced_native::command::{self, Command}; 152 | 153 | pub fn focus(id: Id) -> Command { 154 | Command::widget(Focus(id)) 155 | } 156 | ``` 157 | 158 | As shown above, `Command::widget` takes an implementor of a brand new trait: `widget::Operation`. 159 | 160 | ### The `Operation` trait 161 | The `Operation` trait in the `widget` module defines the logic of a widget operation. A widget operation usually represents some state that is changed as it traverses a widget tree, producing some output `T` as a result. 162 | 163 | An operation is split into different methods that represent different widget types. Widgets will call the applicable methods of the `Operation` trait given their particular nature. An operation may be called multiple times per widget, if applicable. 164 | 165 | In a way, the `Operation` trait is analogous to [the `Hasher` trait](https://doc.rust-lang.org/std/hash/trait.Hasher.html), but the hashed values (the operands!) are the widgets themselves. 166 | 167 | The trait is defined as follows: 168 | 169 | ```rust 170 | use crate::widget::Id; 171 | use crate::widget::state; 172 | 173 | pub trait Operation { 174 | fn clickable(&mut self, state: &mut dyn operation::Clickable, id: Option); 175 | fn focusable(&mut self, state: &mut dyn operation::Focusable, id: Option); 176 | fn editable(&mut self, state: &mut dyn operation::Editable, id: Option); 177 | fn text(&mut self, contents: &str, id: Option); 178 | 179 | fn container( 180 | &mut self, 181 | _id: Option, 182 | operate_on_children: &mut dyn FnMut(&mut dyn Operation), 183 | ); 184 | 185 | // ... 186 | // Further methods can be added for additional widget types! 187 | // ... 188 | 189 | fn finish(&self) -> Outcome; 190 | } 191 | ``` 192 | 193 | As shown, an operation can operate on different types of widget. Where applicable, the methods of the trait receive mutable widget state that can be used to query it or update it. 194 | 195 | For instance, we could implement the `focus` operation by leveraging the `focusable` method: 196 | 197 | ```rust 198 | use iced_native::command::{self, Command}; 199 | use iced_native::widget; 200 | use iced_native::widget::state; 201 | 202 | pub fn focus(id: Id) -> Command { 203 | Command::widget(Focus(id)) 204 | } 205 | 206 | struct Focus(Id); 207 | 208 | impl widget::Operation for Focus { 209 | fn focusable(&mut self, state: &mut dyn operation::Focusable, id: Option) { 210 | // If the current widget has an identifier... 211 | if let Some(candidate_id) = id { 212 | // And it matches the identifier we are looking for... 213 | if self.0 == id { 214 | // Then, we focus the input! 215 | state.focus(); 216 | } 217 | } 218 | } 219 | 220 | fn container( 221 | &mut self, 222 | _id: Option, 223 | operate_on_children: &mut dyn FnMut(&mut dyn Operation), 224 | ) { 225 | // If the current widget is a container, we just keep traversing the tree. 226 | operate_on_children(self) 227 | } 228 | 229 | fn finish(&self) -> Outcome { 230 | Outcome::None 231 | } 232 | 233 | // ... 234 | } 235 | ``` 236 | 237 | In the `focusable` method we look for any focusable widget that has the identifier we are interested in, then we use the generic `Focusable` widget state to `focus` that specific widget. 238 | 239 | The `container` implementation will be called by any widget that contains other widgets and, as a result, it can be leveraged to control the traversal of the widget tree. In this case, we just keep traversing the widget tree unconditionally. 240 | 241 | An `Operation` may produce some `Outcome` when `finish` is called. The `focus` operation does not produce any. 242 | 243 | The implementation of the other methods in this particular example is empty and has been omitted. In fact, the `Operation` trait provides a default implementation for all of its methods except `container`: 244 | 245 | ```rust 246 | pub trait Operation { 247 | fn container( 248 | &mut self, 249 | _id: Option, 250 | operate_on_children: &mut dyn FnMut(&mut dyn Operation), 251 | ); 252 | 253 | fn clickable(&mut self, state: &mut dyn operation::Clickable, id: Option) {} 254 | fn focusable(&mut self, state: &mut dyn operation::Focusable, id: Option) {} 255 | fn editable(&mut self, state: &mut dyn operation::Editable, id: Option) {} 256 | fn text(&mut self, contents: &str, id: Option) {} 257 | 258 | // ... 259 | 260 | fn finish(&self) -> Outcome { 261 | Outcome::None 262 | } 263 | } 264 | ``` 265 | 266 | Therefore, an empty implementation of an `Operation` will be the "identity" operation and will simply traverse the whole widget tree without doing anything. 267 | 268 | ### Generic widget state 269 | 270 | For reusability, widget operations should be decoupled from particular widget implementations. Furthermore, it is necessary for operations to work even when custom widgets are present in a `view`. 271 | 272 | Because of this, the `Operation` trait relies on a new set of generic traits meant to represent different kinds of widget state. For instance, instead of directly relying on `text_input::State`, the `Operation` trait takes an implementor of the `operation::Focusable` trait: 273 | 274 | ```rust 275 | pub trait Focusable { 276 | fn is_focused(&self) -> bool; 277 | fn focus(&mut self); 278 | fn unfocus(&mut self); 279 | } 280 | ``` 281 | 282 | This way, an `Operation` can support virtually any widget, as long as the internal widget state implements the proper state traits. For example, `text_input::State` will implement `operation::Focusable`: 283 | 284 | ```rust 285 | impl operation::Focusable for State { 286 | fn is_focused(&self) -> bool { 287 | State::is_focused(self) 288 | } 289 | 290 | fn focus(&mut self) { 291 | State::focus(self) 292 | } 293 | 294 | fn unfocus(&mut self) { 295 | State::unfocus(self) 296 | } 297 | } 298 | ``` 299 | 300 | Further widgets could choose to implement this trait for their internal state, depending on the nature of the widget. It is up to the implementation of a `Widget` to properly support the `Operation` API. 301 | 302 | 303 | ### Outcomes 304 | 305 | The `Outcome` of an operation can take one of 3 shapes: 306 | 307 | * `None` means the operation produced no result. 308 | * `Some` means the operation produced a result of type `T`. This type `T` will be, at the same time, the output of its respective `Command`. Generally, this will lead to a new `Message` being produced and fed to `update`. 309 | * `Chain` means the operation produced a brand new operation as a result, and should be executed. 310 | 311 | ```rust 312 | pub enum Outcome { 313 | None, 314 | Some(T), 315 | Chain(Box>), 316 | } 317 | ``` 318 | 319 | In many cases, traversing the widget tree once may not be enough to complete an operation. For instance, an operation like `text_input::focus_previous` needs to traverse the tree once to find the current focused widget, unfocus it, and then traverse it again to focus the previous focusable widget with the information gathered in the first traversal. 320 | 321 | For this reason, it is possible for an operation to be split into many, chained smaller ones. 322 | 323 | 324 | ### Running an operation 325 | 326 | The shell implementations (i.e. `iced_winit` and `iced_glutin`, currently) will need to handle the new `Widget` variant of `command::Action`: 327 | 328 | ```rust 329 | for action in command.actions() { 330 | match action { 331 | // ... 332 | command::Action::Widget(operation) => { 333 | let mut current_cache = std::mem::take(cache); 334 | let mut current_operation = Some(action.into_operation()); 335 | 336 | let mut user_interface = build_user_interface( 337 | application, 338 | current_cache, 339 | renderer, 340 | state.logical_size(), 341 | debug, 342 | ); 343 | 344 | while let Some(mut operation) = current_operation.take() { 345 | user_interface.operate(renderer, operation.as_mut()); 346 | 347 | match operation.finish() { 348 | operation::Outcome::None => {} 349 | operation::Outcome::Some(message) => { 350 | proxy 351 | .send_event(message) 352 | .expect("Send message to event loop"); 353 | } 354 | operation::Outcome::Chain(next) => { 355 | current_operation = Some(next); 356 | } 357 | } 358 | } 359 | 360 | current_cache = user_interface.into_cache(); 361 | *cache = current_cache; 362 | } 363 | } 364 | } 365 | ``` 366 | 367 | As shown above, `UserInterface` will have a new `operate` method: 368 | 369 | ```rust 370 | impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> 371 | where 372 | Renderer: crate::Renderer, 373 | Renderer::Theme: application::StyleSheet, 374 | { 375 | // ... 376 | 377 | /// Applies a [`widget::Operation`] to the [`UserInterface`]. 378 | pub fn operate( 379 | &mut self, 380 | renderer: &Renderer, 381 | operation: &mut dyn widget::Operation, 382 | ) { 383 | self.root.operate(Layout::new(&self.base), operation); 384 | 385 | if let Some(layout) = self.overlay.as_ref() { 386 | if let Some(overlay) = 387 | self.root.overlay(Layout::new(&self.base), renderer) 388 | { 389 | overlay.operate(Layout::new(layout), operation); 390 | } 391 | } 392 | } 393 | } 394 | ``` 395 | 396 | This new method in turn uses a new `operate` method on both the `Widget` and `Overlay` traits: 397 | 398 | ```rust 399 | /// Applies an [`Operation`] to the [`Widget`]. 400 | fn operate( 401 | &mut self, 402 | _state: &mut Tree, // Only the pure traits have this argument 403 | _layout: Layout<'_>, 404 | _operation: &mut dyn Operation, 405 | ); 406 | ``` 407 | 408 | Each particular widget implementation will call the proper methods of the `Operation` based on the nature of the widget. 409 | 410 | For instance, `TextInput` could implement `operate` as follows: 411 | 412 | ```rust 413 | impl<'a, Message, Renderer> Widget 414 | for TextInput<'a, Message, Renderer> 415 | where 416 | Message: Clone, 417 | Renderer: text::Renderer, 418 | Renderer::Theme: StyleSheet, 419 | { 420 | // ... 421 | 422 | fn operate( 423 | &self, 424 | tree: &mut Tree, 425 | _layout: Layout<'_>, 426 | operation: &mut dyn widget::Operation, 427 | ) { 428 | let state = tree.state.downcast_mut::(); 429 | 430 | // We can call `focusable` since `text_input::State` implements 431 | // `operation::Focusable` 432 | operation.focusable(state, self.id); 433 | 434 | // `editable` can be called as well, analogously. 435 | operation.editable(state, self.id); 436 | } 437 | } 438 | ``` 439 | 440 | Other widgets will be implemented in a similar way! 441 | 442 | ## Drawbacks 443 | 444 | There aren't many drawbacks to this proposal, since most of the changes described here do not break any existing, end-user-facing APIs. Instead, most of the changes revolve around new ideas without affecting the existing codebase. 445 | 446 | 447 | ## Rationale and alternatives 448 | 449 | The design described here is relatively simple. Since operations are exposed through the `Command` API, end users only really need to get familiarized with the idea of identifiers. 450 | 451 | Identifiers are common in the Web and other GUI toolkits, and so the friction for end users should be minor. 452 | 453 | Furthermore, Elm uses a very similar design where [internal widget state can be updated through tasks and identifiers as well](https://package.elm-lang.org/packages/elm/browser/latest/Browser-Dom#focus). 454 | 455 | The design should be generic enough to work well with custom widgets and, at the same time, should be extensible and allow us to iterate easily if new widgets types are necessary. 456 | 457 | Finally, the implementation should be quite straightforward with no apparent hacks or important refactors necessary. 458 | 459 | 460 | ## Unresolved questions 461 | 462 | - Should we rename `Outcome` to something else? An operation producing a `Result` makes sense, but a `Result` type in Rust generally has an error variant and an operation cannot really fail as of now. 463 | - What other methods should we include in `widget::Operation`? Since many methods of an operation can be called per widget, should we include methods to mark the "start" and "end" of a widget? 464 | 465 | 466 | ## Future possibilities 467 | 468 | - We could allow some methods of the generic state traits to produce messages as output. This would allow us to implement interesting use cases in the future (e.g. a test framework that queries and interacts with the GUI exclusively through operations). 469 | - Many useful operations should be possible with this design and could be built-in in the library, like querying the current size of a particular widget or controlling a `Scrollable` programmatically. 470 | -------------------------------------------------------------------------------- /text/0013-client-side-controls.md: -------------------------------------------------------------------------------- 1 | # Client-side window controls 2 | 3 | ## Summary 4 | 5 | Enables applications with client-side decorations to maximize, minimize, and initiate a window drag with the mouse. 6 | 7 | ## Motivation 8 | 9 | GUI toolkits implementing client-side decorations need to implement a headerbar widget which has access to triggering window methods to maximize, minimize, and drag the window with the mouse. 10 | 11 | ## Guide-level explanation 12 | 13 | An application will be able define messages for window close, drag, minimize, and maximize, then call upon these window commands from the update method. 14 | 15 | ```rs 16 | #[derive(Debug, Clone)] 17 | pub enum Message { 18 | Close, 19 | Drag, 20 | Minimize, 21 | Maximize, 22 | } 23 | 24 | fn update(&mut self, message: Message) -> iced::Command { 25 | match message 26 | Message::Close => self.should_exit = true, 27 | Message::Drag => return iced_native::window::drag(), 28 | Message::Minimize => return iced_native::window::minimize(), 29 | Message::Maximize => return iced_native::window::toggle_maximize(), 30 | ... 31 | } 32 | 33 | Command::none() 34 | } 35 | ``` 36 | 37 | A headerbar widget can be utilized or implemented that passes these events to the applications. 38 | 39 | 40 | ## Implementation strategy 41 | 42 | Requires adding the following new variants to `native::window::action::Action`: 43 | 44 | - `Drag` 45 | - `Maximize(bool)`, 46 | - `Minimize(bool)`, 47 | 48 | It's also possible to implement a convenience method for toggling maximization: 49 | 50 | - `ToggleMaximize` 51 | 52 | Then `iced_winit::application::run_command` can check for these methods and call the respective winit methods. 53 | 54 | ## Drawbacks 55 | 56 | A headerbar widget is not currently provided by iced, so it is required for applications to create their own to trigger these events. This also requires using the `Application` trait rather than `Sandbox` to take advantage of commands. 57 | 58 | ## Rationale and alternatives 59 | 60 | There already exists an enum for handling window actions such as these, and functions for generating commands, so it would make sense to implement the support in this way. The drag event is crucial to handle the moment the event is received so that the drag event can immediately begin. 61 | 62 | An alternative approach would be expanding the `Application` and `Sandbox` traits to provide `should_maximize`, `should_minimize`, and `should_drag` methods; which would be similar to the existing `should_exit` method. 63 | 64 | ## Unresolved questions 65 | 66 | None 67 | 68 | ## Future Possibilities 69 | 70 | None 71 | -------------------------------------------------------------------------------- /text/0015-svg-theming.md: -------------------------------------------------------------------------------- 1 | # SVG Theming 2 | 3 | ## Summary 4 | 5 | The ability to specify the fill color of a SVG icon. 6 | 7 | ## Motivation 8 | 9 | Symbolic icons are SVGs which define a single color with varying alpha transparency to every path. GUI themes can utilize these to adjust their colors against the background, either to support dark and light theme variants, or to apply extra emphasis to make the icon stand out against the rest, or even de-emphasize options that are disabled by darkening or lightening the color closer to the background color. 10 | 11 | For example, the UX for libcosmic will apply a darker blue color for icons in the headerbar while using the light variation of the theme, whereas the dark variant of the theme will use a lighter blue color. Meanwhile, the rest of the symbolic icons will be assigned to a bright white color on the dark theme and a dark black on the light theme. While disabled options will have a brighter black on the light theme and a darker white on the dark theme. 12 | 13 | ## Guide-level explanation 14 | 15 | This will be most useful for the processing of symbolic icons. Therefore, the first step is to load a symbolic icon from an icon theme. On Linux, icons from a designated icon theme can be located using the `freedesktop-icons` crate. 16 | 17 | ```rs 18 | let handle: svg::Handle = freedesktop_icons::lookup("window-close-symbolic") 19 | .with_size(24) 20 | .with_theme("Pop") 21 | .with_cache() 22 | .force_svg() 23 | .find() 24 | .map_or_else( 25 | || svg::Handle::from_memory(Vec::new()), 26 | svg::Handle::from_path 27 | ); 28 | ``` 29 | 30 | This SVG may then have its fill color altered by defining a custom appearance: 31 | 32 | ```rs 33 | let svg = iced::widget::svg::Svg::new(handle) 34 | .width(Length::Units(24)) 35 | .height(Length::Units(24)) 36 | .style(iced_style::theme::Svg::Custom(|theme| { 37 | match theme { 38 | Theme::Light => Appearance { 39 | fill: Some(Color::from([0, 0.28627452, 0.42745098]), 40 | }, 41 | Theme::Dark => Appearance { 42 | fill: Some(Color::from([0.5803922, 0.92156863, 0.92156863])) 43 | } 44 | } 45 | })); 46 | ``` 47 | 48 | Alternating between the dark and light theme will then change the SVG accordingly. 49 | 50 | ## Implementation strategy 51 | 52 | Add styling support to `iced_style`: 53 | 54 | ```rs 55 | use iced_core::Color; 56 | 57 | #[derive(Debug, Default, Clone, Copy)] 58 | pub struct Appearance { 59 | pub fill: Option, 60 | } 61 | 62 | pub trait StyleSheet { 63 | type Style: Default + Copy; 64 | 65 | fn appearance(&self, style: Self::Style) -> Appearance; 66 | } 67 | ``` 68 | 69 | Implement support for the built-in theme: 70 | 71 | ``` 72 | #[derive(Default, Clone, Copy)] 73 | pub enum Svg { 74 | Custom(fn(&Theme) -> svg::Appearance), 75 | #[default] 76 | Default, 77 | } 78 | 79 | impl svg::StyleSheet for Theme { 80 | type Style = Svg; 81 | 82 | fn appearance(&self, style: Self::Style) -> svg::Appearance { 83 | match style { 84 | Svg::Default => Default::default(), 85 | Svg::Custom(appearance) => appearance(self), 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | Add an `iced_style::svg::Appearance` field to `svg::Handle` 92 | 93 | ```rs 94 | /// Returns the styling [`Appearance`] for the SVG. 95 | pub fn appearance(&self) -> Appearance { 96 | self.appearance 97 | } 98 | 99 | /// Set the [`Appearance`] for the SVG. 100 | pub fn set_appearance(&mut self, appearance: Appearance) { 101 | self.appearance = appearance; 102 | } 103 | ``` 104 | 105 | Add a `style: ::Style` field to `native::widget::svg` which can be defined with: 106 | 107 | ```rs 108 | /// Sets the style variant of this [`Svg`]. 109 | pub fn style( 110 | mut self, 111 | style: ::Style, 112 | ) -> Self { 113 | self.style = style; 114 | self 115 | } 116 | ``` 117 | 118 | Then within the `draw` method, grab the appearance from the theme: 119 | 120 | ```rs 121 | let mut handle = self.handle.clone(); 122 | handle.set_appearance(theme.appearance(self.style)); 123 | renderer.draw(handle, drawing_bounds + offset); 124 | ``` 125 | 126 | Add a `into_rgb8` method to `iced_core::Color`: 127 | 128 | ```rs 129 | /// Converts the [`Color`] into its RGBA8 equivalent. 130 | pub fn into_rgba8(self) -> [u8; 4] { 131 | [ 132 | (self.r * 255.0).round() as u8, 133 | (self.g * 255.0).round() as u8, 134 | (self.b * 255.0).round() as u8, 135 | (self.a * 255.0).round() as u8, 136 | ] 137 | } 138 | ``` 139 | 140 | Within `wgpu::src/image/vector.rs`, apply the fill color to sufficiently non-transparent pixels: 141 | 142 | 143 | ```rs 144 | let fill = handle.appearance().fill.map(crate::Color::into_rgba8); 145 | 146 | ... 147 | 148 | rgba.chunks_exact_mut(4).for_each(|rgba| { 149 | if rgba[3] > 50 { 150 | if let Some(color) = fill { 151 | rgba[0] = color[0]; 152 | rgba[1] = color[1]; 153 | rgba[2] = color[2]; 154 | } 155 | } 156 | 157 | rgba.swap(0, 2); 158 | }); 159 | ``` 160 | 161 | While being mindful to update the `Cache` to keep track of the appearance so that it is redrawn when the appearance changes. 162 | 163 | ## Drawbacks 164 | 165 | It requires changing `widget::Svg` to `widget::Svg` to add the `style` field. Custom themes will also have to implement the new `svg::StyleSheet` trait. 166 | 167 | ## Rationale and alternatives 168 | 169 | Packaging multiple colored variations of symbolic icons won't be practical. This solution applies the fill color at the time that the SVG is being rasterized, so icons can be in any color that the application developer or custom theme desires. This is able to leverage the advantages of the cache so that the fill does not have to be applied each time the widget is created. 170 | 171 | The alternative is parsing the SVGs, traversing SVG nodes, and recreating a new SVG in-memory each time a svg widget is recreated. This would consume a large amount of CPU with the current way that the view is eagerly updated. 172 | 173 | ## Prior Art 174 | 175 | CSS and GTK allow overriding the color of symbolic icons. Most commonly used to display a light variant of a symbolic icon against a dark background, or the default dark variant against a light background. 176 | 177 | ## Unresolved questions 178 | 179 | None 180 | 181 | ## Future Possibilities 182 | 183 | - The default theme could automatically choose a color based on the background it's to be rendered onto when the style is set to `Default`. 184 | - More advanced forms of filtering could be implemented in the future. Such as darken and brighten. 185 | - Perhaps it could be possible to pass a closure to the `Appearance` that would give access to mutating the rgba buffer. 186 | - A similar strategy could be used for images to apply filters for them. 187 | -------------------------------------------------------------------------------- /text/23-custom-pipelines.md: -------------------------------------------------------------------------------- 1 | # 🌈 Custom WGPU Shaders & Pipelines 2 | 3 | ## 📜 Summary 4 | 5 | This RFC aims to reach a consensus on the following: 6 | 1) Should we integrate custom WGPU shader & pipeline support into Iced? 7 | 2) If so, to what extent? And how? 8 | 9 | This RFC is similar to [RFC #19](https://github.com/iced-rs/rfcs/pull/19), but with a broader scope (for example, the 10 | custom shader proposed in #19 could be built on top of this framework!). The idea is to allow users to use their 11 | own WGPU pipelines & custom primitives to render their own content seamlessly alongside Iced's existing supported primitives. 12 | 13 | **Please note that this RFC is working off of the changes already present in the `advanced-text` branch!** 14 | 15 | ## 🦾 Motivation 16 | 17 | Currently, the only way to use a custom shader with Iced is to do something like the WGPU 18 | `integration` example, where you can render your own scene using Iced *independently* of the existing widget tree, 19 | or create a custom `Renderer` and do everything yourself. You must either render your content before or after Iced 20 | renders its user interface. This works in niche situations, for example a custom 3D scene with a 2d Iced overlay 21 | rendered on top, but if you wish to adjust the shader used to fill, for example, a container that exists deep 22 | within a widget tree, this is currently impossible without a fork or recreating the effect with potentially 23 | very expensive CPU calculations on a `Canvas`. 24 | 25 | Adding custom WGPU shader & pipeline support to Iced will add extensibility, customizability, and modularity to the 26 | existing rendering pipeline. I imagine a near future where we have repositories similar to `iced_aw`, where library 27 | authors can provide their own pipeline integrations into Iced for all kinds of shaders, primitives, and even 28 | post-processing effects! 🤯 29 | 30 | Most GUI libraries out there allow some way of embedding the underlying graphics API calls alongside existing 31 | widgets for further customizability, an example being querying a raw `WebGL` context from a HTML5 canvas and 32 | doing what you will with it. I believe Iced would benefit immensely from this added functionality. 33 | 34 | The result of this RFC should be that there is a clear path for allowing "embeddable" WGPU pipelines in some fashion 35 | to Iced, where library authors can create their own primitives & rendering pipelines which integrate seamlessly into 36 | Iced's existing renderer & backend. 37 | 38 | ## 🤠 Guide-level explanation 39 | 40 | ### 📚 Concepts 41 | 42 | *Foreword:* Coming up with a strategy for adding custom pipeline support to Iced is not really about what, nor how, but 43 | *when*. 44 | At what 45 | point in the frame presentation does the custom primitive get inserted into the primitive queue? Does it even get 46 | inserted into the primitive queue at all? If it's not inserted, how do we know when to render it? If it is inserted, 47 | what does a custom primitive actually mean? What *is* it? How do we reference the correct pipeline from the custom 48 | primitive? If the custom primitive has its own primitive type, do we use codegen/generics and just take the generic 49 | propagation L? Do we use reflection? Do we use trait objects & dynamic dispatching? Do we completely restructure 50 | Iced's rendering pipeline to allow for multiple backends of the same type? If so, how do we allow for any arbitrary 51 | amount of additive backends? Do we start getting down & dirty with macros to shave off a few additional pointer 52 | hops? Could we do this with a minimal unsafe abstraction layer? Should we even do this at all? Why am I doing this? 53 | *Why do I even exist?* 54 | 55 | ![](silvia.jpeg) 56 | 57 | As you can see, this is a somewhat complex topic with a lot of tradeoffs between implementation strategies. Of 58 | course, I would love if there was a better idea floating around out there that I haven't thought of! That being said, 59 | here are a few different designs & their concepts that would need to be introduced to Iced. I've broken them into 60 | their own markdown files for an easier time reading! 61 | 62 | 1) [Custom Primitive Pointer Design](designs/23-01-pointer.md) 63 | 2) [Custom Shader Widget Design](designs/23-02-widget.md) 64 | 3) [Multi-Backend Design](designs/23-03-multi-backend.md) 65 | 66 | All of these designs must be flagged under `wgpu`, unless we wanted to do some kind of fallback for tiny-skia which 67 | I don't think is viable. What would we fall back to for the software renderer if a user tries to render a 3D object, 68 | which tiny-skia does not support? Blue screen? :P 69 | 70 | Overall, I'm the most happy with design #3 and think that it offers the most flexibility for advanced users to 71 | truly render anything they want. 72 | 73 | 74 | ## 🎯 Implementation strategy 75 | 76 | ### 🙌 #1: Custom Primitive Pointer Design 77 | 78 | Behind the scenes, this would require very little changes to Iced! 79 | 80 | A `Primitive::Custom` variant would need to be added to the existing `iced_graphics::Primitive` enum, in order to 81 | have a way to pass a pipeline pointer to the `Renderer` and indicate its proper order in the primitive stack. 82 | 83 | We would also need to add a new field to an `iced_wgpu::Layer`, something along the lines of: 84 | 85 | ```rust 86 | pub struct Layer<'a> { 87 | //... 88 | custom: Vec, 89 | } 90 | ``` 91 | 92 | To indicate which pipelines are grouped within this layer. Or perhaps we could require that all custom pipelines are 93 | on separate layers, though that has performance implications. 94 | 95 | We would also need a way to cache & perform lookups for trait objects which implement `Renderable`; in my prototype 96 | I've simply used a `HashMap` inside of the `iced_wgpu::Backend`. 97 | 98 | Then, when rendering during frame presentation, we simply perform a lookup for the `PipelineId`s contains within the 99 | `Layer`, and perform their `prepare()` and `render()` methods. Done! 100 | 101 | ✅ **Pros of this design:** 102 | 103 | - Simple to integrate into existing Iced infrastructure without major refactors. 104 | - Performance is acceptable 105 | 106 | ❌ **Cons of this design:** 107 | 108 | - Not flexible 109 | - Even with preparing this very simple example I found myself needing to adjust the `Renderable` trait to give me 110 | more and more data unique to that pipeline that I needed for that specific render pass to render a cube. 111 | - Feels kinda hacky 112 | - `Primitive::Custom` feels as though it doesn't really belong in the existing `iced_graphics::Primitive` enum, 113 | but that's subjective! 114 | - `prepare()` and `render()` calls must be dynamically dispatched every frame per-pipeline & thus cannot be inlined. 115 | 116 | Overall I'm pretty unhappy with this implementation strategy, and feel as though it's too narrow for creating a truly 117 | flexible & modular system of adding custom shaders & pipelines to Iced. 118 | 119 | ### 🎨 #2: Custom Shader Widget 120 | 121 | The internals for this custom shader widget are very similar to the previous strategy; the main difference is that 122 | internally, *we* would create the custom primitive which holds the pointer to the pipeline data, not the user. The 123 | other difference is that the `Program` trait is merged with the `Renderable` trait, and that we create the widget 124 | implementation for the user, no custom widget required. 125 | 126 | Other concepts must be added, like the concept of `Time` in a render pass. In my prototype, I've implemented it at 127 | the `wgpu::Backend` level, but in its final form we would need to shift it up to the `Compositor` level, I believe. 128 | It's exposed to the user as a simple `Duration`, which is calculated from the difference between when the 129 | `iced_wgpu::Backend` is initialized up until that frame. 130 | 131 | There may be other information needed in the `Program` trait which is discovered as the implementation evolves. 132 | 133 | ✅ **Pros:** 134 | 135 | - Users who are already familiar with `Canvas` might find this type of widget familiar & intuitive to use. 136 | - More in line with Iced's style 137 | 138 | And, like the previous strategy: 139 | - Simple to integrate into existing Iced infrastructure without major refactors. 140 | - Performance is acceptable 141 | 142 | ❌ **Cons:** 143 | - Same cons as the previous strategy; very little flexibility, users must shoehorn their pipeline code to fit into 144 | this very specific trait `Program` provided by Iced. 145 | 146 | ### 🔠 Multiple Backend Support for Compositors 147 | 148 | Internally, this design is the most complex and requires the most changes to Iced, but I don't think it's so wildly 149 | complex that it would be hard to maintain! This design would require a few new concepts added to the wgpu `Compositor`: 150 | 151 | 💠 **Primitive Queue** 152 | 153 | There must be a backend-aware queue which keeps track of the actual ordering of how primitives should be 154 | rendered across all backends. I believe this could be implemented fairly easily either by having each `Backend` keep 155 | track of its own queue and having some data structure delegate at the appropriate moment with some form of marker 156 | indicating that we need to start rendering on another `Backend`. Some kind of order-tracking data structure is 157 | essential for ensuring proper rendering order when there are multiple backends. 158 | 159 | Widgets would request that their custom primitives be added to this queue when calling `renderer.draw_primitive()`. 160 | 161 | 👨‍💼 **Backend "Manager"** 162 | 163 | This would essentially be responsible for initializing all the backends (lazily, perhaps!) & delegating the proper 164 | primitives to the multiple `Backend`s for rendering. This would be initialized with the `Compositor` on application 165 | start. 166 | 167 | 👨‍💻 **Declarative backends!() macro** 168 | 169 | This would be initialized in `Application::run()` as a parameter, or could be exposed somewhere else potentially 170 | (perhaps as an associated type of `Application`?). I haven't super thoroughly thought it through, but my initial 171 | idea is to have it return a `backend::Manager` from its `TokenStream` which would be moved into the `Compositor`. 172 | 173 | ✅ **Pros:** 174 | - Flexible, users can do whatever they want with their own custom `Backend`. 175 | - Modular & additive; users can create a custom `Backend` library with their own primitives that it supports that 176 | can be initialized with the `Compositor`. 177 | - For users wanting to use a custom primitive from another library, or one they made, they would use it exactly how 178 | you use currently supported `Primitive`s in Iced, which would feel intuitive. 179 | 180 | ❌ **Cons:** 181 | - Would involve a hefty amount of codegen to do performantly 182 | - This would be quite a heavy refactor for the `iced_wgpu::Compositor`! 183 | - This design would preclude custom primitives being clipped together with other backend's primitives in 184 | its own `Layer` for transformations, scaling, etc. which might be undesirable. There might be a way to implement 185 | this within the `backend::Manager`, however! 186 | 187 | ### 🤔 Other Ideas 188 | 189 | I've had several ~~thousand~~ other ideas that I thought I'd give a brief mention. I've dismissed most of these as 190 | not being viable, but perhaps someone can think of a better way to implement them. 191 | 192 | 1) Primitive Concatenation with `#[primitive]` attribute macro 193 | 194 | This idea would involve heavily ~~ab~~using proc macros to essentially "embed" Iced's primitives into a user's custom 195 | primitive type, allowing them to set the primitive type of a `Renderer` at compile time, and include Iced's already 196 | supported primitives, without needing dynamic dispatch. Library authors of custom primitives & pipelines could then 197 | provide their own macro, which would be embedded into the `#[primitive]` attribute macro to keep concatenating 198 | enums until you had a final `Primitive` type. This however feels like more of a hack than a real solution, at least 199 | to me! 200 | 201 | 2) Fck it, build script 202 | 203 | This idea would involve a hefty amount of codegen to generate both the final `Primitive` type similar to above, and 204 | also the whole `Backend` with any custom pipelines embedded in it. This way a user has access to the exact same API 205 | as currently offered in Iced when working with custom primitives. Also dismissed due to hackiness, but some sort of 206 | build script might still be needed in the end (hopefully not to this extent!). 207 | 208 | ## 😶‍🌫️ Drawbacks 209 | 210 | After prototyping out ideas for the last 2 weeks, I have come to the conclusion that whatever strategy we 211 | decide, unless we do a hefty bit of codegen, will ultimately be less performant than just forking Iced & adding a 212 | new pipeline/primitive directly to the existing WGPU backend. I'm of the opinion it's not a bad thing to fork a 213 | library if you want to add something that others might not want, so perhaps that is the real solution. It's 214 | certainly the simplest! 215 | 216 | You *can* also use custom shaders right now with Iced, albeit in a limited way, so perhaps further integration is 217 | not needed. Perhaps we should look at a more composable way of creating a `Renderer` that can leverage already 218 | implemented support from Iced's `Renderer`s instead, or something along those lines. 219 | 220 | 221 | ## 🧐 Rationale and alternatives 222 | 223 | I believe I've already addressed most of these pros & cons in the `Implementation` section above! I will say that the 224 | implications of *not* doing this is that Iced would continue to be one of the few GUI libraries that does not offer 225 | direct access to its rendering backend (e.g. the `wgpu::Device` and `wgpu::CommandEncoder`) or just the ability in 226 | general to embed graphics-api-specific content in its existing widget tree. I believe that not allowing users to 227 | leverage the GPU for more complex scenes alongside the ease of using Iced's built-in widgets and rendering pipelines 228 | would be tragic! 😿 229 | 230 | 231 | ## 🧑‍🎨 Prior art 232 | 233 | The most prevalent example of this feature that I can think of is being able to get a direct `WebGL` context from a 234 | HTML5 `Canvas`, which allows users to render whatever they want (except things that use storage buffers! :P) on the 235 | web. This has allowed all kinds of content for the web that we didn't have before, like complex 3D scenes, whole 236 | games (RIP Flash), etc. that I think have made the web richer. Of course there is a performance implication of 237 | allowing such raw access on a platform which some might say is unoptimized for such tasks, but that is not really a 238 | consideration with Iced. 239 | 240 | A lot of other GUI frameworks allow use of custom shaders via OOP & inheritance (for example, by simply extending a 241 | `Primitive` class or interface which defines how to draw itself). Obviously Iced's implementation will need to 242 | differ since it is a functional library (although some form of trait object might be needed). Any game engine out there 243 | allows custom shaders & entities, all submitted to one (or multiple) render queues. 244 | 245 | Android allows for "shaders" using their own shader language, AGSL, which functions similar to Iced's `Canvas`, but 246 | also with GLSL and hooking directly into the GL context. They also use OOP & inheritance with a `GlSurfaceView` which 247 | extends `View` (which is the base abstract class for all Android widget tree elements). This allows users to 248 | interact with the (E)GL surface which provides a bounds that they can execute OpenGL commands within. 249 | 250 | The common thread amongst all of these implementations is that advanced users can access a raw handle to the 251 | graphics backend and draw to a specific region that is integrated into the rest of the DOM or ECS or widget tree or 252 | w/e to enable advanced drawing capabilities leveraging the GPU. I think the benefits of this type of flexible 253 | integration are readily apparent! 254 | 255 | ## 😵‍💫 Unresolved questions 256 | 257 | - What parts of the design do you expect to resolve through the RFC process before this gets merged? 258 | 259 | I would expect that this RFC resolves the overall direction of integrating flexible custom shader & pipeline support 260 | into Iced. I'd like to discuss & settle on a game plan before committing real time to the final implementation. 261 | 262 | - What parts of the design do you expect to resolve through the implementation of this feature before stabilization? 263 | 264 | The exact implementation of the intermediary data structures (if needed!) for managing rendering order & backends, 265 | small things that pop up during implementation that weren't considered. Once the general design is agreed upon I 266 | think the implementation details can be iterated on in PRs or through discussion in this RFC/Discord. 267 | 268 | - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? 269 | 270 | I think any actual implementations of custom shaders would be built on-top of this RFC, possibly in a separate crate 271 | within the Iced organization. 272 | 273 | ## 🤖 Future possibilities 274 | 275 | I think getting this design implemented correctly would open a huge number of doors for the Iced ecosystem. 276 | Being able to write your own shaders and create a custom widget which any user can plug into their own application 277 | and use seamlessly as part of Iced's widget tree is the ultimate form of customization. 278 | 279 | ### If you made it to the end, congratulations! 🥳 280 | 281 | Now, let's discuss! 282 | 283 | -------------------------------------------------------------------------------- /text/designs/23-01-pointer.md: -------------------------------------------------------------------------------- 1 | # 🙌 #1: Custom Primitive Pointer Design 2 | 3 | You can view a small, very, *very* rough and unrefined prototype of this design [here](https://github.com/bungoboingo/iced/tree/custom-shader/pipeline-marker/examples/custom_shader/src). 4 | 5 | The design of this implementation focuses on providing a fn pointer which both initializes the state of a custom 6 | pipeline, and returns a pointer to that state so the backend can cache & reuse. We would need to expose a type of 7 | trait which has methods necessary for rendering, something along the lines of: 8 | 9 | ```rust 10 | pub trait Renderable { 11 | fn prepare( 12 | &mut self, 13 | _device: &wgpu::Device, 14 | _queue: &wgpu::Queue, 15 | _encoder: &mut wgpu::CommandEncoder, 16 | _scale_factor: f32, 17 | _transformation: Transformation, 18 | _time: Duration, //used for shader animations, calculated every frame 19 | ); 20 | 21 | fn render( 22 | &self, 23 | encoder: &mut wgpu::CommandEncoder, 24 | _device: &wgpu::Device, 25 | _target: &wgpu::TextureView, 26 | _clear_color: Option, 27 | _scale_factor: f32, 28 | _target_size: Size, 29 | // final implementation may contain more information 30 | ); 31 | } 32 | ``` 33 | And have the ability to initialize a pipeline & retrieve a pointer to it's state: 34 | 35 | ```rust 36 | pub init: fn( 37 | device: &wgpu::Device, 38 | format: wgpu::TextureFormat, 39 | ) -> Box; 40 | ``` 41 | 42 | Which would need to be stored in a `Primitive`, e.g. `Primitive::Custom`, which could be used by a user like this: 43 | 44 | ```rust 45 | renderer.draw_primitive(Primitive::Custom { 46 | bounds, 47 | pipeline: CustomPipeline { 48 | id: self.id, // a pipeline identifier so we can look up the data pointer 49 | init: State::init, // an initialization fn pointer which returns a pointer to the pipeline data 50 | }, 51 | }) 52 | ``` 53 | 54 | This would be the entirety of the API exposed to a user of Iced. The rest of the implementation details would be 55 | handled internally by the wgpu `Backend`. 56 | 57 | For example, a typical implementation from a user in this "hands off" scenario might look something like this: 58 | 59 | ```rust 60 | pub struct Pipeline { 61 | pipeline: wgpu::RenderPipeline, 62 | vertices: wgpu::Buffer, 63 | indices: wgpu::Buffer, 64 | // ... 65 | } 66 | 67 | impl Pipeline { 68 | // We must provide a way for the Renderer to initialize this pipeline since it needs to hold a 69 | // pointer to this `State` 70 | fn init( 71 | device: &wgpu::Device, 72 | format: wgpu::TextureFormat, 73 | target_size: Size, 74 | ) -> Box { 75 | let vertices = device.create_buffer(&wgpu::BufferDescriptor { 76 | label: Some("cubes vertex buffer"), 77 | size: std::mem::size_of::<[Vertex3D; 8]>() as u64, 78 | usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, 79 | mapped_at_creation: false, 80 | }); 81 | 82 | //... rest of the wgpu pipeline initialization omitted for brevity! 83 | 84 | Box::new(Self { 85 | pipeline, 86 | vertices, 87 | indices, 88 | }) 89 | } 90 | } 91 | 92 | /// Implement the "renderable" trait for this `State` struct 93 | impl Renderable for Pipeline { 94 | fn prepare( 95 | &mut self, 96 | _device: &wgpu::Device, 97 | _queue: &wgpu::Queue, 98 | _encoder: &mut wgpu::CommandEncoder, 99 | _scale_factor: f32, 100 | _transformation: Transformation, 101 | _time: Duration, 102 | ) { 103 | // Allocate what data you want to render 104 | let mut cube = Cube::new(); 105 | queue.write_buffer(&self.vertices, 0, bytemuck::bytes_of(&cube)); 106 | } 107 | 108 | fn render( 109 | &self, 110 | encoder: &mut wgpu::CommandEncoder, 111 | _device: &wgpu::Device, 112 | _target: &wgpu::TextureView, 113 | _clear_color: Option, 114 | _scale_factor: f32, 115 | _target_size: Size, 116 | ) { 117 | let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 118 | /// omitted for brevity 119 | }); 120 | 121 | render_pass.set_pipeline(&self.pipeline); 122 | // issues command to the render_pass 123 | } 124 | } 125 | 126 | /// Include the custom "primitive" into a custom widget 127 | struct Cubes { 128 | height: Length, 129 | width: Length, 130 | id: u64, // a pipeline identifier 131 | } 132 | 133 | impl Widget> for Cubes { 134 | // ... 135 | 136 | fn draw( 137 | &self, 138 | _state: &Tree, 139 | renderer: &mut iced_graphics::Renderer, 140 | _theme: &T, 141 | _style: &Style, 142 | layout: Layout<'_>, 143 | _cursor_position: Point, 144 | _viewport: &Rectangle, 145 | ) { 146 | ///.. 147 | /// now pass the pointer to the renderer along with a unique pipeline ID for caching & lookup 148 | renderer.draw_primitive(Primitive::Custom { 149 | bounds, 150 | pipeline: CustomPipeline { 151 | id: self.id, 152 | init: Pipeline::init, 153 | }, 154 | }) 155 | } 156 | } 157 | ``` 158 | 159 | ```rust 160 | /// In your application code.. 161 | fn view(&self) -> Element<'_, Self::Message, Renderer> { 162 | Cubes::new() // Initiate your custom widget which draws a custom primitive 163 | .width(Length::Fill) 164 | .height(Length::Fill) 165 | .id(0) // set a pipeline ID so we can store a pointer to a specific "renderable" state 166 | .into() 167 | } 168 | ``` -------------------------------------------------------------------------------- /text/designs/23-02-widget.md: -------------------------------------------------------------------------------- 1 | # 🎨 #2: Custom Shader Widget 2 | 3 | You can view a rough prototype of this strategy here: [here](https://github.com/bungoboingo/iced/tree/custom-shader/widget/examples/custom_shader/src) 4 | 5 | Similar to how we currently have `Canvas` in Iced, this design would involve exposing a new built-in widget which is 6 | dependent on `wgpu` that has its own `Program` where a user can define how to render their own data. 7 | 8 | A custom shader widget might have a `Program` trait similar to a `canvas::Program`, but with methods from the 9 | `Renderable` trait from strategy #1. 10 | 11 | ```rust 12 | pub trait Program { 13 | fn update( 14 | &mut self, 15 | _event: Event, 16 | _bounds: Rectangle, 17 | _cursor: Cursor, 18 | _device: &wgpu::Device, 19 | _queue: &wgpu::Queue, 20 | _encoder: &mut wgpu::CommandEncoder, 21 | _scale_factor: f32, 22 | _transformation: Transformation, 23 | _time: Duration, 24 | ) -> (event::Status, Option); 25 | 26 | fn render( 27 | &self, 28 | _encoder: &mut wgpu::CommandEncoder, 29 | _device: &wgpu::Device, 30 | _target: &wgpu::TextureView, 31 | _clear_color: Option, 32 | _scale_factor: f32, 33 | _target_size: Size, 34 | ) -> RenderStatus; 35 | 36 | fn mouse_interaction( 37 | &self, 38 | _state: &Self::State, 39 | _bounds: Rectangle, 40 | _cursor: Cursor, 41 | ) -> mouse::Interaction { 42 | mouse::Interaction::default() 43 | } 44 | 45 | //possibly some more needed methods 46 | } 47 | ``` 48 | 49 | Similar to `Canvas`, but without a `draw()` method; instead there is a `render()` method which returns a new enum, 50 | `RenderStatus` (name TBD). Might need to change the name `Program`, as `Shader::Program` is an overloaded term! 51 | 52 | Users would return either `RenderStatus::Done` or `RenderStatus::Redraw` to indicate their render operation either 53 | can wait to be redrawn until the next application update, or must be redrawn immediately. This is the case when a 54 | shader has an animation. 55 | 56 | New to Iced & a parameter of the `update()` method is the concept of `time`. This is simply a duration of time that 57 | has passed since the start of the application. This can be used to animate shaders (see my prototype above for an 58 | example!). 59 | 60 | We will probably also end up using an associated `State` type similar to `Canvas` for handling internal state mutation. 61 | 62 | A user will create a new custom shader widget using the `Shader` widget implementation in `iced_graphics`. 63 | 64 | ```rust 65 | pub struct Shader { 66 | width: Length, 67 | height: Length, 68 | init: fn( 69 | device: &wgpu::Device, 70 | format: wgpu::TextureFormat, 71 | target_size: Size, 72 | ) -> Box, 73 | id: u64, //unique pipeline ID 74 | //properties subject to change! 75 | } 76 | ``` 77 | 78 | This provides the `Program` pointer, similar to the "custom primitive pointer design" I've listed in the previous file. 79 | Users will simply implement the `Program` trait for their own data structure & pass the initializer to the `Shader` 80 | widget. 81 | 82 | ```rust 83 | fn view(&self) -> Element<'_, Self::Message, Renderer> { 84 | Shader::new(Pipeline::init, 0) 85 | .width(Length::Fill) 86 | .height(Length::Fill) 87 | .into() 88 | } 89 | ``` 90 | 91 | Where `Pipeline::init` creates the pipeline code for wgpu. This is about all that is exposed to a user of the 92 | library! The rest is internal implementation details. 93 | -------------------------------------------------------------------------------- /text/designs/23-03-multi-backend.md: -------------------------------------------------------------------------------- 1 | # 🔠 Multiple Backend Support for Compositors 2 | 3 | I have no prototype to speak of this with strategy; it will involve a good amount of restructuring, possibly some 4 | codegen for performance reasons, and some intermediate data structures added to the compositor. That being said, I 5 | believe this is more along the lines of a "correct" solution for integrating custom shaders & pipelines into Iced as 6 | it allows the most flexibility & feels the least hacky. 7 | 8 | This strategy involves adding support for multiple `Backend`s per `Compositor`. See the diagram below for a rough 9 | outline of how it would work: 10 | 11 | ![](diagram.png) 12 | 13 | A user or library author would be responsible for creating their own `Backend` data structure that handles 14 | all primitives of a certain type. 15 | 16 | ```rust 17 | struct CustomBackend { 18 | // pipelines that the user has created! 19 | pipeline_3d: Pipeline3D, 20 | //... 21 | } 22 | 23 | impl Backend for CustomBackend { 24 | type Primitive = CustomPrimitive; 25 | type Layer = CustomLayer; 26 | } 27 | 28 | pub enum CustomPrimitive { 29 | Sphere(Sphere), 30 | Cube(Cube), 31 | //... 32 | } 33 | 34 | struct CustomLayer { 35 | pub spheres: Vec, 36 | pub cubes: Vec, 37 | //... 38 | } 39 | ``` 40 | This `CustomBackend` would need to implement a certain trait type, here named `Backend`, which could be defined 41 | something like this: 42 | 43 | ```rust 44 | pub trait Backend { 45 | type Primitive; 46 | type Layer; 47 | 48 | fn present( 49 | &mut self, 50 | device: &wgpu::Device, 51 | queue: &wgpu::Queue, 52 | encoder: &mut wgpu::CommandEncoder, 53 | clear_color: Option, 54 | format: wgpu::TextureFormat, 55 | frame: &wgpu::TextureView, 56 | primitives: &[Self::Primitive], 57 | viewport: &Viewport, 58 | ); 59 | 60 | fn prepare( 61 | &mut self, 62 | device: &wgpu::Device, 63 | queue: &wgpu::Queue, 64 | encoder: &mut wgpu::CommandEncoder, 65 | scale_factor: f32, 66 | transformation: Transformation, 67 | layers: &[Self::Layer<'_>], 68 | ); 69 | 70 | fn render( 71 | &mut self, 72 | device: &wgpu::Device, 73 | encoder: &mut wgpu::CommandEncoder, 74 | target: &wgpu::TextureView, 75 | clear_color: Option, 76 | scale_factor: f32, 77 | target_size: Size, 78 | layers: &[Self::Layer<'_>], 79 | ); 80 | 81 | // other methods might be needed! 82 | } 83 | ``` 84 | 85 | Users would include this send this backend to the Compositor in some way, either at runtime (box'd, dyn'd) with a 86 | `Command`, or I was thinking of a more performant solution involving codegen, either a build script or with a 87 | declarative macro, something like: 88 | 89 | ```rust 90 | Application::run( 91 | iced::Settings::default(), 92 | //.. other backends provided by library authors could be added in this declarative macro! 93 | backends!(Iced, CustomBackend, OtherBacked,), 94 | ) 95 | ``` 96 | 97 | From a user's perspective, that's it! Just including the backend would map it internally to a custom primitive type, 98 | and then they would be able to draw a custom primitive same as any other Iced primitive. 99 | 100 | ```rust 101 | //... 102 | renderer.draw_primitive( 103 | CustomPrimitive::Sphere(Sphere { 104 | u_sections: 16, 105 | v_sections: 16, 106 | radius: 4.0, 107 | }) 108 | ) 109 | ``` 110 | 111 | -------------------------------------------------------------------------------- /text/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iced-rs/rfcs/464a0fd52693a509228bdd99e06f9b25670dbf9b/text/diagram.png -------------------------------------------------------------------------------- /text/silvia.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iced-rs/rfcs/464a0fd52693a509228bdd99e06f9b25670dbf9b/text/silvia.jpeg --------------------------------------------------------------------------------