mtk-rs 0.1.0-beta.4

Muse Toolkit
use mtk::animation::Spring;
use mtk::style::{AlignItems, JustifyContent, Size, Style, TextStyle};
use mtk::text_property::FontWeight;
use mtk::ui::event::{EventKind, ViewEventExt as _};
use mtk::ui::morph::MorphViewExt;
use mtk::ui::transition::PageTransition;
use mtk::ui::widgets::{button, column, router, row, text};
use mtk::ui::{View, ViewStyleExt};
use mtk::windowing::{Window, WindowAttributes, WindowDimension};
use mtk::{BoxShadow, clr, rgb, rgba};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Screen {
    Gallery,
    Details(usize),
}

#[derive(Clone, Debug)]
pub struct CardData {
    pub id: usize,
    pub title: &'static str,
    pub subtitle: &'static str,
    pub description: &'static str,
    pub color_card: mtk::Color,
    pub color_hero: mtk::Color,
}

const ITEMS: [CardData; 3] = [
    CardData {
        id: 0,
        title: "Aurora Engine",
        subtitle: "High-Performance Vector Graphics",
        description: "A modern, GPU-accelerated rendering architecture designed for rich desktop interfaces with sub-millisecond frame times and analytical scissoring.",
        color_card: rgb!(79, 70, 229),
        color_hero: rgb!(67, 56, 202),
    },
    CardData {
        id: 1,
        title: "Nebula Shaders",
        subtitle: "Realtime Procedural Effects",
        description: "Composable multi-pass shader pipelines supporting frosted glass, directional blurs, analytical drop shadows, and responsive morph animations.",
        color_card: rgb!(219, 39, 119),
        color_hero: rgb!(190, 24, 93),
    },
    CardData {
        id: 2,
        title: "Emerald Core",
        subtitle: "Zero-Cost Reactive State",
        description: "Ultra-lightweight state management and memoized view hierarchies that bring desktop applications the speed of native C with declarative ergonomics.",
        color_card: rgb!(5, 150, 105),
        color_hero: rgb!(4, 120, 87),
    },
];

#[derive(Clone, Debug)]
pub struct AppState {
    pub screen: Screen,
}

#[derive(Clone, Debug)]
pub enum AppMsg {
    OpenDetails(usize),
    BackToGallery,
}

fn update(state: &mut AppState, msg: AppMsg) {
    match msg {
        AppMsg::OpenDetails(idx) => {
            state.screen = Screen::Details(idx);
        }
        AppMsg::BackToGallery => {
            state.screen = Screen::Gallery;
        }
    }
}

fn render_gallery() -> impl View<AppState, Message = AppMsg> + use<> {
    let header = column((
        text::<_, AppMsg>("MTK Shared Element Transitions").style(
            Style::new().font_size(28.0).set_text_style(TextStyle {
                color: rgb!(15, 23, 42),
                font_weight: FontWeight::BOLD,
                ..Default::default()
            }),
        ),
        text::<_, AppMsg>(
            "Click any card to observe the spring-driven morph animation across page routes",
        )
        .style(Style::new().font_size(14.0).set_text_style(TextStyle {
            color: rgb!(100, 116, 139),
            ..Default::default()
        })),
    ))
    .style(Style::new().align_items(AlignItems::Center).gap(6.0));

    let mut card_views = Vec::new();
    for item in &ITEMS {
        let card_id = item.id;
        let morph_id = format!("morph-card-{}", card_id);

        let card_content = column((
            text::<_, AppMsg>(item.title).style(Style::new().font_size(18.0).set_text_style(
                TextStyle {
                    color: clr!(white),
                    font_weight: FontWeight::BOLD,
                    ..Default::default()
                },
            )),
            text::<_, AppMsg>(item.subtitle).style(
                Style::new()
                    .width(Size::Fill)
                    .font_size(12.0)
                    .set_text_style(TextStyle {
                        color: rgba!(255, 255, 255, 220),
                        wrap: true,
                        ..Default::default()
                    }),
            ),
        ))
        .style(
            Style::new()
                .width(Size::Fixed(240))
                .height(Size::Fixed(160))
                .padding(20.0)
                .bg_color(item.color_card)
                .corner_radius(14.0)
                .box_shadow(
                    BoxShadow::drop(rgba!(0, 0, 0, 45))
                        .offset(0.0, 4.0)
                        .blur(10.0),
                )
                .justify_content(JustifyContent::SpaceBetween),
        )
        .morph(morph_id)
        .spring(Spring::gentle())
        .on_event(EventKind::Click, move |_| {
            Some(AppMsg::OpenDetails(card_id))
        });

        card_views.push(card_content);
    }

    let cards_row = row(card_views).style(
        Style::new()
            .gap(24.0)
            .justify_content(JustifyContent::Center)
            .align_items(AlignItems::Center),
    );

    column((header, cards_row)).style(
        Style::new()
            .width(Size::Percent(1.0))
            .height(Size::Percent(1.0))
            .align_items(AlignItems::Center)
            .justify_content(JustifyContent::Center)
            .gap(28.0)
            .bg_color(rgb!(248, 250, 252)),
    )
}

