use super::{BridgeRetainedState, BridgeView, render_pass::RenderPassState};
use crate::ir::{
CanvasCommand, CanvasNode, ColorToken, DataTableCell, DataTableColumn, DataTableColumnWidth,
DataTableNode, DataTableRow, DivNode, IrNode, ListItem, ScrollAxis, ShortcutBinding,
StyleColor, StyleOp, TreeItem, TreeNode, UniformListItem,
};
use gpui::{
KeybindingKeystroke, Keystroke, ListAlignment, ListState, Modifiers, MouseButton, Render,
ScrollHandle, point, px, size,
};
#[test]
fn prune_retained_state_drops_dead_scroll_handles() {
let mut view = BridgeView {
view_id: 7,
ir: IrNode::text("hello"),
retained: BridgeRetainedState::default(),
};
view.retained
.scroll_handles
.insert("keep".into(), ScrollHandle::new());
view.retained
.scroll_handles
.insert("drop".into(), ScrollHandle::new());
let state = RenderPassState {
live_scroll_ids: ["keep".to_string()].into_iter().collect(),
..Default::default()
};
view.prune_retained_state(state);
assert!(view.retained.scroll_handles.contains_key("keep"));
assert!(!view.retained.scroll_handles.contains_key("drop"));
}
#[test]
fn prune_retained_state_drops_dead_list_states() {
let mut view = BridgeView {
view_id: 7,
ir: IrNode::text("hello"),
retained: BridgeRetainedState::default(),
};
view.retained.list_states.insert(
"keep".into(),
ListState::new(1, ListAlignment::Top, px(100.0)),
);
view.retained.list_states.insert(
"drop".into(),
ListState::new(1, ListAlignment::Top, px(100.0)),
);
let state = RenderPassState {
live_list_ids: ["keep".to_string()].into_iter().collect(),
..Default::default()
};
view.prune_retained_state(state);
assert!(view.retained.list_states.contains_key("keep"));
assert!(!view.retained.list_states.contains_key("drop"));
}
#[gpui::test]
fn simulated_gpui_click_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
let before = crate::native_event_send_snapshot_for_test();
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 42,
ir: clickable_div(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_click(point(px(10.), px(10.)), Modifiers::none());
let after = crate::native_event_send_snapshot_for_test();
assert!(after.0 > before.0);
assert!(after.1 > before.1);
}
#[gpui::test]
fn focused_element_shortcut_takes_priority_over_app_command_root(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 72,
ir: nested_app_command_shortcut_divs(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["local_save_button"].is_focused(window));
});
cx.simulate_keystrokes("cmd-s");
let event = crate::take_basic_event_snapshot_matching_for_test("action", 72).unwrap();
assert_eq!(event.node_id.as_deref(), Some("local_save_button"));
assert_eq!(event.callback_id.as_deref(), Some("local_save"));
assert_eq!(event.value.as_deref(), Some("save"));
assert!(crate::take_basic_event_snapshot_matching_for_test("action", 72).is_none());
}
#[gpui::test]
fn focused_child_shortcut_takes_priority_over_ancestor(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 65,
ir: nested_shortcut_divs(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["shortcut_child"].is_focused(window));
});
cx.simulate_keystrokes("cmd-k");
let event = crate::take_basic_event_snapshot_matching_for_test("action", 65).unwrap();
assert_eq!(event.event, "action");
assert_eq!(event.view_id, 65);
assert_eq!(event.node_id.as_deref(), Some("shortcut_child"));
assert_eq!(event.callback_id.as_deref(), Some("child_action"));
assert!(crate::take_basic_event_snapshot_matching_for_test("action", 65).is_none());
}
#[gpui::test]
fn keyboard_context_menu_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 46,
ir: context_menu_div(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("shift-f10");
let event = crate::take_basic_event_snapshot_matching_for_test("context_menu", 46).unwrap();
assert_eq!(event.event, "context_menu");
assert_eq!(event.view_id, 46);
assert_eq!(event.node_id.as_deref(), Some("click_target"));
assert_eq!(event.callback_id.as_deref(), Some("contexted"));
}
#[gpui::test]
fn window_lifecycle_observers_reach_native_event_bridge(cx: &mut gpui::TestAppContext) {
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 48,
ir: IrNode::text("Lifecycle"),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
let before_activation = crate::native_event_send_snapshot_for_test();
cx.update(|window, _| window.activate_window());
cx.run_until_parked();
let after_activation = crate::native_event_send_snapshot_for_test();
assert!(after_activation.0 >= before_activation.0 + 2);
let event = crate::take_basic_event_snapshot_matching_for_test("window_focused", 48).unwrap();
assert_eq!(event.event, "window_focused");
assert_eq!(event.view_id, 48);
cx.deactivate_window();
let event = crate::take_basic_event_snapshot_matching_for_test("window_blurred", 48).unwrap();
assert_eq!(event.event, "window_blurred");
assert_eq!(event.view_id, 48);
cx.simulate_resize(size(px(320.0), px(240.0)));
let event = crate::take_basic_event_snapshot_matching_for_test("window_resized", 48).unwrap();
assert_eq!(event.event, "window_resized");
assert_eq!(event.view_id, 48);
}
#[gpui::test]
fn simulated_canvas_click_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
let before = crate::native_event_send_snapshot_for_test();
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 44,
ir: canvas_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_click(point(px(10.), px(10.)), Modifiers::none());
let after = crate::native_event_send_snapshot_for_test();
assert!(after.0 > before.0);
assert!(after.1 > before.1);
}
#[gpui::test]
fn simulated_canvas_context_menu_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 47,
ir: canvas_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_mouse_down(
point(px(10.), px(10.)),
MouseButton::Right,
Modifiers::none(),
);
let event = crate::take_basic_event_snapshot_matching_for_test("context_menu", 47).unwrap();
assert_eq!(event.event, "context_menu");
assert_eq!(event.view_id, 47);
assert_eq!(event.node_id.as_deref(), Some("summary_canvas"));
assert_eq!(event.callback_id.as_deref(), Some("canvas_context_menu"));
}
#[gpui::test]
fn render_canvas_keeps_no_retained_resources(cx: &mut gpui::TestAppContext) {
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 45,
ir: canvas_ir(),
retained: BridgeRetainedState::default(),
});
view.update_in(cx, |view, window, view_cx| {
let _ = view.render(window, view_cx);
assert!(view.retained.scroll_handles.is_empty());
assert!(view.retained.list_states.is_empty());
assert!(view.retained.focus_handles.is_empty());
assert!(view.retained.text_inputs.is_empty());
});
}
#[gpui::test]
fn render_prunes_dead_list_row_control_focus_handles(cx: &mut gpui::TestAppContext) {
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 43,
ir: list_with_row_button(),
retained: BridgeRetainedState::default(),
});
view.update_in(cx, |view, window, view_cx| {
let _ = view.render(window, view_cx);
assert!(
view.retained
.focus_handles
.contains_key("guppy-row-control:v1:43:746f646f5f6c697374:726f775f31:6f70656e")
);
view.ir = IrNode::text("no list anymore");
let _ = view.render(window, view_cx);
assert!(view.retained.focus_handles.is_empty());
});
}
#[gpui::test]
fn simulated_list_row_button_click_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
let before = crate::native_event_send_snapshot_for_test();
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 43,
ir: list_with_row_button(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_click(point(px(10.), px(10.)), Modifiers::none());
let after = crate::native_event_send_snapshot_for_test();
assert!(after.0 > before.0);
assert!(after.1 > before.1);
let event = crate::take_row_control_event_snapshot_for_test().unwrap();
assert_eq!(event.event, "click");
assert_eq!(event.view_id, 43);
assert_eq!(event.callback_id, "open_row");
assert_eq!(event.list_id, "todo_list");
assert_eq!(event.row_id, "row_1");
assert_eq!(event.control_id, "open");
assert_eq!(
event.node_id,
"guppy-row-control:v1:43:746f646f5f6c697374:726f775f31:6f70656e"
);
}
#[gpui::test]
fn uniform_list_items_support_keyboard_actions_and_arrow_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 71,
ir: keyboard_uniform_list_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["uniform_items.item_1"].is_focused(window));
});
cx.simulate_keystrokes("enter");
let event = crate::take_basic_event_snapshot_matching_for_test("click", 71).unwrap();
assert_eq!(event.node_id.as_deref(), Some("uniform_items.item_1"));
assert_eq!(event.callback_id.as_deref(), Some("uniform_clicked"));
cx.simulate_keystrokes("end");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["uniform_items.item_2"].is_focused(window));
});
cx.simulate_keystrokes("home");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["uniform_items.item_1"].is_focused(window));
});
cx.simulate_keystrokes("down");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["uniform_items.item_2"].is_focused(window));
});
cx.simulate_keystrokes("shift-f10");
let event = crate::take_basic_event_snapshot_matching_for_test("context_menu", 71).unwrap();
assert_eq!(event.node_id.as_deref(), Some("uniform_items.item_2"));
assert_eq!(event.callback_id.as_deref(), Some("uniform_context"));
}
#[gpui::test]
fn simulated_uniform_list_context_menu_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 51,
ir: IrNode::UniformList {
id: Some("uniform_items".into()),
items: vec![UniformListItem {
id: "item_1".into(),
label: "Item 1".into(),
}]
.into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
item_style: Vec::new().into(),
click: None,
context_menu: Some("uniform_context".into()),
},
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_mouse_down(
point(px(10.), px(10.)),
MouseButton::Right,
Modifiers::none(),
);
let event = crate::take_basic_event_snapshot_matching_for_test("context_menu", 51).unwrap();
assert_eq!(event.event, "context_menu");
assert_eq!(event.node_id.as_deref(), Some("uniform_items.item_1"));
assert_eq!(event.callback_id.as_deref(), Some("uniform_context"));
}
#[gpui::test]
fn sortable_data_table_headers_are_keyboard_actionable(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 56,
ir: data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("enter");
let event =
crate::take_semantic_event_snapshot_matching_for_test("data_table_sort", 56).unwrap();
assert_eq!(event.event, "data_table_sort");
assert_eq!(event.view_id, 56);
assert_eq!(event.node_id, "tasks.header.task");
assert_eq!(event.callback_id, "sort_table");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.column_id.as_deref(), Some("task"));
}
#[gpui::test]
fn list_rows_support_arrow_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 70,
ir: keyboard_list_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["todo_list.row_1"].is_focused(window));
});
cx.simulate_keystrokes("down");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["todo_list.row_2"].is_focused(window));
});
cx.simulate_keystrokes("up");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["todo_list.row_1"].is_focused(window));
});
}
#[gpui::test]
fn list_rows_support_home_end_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 72,
ir: keyboard_list_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["todo_list.row_1"].is_focused(window));
});
cx.simulate_keystrokes("end");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["todo_list.row_2"].is_focused(window));
});
cx.simulate_keystrokes("home");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["todo_list.row_1"].is_focused(window));
});
}
#[gpui::test]
fn list_rows_are_keyboard_actionable(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 69,
ir: keyboard_list_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["todo_list.row_1"].is_focused(window));
});
cx.simulate_keystrokes("enter");
let event = crate::take_basic_event_snapshot_matching_for_test("click", 69).unwrap();
assert_eq!(event.node_id.as_deref(), Some("todo_list.row_1"));
assert_eq!(event.callback_id.as_deref(), Some("row_clicked"));
cx.simulate_keystrokes("shift-f10");
let event = crate::take_basic_event_snapshot_matching_for_test("context_menu", 69).unwrap();
assert_eq!(event.node_id.as_deref(), Some("todo_list.row_1"));
assert_eq!(event.callback_id.as_deref(), Some("row_context"));
}
#[gpui::test]
fn simulated_list_row_context_menu_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 49,
ir: list_with_row_button(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_mouse_down(
point(px(10.), px(10.)),
MouseButton::Right,
Modifiers::none(),
);
let event = crate::take_basic_event_snapshot_matching_for_test("context_menu", 49).unwrap();
assert_eq!(event.event, "context_menu");
assert_eq!(event.view_id, 49);
assert_eq!(event.node_id.as_deref(), Some("todo_list.row_1"));
assert_eq!(event.callback_id.as_deref(), Some("row_context"));
}
#[gpui::test]
fn tab_key_moves_focus_across_guppy_tab_stops(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 12,
ir: IrNode::Div(Box::new(DivNode {
id: Some("root".into()),
style: Vec::new().into(),
hover_style: Vec::new().into(),
focus_style: Vec::new().into(),
focus_visible_style: Vec::new().into(),
in_focus_style: Vec::new().into(),
active_style: Vec::new().into(),
disabled_style: Vec::new().into(),
animation: None,
disabled: false,
stack_priority: None,
occlude: false,
focusable: false,
tab_stop: None,
tab_index: None,
track_scroll: false,
anchor_scroll: false,
scroll_to: false,
tooltip: None,
shortcuts: Vec::new().into(),
children: vec![
tab_stop_div_with("second", 2),
tab_stop_div_with("first", 1),
]
.into(),
click: None,
hover: None,
focus: None,
blur: None,
key_down: None,
key_up: None,
context_menu: None,
drag_start: None,
drag_move: None,
drop: None,
mouse_down: None,
mouse_up: None,
mouse_move: None,
scroll_wheel: None,
})),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _view_cx| {
assert!(view.retained.focus_visible);
assert!(view.retained.focus_handles["first"].is_focused(window));
assert!(!view.retained.focus_handles["second"].is_focused(window));
});
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _view_cx| {
assert!(view.retained.focus_visible);
assert!(!view.retained.focus_handles["first"].is_focused(window));
assert!(view.retained.focus_handles["second"].is_focused(window));
});
cx.simulate_click(point(px(10.), px(10.)), Modifiers::none());
view.update_in(cx, |view, _window, _view_cx| {
assert!(!view.retained.focus_visible);
});
}
#[gpui::test]
fn render_retains_scroll_and_focus_state_for_compliance_smoke(cx: &mut gpui::TestAppContext) {
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 10,
ir: IrNode::Scroll {
id: Some("compliance_scroll".into()),
axis: ScrollAxis::Y,
style: Vec::new().into(),
children: vec![tab_stop_div()].into(),
},
retained: BridgeRetainedState::default(),
});
view.update_in(cx, |view, window, view_cx| {
let _ = view.render(window, view_cx);
assert!(
view.retained
.scroll_handles
.contains_key("compliance_scroll")
);
assert!(view.retained.focus_handles.contains_key("tab_target"));
});
}
#[gpui::test]
fn text_input_shortcuts_are_keyboard_actionable(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 66,
ir: IrNode::TextInput {
id: Some("name_input".into()),
value: "Jason".into(),
placeholder: "Name".into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
disabled: false,
tab_index: None,
shortcuts: vec![shortcut_binding("cmd-enter", "submit", "submit_name")].into(),
change: None,
focus: None,
blur: None,
context_menu: None,
},
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("cmd-enter");
let event = crate::take_basic_event_snapshot_matching_for_test("action", 66).unwrap();
assert_eq!(event.event, "action");
assert_eq!(event.view_id, 66);
assert_eq!(event.node_id.as_deref(), Some("name_input"));
assert_eq!(event.callback_id.as_deref(), Some("submit_name"));
assert_eq!(event.value.as_deref(), Some("submit"));
}
#[gpui::test]
fn simulated_text_input_context_menu_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 50,
ir: IrNode::TextInput {
id: Some("name_input".into()),
value: "Jason".into(),
placeholder: "Name".into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
disabled: false,
tab_index: None,
shortcuts: Vec::new().into(),
change: None,
focus: None,
blur: None,
context_menu: Some("name_context".into()),
},
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_mouse_down(
point(px(10.), px(10.)),
MouseButton::Right,
Modifiers::none(),
);
let event = crate::take_basic_event_snapshot_matching_for_test("context_menu", 50).unwrap();
assert_eq!(event.event, "context_menu");
assert_eq!(event.view_id, 50);
assert_eq!(event.node_id.as_deref(), Some("name_input"));
assert_eq!(event.callback_id.as_deref(), Some("name_context"));
}
#[gpui::test]
fn simulated_textarea_context_menu_reaches_native_event_bridge(cx: &mut gpui::TestAppContext) {
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 52,
ir: IrNode::Textarea {
id: Some("notes_input".into()),
value: "Notes".into(),
placeholder: "Notes".into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
disabled: false,
tab_index: None,
shortcuts: Vec::new().into(),
change: None,
focus: None,
blur: None,
context_menu: Some("notes_context".into()),
},
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_mouse_down(
point(px(10.), px(10.)),
MouseButton::Right,
Modifiers::none(),
);
let event = crate::take_basic_event_snapshot_matching_for_test("context_menu", 52).unwrap();
assert_eq!(event.event, "context_menu");
assert_eq!(event.node_id.as_deref(), Some("notes_input"));
assert_eq!(event.callback_id.as_deref(), Some("notes_context"));
}
#[gpui::test]
fn data_table_headers_emit_keyboard_column_reorder(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 66,
ir: header_navigation_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("alt-right");
let event =
crate::take_semantic_event_snapshot_matching_for_test("data_table_column_reorder", 66)
.unwrap();
assert_eq!(event.node_id, "tasks.header.task");
assert_eq!(event.callback_id, "reorder_column");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.column_id.as_deref(), Some("task"));
assert_eq!(event.target_column_id.as_deref(), Some("status"));
assert_eq!(event.direction.as_deref(), Some("right"));
}
#[gpui::test]
fn data_table_headers_emit_keyboard_column_resize(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 67,
ir: header_navigation_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("shift-right");
let event =
crate::take_semantic_event_snapshot_matching_for_test("data_table_column_resize", 67)
.unwrap();
assert_eq!(event.node_id, "tasks.header.task");
assert_eq!(event.callback_id, "resize_column");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.column_id.as_deref(), Some("task"));
assert_eq!(event.width_delta, Some(16));
}
#[gpui::test]
fn data_table_headers_emit_pointer_column_resize(cx: &mut gpui::TestAppContext) {
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 68,
ir: header_navigation_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
let header = cx.debug_bounds("tasks.header.task").unwrap();
let start = header.center();
cx.simulate_mouse_down(start, MouseButton::Left, Modifiers::none());
cx.simulate_mouse_move(
point(start.x + px(16.0), start.y),
MouseButton::Left,
Modifiers::none(),
);
cx.simulate_mouse_move(
point(start.x + px(32.0), start.y),
MouseButton::Left,
Modifiers::none(),
);
let event =
crate::take_semantic_event_snapshot_matching_for_test("data_table_column_resize", 68)
.unwrap();
assert_eq!(event.node_id, "tasks.header.task");
assert_eq!(event.callback_id, "resize_column");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.column_id.as_deref(), Some("task"));
assert_eq!(event.width_delta, Some(16));
}
#[gpui::test]
fn data_table_headers_emit_pointer_column_reorder(cx: &mut gpui::TestAppContext) {
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 69,
ir: header_navigation_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
let header = cx.debug_bounds("tasks.header.task").unwrap();
let start = header.center();
cx.simulate_mouse_down(start, MouseButton::Left, Modifiers::alt());
cx.simulate_mouse_move(
point(start.x + px(16.0), start.y),
MouseButton::Left,
Modifiers::alt(),
);
cx.simulate_mouse_move(
point(start.x + px(32.0), start.y),
MouseButton::Left,
Modifiers::alt(),
);
let event =
crate::take_semantic_event_snapshot_matching_for_test("data_table_column_reorder", 69)
.unwrap();
assert_eq!(event.node_id, "tasks.header.task");
assert_eq!(event.callback_id, "reorder_column");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.column_id.as_deref(), Some("task"));
assert_eq!(event.target_column_id.as_deref(), Some("status"));
assert_eq!(event.direction.as_deref(), Some("right"));
}
#[gpui::test]
fn data_table_header_arrow_keys_move_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 64,
ir: header_navigation_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.task"].is_focused(window));
});
cx.simulate_keystrokes("right");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.status"].is_focused(window));
});
cx.simulate_keystrokes("left");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.task"].is_focused(window));
});
}
#[gpui::test]
fn pinned_data_table_columns_drive_header_focus_order(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 76,
ir: pinned_header_navigation_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.task"].is_focused(window));
});
cx.simulate_keystrokes("right");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.status"].is_focused(window));
});
cx.simulate_keystrokes("home");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.task"].is_focused(window));
});
}
#[gpui::test]
fn data_table_header_home_end_keys_move_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 65,
ir: header_navigation_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.task"].is_focused(window));
});
cx.simulate_keystrokes("end");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.status"].is_focused(window));
});
cx.simulate_keystrokes("home");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.task"].is_focused(window));
});
}
#[gpui::test]
fn data_table_header_down_and_cell_up_move_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 63,
ir: navigable_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.task"].is_focused(window));
});
cx.simulate_keystrokes("down");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.cell.row_1.task"].is_focused(window));
});
cx.simulate_keystrokes("up");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.header.task"].is_focused(window));
});
}
#[gpui::test]
fn data_table_rows_and_cells_are_keyboard_actionable(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 59,
ir: data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("enter");
let event =
crate::take_semantic_event_snapshot_matching_for_test("data_table_row_click", 59).unwrap();
assert_eq!(event.node_id, "tasks.row.row_1");
assert_eq!(event.callback_id, "select_row");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.row_id.as_deref(), Some("row_1"));
assert_eq!(event.column_id, None);
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("enter");
let event =
crate::take_semantic_event_snapshot_matching_for_test("data_table_cell_click", 59).unwrap();
assert_eq!(event.node_id, "tasks.cell.row_1.task");
assert_eq!(event.callback_id, "select_cell");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.row_id.as_deref(), Some("row_1"));
assert_eq!(event.column_id.as_deref(), Some("task"));
}
#[gpui::test]
fn data_table_rows_and_cells_support_keyboard_context_menu(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 60,
ir: data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("shift-f10");
let event = crate::take_semantic_event_snapshot_matching_for_test("context_menu", 60).unwrap();
assert_eq!(event.node_id, "tasks.row.row_1");
assert_eq!(event.callback_id, "row_context");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.row_id.as_deref(), Some("row_1"));
assert_eq!(event.column_id, None);
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("shift-f10");
let event = crate::take_semantic_event_snapshot_matching_for_test("context_menu", 60).unwrap();
assert_eq!(event.node_id, "tasks.cell.row_1.task");
assert_eq!(event.callback_id, "cell_context");
assert_eq!(event.table_id.as_deref(), Some("tasks"));
assert_eq!(event.row_id.as_deref(), Some("row_1"));
assert_eq!(event.column_id.as_deref(), Some("task"));
}
#[gpui::test]
fn tree_rows_are_keyboard_actionable(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 57,
ir: tree_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("enter");
let event = crate::take_semantic_event_snapshot_matching_for_test("tree_select", 57).unwrap();
assert_eq!(event.event, "tree_select");
assert_eq!(event.view_id, 57);
assert_eq!(event.node_id, "outline.row.root");
assert_eq!(event.callback_id, "select_node");
assert_eq!(event.tree_id.as_deref(), Some("outline"));
assert_eq!(event.item_id.as_deref(), Some("root"));
cx.simulate_keystrokes("space");
let event = crate::take_semantic_event_snapshot_matching_for_test("tree_toggle", 57).unwrap();
assert_eq!(event.event, "tree_toggle");
assert_eq!(event.view_id, 57);
assert_eq!(event.node_id, "outline.row.root");
assert_eq!(event.callback_id, "toggle_node");
assert_eq!(event.tree_id.as_deref(), Some("outline"));
assert_eq!(event.item_id.as_deref(), Some("root"));
}
#[gpui::test]
fn data_table_home_end_keys_move_body_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 75,
ir: navigable_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.row.row_1"].is_focused(window));
});
cx.simulate_keystrokes("end");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.row.row_2"].is_focused(window));
});
cx.simulate_keystrokes("home");
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.cell.row_1.task"].is_focused(window));
});
cx.simulate_keystrokes("end");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.cell.row_1.status"].is_focused(window));
});
cx.simulate_keystrokes("home");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.cell.row_1.task"].is_focused(window));
});
}
#[gpui::test]
fn data_table_row_right_and_first_cell_left_move_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 73,
ir: navigable_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.row.row_1"].is_focused(window));
});
cx.simulate_keystrokes("right");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.cell.row_1.task"].is_focused(window));
});
cx.simulate_keystrokes("left");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.row.row_1"].is_focused(window));
});
}
#[gpui::test]
fn data_table_arrow_keys_move_body_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 62,
ir: navigable_data_table_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.row.row_1"].is_focused(window));
});
cx.simulate_keystrokes("down");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.row.row_2"].is_focused(window));
});
cx.simulate_keystrokes("up");
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.cell.row_1.task"].is_focused(window));
});
cx.simulate_keystrokes("right");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.cell.row_1.status"].is_focused(window));
});
cx.simulate_keystrokes("down");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["tasks.cell.row_2.status"].is_focused(window));
});
}
#[gpui::test]
fn tree_right_arrow_focuses_first_child_row(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 68,
ir: tree_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.root"].is_focused(window));
});
cx.simulate_keystrokes("right");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.child"].is_focused(window));
});
}
#[gpui::test]
fn tree_left_arrow_focuses_parent_row(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 67,
ir: tree_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("down");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.child"].is_focused(window));
});
cx.simulate_keystrokes("left");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.root"].is_focused(window));
});
}
#[gpui::test]
fn tree_home_end_keys_move_row_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 74,
ir: tree_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.root"].is_focused(window));
});
cx.simulate_keystrokes("end");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.child"].is_focused(window));
});
cx.simulate_keystrokes("home");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.root"].is_focused(window));
});
}
#[gpui::test]
fn tree_arrow_keys_move_row_focus(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 61,
ir: tree_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.root"].is_focused(window));
});
cx.simulate_keystrokes("down");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.child"].is_focused(window));
});
cx.simulate_keystrokes("up");
view.update_in(cx, |view, window, _| {
assert!(view.retained.focus_handles["outline.row.root"].is_focused(window));
});
}
#[gpui::test]
fn tree_rows_support_keyboard_context_menu(cx: &mut gpui::TestAppContext) {
cx.update(super::bind_focus_keys);
let (_view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 58,
ir: tree_ir(),
retained: BridgeRetainedState::default(),
});
cx.update(|window, cx| window.draw(cx).clear());
cx.simulate_keystrokes("tab");
cx.simulate_keystrokes("shift-f10");
let event = crate::take_semantic_event_snapshot_matching_for_test("context_menu", 58).unwrap();
assert_eq!(event.event, "context_menu");
assert_eq!(event.view_id, 58);
assert_eq!(event.node_id, "outline.row.root");
assert_eq!(event.callback_id, "tree_context_menu");
assert_eq!(event.tree_id.as_deref(), Some("outline"));
assert_eq!(event.item_id.as_deref(), Some("root"));
}
#[gpui::test]
fn render_retains_data_table_and_tree_list_states(cx: &mut gpui::TestAppContext) {
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 55,
ir: IrNode::Div(Box::new(DivNode {
id: Some("semantic_root".into()),
style: Vec::new().into(),
hover_style: Vec::new().into(),
focus_style: Vec::new().into(),
focus_visible_style: Vec::new().into(),
in_focus_style: Vec::new().into(),
active_style: Vec::new().into(),
disabled_style: Vec::new().into(),
animation: None,
disabled: false,
stack_priority: None,
occlude: false,
focusable: false,
tab_stop: None,
tab_index: None,
track_scroll: false,
anchor_scroll: false,
scroll_to: false,
tooltip: None,
shortcuts: Vec::new().into(),
children: vec![data_table_ir(), tree_ir()].into(),
click: None,
hover: None,
focus: None,
blur: None,
key_down: None,
key_up: None,
context_menu: None,
drag_start: None,
drag_move: None,
drop: None,
mouse_down: None,
mouse_up: None,
mouse_move: None,
scroll_wheel: None,
})),
retained: BridgeRetainedState::default(),
});
view.update_in(cx, |view, window, view_cx| {
let _ = view.render(window, view_cx);
assert!(view.retained.list_states.contains_key("tasks.rows"));
assert!(view.retained.list_states.contains_key("outline.rows"));
});
}
#[gpui::test]
fn render_prunes_dead_text_input_entities(cx: &mut gpui::TestAppContext) {
let (view, cx) = cx.add_window_view(|_, _| BridgeView {
view_id: 9,
ir: IrNode::TextInput {
id: Some("name_input".into()),
value: "Jason".into(),
placeholder: "Name".into(),
style: Vec::new().into(),
disabled: false,
tab_index: None,
shortcuts: Vec::new().into(),
change: Some("name_changed".into()),
focus: Some("name_focused".into()),
blur: Some("name_blurred".into()),
context_menu: Some("name_context".into()),
},
retained: BridgeRetainedState::default(),
});
view.update_in(cx, |view, window, view_cx| {
let _ = view.render(window, view_cx);
assert_eq!(view.retained.text_inputs.len(), 1);
view.ir = IrNode::text("no input anymore");
let _ = view.render(window, view_cx);
assert!(view.retained.text_inputs.is_empty());
});
}
fn tab_stop_div() -> IrNode {
tab_stop_div_with("tab_target", 1)
}
fn tab_stop_div_with(id: &str, tab_index: isize) -> IrNode {
IrNode::Div(Box::new(DivNode {
id: Some(id.into()),
style: vec![StyleOp::W96, StyleOp::H32].into(),
hover_style: Vec::new().into(),
focus_style: Vec::new().into(),
focus_visible_style: vec![StyleOp::Border1].into(),
in_focus_style: Vec::new().into(),
active_style: Vec::new().into(),
disabled_style: Vec::new().into(),
animation: None,
disabled: false,
stack_priority: None,
occlude: false,
focusable: true,
tab_stop: Some(true),
tab_index: Some(tab_index),
track_scroll: false,
anchor_scroll: false,
scroll_to: false,
tooltip: None,
shortcuts: Vec::new().into(),
children: vec![IrNode::text("tab target")].into(),
click: None,
hover: None,
focus: None,
blur: None,
key_down: None,
key_up: None,
context_menu: None,
drag_start: None,
drag_move: None,
drop: None,
mouse_down: None,
mouse_up: None,
mouse_move: None,
scroll_wheel: None,
}))
}
fn data_table_ir() -> IrNode {
IrNode::DataTable(Box::new(DataTableNode {
id: Some("tasks".into()),
columns: vec![DataTableColumn {
id: "task".into(),
label: "Task".into(),
width: DataTableColumnWidth::Fr(1),
sortable: true,
pinned: false,
style: Vec::new().into(),
}]
.into(),
rows: vec![DataTableRow {
id: "row_1".into(),
cells: vec![DataTableCell {
column_id: "task".into(),
children: vec![IrNode::text("Ship")].into(),
style: Vec::new().into(),
}]
.into(),
style: Vec::new().into(),
}]
.into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
header_style: Vec::new().into(),
row_style: Vec::new().into(),
cell_style: Vec::new().into(),
selected_row_id: Some("row_1".into()),
selected_cell: Some(("row_1".into(), "task".into())),
sort: None,
row_click: Some("select_row".into()),
cell_click: Some("select_cell".into()),
sort_callback: Some("sort_table".into()),
column_reorder: None,
column_resize: None,
row_context_menu: Some("row_context".into()),
cell_context_menu: Some("cell_context".into()),
}))
}
fn header_navigation_data_table_ir() -> IrNode {
IrNode::DataTable(Box::new(DataTableNode {
id: Some("tasks".into()),
columns: vec![
DataTableColumn {
id: "task".into(),
label: "Task".into(),
width: DataTableColumnWidth::Fr(1),
sortable: true,
pinned: false,
style: Vec::new().into(),
},
DataTableColumn {
id: "status".into(),
label: "Status".into(),
width: DataTableColumnWidth::Fr(1),
sortable: true,
pinned: false,
style: Vec::new().into(),
},
]
.into(),
rows: vec![data_table_row_for_navigation("row_1", "Ship", "Ready")].into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
header_style: Vec::new().into(),
row_style: Vec::new().into(),
cell_style: Vec::new().into(),
selected_row_id: None,
selected_cell: None,
sort: None,
row_click: Some("select_row".into()),
cell_click: Some("select_cell".into()),
sort_callback: Some("sort_table".into()),
column_reorder: Some("reorder_column".into()),
column_resize: Some("resize_column".into()),
row_context_menu: Some("row_context".into()),
cell_context_menu: Some("cell_context".into()),
}))
}
fn pinned_header_navigation_data_table_ir() -> IrNode {
IrNode::DataTable(Box::new(DataTableNode {
id: Some("tasks".into()),
columns: vec![
DataTableColumn {
id: "status".into(),
label: "Status".into(),
width: DataTableColumnWidth::Fr(1),
sortable: true,
pinned: false,
style: Vec::new().into(),
},
DataTableColumn {
id: "task".into(),
label: "Task".into(),
width: DataTableColumnWidth::Fr(1),
sortable: true,
pinned: true,
style: Vec::new().into(),
},
]
.into(),
rows: vec![data_table_row_for_navigation("row_1", "Ship", "Ready")].into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
header_style: Vec::new().into(),
row_style: Vec::new().into(),
cell_style: Vec::new().into(),
selected_row_id: None,
selected_cell: None,
sort: None,
row_click: Some("select_row".into()),
cell_click: Some("select_cell".into()),
sort_callback: Some("sort_table".into()),
column_reorder: Some("reorder_column".into()),
column_resize: Some("resize_column".into()),
row_context_menu: Some("row_context".into()),
cell_context_menu: Some("cell_context".into()),
}))
}
fn navigable_data_table_ir() -> IrNode {
IrNode::DataTable(Box::new(DataTableNode {
id: Some("tasks".into()),
columns: vec![
DataTableColumn {
id: "task".into(),
label: "Task".into(),
width: DataTableColumnWidth::Fr(1),
sortable: true,
pinned: false,
style: Vec::new().into(),
},
DataTableColumn {
id: "status".into(),
label: "Status".into(),
width: DataTableColumnWidth::Fr(1),
sortable: false,
pinned: false,
style: Vec::new().into(),
},
]
.into(),
rows: vec![
data_table_row_for_navigation("row_1", "Ship", "Ready"),
data_table_row_for_navigation("row_2", "Test", "Blocked"),
]
.into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
header_style: Vec::new().into(),
row_style: Vec::new().into(),
cell_style: Vec::new().into(),
selected_row_id: None,
selected_cell: None,
sort: None,
row_click: Some("select_row".into()),
cell_click: Some("select_cell".into()),
sort_callback: Some("sort_table".into()),
column_reorder: None,
column_resize: None,
row_context_menu: Some("row_context".into()),
cell_context_menu: Some("cell_context".into()),
}))
}
fn data_table_row_for_navigation(id: &str, task: &str, status: &str) -> DataTableRow {
DataTableRow {
id: id.into(),
cells: vec![
DataTableCell {
column_id: "task".into(),
children: vec![IrNode::text(task)].into(),
style: Vec::new().into(),
},
DataTableCell {
column_id: "status".into(),
children: vec![IrNode::text(status)].into(),
style: Vec::new().into(),
},
]
.into(),
style: Vec::new().into(),
}
}
fn tree_ir() -> IrNode {
IrNode::Tree(Box::new(TreeNode {
id: Some("outline".into()),
nodes: vec![TreeItem {
id: "root".into(),
label: "Root".into(),
expanded: true,
children: vec![TreeItem {
id: "child".into(),
label: "Child".into(),
expanded: false,
children: Vec::new().into(),
style: Vec::new().into(),
}]
.into(),
style: Vec::new().into(),
}]
.into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
row_style: Vec::new().into(),
selected_id: Some("child".into()),
select: Some("select_node".into()),
toggle: Some("toggle_node".into()),
context_menu: Some("tree_context_menu".into()),
}))
}
fn canvas_ir() -> IrNode {
IrNode::Canvas(Box::new(CanvasNode {
id: Some("summary_canvas".into()),
commands: vec![
CanvasCommand::Rect {
x: 0.0,
y: 0.0,
width: 120.0,
height: 80.0,
fill: StyleColor::Hex(0x0f172a),
radius: 0.0,
},
CanvasCommand::PatternRect {
x: 12.0,
y: 12.0,
width: 96.0,
height: 24.0,
color: StyleColor::Token(ColorToken::Blue),
line_width: 0.05,
interval: 0.12,
radius: 8.0,
},
]
.into(),
style: vec![StyleOp::WPx(120.0), StyleOp::HPx(80.0)].into(),
click: Some("canvas_clicked".into()),
context_menu: Some("canvas_context_menu".into()),
}))
}
fn keyboard_uniform_list_ir() -> IrNode {
IrNode::UniformList {
id: Some("uniform_items".into()),
items: vec![
UniformListItem {
id: "item_1".into(),
label: "Item 1".into(),
},
UniformListItem {
id: "item_2".into(),
label: "Item 2".into(),
},
]
.into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
item_style: Vec::new().into(),
click: Some("uniform_clicked".into()),
context_menu: Some("uniform_context".into()),
}
}
fn keyboard_list_ir() -> IrNode {
IrNode::List {
id: Some("todo_list".into()),
items: vec![
ListItem {
id: "row_1".into(),
children: vec![IrNode::text("Row one")].into(),
},
ListItem {
id: "row_2".into(),
children: vec![IrNode::text("Row two")].into(),
},
]
.into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
item_style: Vec::new().into(),
click: Some("row_clicked".into()),
context_menu: Some("row_context".into()),
}
}
fn list_with_row_button() -> IrNode {
IrNode::List {
id: Some("todo_list".into()),
items: vec![ListItem {
id: "row_1".into(),
children: vec![IrNode::Button(Box::new(DivNode {
id: Some("open".into()),
style: vec![StyleOp::W96, StyleOp::H32, StyleOp::P4, StyleOp::Border1].into(),
hover_style: Vec::new().into(),
focus_style: Vec::new().into(),
focus_visible_style: Vec::new().into(),
in_focus_style: Vec::new().into(),
active_style: Vec::new().into(),
disabled_style: Vec::new().into(),
animation: None,
disabled: false,
stack_priority: None,
occlude: false,
focusable: true,
tab_stop: Some(true),
tab_index: None,
track_scroll: false,
anchor_scroll: false,
scroll_to: false,
tooltip: None,
shortcuts: Vec::new().into(),
children: vec![IrNode::text("Open")].into(),
click: Some("open_row".into()),
hover: None,
focus: None,
blur: None,
key_down: None,
key_up: None,
context_menu: None,
drag_start: None,
drag_move: None,
drop: None,
mouse_down: None,
mouse_up: None,
mouse_move: None,
scroll_wheel: None,
}))]
.into(),
}]
.into(),
style: vec![StyleOp::W96, StyleOp::H32].into(),
item_style: Vec::new().into(),
click: None,
context_menu: Some("row_context".into()),
}
}
fn nested_app_command_shortcut_divs() -> IrNode {
let child = IrNode::Div(Box::new(shortcut_div_with_shortcut(
"local_save_button",
2,
"cmd-s",
"save",
"local_save",
vec![IrNode::text("save here")],
)));
IrNode::Div(Box::new(shortcut_div_with_shortcut(
"app_command_root",
1,
"cmd-s",
"save",
"guppy.app.command",
vec![child],
)))
}
fn nested_shortcut_divs() -> IrNode {
let child = IrNode::Div(Box::new(shortcut_div(
"shortcut_child",
2,
"child_action",
vec![IrNode::text("child")],
)));
IrNode::Div(Box::new(shortcut_div(
"shortcut_parent",
1,
"parent_action",
vec![child],
)))
}
fn shortcut_div(id: &str, tab_index: isize, callback: &str, children: Vec<IrNode>) -> DivNode {
shortcut_div_with_shortcut(id, tab_index, "cmd-k", "open", callback, children)
}
fn shortcut_div_with_shortcut(
id: &str,
tab_index: isize,
shortcut: &str,
action: &str,
callback: &str,
children: Vec<IrNode>,
) -> DivNode {
DivNode {
id: Some(id.into()),
style: vec![StyleOp::W96, StyleOp::H32, StyleOp::P4, StyleOp::Border1].into(),
hover_style: Vec::new().into(),
focus_style: Vec::new().into(),
focus_visible_style: Vec::new().into(),
in_focus_style: Vec::new().into(),
active_style: Vec::new().into(),
disabled_style: Vec::new().into(),
animation: None,
disabled: false,
stack_priority: None,
occlude: false,
focusable: false,
tab_stop: Some(true),
tab_index: Some(tab_index),
track_scroll: false,
anchor_scroll: false,
scroll_to: false,
tooltip: None,
shortcuts: vec![shortcut_binding(shortcut, action, callback)].into(),
children: children.into(),
click: None,
hover: None,
focus: None,
blur: None,
key_down: None,
key_up: None,
context_menu: None,
drag_start: None,
drag_move: None,
drop: None,
mouse_down: None,
mouse_up: None,
mouse_move: None,
scroll_wheel: None,
}
}
fn shortcut_binding(shortcut: &str, action: &str, callback: &str) -> ShortcutBinding {
ShortcutBinding {
shortcut: shortcut.into(),
action: action.into(),
callback: callback.into(),
parsed: KeybindingKeystroke::from_keystroke(Keystroke::parse(shortcut).unwrap()),
}
}
fn context_menu_div() -> IrNode {
let mut node = clickable_div();
if let IrNode::Div(div) = &mut node {
div.context_menu = Some("contexted".into());
}
node
}
fn clickable_div() -> IrNode {
IrNode::Div(Box::new(DivNode {
id: Some("click_target".into()),
style: vec![StyleOp::W96, StyleOp::H32, StyleOp::P4, StyleOp::Border1].into(),
hover_style: Vec::new().into(),
focus_style: Vec::new().into(),
focus_visible_style: Vec::new().into(),
in_focus_style: Vec::new().into(),
active_style: Vec::new().into(),
disabled_style: Vec::new().into(),
animation: None,
disabled: false,
stack_priority: None,
occlude: false,
focusable: false,
tab_stop: None,
tab_index: None,
track_scroll: false,
anchor_scroll: false,
scroll_to: false,
tooltip: None,
shortcuts: Vec::new().into(),
children: vec![IrNode::text("click me")].into(),
click: Some("clicked".into()),
hover: None,
focus: None,
blur: None,
key_down: None,
key_up: None,
context_menu: None,
drag_start: None,
drag_move: None,
drop: None,
mouse_down: None,
mouse_up: None,
mouse_move: None,
scroll_wheel: None,
}))
}