buoyant 0.6.1

SwiftUI-like UIs in Rust for embedded devices
Documentation
use core::time::Duration;

mod common;
use crate::common::{touch_down, touch_move, touch_up};

use buoyant::{
    environment::DefaultEnvironment,
    event::EventContext,
    font::CharacterBufferFont,
    primitives::{Point, Size},
    render::Render,
    render_target::FixedTextBuffer,
    view::prelude::*,
};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct AppState {
    pub a: i32,
    pub b: i32,
}

fn counter_view(state: &AppState) -> impl View<char, AppState> + use<> {
    VStack::new((
        Lens::new(count_view(state.a), |captures: &mut AppState| {
            &mut captures.a
        }),
        Lens::new(count_view(state.b), |captures: &mut AppState| {
            &mut captures.b
        }),
    ))
    .with_alignment(HorizontalAlignment::Leading)
}

fn count_view(count: i32) -> impl View<char, i32> {
    VStack::new((
        Text::new_fmt::<16>(format_args!("count: {count}"), &CharacterBufferFont),
        Button::new(
            |count: &mut i32| {
                *count += 1;
            },
            |_| Text::new("Increment", &CharacterBufferFont),
        ),
        Button::new(
            |count: &mut i32| {
                *count -= 1;
            },
            |_| Text::new("Decrement", &CharacterBufferFont),
        ),
    ))
}

fn rebuild_tree<V, F>(
    app_state: &mut AppState,
    view_state: &mut V::State,
    view: F,
    size: Size,
) -> V::Renderables
where
    V: View<char, AppState>,
    F: Fn(&AppState) -> V,
{
    let env = DefaultEnvironment::default();
    let view = (view)(app_state);
    let layout = view.layout(&size.into(), &env, app_state, view_state);
    view.render_tree(&layout, Point::zero(), &env, app_state, view_state)
}

#[test]
fn increment_single_frame() {
    let mut app_state = AppState::default();
    let view = counter_view(&app_state);
    let mut view_state = view.build_state(&mut app_state);

    let mut buffer = FixedTextBuffer::<10, 6>::default();
    let mut tree = rebuild_tree(&mut app_state, &mut view_state, counter_view, buffer.size());
    tree.render(&mut buffer, &' ');
    assert_eq!(buffer.text[0].iter().collect::<String>(), "count: 0  ");
    assert_eq!(buffer.text[1].iter().collect::<String>(), "Increment ");
    assert_eq!(buffer.text[2].iter().collect::<String>(), "Decrement ");
    assert_eq!(buffer.text[3].iter().collect::<String>(), "count: 0  ");
    assert_eq!(buffer.text[4].iter().collect::<String>(), "Increment ");
    assert_eq!(buffer.text[5].iter().collect::<String>(), "Decrement ");
    assert_eq!(app_state, AppState { a: 0, b: 0 });

    view.handle_event(
        &touch_down(1, 1),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 0, b: 0 });
    view.handle_event(
        &touch_up(1, 1),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 1, b: 0 });

    let mut buffer = FixedTextBuffer::<10, 6>::default();
    let tree = rebuild_tree(&mut app_state, &mut view_state, counter_view, buffer.size());
    tree.render(&mut buffer, &' ');
    assert_eq!(buffer.text[0].iter().collect::<String>(), "count: 1  ");
    assert_eq!(buffer.text[1].iter().collect::<String>(), "Increment ");
    assert_eq!(buffer.text[2].iter().collect::<String>(), "Decrement ");
    assert_eq!(buffer.text[3].iter().collect::<String>(), "count: 0  ");
    assert_eq!(buffer.text[4].iter().collect::<String>(), "Increment ");
    assert_eq!(buffer.text[5].iter().collect::<String>(), "Decrement ");
}

#[test]
fn drag_cancel() {
    let mut app_state = AppState::default();
    let view = counter_view(&app_state);
    let mut view_state = view.build_state(&mut app_state);

    let mut tree = rebuild_tree(
        &mut app_state,
        &mut view_state,
        counter_view,
        Size::new(10, 6),
    );

    view.handle_event(
        &touch_down(1, 1),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 0, b: 0 });
    view.handle_event(
        &touch_move(1, 2),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 0, b: 0 });
    view.handle_event(
        &touch_up(1, 2),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 0, b: 0 });
}

#[test]
fn drag_cancel_uncancel() {
    let mut app_state = AppState::default();
    let view = counter_view(&app_state);
    let mut view_state = view.build_state(&mut app_state);

    let mut tree = rebuild_tree(
        &mut app_state,
        &mut view_state,
        counter_view,
        Size::new(10, 6),
    );

    view.handle_event(
        &touch_down(1, 1),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 0, b: 0 });
    view.handle_event(
        &touch_move(1, 2),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 0, b: 0 });
    view.handle_event(
        &touch_move(5, 1),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 0, b: 0 });
    view.handle_event(
        &touch_up(5, 1),
        &EventContext::new(Duration::ZERO),
        &mut tree,
        &mut app_state,
        &mut view_state,
    );
    assert_eq!(app_state, AppState { a: 1, b: 0 });
}