From a8bc89d8fc0b3ce8b1c7cd8033f92e19e0264b63 Mon Sep 17 00:00:00 2001 From: Billy Messenger <60663878+BillyDM@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:12:30 -0500 Subject: [PATCH] edits, and write Responding to Actions section --- src/.quick_start/setting_up.md | 1 - src/SUMMARY.md | 3 +- src/getting_started.md | 15 +- src/img/responding_to_actions_1.png | Bin 0 -> 6340 bytes src/img/static_layout_3.png | Bin 0 -> 4221 bytes src/quick_start.md | 2 +- src/quick_start/adding_style.md | 24 +- src/quick_start/dynamic_layout.md | 6 +- src/quick_start/responding_to_actions.md | 316 ++++++++++++++++++++++- src/quick_start/setting_up.md | 32 +-- src/quick_start/static_layout.md | 10 +- src/quick_start/your_first_element.md | 21 +- src/quick_start/z_indexes.md | 3 + 13 files changed, 359 insertions(+), 74 deletions(-) delete mode 100644 src/.quick_start/setting_up.md create mode 100644 src/img/responding_to_actions_1.png create mode 100644 src/img/static_layout_3.png create mode 100644 src/quick_start/z_indexes.md diff --git a/src/.quick_start/setting_up.md b/src/.quick_start/setting_up.md deleted file mode 100644 index 2519740..0000000 --- a/src/.quick_start/setting_up.md +++ /dev/null @@ -1 +0,0 @@ -# Setting Up diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 2029273..5255045 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -9,4 +9,5 @@ - [Static Layout](./quick_start/static_layout.md) - [Dynamic Layout](./quick_start/dynamic_layout.md) - [Adding Style](./quick_start/adding_style.md) - - [Responding to Actions](./quick_start/responding_to_actions.md) \ No newline at end of file + - [Responding to Actions](./quick_start/responding_to_actions.md) + - [Z Indexes](./quick_start/z_indexes.md) \ No newline at end of file diff --git a/src/getting_started.md b/src/getting_started.md index 98bb131..888aacd 100644 --- a/src/getting_started.md +++ b/src/getting_started.md @@ -6,11 +6,22 @@ The Yarrow framework is built using the Rust programming language. To use Yarrow ## Running the Examples -The [Yarrow repository](https://github.com/MeadowlarkDAW/Yarrow) on github contains some example applications. To run these examples, first clone the repository to a local directory. Then inside a terminal, navigate to the root directory and run the following command: +The [Yarrow repository](https://github.com/MeadowlarkDAW/Yarrow) on github contains some [example applications](https://github.com/MeadowlarkDAW/Yarrow/tree/main/examples). To run these examples, first clone the repository and enter the root directory of the project in a terminal: + +``` +git clone https://github.com/MeadowlarkDAW/Yarrow.git +cd Yarrow +``` + +Then run the following command: `cargo run --example ` -Where `` should be replaced with the example name (i.e. `cargo run --example gallery`) +Where `` should be replaced with the example name, for example: + +`cargo run --example gallery` + +> The first compile may take quite a while as it is compiling an entire GUI library from scratch. Subsequent compiles should be much faster. ## Join the Community diff --git a/src/img/responding_to_actions_1.png b/src/img/responding_to_actions_1.png new file mode 100644 index 0000000000000000000000000000000000000000..0802dd4102bcd3f85071ae74f98c490b45e12759 GIT binary patch literal 6340 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 literal 0 HcmV?d00001 diff --git a/src/quick_start.md b/src/quick_start.md index 278b04f..5528470 100644 --- a/src/quick_start.md +++ b/src/quick_start.md @@ -4,4 +4,4 @@ This chapter will walk you through the basics of Yarrow by creating a simple application. -This book is not meant as an introduction to the Rust programming language. If you are new to Rust, it is highly recommended to first read through the [official Rust book](https://doc.rust-lang.org/book/). You can find even more Rust learning resources at [https://www.rust-lang.org/learn](https://www.rust-lang.org/learn). \ No newline at end of file +This book is not meant as an introduction to the Rust programming language. If you are new to Rust, it is highly recommended to first read through the [official Rust book](https://doc.rust-lang.org/book/). More Rust learning resources can also be found at [https://www.rust-lang.org/learn](https://www.rust-lang.org/learn). \ No newline at end of file diff --git a/src/quick_start/adding_style.md b/src/quick_start/adding_style.md index 1d75ce5..e4b80d4 100644 --- a/src/quick_start/adding_style.md +++ b/src/quick_start/adding_style.md @@ -21,14 +21,10 @@ impl MyStyle { true, // 4 LabelStyle { // 5 back_quad: QuadStyle { - bg: Background::Solid(RGBA8::new(100, 30, 80, 255)), - border: BorderStyle { - color: RGBA8::new(200, 60, 160, 255), - width: 2.0, - radius: 10.0.into(), - }, + bg: background_hex(0x641e50), + border: border(hex(0xc83ca0), 2.0, radius(10.0)), }, - text_padding: Padding::new(10.0, 10.0, 10.0, 10.0), + text_padding: padding_all_same(10.0), ..Default::default() // 6 }, ); @@ -46,6 +42,7 @@ impl MyStyle { Now store our new style struct in `MyApp` and load it when the application starts: ```rust +#[derive(Default)] struct MyApp { // ... @@ -53,17 +50,6 @@ struct MyApp { style: MyStyle, } -impl MyApp { - fn new(action_sender: ActionSender<()>, action_receiver: ActionReceiver<()>) -> Self { - Self { - // ... - - // new - style: MyStyle::default(), - } - } -} - impl Application for MyApp { type Action = (); @@ -102,7 +88,7 @@ By default all elements have a style which is very bare-bones (and most of the t At the time of this writing, Yarrow has only one built-in theme called "Yarrow dark". To use it, simply add this inside of `MyStyle::load()`: ```rust -yarrow::theme::yarrow_dark::load(None, None, res); +yarrow::theme::yarrow_dark::load(Default::default(), res); ``` After loading a theme, it's probably a good idea to use a class for our custom fancy label so it doesn't conflict with the default one in the theme: diff --git a/src/quick_start/dynamic_layout.md b/src/quick_start/dynamic_layout.md index 0be3729..3b7847e 100644 --- a/src/quick_start/dynamic_layout.md +++ b/src/quick_start/dynamic_layout.md @@ -1,8 +1,8 @@ # Dynamic Layout -Setting the bounding rectangle inside of the element builder works great and all for static content, but what if we wanted the layout to dynamically change due to a change in the application state (or the window being resized)? And for that matter, what if we wanted to layout other elements based on the size of the text in the label element? +Setting the bounding rectangle inside via element builder works fine for static content, but what if we wanted the layout to dynamically change due to a change in the application state (or the window being resized)? And for that matter, what if we wanted to layout other elements based on the size of the text in the label element? -To achieve this, we will define a "layout function" for our main window. Remove the `.bounding_rect` property from the Label builder and then add the following method to `MainWindowElements`: +To achieve this, we will define a "layout function" for our main window. Remove the `.rect` property from the Label builder and then add the following method to `MainWindowElements`: ```rust impl MainWindowElements { @@ -32,7 +32,7 @@ impl MainWindowElements { > self.hello_label.layout_aligned(window_rect.center(), Align2::CENTER, cx.res); > ``` -No we must call the layout function after the main window is built and whenever the window resizes. To do this, add the following to the `on_window_event` trait method: +Now we must call that layout function after the main window is built and whenever the window resizes. To do this, add the following to the `on_window_event` trait method: ```rust fn on_window_event( diff --git a/src/quick_start/responding_to_actions.md b/src/quick_start/responding_to_actions.md index 5c4ab5d..4f716e2 100644 --- a/src/quick_start/responding_to_actions.md +++ b/src/quick_start/responding_to_actions.md @@ -1,3 +1,317 @@ # Responding to Actions -[TODO] \ No newline at end of file +Now it's time to make our application do things. Let's start by making a simple counter that gets incremented when the user selects the "increase" button, gets decremented when the user selects the "decrease" button, and resets when the user selects the "reset" button. + +In Yarrow, any single event that a user can perform in your application is called an "Action". *(While I could have called it an "Event", I chose this name to differentiate it from events that elements receive from Yarrow.)* + +## Defining the Action Type + +We get to define what actions we want. Rust's `enum` type works especially well here 🦀: + +```rust +#[derive(Clone)] // 1 +pub enum MyAction { + OffsetCounterBy(i32), // 2 + ResetCounter, +} +``` + +1. Actions must implement [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html). +2. While we could have just defined two enum variants called `IncrementValue` and `DecrementValue`, I want to show an example of an action that has associated data. + +Now that we have defined a type for our actions, update the code with the following changes: + +```rust +impl MainWindowElements { + fn build(cx: &mut WindowContext<'_, MyAction>) -> Self { +// ... + fn layout(&mut self, cx: &mut WindowContext<'_, MyAction>) { +// ... + +impl Application for MyApp { + type Action = MyAction; +// ... +``` + +## Storing and Syncing State + +We also need to store the state of our counter somewhere. Also, let's add a method to `MyApp` that syncs the label text to the state: + +```rust +#[derive(Default)] +struct MyApp { + count: i32, // new +// ... + +// new +impl MyApp { + pub fn sync_state(&mut self, cx: &mut WindowContext<'_, MyAction>) { + let Some(elements) = &mut self.main_window_elements else { // 1 + return; + }; + + // 2 + if elements + .hello_label + .set_text(Some(&format!("{}", self.count)), cx.res) // 3 + { + // Changing the text may resize the label, so do a layout. + elements.layout(&self.style, cx); + } + } +} + +impl Application for MyApp { + // ... + + fn on_window_event( + &mut self, + event: AppWindowEvent, + window_id: WindowID, + cx: &mut AppContext, + ) { + match event { + AppWindowEvent::WindowOpened => { + if window_id == MAIN_WINDOW { + self.style.load(&mut cx.res); + + let mut main_window_cx = cx.window_context(MAIN_WINDOW).unwrap(); + + self.main_window_elements = + Some(MainWindowElements::build(&mut main_window_cx)); + + // new + self.sync_state(&mut main_window_cx); + } + } +// ... +``` + +1. The [`let-else pattern`](https://doc.rust-lang.org/beta/rust-by-example/flow_control/let_else.html) in Rust is useful to extract the elements without placing ugly `unwrap`'s everywhere. +2. Each setter on an element handle returns a bool on whether or not the value has changed. (TODO: Actually implement this lol) +3. Note that setting a value on an element handle won't trigger an update in Yarrow's system unless that value has changed. Therefore you can still get good performance even when you have a single `sync_state` function like this. However, if the performance still isn't good enough, you can change it to be as optimized and fine-grained as you wish. You are in control! + +## Add Buttons + +Now we will create the three new buttons: + +```rust +struct MainWindowElements { + // new + increment_btn: Button, + decrement_btn: Button, + reset_btn: Button, + +// ... + +impl MainWindowElements { + fn build(count: i32, cx: &mut WindowContext<'_, MyAction>) -> Self { + Self { + // new + increment_btn: Button::builder() + .text("+") + .build(cx), + decrement_btn: Button::builder() + .text("-") + .build(cx), + reset_btn: Button::builder() + .text("reset") + .build(cx), +// ... +``` + +Now to lay out the buttons so they are visible. While we're at it, let's add additional styling information in our `MyStyle` struct so we can easily tweak all style parameters from a single location: + +```rust +struct MyStyle { + // new + window_padding: Padding, + button_spacing: f32, +} + +// new +impl Default for MyStyle { + fn default() -> Self { + Self { + window_padding: padding_all_same(10.0), + button_spacing: 8.0, + } + } +} + +impl MainWindowElements { + // ... + + // changed + fn layout(&mut self, style: &MyStyle, cx: &mut WindowContext<'_, MyAction>) { + // ... + + // new + self.increment_btn.layout( + point(style.window_padding.left, style.window_padding.top), + cx.res, + ); + self.decrement_btn.layout( + point( + self.increment_btn.el.rect().max_x() + style.button_spacing, + style.window_padding.top, + ), + cx.res, + ); + self.reset_btn.layout( + point( + self.decrement_btn.el.rect().max_x() + style.button_spacing, + style.window_padding.top, + ), + cx.res, + ); + } +// ... + +impl Application for MyApp { + type Action = MyAction; + + fn on_window_event( + &mut self, + event: AppWindowEvent, + window_id: WindowID, + cx: &mut AppContext, + ) { + match event { + AppWindowEvent::WindowOpened => { + if window_id == MAIN_WINDOW { + // ... + + self.main_window_elements + .as_mut() + .unwrap() + .layout(&self.style, &mut main_window_cx) // changed + } + } + AppWindowEvent::WindowResized => { + if window_id == MAIN_WINDOW { + // ... + + self.main_window_elements + .as_mut() + .unwrap() + .layout(&self.style, &mut main_window_cx) // changed +// ... + +``` + +The app should now display the following: + +![Responding to Actions 1](../img/responding_to_actions_1.png) + +## Handle the Actions + +Finally, let's add the appropriate actions to the buttons and handle them: + +```rust +impl MainWindowElements { + fn build(cx: &mut WindowContext<'_, MyAction>, count: i32) -> Self { + Self { + increment_btn: Button::builder() + .text("+") + .on_select(MyAction::OffsetCounterBy(1)) // new + .build(cx), + decrement_btn: Button::builder() + .text("-") + .on_select(MyAction::OffsetCounterBy(-1)) // new + .build(cx), + reset_btn: Button::builder() + .text("reset") + .on_select(MyAction::ResetCounter) // new + .build(cx), +// ... + +impl Application for MyApp { + type Action = MyAction; + + // new + fn on_action_emitted(&mut self, cx: &mut AppContext) { + let Some(mut main_window_cx) = cx.window_context(MAIN_WINDOW) else { // 1 + return; + }; + + while let Ok(action) = cx.action_receiver.try_recv() { + match action { + MyAction::OffsetCounterBy(offset) => { + self.count += offset; + self.sync_state(&mut main_window_cx); + } + MyAction::ResetCounter => { + self.count = 0; + self.sync_state(&mut main_window_cx); + } + } + } + } +// ... +``` + +1. The [`let-else pattern`](https://doc.rust-lang.org/beta/rust-by-example/flow_control/let_else.html) can be useful here again so the window context doesn't have to be extracted from the application context inside every match variant. Although note this won't work if you have multiple windows in your application. + +Now run the application and count things! + +## Improving `sync_state` + +One last thing, while our `sync_count` function is fine for this simple application, in a larger application you will probably want to only perform a single state sync and layout once for every invocation of `on_action_emitted`. + +One strategy is to pass around a bool like this: + +```rust +impl MyApp { + pub fn sync_state(&mut self, cx: &mut WindowContext<'_, MyAction>) { + let Some(elements) = &mut self.main_window_elements else { + return; + }; + + let mut needs_layout = false; + + if elements + .hello_label + .set_text(Some(&format!("{}", self.count)), cx.res) + { + // Changing the text may resize the label, so do a layout. + needs_layout = true; + } + + if needs_layout { + elements.layout(&self.style, cx); + } + } +} + +impl Application for MyApp { + type Action = MyAction; + + fn on_action_emitted(&mut self, cx: &mut AppContext) { + let Some(mut main_window_cx) = cx.window_context(MAIN_WINDOW) else { + return; + }; + + let mut state_changed = false; + + while let Ok(action) = main_window_cx.action_receiver.try_recv() { + match action { + MyAction::OffsetCounterBy(offset) => { + self.count += offset; + state_changed = true; + } + MyAction::ResetCounter => { + self.count = 0; + state_changed = true; + } + } + } + + if state_changed { + self.sync_state(&mut main_window_cx); + } + } +// ... +``` + +And again, it's up to you how fine-grained and optimized you want your state synchronization and layout to be. You are in control! \ No newline at end of file diff --git a/src/quick_start/setting_up.md b/src/quick_start/setting_up.md index 5e48631..897dfbd 100644 --- a/src/quick_start/setting_up.md +++ b/src/quick_start/setting_up.md @@ -15,7 +15,7 @@ version = "0.1.0" edition = "2021" [dependencies] -yarrow = { git = "https://github.com/MeadowlarkDAW/Yarrow.git", branch = "main" } +yarrow = { git = "https://github.com/MeadowlarkDAW/Yarrow.git", rev = "ddc9636363e319be910d701686403a2f1348ebf0" } ``` > Yarrow does not have a recent version published on crates.io yet, so you will need to use the git version for now. @@ -29,6 +29,8 @@ Some aspects of Yarrow such as text shaping can run very slow when compiled in d opt-level = 1 ``` +This tells cargo to enable a small amount of optimizations when compiling in debug mode. You can learn more about cargo profiles [here](https://doc.rust-lang.org/cargo/reference/profiles.html). + ## Initializing the Application In `main.rs`, replace the default contents with the following: @@ -40,28 +42,14 @@ pub fn main() { let (action_sender, action_receiver) = yarrow::action_channel(); // 2 yarrow::run_blocking( // 3 - MyApp::new(action_sender.clone(), action_receiver), + MyApp::default(), action_sender, ) .unwrap(); } -struct MyApp { // 4 - _action_sender: ActionSender<()>, - _action_receiver: ActionReceiver<()>, -} - -impl MyApp { - fn new( - action_sender: ActionSender<()>, - action_receiver: ActionReceiver<()> - ) -> Self { - Self { - _action_sender: action_sender, - _action_receiver: action_receiver, - } - } -} +#[derive(Default)] +struct MyApp {} // 4 impl Application for MyApp { // 5 type Action = (); // 6 @@ -70,12 +58,12 @@ impl Application for MyApp { // 5 Here is a breakdown of what is happening: -1. Import a bunch of types from Yarrow. Yarrow provides a `prelude` module that re-exports most of everything you need for simplicity. +1. Import a bunch of types from Yarrow. For simplicity, Yarrow provides a `prelude` module that re-exports most of everything you need. 2. Create an action channel. We will cover what actions are and how they work later in this chapter. For now just know this is essentially an [mpsc](https://doc.rust-lang.org/std/sync/mpsc/) channel from Rust's standard library. -3. Run our application. For standalone applications you will want to use `run_blocking`, but Yarrow contains other methods for running in different kinds of contexts (like an audio plugin). -4. Our application struct. This is where we will store everything related to our application. Note that we are holding onto the action channel objects, as we will use them later. (Your application struct must hold on to at least the `ActionReceiver` object or it might panic). +3. Run the application. For standalone applications you will want to use `run_blocking`, but Yarrow contains other methods for running in different contexts (like an audio plugin). +4. Our application struct. This is where we will store everything related to our application. 5. The `yarrow::Application` trait. We must implement this for our application object. -6. Define the type to use as our applications's action message. For now we have no actions, so we will set it to the empty type `()`. +6. Define the type to use as our applications's action enum. For now we have no actions, so set it to the empty type `()`. ## Running the Application diff --git a/src/quick_start/static_layout.md b/src/quick_start/static_layout.md index f0f36af..058b80f 100644 --- a/src/quick_start/static_layout.md +++ b/src/quick_start/static_layout.md @@ -2,12 +2,12 @@ If you run the application now, you will see that the window is still blank. This is because in order for elements to become active and be rendered, they must have a defined "bounding rectangle". -You can add a bounding rectangle by using the `bounding_rect` property on the label builder like this. Now the label is visible when the app is run. +You can add a bounding rectangle by using the `rect` property on the label builder like this. Now the label is visible when the app is run. ```rust hello_label: Label::builder() .text("Hello World!") - .bounding_rect(rect(0.0, 0.0, 100.0, 30.0)) // new + .rect(rect(0.0, 0.0, 100.0, 30.0)) // new .build(cx), ``` @@ -16,7 +16,7 @@ hello_label: Label::builder() If we want to place the label in a different position, all we need to do is change the (x, y) values in the bounding rect: ```rust -.bounding_rect(rect(100.0, 40.0, 100.0, 30.0)) +.rect(rect(100.0, 40.0, 100.0, 30.0)) ``` ![Static Layout 2](../img/static_layout_2.png) @@ -24,7 +24,7 @@ If we want to place the label in a different position, all we need to do is chan Also note that if we make the (width, height) values in the bounding rect small enough, the text will be clipped: ```rust -.bounding_rect(rect(100.0, 40.0, 30.0, 10.0)) +.rect(rect(100.0, 40.0, 55.0, 10.0)) ``` -[TODO: screenshot, and actually make the text clip because I must have broke that lol] \ No newline at end of file +![Static Layout 3](../img/static_layout_3.png) \ No newline at end of file diff --git a/src/quick_start/your_first_element.md b/src/quick_start/your_first_element.md index 03f9a3b..09c21db 100644 --- a/src/quick_start/your_first_element.md +++ b/src/quick_start/your_first_element.md @@ -12,31 +12,14 @@ pub struct MainWindowElements { } ``` -Why can't we just add `hello_label` directly to our `MyApp` struct? The reason is that a Yarrow application is designed to work even when the main window isn't open. This is a useful concept for audio plugins. Plus, this allows the same behavior to work for both the main window and any child windows in our application (Yarrow has first-class multi-window support!) +So why can't we just add `hello_label` directly to our `MyApp` struct? The reason is that Yarrow applications are designed to work even when the main window isn't open. Not only is this a useful concept for audio plugins, but this allows the same behavior to work for both the main window and any child windows in our application (Yarrow has first-class multi-window support!) Now add the following field to the `MyApp` struct: ```rust +#[derive(Default)] struct MyApp { main_window_elements: Option, // new - - _action_sender: ActionSender<()>, - _action_receiver: ActionReceiver<()>, -} -``` - -And in `MyApp::new()`: -```rust -fn new( - action_sender: ActionSender<()>, - action_receiver: ActionReceiver<()> -) -> Self { - Self { - main_window_elements: None, // new - - _action_sender: action_sender, - _action_receiver: action_receiver, - } } ``` diff --git a/src/quick_start/z_indexes.md b/src/quick_start/z_indexes.md new file mode 100644 index 0000000..f0d72ce --- /dev/null +++ b/src/quick_start/z_indexes.md @@ -0,0 +1,3 @@ +# Z Indexes + +[TODO] \ No newline at end of file