fn render_details(idx: usize) -> impl View<AppState, Message = AppMsg> + use<> {
    let item = &ITEMS[idx.min(ITEMS.len() - 1)];
    let morph_id = format!("morph-card-{}", item.id);

    let hero_banner = column((
        row((
            button("< Back").on_click(AppMsg::BackToGallery).style(
                Style::new()
                    .padding_xy(12.0, 6.0)
                    .bg_color(rgba!(255, 255, 255, 50))
                    .corner_radius(8.0)
                    .set_text_style(TextStyle {
                        color: clr!(white),
                        font_weight: FontWeight::BOLD,
                        font_size: 13.0,
                        ..Default::default()
                    }),
            ),
            text::<_, AppMsg>(item.subtitle).style(Style::new().font_size(13.0).set_text_style(
                TextStyle {
                    color: rgba!(255, 255, 255, 230),
                    ..Default::default()
                },
            )),
        ))
        .style(
            Style::new()
                .width(Size::Percent(1.0))
                .justify_content(JustifyContent::SpaceBetween)
                .align_items(AlignItems::Center),
        ),
        column((
            text::<_, AppMsg>(item.title).style(Style::new().font_size(32.0).set_text_style(
                TextStyle {
                    color: clr!(white),
                    font_weight: FontWeight::BOLD,
                    ..Default::default()
                },
            )),
            text::<_, AppMsg>(item.description).style(
                Style::new()
                    .width(Size::Fill)
                    .font_size(15.0)
                    .set_text_style(TextStyle {
                        color: rgba!(255, 255, 255, 235),
                        wrap: true,
                        ..Default::default()
                    }),
            ),
        ))
        .style(Style::new().width(Size::Percent(1.0)).gap(10.0)),
    ))
    .style(
        Style::new()
            .width(Size::Fixed(760))
            .height(Size::Fixed(320))
            .padding(28.0)
            .bg_color(item.color_hero)
            .corner_radius(26.0)
            .box_shadow(
                BoxShadow::drop(rgba!(0, 0, 0, 65))
                    .offset(0.0, 12.0)
                    .blur(28.0),
            )
            .justify_content(JustifyContent::SpaceBetween),
    )
    .morph(morph_id)
    .spring(Spring::gentle());

    let actions = row((button("Return to Gallery")
        .on_click(AppMsg::BackToGallery)
        .style(
            Style::new()
                .padding_xy(20.0, 10.0)
                .bg_color(rgb!(15, 23, 42))
                .corner_radius(10.0)
                .set_text_style(TextStyle {
                    color: clr!(white),
                    font_weight: FontWeight::MEDIUM,
                    font_size: 14.0,
                    ..Default::default()
                }),
        ),))
    .style(Style::new().justify_content(JustifyContent::Center));

    column((hero_banner, actions)).style(
        Style::new()
            .width(Size::Percent(1.0))
            .height(Size::Percent(1.0))
            .align_items(AlignItems::Center)
            .justify_content(JustifyContent::Center)
            .gap(20.0)
            .bg_color(rgb!(248, 250, 252)),
    )
}

fn app(state: &AppState) -> impl View<AppState, Message = AppMsg> + use<> {
    let view = match state.screen {
        Screen::Gallery => mtk::ui::boxed::BoxedView::new(render_gallery()),
        Screen::Details(idx) => mtk::ui::boxed::BoxedView::new(render_details(idx)),
    };

    router(state.screen, view).transition(PageTransition::fade().duration_secs(1.))
}

fn main() {
    let initial_state = AppState {
        screen: Screen::Gallery,
    };

    #[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 - Shared Element Morph Transitions Demo")
            .with_size(WindowDimension {
                width: 1000,
                height: 650,
            }),
    );
}