faststep 0.1.0

UIKit-inspired embedded UI framework built on embedded-graphics
Documentation
use core::convert::Infallible;

use embedded_graphics::{
    Drawable, Pixel,
    draw_target::DrawTarget,
    geometry::{OriginDimensions, Size},
    pixelcolor::Rgb565,
    prelude::{Point, Primitive, RgbColor},
    primitives::Rectangle,
};

use faststep::{
    ChildView, DisplayPort, FsTheme, I18n, ListDataSource, ListDelegate, ListRow, ListSelection,
    ListView, Localized, TouchEvent, TouchPhase, UiCanvas, UiSystem, UiView, ViewEnvironment,
    ViewEvent, ViewKind, ViewRedraw, ViewRegistration,
};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum DemoViewId {
    DevicesList,
    AlertOverlay,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum DemoMessage {
    DeviceTapped(u8),
}

struct RootView {
    list: ListView<DeviceDataSource, DeviceDelegate>,
}

impl RootView {
    fn new() -> Self {
        Self {
            list: ListView::new(DeviceDataSource, DeviceDelegate),
        }
    }
}

impl UiView<'static, DemoViewId, DemoMessage, 2> for RootView {
    fn configure(&mut self, registration: &mut ViewRegistration<'static, DemoViewId, 2>) {
        registration.set_title(Localized::new("root.title", "Devices"));
        registration.set_clips_to_bounds(true);
        let list_frame = registration.frame();
        let _ = registration.add_child(
            ChildView::new(DemoViewId::DevicesList, list_frame)
                .with_kind(ViewKind::List)
                .with_title(Localized::new("devices.title", "Devices")),
        );
        let _ = registration.add_child(
            ChildView::new(
                DemoViewId::AlertOverlay,
                Rectangle::new(Point::new(16, 16), Size::new(0, 0)),
            )
            .with_kind(ViewKind::AlertHost)
            .with_hidden(true)
            .with_alpha(0),
        );
    }

    fn update(
        &mut self,
        dt_ms: u32,
        registration: &ViewRegistration<'static, DemoViewId, 2>,
        _env: &ViewEnvironment<'_, 'static>,
    ) -> ViewEvent<DemoMessage> {
        let list = registration.children()[0].frame;
        ViewEvent::redraw(self.list.tick(dt_ms, list))
    }

    fn handle_touch(
        &mut self,
        touch: TouchEvent,
        registration: &ViewRegistration<'static, DemoViewId, 2>,
        _env: &ViewEnvironment<'_, 'static>,
    ) -> ViewEvent<DemoMessage> {
        self.list
            .handle_touch(touch, registration.children()[0].frame)
            .into_view_event()
    }

    fn draw<D>(
        &self,
        display: &mut D,
        registration: &ViewRegistration<'static, DemoViewId, 2>,
        env: &ViewEnvironment<'_, 'static>,
    ) where
        D: DrawTarget<Color = Rgb565>,
    {
        self.list
            .draw(display, registration.children()[0].frame, env);
    }
}

struct DeviceDataSource;

impl ListDataSource for DeviceDataSource {
    type ItemId = u8;

    fn item_count(&self) -> usize {
        3
    }

    fn item_id(&self, index: usize) -> Self::ItemId {
        index as u8
    }

    fn item_height(&self, index: usize) -> u32 {
        match index {
            0 => 64,
            1 => 88,
            _ => 72,
        }
    }
}

struct DeviceDelegate;

impl ListDelegate<'static, u8> for DeviceDelegate {
    type Message = DemoMessage;

    fn draw_row<D>(&self, display: &mut D, row: ListRow<u8>, _env: &ViewEnvironment<'_, 'static>)
    where
        D: DrawTarget<Color = Rgb565>,
    {
        let color = if row.state.highlighted {
            Rgb565::WHITE
        } else {
            match row.item.id {
                0 => Rgb565::RED,
                1 => Rgb565::GREEN,
                _ => Rgb565::BLUE,
            }
        };
        row.item
            .frame
            .into_styled(embedded_graphics::primitives::PrimitiveStyle::with_fill(
                color,
            ))
            .draw(display)
            .ok();
    }

    fn did_select_item(&mut self, selection: ListSelection<u8>) -> Option<Self::Message> {
        Some(DemoMessage::DeviceTapped(selection.id))
    }
}

struct NullDisplay {
    bounds: Rectangle,
}

impl NullDisplay {
    fn new(bounds: Rectangle) -> Self {
        Self { bounds }
    }
}

struct NullCanvas {
    size: Size,
}

impl OriginDimensions for NullCanvas {
    fn size(&self) -> Size {
        self.size
    }
}

impl DrawTarget for NullCanvas {
    type Color = Rgb565;
    type Error = Infallible;

    fn draw_iter<I>(&mut self, _pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Pixel<Self::Color>>,
    {
        Ok(())
    }
}

impl UiCanvas for NullCanvas {
    fn dim_rect(&mut self, _area: Rectangle, _alpha: u8) {}
}

impl DisplayPort for NullDisplay {
    type Canvas = NullCanvas;
    type Error = Infallible;

    fn bounds(&self) -> Rectangle {
        self.bounds
    }

    fn draw_frame<F>(&mut self, draw: F) -> Result<(), Self::Error>
    where
        F: FnOnce(&mut Self::Canvas),
    {
        let mut canvas = NullCanvas {
            size: self.bounds.size,
        };
        draw(&mut canvas);
        Ok(())
    }
}

fn main() {
    let bounds = Rectangle::new(Point::zero(), Size::new(320, 240));
    let mut system = UiSystem::new(
        NullDisplay::new(bounds),
        RootView::new(),
        FsTheme::default(),
        I18n::new("en", "en", &[]),
    );

    let redraw = system.update(16).redraw;
    let _ = system.draw_redraw(redraw);
    let _ = system.handle_touch(TouchEvent::new(Point::new(24, 24), TouchPhase::Start, 1));
    let _ = system.handle_touch(TouchEvent::new(Point::new(24, 24), TouchPhase::End, 2));
    let _ = system.draw();
    let _ = system.registration();
    let _ = ViewRedraw::Full;
}