maruzzella 0.1.1

GTK4 desktop shell prototype in Rust with persisted layouts and plugin-backed views.
Documentation
use maruzzella::{
    default_product_spec, run, text_tab, BottomPanelLayout, ButtonAppearance, ButtonStyle,
    InputAppearance, MaruzzellaConfig, SurfaceAppearance, SurfaceLevel, TabGroupSpec,
    TabStripAppearance, TabStripStyle, TextAppearance, TextRole, ThemeSpec, Tone,
    WorkbenchNodeSpec,
};

fn main() {
    let mut product = default_product_spec();
    product.branding.title = "Semantic Appearance Demo".to_string();
    product.branding.search_placeholder = "Search semantic appearance demo".to_string();
    product.branding.status_text = "Panels, buttons, typography, and tabs styled by named roles"
        .to_string();

    product.layout.left_panel = TabGroupSpec::new(
        "panel-left",
        Some("overview"),
        vec![
            text_tab(
                "overview",
                "panel-left",
                "Overview",
                "This panel uses the `primary-panel` appearance id.",
                false,
            ),
            text_tab(
                "teams",
                "panel-left",
                "Teams",
                "Downstream apps can override the meaning of `primary-panel` in ThemeSpec.",
                false,
            ),
        ],
    )
    .with_panel_appearance("primary-panel")
    .with_panel_header_appearance("panel-header")
    .with_tab_strip_appearance("side-tabs");

    product.layout.right_panel = TabGroupSpec::new(
        "panel-right",
        Some("inspector"),
        vec![
            text_tab(
                "inspector",
                "panel-right",
                "Inspector",
                "This panel uses `secondary-panel` without any raw CSS selectors.",
                false,
            ),
            text_tab(
                "history",
                "panel-right",
                "History",
                "Buttons, labels, and tab strips resolve through the same semantic registry.",
                false,
            ),
        ],
    )
    .with_panel_appearance("secondary-panel")
    .with_panel_header_appearance("panel-header")
    .with_tab_strip_appearance("side-tabs");

    product.layout.bottom_panel = TabGroupSpec::new(
        "panel-bottom",
        Some("console"),
        vec![
            text_tab(
                "console",
                "panel-bottom",
                "Console",
                "The bottom panel uses a console-specific panel and tab-strip role.",
                false,
            )
            .with_text_appearance("code"),
        ],
    )
    .with_panel_appearance("console-panel")
    .with_panel_header_appearance("panel-header")
    .with_tab_strip_appearance("console-tabs")
    .with_text_appearance("code");
    product.layout.bottom_panel_layout = BottomPanelLayout::FullWidth;

    product.layout.workbench = WorkbenchNodeSpec::Group(
        TabGroupSpec::new(
            "workbench-main",
            Some("brief"),
            vec![
                text_tab(
                    "brief",
                    "workbench-main",
                    "Brief",
                    "The central workbench uses `canvas-panel` plus an `editor-tabs` strip.",
                    false,
                ),
                text_tab(
                    "copy",
                    "workbench-main",
                    "Typography",
                    "This example also overrides named text roles such as `title`, `meta`, and `code`.",
                    true,
                )
                .with_text_appearance("meta"),
            ],
        )
        .with_panel_appearance("canvas-panel")
        .with_panel_header_appearance("panel-header")
        .with_tab_strip_appearance("editor-tabs"),
    );

    let config = MaruzzellaConfig::new("com.example.semantic-appearance")
        .with_persistence_id("semantic-appearance-demo")
        .with_theme(semantic_theme())
        .with_product(product);

    run(config);
}

fn semantic_theme() -> ThemeSpec {
    let mut theme = ThemeSpec::default();
    theme.typography.font_family = "\"Azeret Mono\", \"Noto Sans\", sans-serif".to_string();
    theme.typography.mono_font_family = "\"IBM Plex Mono\", monospace".to_string();
    theme.typography.font_size_base = 14;
    theme.typography.font_size_ui = 13;
    theme.typography.font_size_small = 12;
    theme.typography.font_size_tiny = 11;
    theme.typography.font_size_title = 28;
    theme.palette.bg_0 = "#f1efe6".to_string();
    theme.palette.bg_1 = "#e3dfd3".to_string();
    theme.palette.workbench = "#f8f6ef".to_string();
    theme.palette.panel_left = "#d7e1dd".to_string();
    theme.palette.panel_right = "#e7ddd0".to_string();
    theme.palette.panel_bottom = "#d9d5df".to_string();
    theme.palette.border = "#b5b0a3".to_string();
    theme.palette.border_strong = "#8f8777".to_string();
    theme.palette.text_0 = "#1f1e1a".to_string();
    theme.palette.text_1 = "#4b463d".to_string();
    theme.palette.text_2 = "#6d665a".to_string();
    theme.palette.accent = "#0f766e".to_string();
    theme.palette.accent_strong = "#0b5f58".to_string();
    theme.density.radius_medium = 12;
    theme.density.radius_large = 16;
    theme.density.toolbar_height = 46;
    theme.density.tab_height = 32;
    theme.density.panel_header_height = 30;

    theme
        .with_surface_appearance(
            "primary-panel",
            SurfaceAppearance::new(Tone::Primary, SurfaceLevel::Raised, TextRole::Body),
        )
        .with_surface_appearance(
            "secondary-panel",
            SurfaceAppearance::new(Tone::Secondary, SurfaceLevel::Raised, TextRole::Body),
        )
        .with_surface_appearance(
            "console-panel",
            SurfaceAppearance::new(Tone::Tertiary, SurfaceLevel::Sunken, TextRole::Code),
        )
        .with_surface_appearance(
            "canvas-panel",
            SurfaceAppearance::new(Tone::Neutral, SurfaceLevel::Sunken, TextRole::Body)
                .borderless(),
        )
        .with_surface_appearance(
            "panel-header",
            SurfaceAppearance::new(Tone::Neutral, SurfaceLevel::Flat, TextRole::SectionLabel),
        )
        .with_button_appearance(
            "primary",
            ButtonAppearance::new(Tone::Accent, ButtonStyle::Solid, TextRole::BodyStrong),
        )
        .with_button_appearance(
            "secondary",
            ButtonAppearance::new(Tone::Primary, ButtonStyle::Soft, TextRole::Body),
        )
        .with_button_appearance(
            "ghost",
            ButtonAppearance::new(Tone::Neutral, ButtonStyle::Ghost, TextRole::Body),
        )
        .with_input_appearance(
            "search",
            InputAppearance::new(Tone::Secondary, SurfaceLevel::Sunken, TextRole::Body),
        )
        .with_text_appearance(
            "title",
            TextAppearance {
                role: TextRole::Title,
                tone: Tone::Accent,
            },
        )
        .with_text_appearance(
            "meta",
            TextAppearance {
                role: TextRole::Meta,
                tone: Tone::Neutral,
            },
        )
        .with_tab_strip_appearance(
            "side-tabs",
            TabStripAppearance::new(Tone::Primary, TabStripStyle::Utility, TextRole::TabLabel),
        )
        .with_tab_strip_appearance(
            "editor-tabs",
            TabStripAppearance::new(Tone::Neutral, TabStripStyle::Editor, TextRole::TabLabel),
        )
        .with_tab_strip_appearance(
            "console-tabs",
            TabStripAppearance::new(Tone::Tertiary, TabStripStyle::Console, TextRole::TabLabel),
        )
}