embedded-gui 0.1.0

no_std GUI and HUD primitives for embedded-graphics displays
Documentation
use embedded_graphics_core::{
    draw_target::DrawTarget,
    geometry::Size,
    pixelcolor::{Rgb565, RgbColor},
};
use embedded_graphics_simulator::{
    OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, sdl2::Keycode,
};
use embedded_gui::prelude::*;
use heapless::Vec;

const W: u32 = 192;
const H: u32 = 120;
const BOOT: ScreenId = ScreenId::new(1);
const MAIN: ScreenId = ScreenId::new(2);
const SETTINGS: ScreenId = ScreenId::new(3);
const DIALOG: ScreenId = ScreenId::new(4);

static TABS: [&str; 3] = ["SYS", "GFX", "NET"];
static SETTINGS_ITEMS: [&str; 6] = ["DITHER", "AUDIO", "RADAR", "VIBRO", "DEBUG", "ABOUT"];

#[derive(Default)]
struct DashboardIds {
    boot_progress: Option<WidgetId>,
    load_meter: Option<WidgetId>,
    fps_value: Option<WidgetId>,
    present_value: Option<WidgetId>,
    toast: Option<WidgetId>,
}

fn main() {
    let mut display = SimulatorDisplay::<Rgb565>::new(Size::new(W, H));
    let settings = OutputSettingsBuilder::new().scale(4).build();
    let mut window = Window::new("embedded-gui dashboard app", &settings);
    let mut gui = GuiContext::<32, 32, 24>::new(Rect::new(0, 0, W, H));
    let mut lifecycle = Vec::<ScreenLifecycleEvent, 16>::new();
    let mut screens = ScreenStack::<6>::with_root_lifecycle(BOOT, &mut lifecycle).unwrap();
    let mut ids = DashboardIds::default();
    let mut boot = Tween::new(0.0, 1.0, 1800, Easing::Smoothstep);
    let mut load = Tween::new(0.15, 0.95, 2200, Easing::EaseOut);
    rebuild(&mut gui, screens.current().unwrap(), &mut ids);

    'running: loop {
        if screens.current() == Some(BOOT) {
            if boot.tick(16) {
                screens
                    .apply_lifecycle(ScreenCommand::Replace(MAIN), &mut lifecycle)
                    .unwrap();
                rebuild(&mut gui, MAIN, &mut ids);
            } else if let Some(progress) = ids.boot_progress {
                gui.set_progress(progress, boot.value()).unwrap();
            }
        } else if screens.current() == Some(MAIN) {
            if load.tick(16) {
                load.reset();
            }
            if let Some(meter) = ids.load_meter {
                gui.set_meter_value(meter, load.value()).unwrap();
            }
            if let Some(fps) = ids.fps_value {
                gui.set_value_label(fps, 60).unwrap();
            }
            if let Some(toast) = ids.toast {
                gui.tick_toast(toast, 16).unwrap();
            }
        }

        if let Some(present) = ids.present_value {
            gui.set_value_label(present, gui.present_regions().count() as i32)
                .unwrap();
        }

        display.clear(Rgb565::BLACK).unwrap();
        gui.render(&mut display).unwrap();
        gui.clear_dirty();
        lifecycle.clear();
        window.update(&display);

        for event in window.events() {
            match event {
                SimulatorEvent::Quit => break 'running,
                SimulatorEvent::KeyDown { keycode, .. } => match keycode {
                    Keycode::Escape => break 'running,
                    Keycode::Space => {
                        if screens.current() == Some(MAIN) {
                            screens
                                .apply_lifecycle(ScreenCommand::Push(SETTINGS), &mut lifecycle)
                                .unwrap();
                            rebuild(&mut gui, SETTINGS, &mut ids);
                        }
                    }
                    Keycode::D => {
                        screens
                            .apply_lifecycle(ScreenCommand::Push(DIALOG), &mut lifecycle)
                            .unwrap();
                        rebuild(&mut gui, DIALOG, &mut ids);
                    }
                    Keycode::Backspace => {
                        if screens.len() > 1 {
                            screens
                                .apply_lifecycle(ScreenCommand::Pop, &mut lifecycle)
                                .unwrap();
                            rebuild(&mut gui, screens.current().unwrap_or(MAIN), &mut ids);
                        }
                    }
                    Keycode::Up => gui.handle_input(InputEvent::Up).unwrap(),
                    Keycode::Down => gui.handle_input(InputEvent::Down).unwrap(),
                    Keycode::Left => gui.handle_input(InputEvent::Left).unwrap(),
                    Keycode::Right => gui.handle_input(InputEvent::Right).unwrap(),
                    Keycode::Return => gui.handle_input(InputEvent::Select).unwrap(),
                    _ => {}
                },
                _ => {}
            }
        }
        while gui.pop_event().is_some() {}

        std::thread::sleep(std::time::Duration::from_millis(16));
    }
}

