Quick Start

Installation

If you haven't already, you can install the Rust Programming Language using rustup.

To create a new cargo project, run cargo new --bin <project-name>.

After creating the project, you can cd into the folder and then run cargo add maycoon to add maycoon as a dependency.

Feature Gates

Most cargo dependencies have feature gates that toggle special modules. You can add features by editing the Cargo.toml like this:

[package]
name = "my_project"
description = "My Awesome App"
# other stuff...

[dependencies]
maycoon = { version = "*", features = ["vg"] }

Following features are available at the time of writing:

  • vg - Enables structures and functions for drawing vector graphics using vello.

  • macros - Enables useful macros for easier development.

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.

Setup

For creating a new project, see the Installation Guide.

We need to enable the macros feature, which is enabled by default, so you shouldn't worry about it.

The App

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

use maycoon::core::app::update::Update;
use maycoon::core::app::MayApp;
use maycoon::core::config::MayConfig;
use maycoon::core::layout::{AlignItems, Dimension, FlexDirection, LayoutStyle};
use maycoon::macros::{val, State};
use maycoon::math::Vector2;
use maycoon::widgets::button::Button;
use maycoon::widgets::container::Container;
use maycoon::widgets::text::Text;

#[derive(State)]
struct MyState {
    count: i32,
}

