mtk-rs 0.1.0-beta.4

Muse Toolkit
use mtk::style::{AlignItems, JustifyContent, PositionStrategy, Size, Style, TextStyle};
use mtk::text_property::FontWeight;
use mtk::ui::presence::presence;
use mtk::ui::transition::{Motion, PageTransition};
use mtk::ui::widgets::{button, column, router, row, text};
use mtk::ui::{View, ViewStyleExt};
use mtk::windowing::{Window, WindowAttributes, WindowDimension};
use mtk::{clr, rgb};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Page {
    P1,
    P2,
    P3,
    P4,
    P5,
}

impl Page {
    fn number(self) -> usize {
        match self {
            Page::P1 => 1,
            Page::P2 => 2,
            Page::P3 => 3,
            Page::P4 => 4,
            Page::P5 => 5,
        }
    }

    fn from_number(n: usize) -> Self {
        match n {
            1 => Page::P1,
            2 => Page::P2,
            3 => Page::P3,
            4 => Page::P4,
            _ => Page::P5,
        }
    }
}

#[derive(Clone, Debug)]
pub struct AppState {
    pub current_page: Page,
    pub history: Vec<Page>,
}

#[derive(Clone, Debug)]
pub enum AppMsg {
    GoTo(Page),
    GoBack,
}

fn update(state: &mut AppState, msg: AppMsg) {
    match msg {
        AppMsg::GoTo(page) => {
            if state.current_page != page {
                state.history.push(state.current_page);
                state.current_page = page;
            }
        }
        AppMsg::GoBack => {
            if let Some(prev) = state.history.pop() {
                state.current_page = prev;
            }
        }
    }
}

fn render_page(num: usize, state: &AppState) -> impl View<AppState, Message = AppMsg> + use<> {
    let bg_colors = [
        rgb!(238, 242, 255),
        rgb!(254, 242, 242),
        rgb!(240, 253, 244),
        rgb!(254, 249, 195),
        rgb!(245, 243, 255),
    ];
    let accent_colors = [
        rgb!(79, 70, 229),
        rgb!(220, 38, 38),
        rgb!(22, 163, 74),
        rgb!(202, 138, 4),
        rgb!(147, 51, 234),
    ];

    let bg_color = bg_colors[(num - 1) % bg_colors.len()];
    let accent = accent_colors[(num - 1) % accent_colors.len()];

    let mut buttons = Vec::new();
    for i in 1..=5 {
        let is_current = i == num;
        let btn = button(format!("Goto({i})"))
            .on_click(AppMsg::GoTo(Page::from_number(i)))
            .style(if is_current {
                Style::new()
                    .padding_xy(16.0, 10.0)
                    .bg_color(accent)
                    .corner_radius(8.0)
                    .set_text_style(TextStyle {
                        color: clr!(white),
                        font_weight: FontWeight::BOLD,
                        font_size: 14.0,
                        ..Default::default()
                    })
            } else {
                Style::new()
                    .padding_xy(16.0, 10.0)
                    .bg_color(clr!(white))
                    .border(1.0, rgb!(203, 213, 225))
                    .corner_radius(8.0)
                    .set_text_style(TextStyle {
                        color: rgb!(51, 65, 85),
                        font_weight: FontWeight::MEDIUM,
                        font_size: 14.0,
                        ..Default::default()
                    })
            });
        buttons.push(btn);
    }

    if !state.history.is_empty() {
        buttons.push(
            button("Go Back").on_click(AppMsg::GoBack).style(
                Style::new()
                    .padding_xy(16.0, 10.0)
                    .bg_color(rgb!(241, 245, 249))
                    .border(1.0, rgb!(148, 163, 184))
                    .corner_radius(8.0)
                    .set_text_style(TextStyle {
                        color: rgb!(15, 23, 42),
                        font_weight: FontWeight::SEMI_BOLD,
                        font_size: 14.0,
                        ..Default::default()
                    }),
            ),
        );
    }

    let history_hint = presence(
        !state.history.is_empty(),
        text(format!(
            "Navigated from Page #{}",
            state.history.last().map(|p| p.number()).unwrap_or(1)
        ))
        .style(
            Style::new()
                .padding_xy(12.0, 6.0)
                .bg_color(clr!(white))
                .border(1.0, rgb!(226, 232, 240))
                .corner_radius(16.0)
                .set_text_style(TextStyle {
                    font_size: 13.0,
                    font_weight: FontWeight::MEDIUM,
                    color: rgb!(100, 116, 139),
                    ..Default::default()
                }),
        ),
    )
    .enter(Motion::slide_in_top().combined(Motion::fade_in()))
    .exit(Motion::slide_out_top().combined(Motion::fade_out()))
    .duration_ms(200.0);

    column((
        history_hint,
        text(format!("Page #{num}")).style(Style::new().set_text_style(TextStyle {
            font_size: 48.0,
            font_weight: FontWeight::BOLD,
            color: accent,
            ..Default::default()
        })),
        row(buttons).style(Style::new().gap(10.0)),
    ))
    .style(
        Style::new()
            .position(PositionStrategy::Absolute {
                top: 0.0,
                left: 0.0,
                bottom: 0.0,
                right: 0.0,
            })
            .width(Size::Percent(1.0))
            .height(Size::Percent(1.0))
            .bg_color(bg_color)
            .align_items(AlignItems::Center)
            .justify_content(JustifyContent::Center)
            .gap(24.0),
    )
}

fn app(state: &AppState) -> impl View<AppState, Message = AppMsg> + use<> {
    router(
        state.current_page,
        render_page(state.current_page.number(), state),
    )
    .transition_spec(|from, to| {
        if to.number() > from.number() {
            PageTransition::push().duration_ms(280.0)
        } else {
            PageTransition::pop().duration_ms(280.0)
        }
    })
}

fn main() {
    let initial_state = AppState {
        current_page: Page::P1,
        history: Vec::new(),
    };

    #[cfg(feature = "debugger")]
    let mut window = Window::with(initial_state, update, app);

    #[cfg(not(feature = "debugger"))]
    let window = Window::with(initial_state, update, app);

    #[cfg(feature = "debugger")]
    window.enable_terminal_debugger();

    window.present_with(
        WindowAttributes::default()
            .with_decorations(true)
            .with_title("MTK - Multi-Page Transition Demo")
            .with_size(WindowDimension {
                width: 900,
                height: 600,
            }),
    );
}