A Basic Counter App

The easiest way to learn about Maycoon is to start studying some examples. In this Tutorial we will find out how to create a basic counter App with increase and decrease buttons, as well as a text widget to display the count.

Setup

For creating a new project, see the Installation Guide.

The App

First we need to import the necessary items from the maycoon crate:

use maycoon::core::app::context::AppContext;
use maycoon::core::app::update::Update;
use maycoon::core::app::Application;
use maycoon::core::config::MayConfig;
use maycoon::core::layout::{AlignItems, Dimension, FlexDirection, LayoutStyle};
use maycoon::core::reference::Ref;
use maycoon::core::signal::eval::EvalSignal;
use maycoon::core::signal::state::StateSignal;
use maycoon::core::signal::{MaybeSignal, Signal};
use maycoon::core::widget::{Widget, WidgetLayoutExt};
use maycoon::math::Vector2;
use maycoon::theme::theme::celeste::CelesteTheme;
use maycoon::widgets::button::Button;
use maycoon::widgets::container::Container;
use maycoon::widgets::text::Text;

// Our application structure
struct MyApp;

impl Application for MyApp {
    // The theme we want to use
    type Theme = CelesteTheme;

    // The root widget
    fn build(context: AppContext) -> impl Widget {
        let counter = context.use_signal(StateSignal::new(0));

        Container::new(vec![
            {
                let counter = counter.clone();

                Box::new(
                    Button::new(Text::new("Increase".to_string())).with_on_pressed(
                        MaybeSignal::signal(context.use_signal(EvalSignal::new(move || {
                            counter.set(*counter.get() + 1);

                            Update::DRAW
                        }))),
                    ),
                )
            },
            {
                let counter = counter.clone();

                Box::new(
                    Button::new(Text::new("Decrease".to_string())).with_on_pressed(
                        MaybeSignal::signal(context.use_signal(EvalSignal::new(move || {
                            counter.set(*counter.get() - 1);

                            Update::DRAW
                        }))),
                    ),
                )
            },
            {
                let counter = counter.clone();
                Box::new(Text::new(counter.map(|i| Ref::Owned(i.to_string()))))
            },
        ])
        .with_layout_style(LayoutStyle {
            size: Vector2::<Dimension>::new(Dimension::percent(1.0), Dimension::percent(1.0)),
            flex_direction: FlexDirection::Column,
            align_items: Some(AlignItems::Center),
            ..Default::default()
        })
    }

    // The configuration
    fn config(&self) -> MayConfig<Self::Theme> {
        MayConfig::default()
    }
}

fn main() {
    MyApp.run()
}

A Container widget draws and handles a collection of widgets specified as a Vector of Boxed Widgets. In our case, we need two buttons: Increase and decrease to manipulate our counter value, as well as a text widget for displaying the counter value.

We use the MaybeSignal::signal function to pass the signals to the buttons and pass fixed strings to the "Increase" and "Decrease" buttons.

For the two buttons, we need to define Updates to apply updates to the App.

The Update::DRAW constant tells the App to only re-draw and not re-layout the application when pressing the buttons.

The with_layout_style function applies a custom layout which centers the widgets in our case.

NOTE: You need to clone signals before using them inside move closures to use them inside multiple closures.

Running the App

To launch the App, you can run cargo run.

You should see a window with "Increase" and "Decrease" buttons along with a text.