fission-core 0.14.1

Core runtime, state, actions, effects, resources, input, and UI model for Fission
Documentation
use fission_core::event::{InputEvent, PointerButton, PointerEvent};
use fission_core::{ActionEnvelope, ActionId, GlobalState, Runtime};
use fission_ir::semantics::ActionTrigger;
use fission_ir::WidgetId;
use fission_ir::{ActionEntry, ActionSet, CompositeStyle, CoreIR, CoreNode, Op, Role, Semantics};
use fission_layout::{LayoutNodeGeometry, LayoutPoint, LayoutRect, LayoutSize, LayoutSnapshot};

#[derive(Default, Debug, Clone)]
struct SliderState {
    changes: usize,
    value: f32,
}

impl GlobalState for SliderState {}

#[test]
fn slider_pointer_up_does_not_replay_template_change_payload() -> anyhow::Result<()> {
    let action_id = ActionId::from_name("fission_core_test::SliderChanged");
    let slider_id = WidgetId::explicit("slider");
    let (ir, layout) = slider_tree(slider_id, action_id);
    let mut runtime = Runtime::default();
    runtime.add_app_state(Box::new(SliderState::default()))?;
    runtime.register_reducer::<SliderState>(action_id, record_slider_change)?;

    let point = LayoutPoint::new(150.0, 20.0);
    runtime.handle_input(
        InputEvent::Pointer(PointerEvent::Down {
            pointer_id: Default::default(),
            kind: Default::default(),
            point,
            button: PointerButton::Primary,
            modifiers: 0,
        }),
        &ir,
        &layout,
    )?;
    runtime.handle_input(
        InputEvent::Pointer(PointerEvent::Up {
            pointer_id: Default::default(),
            kind: Default::default(),
            point,
            button: PointerButton::Primary,
            modifiers: 0,
        }),
        &ir,
        &layout,
    )?;

    let state = runtime
        .get_app_state::<SliderState>()
        .expect("slider state");
    assert_eq!(state.changes, 1);
    assert_eq!(state.value, 25.0);
    Ok(())
}

#[test]
fn secondary_click_does_not_change_slider_value() -> anyhow::Result<()> {
    let action_id = ActionId::from_name("fission_core_test::SliderChanged");
    let slider_id = WidgetId::explicit("slider");
    let (ir, layout) = slider_tree(slider_id, action_id);
    let mut runtime = Runtime::default();
    runtime.add_app_state(Box::new(SliderState::default()))?;
    runtime.register_reducer::<SliderState>(action_id, record_slider_change)?;

    let point = LayoutPoint::new(150.0, 20.0);
    runtime.handle_input(
        InputEvent::Pointer(PointerEvent::Down {
            pointer_id: Default::default(),
            kind: Default::default(),
            point,
            button: PointerButton::Secondary,
            modifiers: 0,
        }),
        &ir,
        &layout,
    )?;
    runtime.handle_input(
        InputEvent::Pointer(PointerEvent::Up {
            pointer_id: Default::default(),
            kind: Default::default(),
            point,
            button: PointerButton::Secondary,
            modifiers: 0,
        }),
        &ir,
        &layout,
    )?;

    let state = runtime
        .get_app_state::<SliderState>()
        .expect("slider state");
    assert_eq!(state.changes, 0);
    assert_eq!(state.value, 0.0);
    Ok(())
}

fn record_slider_change(
    state: &mut SliderState,
    action: &ActionEnvelope,
    _target: WidgetId,
) -> anyhow::Result<()> {
    state.changes += 1;
    state.value = serde_json::from_slice(&action.payload)?;
    Ok(())
}

fn slider_tree(slider_id: WidgetId, action_id: ActionId) -> (CoreIR, LayoutSnapshot) {
    let mut ir = CoreIR::default();
    ir.root = Some(slider_id);
    ir.nodes.insert(
        slider_id,
        CoreNode {
            id: slider_id,
            op: Op::Semantics(Semantics {
                role: Role::Slider,
                focusable: true,
                draggable: true,
                min_value: Some(0.0),
                max_value: Some(100.0),
                current_value: Some(0.0),
                actions: ActionSet {
                    entries: vec![ActionEntry {
                        trigger: ActionTrigger::Change,
                        action_id: action_id.as_u128(),
                        payload_data: Some(serde_json::to_vec(&0.0).unwrap()),
                    }],
                },
                ..Default::default()
            }),
            composite: CompositeStyle::default(),
            children: Vec::new(),
            parent: None,
            hash: 0,
        },
    );

    let mut layout = LayoutSnapshot::new(LayoutSize::new(300.0, 100.0));
    layout.nodes.insert(
        slider_id,
        LayoutNodeGeometry {
            rect: LayoutRect::new(100.0, 10.0, 200.0, 20.0),
            content_size: LayoutSize::new(200.0, 20.0),
        },
    );
    (ir, layout)
}