fn main() {
    MayApp::new(MayConfig::default()).run(
        MyState { count: 0 },
        Container::new(vec![
            Box::new(
                Button::new(Text::new("Increase".to_string())).with_on_pressed(
                    |state: &mut MyState| {
                        state.count += 1;
                        Update::DRAW
                    },
                ),
            ),
            Box::new(
                Button::new(Text::new("Decrease".to_string())).with_on_pressed(
                    |state: &mut MyState| {
                        state.count -= 1;
                        Update::DRAW
                    },
                ),
            ),
            Box::new(Text::new(val!(|state: &MyState| state.count.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()
        }),
    );
}

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.

We use the val!() macro to automatically create a Val from a closure.

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 App surface.

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

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 pop up.

Configuration

You can configure your app using the MayConfig struct just like this:

fn main() {
    let config = MayConfig {
        window: WindowConfig {
            title: "New App".to_string(), // Window Title
            size: Vector2::new(800.0, 600.0), // Window Size
            min_size: None, // Min Window Size
            max_size: None, // Max Window Size
            resizable: true, // If the window is resizeable
            maximized: false, // If the window is maximized on startup
            mode: WindowMode::default(), // The window mode (windowed, fullscreen or exclusive)
            level: Default::default(), // The window level (always top, bottom, normal)
            visible: true, // If the window is visible on startup
            blur: false, // If the background of the window should be blurred
            transparent: false, // If the window background should be transparent
            position: None, // The position (or None if you don't care)
            active: true, // If the window should be active (in focus) on startup
            buttons: WindowButtons::all(), // The enabled window buttons
            decorations: true, // If the window should have decorations/borders
            corners: Default::default(), // The corner configuration
            resize_increments: None, // Optional resize increments
            content_protected: false, // If the content should be protected
            icon: None, // Optional window icon
            cursor: Cursor::default(), // The cursor
            close_on_request: true, // If the window should close when requested via window button
        },
        renderer: RenderConfig {
            antialiasing: AaConfig::Area, // The Anti-Aliasing method
            cpu: false, // If the App should partially use the CPU (still requires a valid GPU) to render
            present_mode: PresentMode::AutoNoVsync, // The buffer presentation mode
        },
        theme: MyAwesomeTheme, // The App Theme
    };

    // your code...
}

State Management

What is state management?

When executing an application, we can't store and retrieve values from frame to frame.

To actually store and access data between frames, we need a global application state structure.

In Maycoon, we can use #[derive(State)] to automatically implement the State trait for a struct.

#![allow(unused)]
fn main() {
#[derive(State)]
struct MyState {
    my_data: MyDataType,
}
}

Managing a state.

There are multiple implementation for handling application states among different frameworks, but Maycoon uses its own kind of state management system.

Every value inside a widget (such as a text) should be wrapped inside a Val<T> where T is the data type.

The Val<T> itself is an enum and has two variants:

  • Val::State { factory, .. } for state dependent values.
  • Val::Val(T) for state independent values.

State dependent values

State dependent means, you need access to the global application State to produce a value.

This might be a simple field (state.value) or a more complex operation (e.g. retrieving something from a database).

To wrap a state dependent value inside a State you can use the Val::new_state() function, but we recommend using the val!() macro to create a state value like this:

#![allow(unused)]
fn main() {
// simple
val!(|state: MyState| state.counter);

// more complex
val!(|state: MyState| {
    let value = state.input;

    let life = meaning_of_life(value);

    return if life.is_some() {
        "life has meaning".to_string()
    } else {
        "life has no meaning".to_string()
    }
});

// or just using the `new_state` constructor
Val::new_state(|state: MyState| state.counter + 1);
}

State independent values

State independent means, you do not need to access the global application State to produce a value.

This means that you can use literals or other "constants" or simply state independent values.

You can create these values using the Val::new_val, the val!() macro or simply cast a value into a Val like this:

#![allow(unused)]
fn main() {
// constructor
Val::new_val(3);

// `val!()` macro
val!("Hello World".to_string());

// cast `into()` the `Val`
103.into();
}

State Mutability

When using Val to access data, a State is always borrowed as immutable variable. This means you only have read-access to the inner value.

If you want to make a widget with mutable state (like an on_click handler), you can use a custom Fn(&mut State) and call it in your update function, when creating a widget.

Built-in Themes

WIP

Widgets

Maycoon has a collection of built-in widgets with basic and advanced logic.

Every widget has precise documentation, an example and fully featured theming properties with documentation.

Text

Hello World example

A generic widget that displays a text...

What else is there to say?

See for yourself:

#![allow(unused)]
fn main() {
Text::new("Hello, World!".to_string())
}

or see the "hello-world" example:

use maycoon::core::app::MayApp;
use maycoon::core::config::MayConfig;
use maycoon::macros::{val, State};
use maycoon::widgets::text::Text;

#[derive(State)]
struct MyState;

fn main() {
    MayApp::new(MayConfig::default())
        .run(MyState, Text::new("Hello, World!".to_string()));
}

Button

Counter example

The button widget is probably one of the most essential things in all of the UI making world.

You can create buttons using

#![allow(unused)]
fn main() {
Button::new(
    Text::new("Hello".to_string())
).with_on_pressed(|state: &mut MyState| {
    println!("Hello World");
    Update::DRAW
})
}

For a more complex example, see the counter example:

use maycoon::core::app::update::Update;
use maycoon::core::app::MayApp;
use maycoon::core::config::MayConfig;
use maycoon::core::layout::{AlignItems, Dimension, FlexDirection, LayoutStyle};
use maycoon::core::state::Val;
use maycoon::macros::{val, State};
use maycoon::math::Vector2;
use maycoon::widgets::button::Button;
use maycoon::widgets::container::Container;
use maycoon::widgets::text::Text;

#[derive(State)]
struct MyState {
    count: i32,
}

fn main() {
    MayApp::new(MayConfig::default()).run(
        MyState { count: 0 },
        Container::new(vec![
            Val::new_val(Box::new(
                Button::new(Text::new("Increase".to_string())).with_on_pressed(
                    |state: &mut MyState| {
                        state.count += 1;
                        Update::DRAW
                    },
                ),
            )),
            Val::new_val(Box::new(
                Button::new(Text::new("Decrease".to_string())).with_on_pressed(
                    |state: &mut MyState| {
                        state.count -= 1;
                        Update::DRAW
                    },
                ),
            )),
            Val::new_val(Box::new(Text::new(val!(|state: &MyState| state
                .count
                .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()
        }),
    );
}

Container

A container is not a real UI widget that displays something, but a list of widget aligned in a certain way.

See the counter example for basic usage:

use maycoon::core::app::update::Update;
use maycoon::core::app::MayApp;
use maycoon::core::config::MayConfig;
use maycoon::core::layout::{AlignItems, Dimension, FlexDirection, LayoutStyle};
use maycoon::core::state::Val;
use maycoon::macros::{val, State};
use maycoon::math::Vector2;
use maycoon::widgets::button::Button;
use maycoon::widgets::container::Container;
use maycoon::widgets::text::Text;

#[derive(State)]
struct MyState {
    count: i32,
}

fn main() {
    MayApp::new(MayConfig::default()).run(
        MyState { count: 0 },
        Container::new(vec![
            Val::new_val(Box::new(
                Button::new(Text::new("Increase".to_string())).with_on_pressed(
                    |state: &mut MyState| {
                        state.count += 1;
                        Update::DRAW
                    },
                ),
            )),
            Val::new_val(Box::new(
                Button::new(Text::new("Decrease".to_string())).with_on_pressed(
                    |state: &mut MyState| {
                        state.count -= 1;
                        Update::DRAW
                    },
                ),
            )),
            Val::new_val(Box::new(Text::new(val!(|state: &MyState| state
                .count
                .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()
        }),
    );
}

Checkbox

counter example

The checkbox widget is used to toggle a value, which should be bound to your state.

#![allow(unused)]
fn main() {
Checkbox::new(
    val!(|state: &MyState| state.checked)
).with_on_change(|state| {
    state.checked = !state.checked;
    Update::DRAW
})
}

or the full example:

use maycoon::core::app::update::Update;
use maycoon::core::app::MayApp;
use maycoon::core::config::MayConfig;
use maycoon::core::layout::{AlignItems, Dimension, FlexDirection, LayoutStyle};
use maycoon::core::state::Val;
use maycoon::macros::{val, State};
use maycoon::math::Vector2;
use maycoon::widgets::checkbox::Checkbox;
use maycoon::widgets::container::Container;
use maycoon::widgets::text::Text;

#[derive(State)]
struct MyState {
    checked: bool,
}

fn main() {
    MayApp::new(MayConfig::default()).run(
        MyState { checked: false },
        Container::new(vec![
            Val::new_val(Box::new(
                Checkbox::new(val!(|state: &MyState| state.checked)).with_on_change(|state| {
                    state.checked = !state.checked;
                    Update::DRAW
                }),
            )),
            Val::new_val(Box::new(Text::new(val!(
                |state: &MyState| if state.checked {
                    "Checked".to_string()
                } else {
                    "Unchecked".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()
        }),
    );
}

Advanced Maycoon