fission-core 0.3.0

Core runtime, state, actions, effects, resources, input, and UI model for Fission
Documentation
use fission_core::env::{
    Clipboard, GestureState, InteractionStateMap, ScrollStateMap, TextEditStateMap,
};
use fission_core::event::{InputEvent, PointerButton, PointerEvent};
use fission_core::input::gesture::GestureController;
use fission_core::input::{ControllerContext, InputController};
use fission_core::scrollbar::scrollbar_geometry_for_node;
use fission_core::Runtime;
use fission_ir::{CompositeStyle, CoreIR, CoreNode, FlexDirection, LayoutOp, NodeId, Op};
use fission_layout::{LayoutNodeGeometry, LayoutPoint, LayoutRect, LayoutSize, LayoutSnapshot};
use std::sync::Arc;

#[derive(Default)]
struct NoClipboard;

impl Clipboard for NoClipboard {
    fn get_text(&self) -> Option<String> {
        None
    }

    fn set_text(&self, _text: &str) {}
}

#[test]
fn dragging_scrollbar_thumb_updates_scroll_offset_directly() {
    let (ir, layout, scroll) = scroll_tree(FlexDirection::Column);

    let mut text_edit = TextEditStateMap::default();
    let mut interaction = InteractionStateMap::default();
    let mut scroll_map = ScrollStateMap::default();
    let mut gesture = GestureState::default();
    let clipboard: Arc<dyn Clipboard> = Arc::new(NoClipboard);
    let mut controller = GestureController;

    let geometry =
        scrollbar_geometry_for_node(&ir, &layout, &scroll_map, scroll).expect("scrollbar");
    let down = LayoutPoint::new(
        geometry.thumb_rect.origin.x + 1.0,
        geometry.thumb_rect.origin.y + 4.0,
    );
    let move_to = LayoutPoint::new(down.x, geometry.rail_rect.bottom() - 1.0);

    let mut ctx = ControllerContext {
        ir: &ir,
        layout: &layout,
        text_edit: &mut text_edit,
        interaction: &mut interaction,
        scroll: &mut scroll_map,
        gesture: &mut gesture,
        clipboard: Some(&clipboard),
        measurer: None,
        dispatched_actions: Vec::new(),
    };

    assert!(controller.handle_event(
        &mut ctx,
        &InputEvent::Pointer(PointerEvent::Down {
            point: down,
            button: PointerButton::Primary,
            modifiers: 0,
        })
    ));
    assert!(ctx.gesture.scrollbar_drag.is_some());

    assert!(controller.handle_event(
        &mut ctx,
        &InputEvent::Pointer(PointerEvent::Move {
            point: move_to,
            modifiers: 0,
        })
    ));
    assert!(ctx.scroll.get_offset(scroll) > 0.0);

    assert!(controller.handle_event(
        &mut ctx,
        &InputEvent::Pointer(PointerEvent::Up {
            point: move_to,
            button: PointerButton::Primary,
            modifiers: 0,
        })
    ));
    assert!(ctx.gesture.scrollbar_drag.is_none());
}

#[test]
fn dragging_nested_scrollbar_uses_visual_pointer_coordinates() {
    let parent = NodeId::derived(82, &[0]);
    let child = NodeId::derived(82, &[1]);
    let mut ir = CoreIR::new();
    ir.add_node(
        child,
        Op::Layout(LayoutOp::Scroll {
            direction: FlexDirection::Row,
            show_scrollbar: true,
            width: Some(100.0),
            height: Some(50.0),
            min_width: None,
            max_width: None,
            min_height: None,
            max_height: None,
            padding: [0.0; 4],
            flex_grow: 0.0,
            flex_shrink: 0.0,
        }),
        vec![],
    );
    ir.add_node(
        parent,
        Op::Layout(LayoutOp::Scroll {
            direction: FlexDirection::Column,
            show_scrollbar: true,
            width: Some(120.0),
            height: Some(120.0),
            min_width: None,
            max_width: None,
            min_height: None,
            max_height: None,
            padding: [0.0; 4],
            flex_grow: 0.0,
            flex_shrink: 0.0,
        }),
        vec![child],
    );
    ir.set_root(parent);

    let mut layout = LayoutSnapshot::new(LayoutSize::new(120.0, 120.0));
    layout.nodes.insert(
        parent,
        LayoutNodeGeometry {
            rect: LayoutRect::new(0.0, 0.0, 120.0, 120.0),
            content_size: LayoutSize::new(120.0, 320.0),
        },
    );
    layout.nodes.insert(
        child,
        LayoutNodeGeometry {
            rect: LayoutRect::new(0.0, 160.0, 100.0, 50.0),
            content_size: LayoutSize::new(300.0, 50.0),
        },
    );

    let mut text_edit = TextEditStateMap::default();
    let mut interaction = InteractionStateMap::default();
    let mut scroll_map = ScrollStateMap::default();
    scroll_map.set_offset(parent, 100.0);
    let mut gesture = GestureState::default();
    let clipboard: Arc<dyn Clipboard> = Arc::new(NoClipboard);
    let mut controller = GestureController;
    let mut ctx = ControllerContext {
        ir: &ir,
        layout: &layout,
        text_edit: &mut text_edit,
        interaction: &mut interaction,
        scroll: &mut scroll_map,
        gesture: &mut gesture,
        clipboard: Some(&clipboard),
        measurer: None,
        dispatched_actions: Vec::new(),
    };

    assert!(controller.handle_event(
        &mut ctx,
        &InputEvent::Pointer(PointerEvent::Down {
            point: LayoutPoint::new(50.0, 104.0),
            button: PointerButton::Primary,
            modifiers: 0,
        })
    ));
    assert!(controller.handle_event(
        &mut ctx,
        &InputEvent::Pointer(PointerEvent::Move {
            point: LayoutPoint::new(98.0, 104.0),
            modifiers: 0,
        })
    ));

    assert!(
        ctx.scroll.get_offset(child) > 100.0,
        "nested horizontal scrollbar should drag in its own layout coordinates"
    );
    assert_eq!(ctx.scroll.get_offset(parent), 100.0);
}