fn rebuild(gui: &mut GuiContext<'static, 32, 32, 24>, screen: ScreenId, ids: &mut DashboardIds) {
    *ids = DashboardIds::default();
    gui.clear_widgets().unwrap();
    gui.add_themed_panel(Rect::new(4, 4, 184, 112)).unwrap();

    match screen {
        BOOT => {
            gui.add_themed_label(Rect::new(16, 28, 160, 14), "BOOTING DASHBOARD")
                .unwrap();
            ids.boot_progress = Some(
                gui.add_themed_progress_bar(Rect::new(24, 58, 144, 10), 0.0)
                    .unwrap(),
            );
        }
        MAIN => {
            gui.add_themed_label(Rect::new(12, 10, 168, 10), "MAIN DASHBOARD")
                .unwrap();
            ids.load_meter = Some(
                gui.add_themed_meter(Rect::new(12, 28, 62, 42), 0.35, 0.0, 1.0)
                    .unwrap(),
            );
            ids.fps_value = Some(
                gui.add_themed_value_label(Rect::new(84, 28, 72, 12), "FPS", 60)
                    .unwrap(),
            );
            ids.present_value = Some(
                gui.add_themed_value_label(Rect::new(84, 44, 72, 12), "REG", 0)
                    .unwrap(),
            );
            gui.add_themed_button(Rect::new(84, 64, 72, 14), "SPACE SETTINGS")
                .unwrap();
            gui.add_themed_icon_button(Rect::new(12, 82, 70, 14), 'D', "DIALOG")
                .unwrap();
            ids.toast = Some(
                gui.add_themed_toast(Rect::new(88, 84, 88, 16), "LIVE VALUES", 4000)
                    .unwrap(),
            );
        }
        SETTINGS => {
            gui.add_themed_label(Rect::new(12, 10, 168, 10), "SETTINGS")
                .unwrap();
            gui.add_themed_tabs(Rect::new(12, 26, 104, 14), &TABS, 0)
                .unwrap();
            let scroll = gui
                .add_themed_scroll_view(Rect::new(12, 44, 104, 54), 0, 96)
                .unwrap();
            let list = gui
                .add_themed_list(Rect::new(4, 4, 92, 42), &SETTINGS_ITEMS, 0, 4)
                .unwrap();
            gui.add_child(scroll, list).unwrap();
            gui.add_themed_toggle(Rect::new(124, 28, 52, 14), "AA", true)
                .unwrap();
            gui.add_themed_slider(Rect::new(124, 50, 52, 14), 0.5, 0.0, 1.0)
                .unwrap();
            gui.add_themed_label(Rect::new(122, 82, 58, 18), "BACKSPACE\nRETURNS")
                .unwrap();
        }
        DIALOG => {
            gui.add_themed_dialog(
                Rect::new(32, 28, 128, 58),
                "CONFIRM",
                "THIS IS A MODAL DIALOG OVERLAY. BACKSPACE CLOSES IT.",
            )
            .unwrap();
        }
        _ => {}
    }
}