diff --git a/book/next/basic_concepts/components.html b/book/next/basic_concepts/components.html index c8c0dbf4aa74..8d0f472c3871 100644 --- a/book/next/basic_concepts/components.html +++ b/book/next/basic_concepts/components.html @@ -184,7 +184,7 @@

Components

The Component trait

The Component trait is the base of every component inside Relm4, it defines how a component should behave, communicate and produce widgets.

The SimpleComponent trait

-

The SimpleComponent trait is a convenience trait that implements the Component trait, but removes some advanced features that are not relevant for most use-cases.

+

The SimpleComponent trait is a convenience trait that implements the Component trait, but removes some advanced features that are not relevant for most use-cases.

For each implementation of SimpleComponent, Relm4 will automatically implement Component as well. Thus, it can also be used instead of Component. This mechanism is called blanket implementation and is used for traits like From in the standard library as well.

diff --git a/book/next/basic_concepts/messages.html b/book/next/basic_concepts/messages.html index 590d052340e6..40df43dd8d6c 100644 --- a/book/next/basic_concepts/messages.html +++ b/book/next/basic_concepts/messages.html @@ -181,7 +181,7 @@

GUI development with Relm4

Messages

To help the computer understand what we want to tell it, we first translate user interactions into messages.

-

In Relm4, a message can be any data type, but most often, an enum is used.

+

In Relm4, a message can be any data type, but most often, an enum is used.

enum AppInput {
     Increment,
     Decrement,
diff --git a/book/next/basic_concepts/model.html b/book/next/basic_concepts/model.html
index 1d6dacbc45da..b47a78d4da8e 100644
--- a/book/next/basic_concepts/model.html
+++ b/book/next/basic_concepts/model.html
@@ -181,7 +181,7 @@ 

GUI development with Relm4

Model

Like a person, a computer needs a brain to be functional. It needs to process our messages and remember the results.

-

Relm4 uses the term model as a data type that represents the application state, the memory of your application.

+

Relm4 uses the term model as a data type that represents the application state, the memory of your application.

For example, to store a counter value, we can store a u8 in our model:

struct AppModel {
     counter: u8,
diff --git a/book/next/child_components.html b/book/next/child_components.html
index e2f1a30102d9..0162ec70df20 100644
--- a/book/next/child_components.html
+++ b/book/next/child_components.html
@@ -264,7 +264,7 @@ 

The a

When initializing the model, we conditionally set up some widgets based on the settings passed by the caller. We set is_active to false since the dialog is not currently displayed.