#[test]
fn wheel_scroll_on_vertical_scrollbar_rail_updates_scroll_offset() {
    let (ir, layout, scroll) = scroll_tree(FlexDirection::Column);
    let mut runtime = Runtime::default();
    let geometry = scrollbar_geometry_for_node(&ir, &layout, &runtime.runtime_state.scroll, scroll)
        .expect("scrollbar");

    runtime
        .handle_input(
            InputEvent::Pointer(PointerEvent::Scroll {
                point: LayoutPoint::new(
                    geometry.rail_rect.origin.x + 1.0,
                    geometry.rail_rect.origin.y + 80.0,
                ),
                delta: LayoutPoint::new(0.0, 90.0),
                modifiers: 0,
            }),
            &ir,
            &layout,
        )
        .expect("scroll rail wheel");

    assert!(runtime.runtime_state.scroll.get_offset(scroll) > 0.0);
}

#[test]
fn wheel_scroll_on_horizontal_scrollbar_rail_updates_scroll_offset() {
    let (ir, layout, scroll) = scroll_tree(FlexDirection::Row);
    let mut runtime = Runtime::default();
    let geometry = scrollbar_geometry_for_node(&ir, &layout, &runtime.runtime_state.scroll, scroll)
        .expect("scrollbar");

    runtime
        .handle_input(
            InputEvent::Pointer(PointerEvent::Scroll {
                point: LayoutPoint::new(
                    geometry.rail_rect.origin.x + 80.0,
                    geometry.rail_rect.origin.y + 1.0,
                ),
                delta: LayoutPoint::new(90.0, 0.0),
                modifiers: 0,
            }),
            &ir,
            &layout,
        )
        .expect("scroll rail wheel");

    assert!(runtime.runtime_state.scroll.get_offset(scroll) > 0.0);
}

fn scroll_tree(direction: FlexDirection) -> (CoreIR, LayoutSnapshot, NodeId) {
    let scroll = NodeId::derived(81, &[1]);
    let mut ir = CoreIR::default();
    ir.nodes.insert(
        scroll,
        CoreNode {
            id: scroll,
            parent: None,
            children: Vec::new(),
            op: Op::Layout(LayoutOp::Scroll {
                direction,
                show_scrollbar: true,
                width: Some(100.0),
                height: Some(200.0),
                min_width: None,
                max_width: None,
                min_height: None,
                max_height: None,
                padding: [0.0; 4],
                flex_grow: 0.0,
                flex_shrink: 0.0,
            }),
            composite: CompositeStyle::default(),
            hash: 0,
        },
    );
    ir.set_root(scroll);

    let mut layout = LayoutSnapshot::new(LayoutSize::new(100.0, 200.0));
    let content_size = match direction {
        FlexDirection::Column => LayoutSize::new(100.0, 600.0),
        FlexDirection::Row => LayoutSize::new(500.0, 200.0),
    };
    layout.nodes.insert(
        scroll,
        LayoutNodeGeometry {
            rect: LayoutRect::new(0.0, 0.0, 100.0, 200.0),
            content_size,
        },
    );

    (ir, layout, scroll)
}