    fn init(
         settings: AlertSettings,
-        root: &Self::Root,
+        root: Self::Root,
         sender: ComponentSender<Self>,
     ) -> ComponentParts<Self> {
         let model = Alert {
@@ -284,8 +284,8 @@ 

The a let accept_widget = widgets .dialog .widget_for_response(gtk::ResponseType::Accept) - .expect("No button for accept response set"); - accept_widget.add_css_class("destructive-action"); + .expect("No button for accept response set"); + accept_widget.add_css_class("destructive-action"); } ComponentParts { model, widgets } @@ -293,7 +293,7 @@

The a

Lastly, the view. Note that the component connects to the response signal of the underlying dialog and sends an input to itself when a response is received.

    view! {
-        #[name = "dialog"]
+        #[name = "dialog"]
         gtk::MessageDialog {
             set_message_type: gtk::MessageType::Question,
             #[watch]
@@ -337,7 +337,7 @@ 

Usage

view! { main_window = gtk::ApplicationWindow { - set_title: Some("Simple app"), + set_title: Some("Simple app"), set_default_width: 300, set_default_height: 100, @@ -352,13 +352,13 @@

Usage

set_spacing: 5, append = &gtk::Button { - set_label: "Increment", + set_label: "Increment", connect_clicked[sender] => move |_| { sender.input(AppMsg::Increment); }, }, append = &gtk::Button { - set_label: "Decrement", + set_label: "Decrement", connect_clicked[sender] => move |_| { sender.input(AppMsg::Decrement); }, @@ -366,10 +366,10 @@

Usage

append = &gtk::Label { set_margin_all: 5, #[watch] - set_label: &format!("Counter: {}", model.counter), + set_label: &format!("Counter: {}", model.counter), }, append = &gtk::Button { - set_label: "Close", + set_label: "Close", connect_clicked[sender] => move |_| { sender.input(AppMsg::CloseRequest); }, @@ -399,7 +399,7 @@

Usage

} } AppMsg::Save => { - println!("* Open save dialog here *"); + println!("* Open save dialog here *"); } AppMsg::Close => { relm4::main_application().quit(); @@ -408,30 +408,30 @@

Usage

} } - fn init(_: (), root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> { + fn init(_: (), root: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> { let model = App { counter: 0, alert_toggle: false, dialog: Alert::builder() - .transient_for(root) + .transient_for(&root) .launch(AlertSettings { - text: String::from("Do you want to quit without saving? (First alert)"), - secondary_text: Some(String::from("Your counter hasn't reached 42 yet")), - confirm_label: String::from("Close without saving"), - cancel_label: String::from("Cancel"), - option_label: Some(String::from("Save")), + text: String::from("Do you want to quit without saving? (First alert)"), + secondary_text: Some(String::from("Your counter hasn't reached 42 yet")), + confirm_label: String::from("Close without saving"), + cancel_label: String::from("Cancel"), + option_label: Some(String::from("Save")), is_modal: true, destructive_accept: true, }) .forward(sender.input_sender(), convert_alert_response), second_dialog: Alert::builder() - .transient_for(root) + .transient_for(&root) .launch(AlertSettings { - text: String::from("Do you want to quit without saving? (Second alert)"), - secondary_text: Some(String::from("Your counter hasn't reached 42 yet")), - confirm_label: String::from("Close without saving"), - cancel_label: String::from("Cancel"), - option_label: Some(String::from("Save")), + text: String::from("Do you want to quit without saving? (Second alert)"), + secondary_text: Some(String::from("Your counter hasn't reached 42 yet")), + confirm_label: String::from("Close without saving"), + cancel_label: String::from("Cancel"), + option_label: Some(String::from("Save")), is_modal: true, destructive_accept: true, }) @@ -453,7 +453,7 @@

Usage

} fn main() { - let app = RelmApp::new("relm4.example.alert"); + let app = RelmApp::new("relm4.example.alert"); app.run::<App>(()); }

This is mostly stuff that we've already done in previous chapters, but there are a few additional things to know about interacting with child components.

@@ -465,30 +465,30 @@

Usage

second_dialog: Controller<Alert>, }

We initialize them with the builder pattern in the init method.

-
    fn init(_: (), root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
+
    fn init(_: (), root: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
         let model = App {
             counter: 0,
             alert_toggle: false,
             dialog: Alert::builder()
-                .transient_for(root)
+                .transient_for(&root)
                 .launch(AlertSettings {
-                    text: String::from("Do you want to quit without saving? (First alert)"),
-                    secondary_text: Some(String::from("Your counter hasn't reached 42 yet")),
-                    confirm_label: String::from("Close without saving"),
-                    cancel_label: String::from("Cancel"),
-                    option_label: Some(String::from("Save")),
+                    text: String::from("Do you want to quit without saving? (First alert)"),
+                    secondary_text: Some(String::from("Your counter hasn't reached 42 yet")),
+                    confirm_label: String::from("Close without saving"),
+                    cancel_label: String::from("Cancel"),
+                    option_label: Some(String::from("Save")),
                     is_modal: true,
                     destructive_accept: true,
                 })
                 .forward(sender.input_sender(), convert_alert_response),
             second_dialog: Alert::builder()
-                .transient_for(root)
+                .transient_for(&root)
                 .launch(AlertSettings {
-                    text: String::from("Do you want to quit without saving? (Second alert)"),
-                    secondary_text: Some(String::from("Your counter hasn't reached 42 yet")),
-                    confirm_label: String::from("Close without saving"),
-                    cancel_label: String::from("Cancel"),
-                    option_label: Some(String::from("Save")),
+                    text: String::from("Do you want to quit without saving? (Second alert)"),
+                    secondary_text: Some(String::from("Your counter hasn't reached 42 yet")),
+                    confirm_label: String::from("Close without saving"),
+                    cancel_label: String::from("Cancel"),
+                    option_label: Some(String::from("Save")),
                     is_modal: true,
                     destructive_accept: true,
                 })
@@ -578,7 +578,7 @@ 

The compl type Output = AlertResponse; view! { - #[name = "dialog"] + #[name = "dialog"] gtk::MessageDialog { set_message_type: gtk::MessageType::Question, #[watch] @@ -598,7 +598,7 @@

The compl fn init( settings: AlertSettings, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let model = Alert { @@ -618,8 +618,8 @@

The compl let accept_widget = widgets .dialog .widget_for_response(gtk::ResponseType::Accept) - .expect("No button for accept response set"); - accept_widget.add_css_class("destructive-action"); + .expect("No button for accept response set"); + accept_widget.add_css_class("destructive-action"); } ComponentParts { model, widgets } @@ -669,7 +669,7 @@

The compl view! { main_window = gtk::ApplicationWindow { - set_title: Some("Simple app"), + set_title: Some("Simple app"), set_default_width: 300, set_default_height: 100, @@ -684,13 +684,13 @@

The compl set_spacing: 5, append = &gtk::Button { - set_label: "Increment", + set_label: "Increment", connect_clicked[sender] => move |_| { sender.input(AppMsg::Increment); }, }, append = &gtk::Button { - set_label: "Decrement", + set_label: "Decrement", connect_clicked[sender] => move |_| { sender.input(AppMsg::Decrement); }, @@ -698,10 +698,10 @@

The compl append = &gtk::Label { set_margin_all: 5, #[watch] - set_label: &format!("Counter: {}", model.counter), + set_label: &format!("Counter: {}", model.counter), }, append = &gtk::Button { - set_label: "Close", + set_label: "Close", connect_clicked[sender] => move |_| { sender.input(AppMsg::CloseRequest); }, @@ -731,7 +731,7 @@

The compl } } AppMsg::Save => { - println!("* Open save dialog here *"); + println!("* Open save dialog here *"); } AppMsg::Close => { relm4::main_application().quit(); @@ -740,30 +740,30 @@

The compl } } - fn init(_: (), root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> { + fn init(_: (), root: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> { let model = App { counter: 0, alert_toggle: false, dialog: Alert::builder() - .transient_for(root) + .transient_for(&root) .launch(AlertSettings { - text: String::from("Do you want to quit without saving? (First alert)"), - secondary_text: Some(String::from("Your counter hasn't reached 42 yet")), - confirm_label: String::from("Close without saving"), - cancel_label: String::from("Cancel"), - option_label: Some(String::from("Save")), + text: String::from("Do you want to quit without saving? (First alert)"), + secondary_text: Some(String::from("Your counter hasn't reached 42 yet")), + confirm_label: String::from("Close without saving"), + cancel_label: String::from("Cancel"), + option_label: Some(String::from("Save")), is_modal: true, destructive_accept: true, }) .forward(sender.input_sender(), convert_alert_response), second_dialog: Alert::builder() - .transient_for(root) + .transient_for(&root) .launch(AlertSettings { - text: String::from("Do you want to quit without saving? (Second alert)"), - secondary_text: Some(String::from("Your counter hasn't reached 42 yet")), - confirm_label: String::from("Close without saving"), - cancel_label: String::from("Cancel"), - option_label: Some(String::from("Save")), + text: String::from("Do you want to quit without saving? (Second alert)"), + secondary_text: Some(String::from("Your counter hasn't reached 42 yet")), + confirm_label: String::from("Close without saving"), + cancel_label: String::from("Cancel"), + option_label: Some(String::from("Save")), is_modal: true, destructive_accept: true, }) @@ -785,7 +785,7 @@

The compl } fn main() { - let app = RelmApp::new("relm4.example.alert"); + let app = RelmApp::new("relm4.example.alert"); app.run::<App>(()); }

diff --git a/book/next/component_macro.html b/book/next/component_macro.html index 22bc1183f478..0f803cf6b522 100644 --- a/book/next/component_macro.html +++ b/book/next/component_macro.html @@ -198,7 +198,7 @@

What's differ view! { gtk::Window { - set_title: Some("Simple app"), + set_title: Some("Simple app"), set_default_width: 300, set_default_height: 100, @@ -208,17 +208,17 @@

What's differ set_margin_all: 5, gtk::Button { - set_label: "Increment", + set_label: "Increment", connect_clicked => AppMsg::Increment }, - gtk::Button::with_label("Decrement") { + gtk::Button::with_label("Decrement") { connect_clicked => AppMsg::Decrement }, gtk::Label { #[watch] - set_label: &format!("Counter: {}", model.counter), + set_label: &format!("Counter: {}", model.counter), set_margin_all: 5, } } @@ -228,7 +228,7 @@

What's differ // Initialize the UI. fn init( counter: Self::Init, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let model = AppModel { counter }; @@ -255,12 +255,12 @@

What's differ

Next up - the heart of the component macro - the nested view! macro. Here, we can easily define widgets and assign properties to them.

Properties

As you see, we start with the gtk::Window which is our root. Then we open up brackets and assign properties to the window. There's not much magic here but actually set_title is a method provided by gtk4-rs. So technically, the macro creates code like this:

-
window.set_title(Some("Simple app"));
+
window.set_title(Some("Simple app"));

Widgets

We assign a child to the window by nesting another widget inside it. Widgets may be nested indefinitely:

            gtk::Box {
-

Sometimes we want to use a constructor function to initialize our widgets. For the second button we used the gtk::Button::with_label function. This function returns a new button with the "Decrement" label already set, so we don't have to call set_label afterwards.

-
                gtk::Button::with_label("Decrement") {
+

Sometimes we want to use a constructor function to initialize our widgets. For the second button we used the gtk::Button::with_label function. This function returns a new button with the "Decrement" label already set, so we don't have to call set_label afterwards.

+
                gtk::Button::with_label("Decrement") {

Events

To connect events, we use this general syntax:

method_name[cloned_var1, cloned_var2, ...] => move |args, ...| { code... }
@@ -275,7 +275,7 @@

Events

UI updates

The last special syntax of the component macro we'll cover here is the #[watch] attribute. It's just like the normal initialization except that it also updates the property in the view function. Without it, the counter label would never be updated.

                    #[watch]
-                    set_label: &format!("Counter: {}", model.counter),
+ set_label: &format!("Counter: {}", model.counter),

The full reference for the syntax of the widget macro can be found here.

@@ -307,7 +307,7 @@

The compl view! { gtk::Window { - set_title: Some("Simple app"), + set_title: Some("Simple app"), set_default_width: 300, set_default_height: 100, @@ -317,17 +317,17 @@

The compl set_margin_all: 5, gtk::Button { - set_label: "Increment", + set_label: "Increment", connect_clicked => AppMsg::Increment }, - gtk::Button::with_label("Decrement") { + gtk::Button::with_label("Decrement") { connect_clicked => AppMsg::Decrement }, gtk::Label { #[watch] - set_label: &format!("Counter: {}", model.counter), + set_label: &format!("Counter: {}", model.counter), set_margin_all: 5, } } @@ -337,7 +337,7 @@

The compl // Initialize the UI. fn init( counter: Self::Init, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let model = AppModel { counter }; @@ -361,7 +361,7 @@

The compl } fn main() { - let app = RelmApp::new("relm4.test.simple"); + let app = RelmApp::new("relm4.test.simple"); app.run::<AppModel>(0); }

diff --git a/book/next/component_macro/expansion.html b/book/next/component_macro/expansion.html index d93c2bc4cd3b..b9a20e4df892 100644 --- a/book/next/component_macro/expansion.html +++ b/book/next/component_macro/expansion.html @@ -209,7 +209,7 @@

The macro

#[root] #[name(main_window)] gtk::Window { - set_title: Some("Macro reference example"), + set_title: Some("Macro reference example"), set_default_width: 300, set_default_height: 100, @@ -219,7 +219,7 @@

The macro

set_margin_all: 5, append: inc_button = &gtk::Button { - set_label: "Increment", + set_label: "Increment", // Only set this if `icon_name` is Some set_icon_name?: icon_name, connect_clicked[sender] => move |_| { @@ -228,7 +228,7 @@

The macro

}, gtk::Button { - set_label: "Decrement", + set_label: "Decrement", connect_clicked[sender] => move |_| { sender.input(AppMsg::Decrement); } @@ -236,39 +236,39 @@

The macro

gtk::Grid { attach[1, 1, 1, 1] = &gtk::Label { - // Alternative: #[track = "counter.value % 10 == 0"] + // Alternative: #[track = "counter.value % 10 == 0"] #[track(counter.value % 10 == 0)] - set_label: &format!("Grid works! ({})", counter.value), + set_label: &format!("Grid works! ({})", counter.value), } }, // A conditional widget - // Alternative: #[transition = "SlideLeft"] + // Alternative: #[transition = "SlideLeft"] #[transition(SlideLeft)] append = if counter.value % 2 == 0 { gtk::Label { - set_label: "Value is even", + set_label: "Value is even", } } else if counter.value % 3 == 0 { gtk::Label { - set_label: "Value is dividable by 3", + set_label: "Value is dividable by 3", } } else { gtk::Label { - set_label: "Value is odd", + set_label: "Value is odd", } }, - #[transition = "SlideRight"] + #[transition = "SlideRight"] append: match_stack = match counter.value { (0..=2) => { gtk::Label { - set_label: "Value is smaller than 3", + set_label: "Value is smaller than 3", } }, _ => { gtk::Label { - set_label: "Value is higher than 2", + set_label: "Value is higher than 2", } } }, @@ -276,22 +276,22 @@

The macro

append = &gtk::Label, gtk::Label::builder() - .label("Builder pattern works!") + .label("Builder pattern works!") .selectable(true) .build(), - gtk::Label::new(Some("Constructors work!")), + gtk::Label::new(Some("Constructors work!")), /// Counter label gtk::Label { #[watch] - set_label: &format!("Counter: {}", counter.value), + set_label: &format!("Counter: {}", counter.value), #[track] set_margin_all: counter.value.into(), }, gtk::ToggleButton { - set_label: "Counter is even", + set_label: "Counter is even", #[watch] #[block_signal(toggle_handler)] set_active: counter.value % 2 == 0, @@ -314,7 +314,7 @@

The macro

} }, gtk::Window { - set_title: Some("Another window"), + set_title: Some("Another window"), set_default_width: 300, set_default_height: 100, set_transient_for: Some(&main_window), @@ -324,9 +324,9 @@

The macro

#[watch] set_visible: counter.value == 42, - #[name = "my_label_name"] + #[name = "my_label_name"] gtk::Label { - set_label: "You made it to 42!", + set_label: "You made it to 42!", } } } @@ -338,7 +338,7 @@

The macro

// Initialize the UI. fn init( init: Self::Init, - renamed_root: &Self::Root, + renamed_root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let counter = AppModel { @@ -348,11 +348,11 @@

The macro

let test_field = 0; - // Set icon name randomly to Some("go-up-symbolic") or None - let icon_name = rand::random::<bool>().then_some("go-up-symbolic"); + // Set icon name randomly to Some("go-up-symbolic") or None + let icon_name = rand::random::<bool>().then_some("go-up-symbolic"); - let local_label = gtk::Label::new(Some("local_label")); - let local_ref_label_value = gtk::Label::new(Some("local_ref_label")); + let local_label = gtk::Label::new(Some("local_label")); + let local_ref_label_value = gtk::Label::new(Some("local_ref_label")); let local_ref_label = &local_ref_label_value; // Insert the macro code generation here let widgets = view_output!(); @@ -454,7 +454,7 @@

Initialization before the view_output entrypoint

-

A large part of the code generated by the macro is dedicated to the initialization of the view. This code is "expanded" from the view_output!() entrypoint. First, let's find the code we wrote before the entry point:

+

A large part of the code generated by the macro is dedicated to the initialization of the view. This code is "expanded" from the view_output!() entrypoint. First, let's find the code we wrote before the entry point:

    fn init(
         init: Self::Init,
         renamed_root: &Self::Root,
@@ -465,9 +465,9 @@ 

Next, the macro initializes all widgets. Widgets defined by their type are initialized with their Default implementation. Any constructors or functions that are invoked manually are left unchanged in the output.

        let main_window = renamed_root.clone();
@@ -488,17 +488,17 @@ 

Assigning properties

Assigning properties looks pretty normal as well. In the middle we have an optional assignment that uses an if let statement to only assign properties that match Some(data). In the macro we marked this line with a ?.

-
        main_window.set_title(Some("Macro reference example"));
+
        main_window.set_title(Some("Macro reference example"));
         main_window.set_default_width(300);
         main_window.set_default_height(100);
         relm4::RelmContainerExt::container_add(&main_window, &_gtk_box_14);
@@ -506,12 +506,12 @@ 

Ass _gtk_box_14.set_spacing(5); _gtk_box_14.set_margin_all(5); _gtk_box_14.append(&inc_button); - inc_button.set_label("Increment"); + inc_button.set_label("Increment"); if let Some(__p_assign) = icon_name { inc_button.set_icon_name(__p_assign); } relm4::RelmContainerExt::container_add(&_gtk_box_14, &_gtk_button_0); - _gtk_button_0.set_label("Decrement"); + _gtk_button_0.set_label("Decrement"); relm4::RelmContainerExt::container_add(&_gtk_box_14, &_gtk_grid_2); _gtk_grid_2.attach(&_gtk_label_1, 1, 1, 1, 1); _gtk_label_1 @@ -519,7 +519,7 @@

Ass &{ let res = ::alloc::fmt::format( ::core::fmt::Arguments::new_v1( - &["Grid works! (", ")"], + &["Grid works! (", ")"], &[::core::fmt::ArgumentV1::new_display(&counter.value)], ), ); @@ -527,17 +527,17 @@

Ass }, ); _gtk_box_14.append(&_conditional_widget_3); - _conditional_widget_3.add_named(&_gtk_label_4, Some("0")); - _gtk_label_4.set_label("Value is even"); - _conditional_widget_3.add_named(&_gtk_label_5, Some("1")); - _gtk_label_5.set_label("Value is dividable by 3"); - _conditional_widget_3.add_named(&_gtk_label_6, Some("2")); - _gtk_label_6.set_label("Value is odd"); + _conditional_widget_3.add_named(&_gtk_label_4, Some("0")); + _gtk_label_4.set_label("Value is even"); + _conditional_widget_3.add_named(&_gtk_label_5, Some("1")); + _gtk_label_5.set_label("Value is dividable by 3"); + _conditional_widget_3.add_named(&_gtk_label_6, Some("2")); + _gtk_label_6.set_label("Value is odd"); _gtk_box_14.append(&match_stack); - match_stack.add_named(&_gtk_label_7, Some("0")); - _gtk_label_7.set_label("Value is smaller than 3"); - match_stack.add_named(&_gtk_label_8, Some("1")); - _gtk_label_8.set_label("Value is higher than 2"); + match_stack.add_named(&_gtk_label_7, Some("0")); + _gtk_label_7.set_label("Value is smaller than 3"); + match_stack.add_named(&_gtk_label_8, Some("1")); + _gtk_label_8.set_label("Value is higher than 2"); _gtk_box_14.append(&_gtk_label_9);

Events

Now the macro generates the code for connecting events.

@@ -570,7 +570,7 @@

Events

};

The code looks very similar to what we wrote in the macro.

                append: inc_button = &gtk::Button {
-                    set_label: "Increment",
+                    set_label: "Increment",
                     // Only set this if `icon_name` is Some
                     set_icon_name?: icon_name,
                     connect_clicked[sender] => move |_| {
@@ -579,7 +579,7 @@ 

Events

}, gtk::Button { - set_label: "Decrement", + set_label: "Decrement", connect_clicked[sender] => move |_| { sender.input(AppMsg::Decrement); } @@ -618,7 +618,7 @@

UI updates

-

The last step of the macro is to generate the update logic within the update_view function. Any code present in the pre_view and post_view "functions" will be expanded before or after the generated code. Note that the generated code returns a private struct to prevent early returns in pre_view from skipping the rest of the view update code.

+

The last step of the macro is to generate the update logic within the update_view function. Any code present in the pre_view and post_view "functions" will be expanded before or after the generated code. Note that the generated code returns a private struct to prevent early returns in pre_view from skipping the rest of the view update code.

    /// Update the view to represent the updated model.
     fn update_view(&self, widgets: &mut Self::Widgets, sender: ComponentSender<Self>) {
         struct __DoNotReturnManually;
@@ -658,7 +658,7 @@ 

UI updates

&{ let res = ::alloc::fmt::format( ::core::fmt::Arguments::new_v1( - &["Grid works! (", ")"], + &["Grid works! (", ")"], &[::core::fmt::ArgumentV1::new_display(&counter.value)], ), ); @@ -668,33 +668,33 @@

UI updates

} let __current_page = _conditional_widget_3 .visible_child_name() - .map_or("".to_string(), |s| s.as_str().to_string()); + .map_or("".to_string(), |s| s.as_str().to_string()); _conditional_widget_3 .set_visible_child_name( if counter.value % 2 == 0 { - let __page_active: bool = (__current_page == "0"); - "0" + let __page_active: bool = (__current_page == "0"); + "0" } else if counter.value % 3 == 0 { - let __page_active: bool = (__current_page == "1"); - "1" + let __page_active: bool = (__current_page == "1"); + "1" } else { - let __page_active: bool = (__current_page == "2"); - "2" + let __page_active: bool = (__current_page == "2"); + "2" }, ); let __current_page = match_stack .visible_child_name() - .map_or("".to_string(), |s| s.as_str().to_string()); + .map_or("".to_string(), |s| s.as_str().to_string()); match_stack .set_visible_child_name( match counter.value { (0..=2) => { - let __page_active: bool = (__current_page == "0"); - "0" + let __page_active: bool = (__current_page == "0"); + "0" } _ => { - let __page_active: bool = (__current_page == "1"); - "1" + let __page_active: bool = (__current_page == "1"); + "1" } }, ); @@ -703,7 +703,7 @@

UI updates

&{ let res = ::alloc::fmt::format( ::core::fmt::Arguments::new_v1( - &["Counter: "], + &["Counter: "], &[::core::fmt::ArgumentV1::new_display(&counter.value)], ), ); @@ -743,7 +743,7 @@

Gen &{ let res = ::alloc::fmt::format( ::core::fmt::Arguments::new_v1( - &["Counter: "], + &["Counter: "], &[::core::fmt::ArgumentV1::new_display(&counter.value)], ), ); @@ -757,14 +757,14 @@

Gen
            _conditional_widget_3
                 .set_visible_child_name(
                     if counter.value % 2 == 0 {
-                        let __page_active: bool = (__current_page == "0");
-                        "0"
+                        let __page_active: bool = (__current_page == "0");
+                        "0"
                     } else if counter.value % 3 == 0 {
-                        let __page_active: bool = (__current_page == "1");
-                        "1"
+                        let __page_active: bool = (__current_page == "1");
+                        "1"
                     } else {
-                        let __page_active: bool = (__current_page == "2");
-                        "2"
+                        let __page_active: bool = (__current_page == "2");
+                        "2"
                     },
                 );

Conclusion

@@ -847,8 +847,8 @@

Con

Some, widgets don't have a Default implementation or it may be more convenient to use a constructor method. In this case, you can use the following syntax:

// Constructor method
-gtk::Label::new(Some("Label from constructor method")) { /* ... */ }
+gtk::Label::new(Some("Label from constructor method")) { /* ... */ }
 
 // Builder pattern
 gtk::Label::builder()
-    .label("Label from builder pattern")
+    .label("Label from builder pattern")
     .selectable(true)
     .build() { /* ... */ }

You can also use regular functions. @@ -235,10 +235,10 @@

Child widgets // Attach the label to a grid attach[0, 0, 1, 2]= &gtk::Label { ... } }

-

This will expand to __grid.attach(__label, 0, 0, 1, 2)

+

This will expand to __grid.attach(__label, 0, 0, 1, 2)

Naming widgets

Widgets may be given a name with the name attribute. These names are accessible as fields on the Widgets struct generated by the macro.

-
#[name = "important_label"]
+
#[name = "important_label"]
 gtk::Label { ... }
 
 // ...
@@ -252,24 +252,24 @@ 

Condi Internally, the macro will use a gtk::Stack, so you can also use different transition types.

if model.value % 2 == 0 {
     gtk::Label {
-        set_label: "The value is even",
+        set_label: "The value is even",
     },
     gtk::Label {
-        set_label: "The value is odd",
+        set_label: "The value is odd",
     }
 }
 
 // Use a transition type to set an animation when the visible widget changes
-#[transition = "SlideRight"]
+#[transition = "SlideRight"]
 match model.value {
     0..=9 => {
         gtk::Label {
-            set_label: "The value is below 10",
+            set_label: "The value is below 10",
         },
     }
     _ => {
         gtk::Label {
-            set_label: "The value is equal or above 10",
+            set_label: "The value is equal or above 10",
         },
     }
 }
@@ -279,10 +279,10 @@

Returned wi To get access to this widget, you can use a special syntax of the view macro:

gtk::Stack {
     add_child = &gtk::Label {
-        set_label: "placeholder",
+        set_label: "placeholder",
     } -> {
         // Access the returned widgets (in this case gtk::StackPage)
-        set_title: "page title",
+        set_title: "page title",
     }
 }

Properties

@@ -324,7 +324,7 @@

gtk::ToggleButton { - set_label: "Counter is even", + set_label: "Counter is even", #[watch] #[block_signal(toggle_handler)] set_active: counter.value % 2 == 0, @@ -370,7 +370,7 @@

Manual view

You can also implement your own view logic, which will be added to the view code that the macro generates. Code inside pre_view() will run before the code of the macro, and post_view() will run after it.

-

Code inside these "functions" isn't like a normal function! +

Code inside these "functions" isn't like a normal function! The macro disallows returning early in pre_view to ensure that the code of the macro will always be executed.

diff --git a/book/next/components.html b/book/next/components.html index 20ee722a391c..4aed32b28fb6 100644 --- a/book/next/components.html +++ b/book/next/components.html @@ -241,10 +241,10 @@

The widgets

gtk::HeaderBar { #[wrap(Some)] set_title_widget = &gtk::Box { - add_css_class: "linked", - #[name = "group"] + add_css_class: "linked", + #[name = "group"] gtk::ToggleButton { - set_label: "View", + set_label: "View", set_active: true, connect_toggled[sender] => move |btn| { if btn.is_active() { @@ -253,7 +253,7 @@

The widgets

}, }, gtk::ToggleButton { - set_label: "Edit", + set_label: "Edit", set_group: Some(&group), connect_toggled[sender] => move |btn| { if btn.is_active() { @@ -262,7 +262,7 @@

The widgets

}, }, gtk::ToggleButton { - set_label: "Export", + set_label: "Export", set_group: Some(&group), connect_toggled[sender] => move |btn| { if btn.is_active() { @@ -307,10 +307,10 @@

The widgetsControllers

When initializing the app component, we construct the child components by passing the appropriate Init and forwarding any desired inputs and outputs. This is done through a builder provided by Component implementations. We pass the initial parameters via the launch method, and then retrieve the final Controller by calling the forward method. In addition to starting the component, the forward method allows us to take the outputs of the component, transform them with a mapping function, and then pass the result as an input message to another sender (in this case, the input sender of the app component). If you don't need to forward any outputs, you can start the component with the detach method instead.

    fn init(
         params: Self::Init,
-        root: &Self::Root,
+        root: Self::Root,
         sender: ComponentSender<Self>,
     ) -> ComponentParts<Self> {
         let header: Controller<HeaderModel> =
@@ -388,7 +388,7 @@ 

Controllers

}); let dialog = DialogModel::builder() - .transient_for(root) + .transient_for(&root) .launch(true) .forward(sender.input_sender(), |msg| match msg { DialogOutput::Close => AppMsg::Close, @@ -403,7 +403,7 @@

Controllers

let widgets = view_output!(); ComponentParts { model, widgets } }
-

Also, we set the set_transient_for property, which actually uses the main window. The dialog should set his parent window so that GTK can handle the dialog better. The GTK docs state: "[set_transient_for] allows window managers to e.g. keep the dialog on top of the main window, or center the dialog over the main window".

+

Also, we set the set_transient_for property, which actually uses the main window. The dialog should set his parent window so that GTK can handle the dialog better. The GTK docs state: "[set_transient_for] allows window managers to e.g. keep the dialog on top of the main window, or center the dialog over the main window".

#[derive(Debug)]
 enum AppMode {
     View,
@@ -433,7 +433,7 @@ 

The widgetsThe compl gtk::HeaderBar { #[wrap(Some)] set_title_widget = &gtk::Box { - add_css_class: "linked", - #[name = "group"] + add_css_class: "linked", + #[name = "group"] gtk::ToggleButton { - set_label: "View", + set_label: "View", set_active: true, connect_toggled[sender] => move |btn| { if btn.is_active() { @@ -484,7 +484,7 @@

The compl }, }, gtk::ToggleButton { - set_label: "Edit", + set_label: "Edit", set_group: Some(&group), connect_toggled[sender] => move |btn| { if btn.is_active() { @@ -493,7 +493,7 @@

The compl }, }, gtk::ToggleButton { - set_label: "Export", + set_label: "Export", set_group: Some(&group), connect_toggled[sender] => move |btn| { if btn.is_active() { @@ -507,7 +507,7 @@

The compl fn init( _params: Self::Init, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let model = HeaderModel; @@ -544,10 +544,10 @@

The compl set_modal: true, #[watch] set_visible: !model.hidden, - set_text: Some("Do you want to close before saving?"), - set_secondary_text: Some("All unsaved changes will be lost"), - add_button: ("Close", gtk::ResponseType::Accept), - add_button: ("Cancel", gtk::ResponseType::Cancel), + set_text: Some("Do you want to close before saving?"), + set_secondary_text: Some("All unsaved changes will be lost"), + add_button: ("Close", gtk::ResponseType::Accept), + add_button: ("Cancel", gtk::ResponseType::Cancel), connect_response[sender] => move |_, resp| { sender.input(if resp == gtk::ResponseType::Accept { DialogInput::Accept @@ -560,7 +560,7 @@

The compl fn init( params: Self::Init, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let model = DialogModel { hidden: params }; @@ -614,7 +614,7 @@

The compl gtk::Label { #[watch] - set_label: &format!("Placeholder for {:?}", model.mode), + set_label: &format!("Placeholder for {:?}", model.mode), }, connect_close_request[sender] => move |_| { sender.input(AppMsg::CloseRequest); @@ -625,7 +625,7 @@

The compl fn init( params: Self::Init, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let header: Controller<HeaderModel> = @@ -638,7 +638,7 @@

The compl }); let dialog = DialogModel::builder() - .transient_for(root) + .transient_for(&root) .launch(true) .forward(sender.input_sender(), |msg| match msg { DialogOutput::Close => AppMsg::Close, @@ -670,7 +670,7 @@

The compl } fn main() { - let relm = RelmApp::new("ewlm4.test.components"); + let relm = RelmApp::new("ewlm4.test.components"); relm.run::<AppModel>(AppMode::Edit); }

diff --git a/book/next/continuous_integration.html b/book/next/continuous_integration.html index d29b70b343bb..2d0019a9849a 100644 --- a/book/next/continuous_integration.html +++ b/book/next/continuous_integration.html @@ -188,9 +188,9 @@

GitHub Actions< on: push: - branches: [ "main" ] + branches: [ "main" ] pull_request: - branches: [ "main" ] + branches: [ "main" ] jobs: build: diff --git a/book/next/efficient_ui/factory.html b/book/next/efficient_ui/factory.html index cd90986cf3b5..6ed06d596b99 100644 --- a/book/next/efficient_ui/factory.html +++ b/book/next/efficient_ui/factory.html @@ -180,7 +180,7 @@

GUI development with Relm4

Initializing the model

-

FactoryComponent has separate functions for initializing the model and the widgets. +

FactoryComponent has separate functions for initializing the model and the widgets. This means, that we are a bit less flexible, but don't need view_output!() here. Also, we just need to implement the init_model function because init_widgets is already implemented by the macro.

    fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
@@ -346,7 +346,7 @@ 

// Initialize the UI. fn init( counter: Self::Init, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let counters = FactoryVecDeque::builder() @@ -373,7 +373,7 @@

view! { gtk::Window { - set_title: Some("Factory example"), + set_title: Some("Factory example"), set_default_size: (300, 100), gtk::Box { @@ -382,12 +382,12 @@

The main

Awesome, we almost made it!

We only need to define the main function to run our application.

fn main() {
-    let app = RelmApp::new("relm4.example.factory");
+    let app = RelmApp::new("relm4.example.factory");
     app.run::<App>(0);
 }

The complete code

@@ -492,19 +492,19 @@

The compl #[name(add_button)] gtk::Button { - set_label: "+", + set_label: "+", connect_clicked => CounterMsg::Increment, }, #[name(remove_button)] gtk::Button { - set_label: "-", + set_label: "-", connect_clicked => CounterMsg::Decrement, }, #[name(move_up_button)] gtk::Button { - set_label: "Up", + set_label: "Up", connect_clicked[sender, index] => move |_| { sender.output(CounterOutput::MoveUp(index.clone())).unwrap(); } @@ -512,7 +512,7 @@

The compl #[name(move_down_button)] gtk::Button { - set_label: "Down", + set_label: "Down", connect_clicked[sender, index] => move |_| { sender.output(CounterOutput::MoveDown(index.clone())).unwrap(); } @@ -520,7 +520,7 @@

The compl #[name(to_front_button)] gtk::Button { - set_label: "To Start", + set_label: "To Start", connect_clicked[sender, index] => move |_| { sender.output(CounterOutput::SendFront(index.clone())).unwrap(); } @@ -566,7 +566,7 @@

The compl view! { gtk::Window { - set_title: Some("Factory example"), + set_title: Some("Factory example"), set_default_size: (300, 100), gtk::Box { @@ -575,12 +575,12 @@

The compl set_margin_all: 5, gtk::Button { - set_label: "Add counter", + set_label: "Add counter", connect_clicked => AppMsg::AddCounter, }, gtk::Button { - set_label: "Remove counter", + set_label: "Remove counter", connect_clicked => AppMsg::RemoveCounter, }, @@ -596,7 +596,7 @@

The compl // Initialize the UI. fn init( counter: Self::Init, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let counters = FactoryVecDeque::builder() @@ -650,7 +650,7 @@

The compl } fn main() { - let app = RelmApp::new("relm4.example.factory"); + let app = RelmApp::new("relm4.example.factory"); app.run::<App>(0); }

diff --git a/book/next/efficient_ui/tracker.html b/book/next/efficient_ui/tracker.html index 6d4e5d42fb8b..4093b20ae048 100644 --- a/book/next/efficient_ui/tracker.html +++ b/book/next/efficient_ui/tracker.html @@ -210,7 +210,7 @@

The track

To reset all previous changes, you can call {struct_var_name}.reset().

Example

First we have to add the tracker library to Cargo.toml:

-
tracker = "0.1"
+
tracker = "0.1"
 

Now let's have a look at a small example.

#[tracker::track]
@@ -224,7 +224,7 @@ 

Example

x: 0, y: 0, // the macro generates a new variable called - // "tracker" which stores the changes + // "tracker" which stores the changes tracker: 0, }; @@ -248,22 +248,22 @@

The icons

Before we can select random icons, we need to quickly implement a function that will return us random image names available in the default GTK icon theme.

const ICON_LIST: &[&str] = &[
-    "bookmark-new-symbolic",
-    "edit-copy-symbolic",
-    "edit-cut-symbolic",
-    "edit-find-symbolic",
-    "starred-symbolic",
-    "system-run-symbolic",
-    "emoji-objects-symbolic",
-    "emoji-nature-symbolic",
-    "display-brightness-symbolic",
+    "bookmark-new-symbolic",
+    "edit-copy-symbolic",
+    "edit-cut-symbolic",
+    "edit-find-symbolic",
+    "starred-symbolic",
+    "system-run-symbolic",
+    "emoji-objects-symbolic",
+    "emoji-nature-symbolic",
+    "display-brightness-symbolic",
 ];
 
 fn random_icon_name() -> &'static str {
     ICON_LIST
         .iter()
         .choose(&mut rand::thread_rng())
-        .expect("Could not choose a random icon")
+        .expect("Could not choose a random icon")
 }

The model

For our model we only need to store the two icon names and whether both of them are identical.

@@ -301,8 +301,8 @@

The view

    view! {
         #[root]
         gtk::ApplicationWindow {
-            #[track = "model.changed(AppModel::identical())"]
-            set_class_active: ("identical", model.identical),
+            #[track = "model.changed(AppModel::identical())"]
+            set_class_active: ("identical", model.identical),
             gtk::Box {
                 set_orientation: gtk::Orientation::Horizontal,
                 set_spacing: 10,
@@ -312,11 +312,11 @@ 

The view

set_spacing: 10, gtk::Image { set_pixel_size: 50, - #[track = "model.changed(AppModel::first_icon())"] + #[track = "model.changed(AppModel::first_icon())"] set_icon_name: Some(model.first_icon), }, gtk::Button { - set_label: "New random image", + set_label: "New random image", connect_clicked[sender] => move |_| { sender.input(AppInput::UpdateFirst) } @@ -327,11 +327,11 @@

The view

set_spacing: 10, gtk::Image { set_pixel_size: 50, - #[track = "model.changed(AppModel::second_icon())"] + #[track = "model.changed(AppModel::second_icon())"] set_icon_name: Some(model.second_icon), }, gtk::Button { - set_label: "New random image", + set_label: "New random image", connect_clicked[sender] => move |_| { sender.input(AppInput::UpdateSecond) } @@ -340,32 +340,34 @@

The view

} } }
-

The overall UI is pretty simple: A window that contains a box. This box itself has two boxes that display the two icons and the two buttons to update them.

-

We also added some additional code in init that runs before the view is constructed. In our case, we want to add custom CSS that sets the background color for elements with class name "identical".

-
        relm4::set_global_css(".identical { background: #00ad5c; }");
-
-        // Insert the macro code generation here
-        let widgets = view_output!();
+

The main function

+

In this example, we need some additional code in fn main() to add custom CSS that sets the background color for elements with class name "identical". +Later, we just need to assign the "identical" class name to a widget to make it match the CSS selector.

+
fn main() {
+    let app = RelmApp::new("relm4.test.simple");
+    app.set_global_css(".identical { background: #00ad5c; }");
+    app.run::<AppModel>(());
+}

The #[track] attribute

The #[track] attribute is applied to method invocations in our view code. It allows us to add a condition to the update: if the condition is true, the method will be called, otherwise, it will be skipped. The attribute syntax looks like this:

-
#[track = "<boolean expression>"]
+
#[track = "<boolean expression>"]

Let's have a look at its first appearance:

-
            #[track = "model.changed(AppModel::identical())"]
-            set_class_active: ("identical", model.identical),
+
            #[track = "model.changed(AppModel::identical())"]
+            set_class_active: ("identical", model.identical),

The set_class_active method is used to either activate or disable a CSS class. It takes two parameters, the first is the class itself and the second is a boolean which specifies if the class should be added (true) or removed (false).

The value of the #[track] attribute is parsed as a boolean expression. This expression will be used as a condition to check whether something has changed. If this condition is true, the set_class_active method will be called with the parameters it guards.

The macro expansion for method calls annotated with the #[track] attribute look roughly like this:

if model.changed(AppModel::identical()) {
-    self.main_window.set_class_active("identical", model.identical);
+    self.main_window.set_class_active("identical", model.identical);
 }

That's all. It's pretty simple, actually. We just use a condition that allows us to update our widgets only when needed.

The second #[track] attribute works similarly:

-
                        #[track = "model.changed(AppModel::first_icon())"]
+
                        #[track = "model.changed(AppModel::first_icon())"]
                         set_icon_name: Some(model.first_icon),
-

Debugging Helper

+

Using a tracker as debugging helper

Since the #[track] attribute parses expressions, you can use the following syntax to debug your trackers:

-

#[track = "{ println!("Update widget"); argument }"]

+

#[track = "{ println!("Update widget"); argument }"]

Initializing the model

There's one last thing to point out. When initializing our model, we need to initialize the tracker field as well. The initial value doesn't really matter because we call reset() in the update function anyway, but usually 0 is used.

@@ -382,22 +384,22 @@

The compl use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent}; const ICON_LIST: &[&str] = &[ - "bookmark-new-symbolic", - "edit-copy-symbolic", - "edit-cut-symbolic", - "edit-find-symbolic", - "starred-symbolic", - "system-run-symbolic", - "emoji-objects-symbolic", - "emoji-nature-symbolic", - "display-brightness-symbolic", + "bookmark-new-symbolic", + "edit-copy-symbolic", + "edit-cut-symbolic", + "edit-find-symbolic", + "starred-symbolic", + "system-run-symbolic", + "emoji-objects-symbolic", + "emoji-nature-symbolic", + "display-brightness-symbolic", ]; fn random_icon_name() -> &'static str { ICON_LIST .iter() .choose(&mut rand::thread_rng()) - .expect("Could not choose a random icon") + .expect("Could not choose a random icon") } // The track proc macro allows to easily track changes to different @@ -424,8 +426,8 @@

The compl view! { #[root] gtk::ApplicationWindow { - #[track = "model.changed(AppModel::identical())"] - set_class_active: ("identical", model.identical), + #[track = "model.changed(AppModel::identical())"] + set_class_active: ("identical", model.identical), gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 10, @@ -435,11 +437,11 @@

The compl set_spacing: 10, gtk::Image { set_pixel_size: 50, - #[track = "model.changed(AppModel::first_icon())"] + #[track = "model.changed(AppModel::first_icon())"] set_icon_name: Some(model.first_icon), }, gtk::Button { - set_label: "New random image", + set_label: "New random image", connect_clicked[sender] => move |_| { sender.input(AppInput::UpdateFirst) } @@ -450,11 +452,11 @@

The compl set_spacing: 10, gtk::Image { set_pixel_size: 50, - #[track = "model.changed(AppModel::second_icon())"] + #[track = "model.changed(AppModel::second_icon())"] set_icon_name: Some(model.second_icon), }, gtk::Button { - set_label: "New random image", + set_label: "New random image", connect_clicked[sender] => move |_| { sender.input(AppInput::UpdateSecond) } @@ -467,7 +469,7 @@

The compl // Initialize the UI. fn init( _params: Self::Init, - root: &Self::Root, + root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { let model = AppModel { @@ -477,8 +479,6 @@

The compl tracker: 0, }; - relm4::set_global_css(".identical { background: #00ad5c; }"); - // Insert the macro code generation here let widgets = view_output!(); @@ -502,7 +502,8 @@

The compl } fn main() { - let app = RelmApp::new("relm4.test.simple"); + let app = RelmApp::new("relm4.test.simple"); + app.set_global_css(".identical { background: #00ad5c; }"); app.run::<AppModel>(()); }

diff --git a/book/next/first_app.html b/book/next/first_app.html index fa11c2a7f9fc..44cf9eaf8071 100644 --- a/book/next/first_app.html +++ b/book/next/first_app.html @@ -194,7 +194,7 @@

Your first app<

Application architecture

Often, programming concepts are easier to understand when explained with examples or metaphors from the real world. To understand how Relm4 apps work, you can think about a computer as a person.

-

Our job as a programmer is to ensure that the users of our app will be able to communicate with the computer through the UI. Since the computer can't understand our human language, it needs some help from us to get the communication going.

+

Our job as a programmer is to ensure that the users of our app will be able to communicate with the computer through the UI. Since the computer can't understand our human language, it needs some help from us to get the communication going.

Let's have a look at what we need to get this done!

Messages

For our app, we just want to tell the computer to either increment or decrement a counter.

@@ -236,7 +236,7 @@

fn init_root() -> Self::Root { gtk::Window::builder() - .title("Simple app") + .title("Simple app") .default_width(300) .default_height(100) .build() @@ -249,7 +249,7 @@

/// Initialize the UI and model. fn init( counter: Self::Init, - window: &Self::Root, + window: Self::Root, sender: ComponentSender<Self>, ) -> relm4::ComponentParts<Self> { let model = AppModel { counter }; @@ -259,10 +259,10 @@

Of course, the computer needs to do more than just remembering things, it also needs to process information. Here, both the model and message types come into play.

The update function of the SimpleComponent trait tells the computer how to process messages and how to update its memory.

    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
@@ -307,12 +307,12 @@ 

Running the App

The last step is to run the app we just wrote. To do so, we just need to initialize our model and pass it into RelmApp::new().

fn main() {
-    let app = RelmApp::new("relm4.test.simple_manual");
+    let app = RelmApp::new("relm4.test.simple_manual");
     app.run::<AppModel>(0);
 }

🎉 Congratulations! You just wrote your first app with Relm4! 🎉

@@ -376,7 +376,7 @@

The compl fn init_root() -> Self::Root { gtk::Window::builder() - .title("Simple app") + .title("Simple app") .default_width(300) .default_height(100) .build() @@ -385,7 +385,7 @@

The compl /// Initialize the UI and model. fn init( counter: Self::Init, - window: &Self::Root, + window: Self::Root, sender: ComponentSender<Self>, ) -> relm4::ComponentParts<Self> { let model = AppModel { counter }; @@ -395,10 +395,10 @@

The compl .spacing(5) .build(); - let inc_button = gtk::Button::with_label("Increment"); - let dec_button = gtk::Button::with_label("Decrement"); + let inc_button = gtk::Button::with_label("Increment"); + let dec_button = gtk::Button::with_label("Decrement"); - let label = gtk::Label::new(Some(&format!("Counter: {}", model.counter))); + let label = gtk::Label::new(Some(&format!("Counter: {}", model.counter))); label.set_margin_all(5); window.set_child(Some(&vbox)); @@ -435,12 +435,12 @@

The compl fn update_view(&self, widgets: &mut Self::Widgets, _sender: ComponentSender<Self>) { widgets .label - .set_label(&format!("Counter: {}", self.counter)); + .set_label(&format!("Counter: {}", self.counter)); } } fn main() { - let app = RelmApp::new("relm4.test.simple_manual"); + let app = RelmApp::new("relm4.test.simple_manual"); app.run::<AppModel>(0); }

diff --git a/book/next/gtk_rs.html b/book/next/gtk_rs.html index 657572a2fe98..3ccd4cbb845e 100644 --- a/book/next/gtk_rs.html +++ b/book/next/gtk_rs.html @@ -187,19 +187,19 @@

gtk-rs overvi

GObjects

GTK is an object-oriented framework that uses the GObject library to implement objects. GObjects have some really useful features that we will discuss in the following sections.

Subclassing

-

Like many other OOP frameworks or languages, GObjects can inherit from other GObjects. This is called subclassing. In the case of GTK, that’s really helpful because it allows us to create custom widgets.

+

Like many other OOP frameworks or languages, GObjects can inherit from other GObjects. This is called subclassing. In the case of GTK, that’s really helpful because it allows us to create custom widgets.

For example, you could use subclassing to create your own button widget that acts as a counter. Or you can create a custom application window that better suits your application.

Read more about subclassing in the gtk-rs book.

Properties

Each GObject can have properties that work similar to the fields of a structure in Rust. You can set them and you can read (get) them. But one thing that's particularly cool is that properties can be bound to other properties.

-

For example, you could bind the "visible" property of a widget to the "active" property of a gtk::ToggleButton. This would allow you to show or hide the widget using the toggle button and the best part is, that it's done fully automatically!

+

For example, you could bind the "visible" property of a widget to the "active" property of a gtk::ToggleButton. This would allow you to show or hide the widget using the toggle button and the best part is, that it's done fully automatically!

Read more about properties in the gtk-rs book.

Signals

-

GObjects can not only have properties but also signals. Actually, we've been using signals all the time, for example, by using the connect_clicked method on a button. This method simply adds an event handler function for the "click" signal.

+

GObjects can not only have properties but also signals. Actually, we've been using signals all the time, for example, by using the connect_clicked method on a button. This method simply adds an event handler function for the "click" signal.

You can create your own signals in custom widgets. You can also use emit to emit signals on you widgets manually.

Read more about signals in the gtk-rs book.

diff --git a/book/next/index.html b/book/next/index.html index 82473678ea0a..b8d0c56ccdee 100644 --- a/book/next/index.html +++ b/book/next/index.html @@ -187,7 +187,7 @@

Matrix Relm4 on crates.io Relm4 docs

-

Relm4 is an idiomatic GUI library inspired by Elm and based on gtk4-rs. +

Relm4 is an idiomatic GUI library inspired by Elm and based on gtk4-rs. It is a new version of relm that's built from scratch and is compatible with GTK4 and libadwaita.