mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 04:37:29 -06:00
The released buttons go after the frame's last pointer movement, at its position. Before, they went first, at the press position, so a press that moved past the click distance in the same frame ended as a click on the widget under the press.
481 lines
22 KiB
Rust
481 lines
22 KiB
Rust
//! The dock area as the user sees it: whole-window frames driven by pointer and key events.
|
||
|
||
use craft_ui::docking::Hit;
|
||
use egui::{Event, PointerButton, Pos2, Rect, pos2, vec2};
|
||
use vectorcraft_engine::Session;
|
||
|
||
use super::*;
|
||
|
||
const SCREEN: Rect = Rect::from_min_max(Pos2::ZERO, pos2(1600.0, 1000.0));
|
||
|
||
struct Harness {
|
||
app: VectorcraftApp,
|
||
ctx: egui::Context,
|
||
time: f64,
|
||
}
|
||
|
||
impl Harness {
|
||
fn new() -> Self {
|
||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||
let mut h = Self { app, ctx: egui::Context::default(), time: 0.0 };
|
||
for _ in 0..3 {
|
||
h.frame(vec![]);
|
||
}
|
||
h
|
||
}
|
||
|
||
fn frame(&mut self, events: Vec<Event>) {
|
||
self.time += 0.05;
|
||
let input = egui::RawInput { time: Some(self.time), events, screen_rect: Some(SCREEN), ..Default::default() };
|
||
self.run(input);
|
||
}
|
||
|
||
/// A frame whose input first goes through [`VectorcraftApp::release_lost_buttons`], with the
|
||
/// system reporting a mouse button down or not.
|
||
fn frame_with_system_buttons(&mut self, events: Vec<Event>, system_held: bool) {
|
||
self.time += 0.05;
|
||
let mut input = egui::RawInput { time: Some(self.time), events, screen_rect: Some(SCREEN), ..Default::default() };
|
||
self.app.release_lost_buttons(&self.ctx, &mut input, system_held);
|
||
self.run(input);
|
||
}
|
||
|
||
fn run(&mut self, input: egui::RawInput) {
|
||
let app = &mut self.app;
|
||
let mut out = self.ctx.run_ui(input, |ui| {
|
||
app.logic(ui.ctx());
|
||
app.ui(ui);
|
||
});
|
||
out.textures_delta.clear();
|
||
}
|
||
|
||
fn hit(&self, hit: &Hit<String>) -> Option<Rect> {
|
||
hit_rects(&self.ctx).into_iter().find(|(h, _)| h == hit).map(|(_, r)| r)
|
||
}
|
||
|
||
fn tab(&self, panel: &str) -> Option<Rect> {
|
||
self.hit(&Hit::Tab(panel.into()))
|
||
}
|
||
|
||
fn click(&mut self, at: Pos2) {
|
||
self.frame(vec![Event::PointerMoved(at), button(at, true)]);
|
||
self.frame(vec![button(at, false)]);
|
||
self.frame(vec![]);
|
||
}
|
||
|
||
/// Press at `from` and move to `to` in steps, holding the button.
|
||
fn press_and_move(&mut self, from: Pos2, to: Pos2) {
|
||
self.frame(vec![Event::PointerMoved(from), button(from, true)]);
|
||
for k in 1..=6 {
|
||
let at = from + (to - from) * (k as f32 / 6.0);
|
||
self.frame(vec![Event::PointerMoved(at)]);
|
||
}
|
||
}
|
||
|
||
fn drag(&mut self, from: Pos2, to: Pos2) {
|
||
self.press_and_move(from, to);
|
||
self.frame(vec![button(to, false)]);
|
||
self.frame(vec![]);
|
||
self.frame(vec![]);
|
||
}
|
||
|
||
fn layout(&self) -> Layout<String> {
|
||
layout_of(&self.app.ui)
|
||
}
|
||
}
|
||
|
||
fn button(pos: Pos2, pressed: bool) -> Event {
|
||
Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Default::default() }
|
||
}
|
||
|
||
fn main_panels(layout: &Layout<String>) -> Vec<String> {
|
||
dock_columns(layout).first().map(|c| c.groups.iter().flat_map(|g| g.panels.clone()).collect()).unwrap_or_default()
|
||
}
|
||
|
||
#[test]
|
||
fn the_default_layout_is_the_canvas_beside_the_main_column_and_the_icon_column() {
|
||
let mut h = Harness::new();
|
||
let layout = h.layout();
|
||
assert!(h.app.ui.docking.is_none(), "an untouched workspace has no saved layout");
|
||
let columns = dock_columns(&layout);
|
||
assert_eq!(main_panels(&layout), ["properties", "layers", "libraries"]);
|
||
let icons = columns.get(1).expect("an icon column");
|
||
assert!(icons.iconic && !columns[0].iconic);
|
||
assert_eq!(icons.groups.len(), ICON_PANEL_GROUPS.len());
|
||
let properties = h.tab("properties").expect("the main column's tabs are drawn");
|
||
let swatches = h.hit(&Hit::Icon("swatches".into())).expect("the icon column is drawn");
|
||
assert!(swatches.right() <= properties.left(), "the icon column is inside the main column");
|
||
// The canvas takes the rest and gets the pointer: the Rectangle tool draws on it.
|
||
let canvas = h.app.canvas_rect.expect("the canvas is drawn");
|
||
assert!(canvas.right() <= swatches.left() + 1.0 && canvas.width() > 800.0);
|
||
h.app.select_tool("rectangle");
|
||
let count = |h: &Harness| h.app.session.active().unwrap().doc.node_count();
|
||
let before = count(&h);
|
||
h.drag(canvas.center(), canvas.center() + vec2(60.0, 40.0));
|
||
assert_eq!(count(&h), before + 1);
|
||
}
|
||
|
||
#[test]
|
||
fn an_icon_opens_its_flyout_and_open_panel_follows() {
|
||
let mut h = Harness::new();
|
||
let icon = h.hit(&Hit::Icon("swatches".into())).unwrap();
|
||
h.click(icon.center());
|
||
assert_eq!(h.app.ui.open_panel.as_deref(), Some("swatches"));
|
||
assert!(h.tab("swatches").is_some(), "the flyout shows its group's tabs");
|
||
// Window › Brushes switches the flyout on the next frame.
|
||
h.app.run("window.panel", json!({"panel": "brushes"})).unwrap();
|
||
h.frame(vec![]);
|
||
h.frame(vec![]);
|
||
assert_eq!(craft_ui::docking::flyout::<String>(&h.ctx, area_id()).as_deref(), Some("brushes"));
|
||
assert_eq!(h.app.ui.open_panel.as_deref(), Some("brushes"));
|
||
h.app.run("window.panel", json!({"panel": "brushes"})).unwrap();
|
||
h.frame(vec![]);
|
||
assert_eq!(craft_ui::docking::flyout::<String>(&h.ctx, area_id()), None);
|
||
}
|
||
|
||
#[test]
|
||
fn a_tab_dragged_out_floats_and_comes_back_where_it_was_after_its_window_closes() {
|
||
let mut h = Harness::new();
|
||
let tab = h.tab("layers").unwrap();
|
||
let canvas = h.app.canvas_rect.unwrap();
|
||
h.drag(tab.center(), canvas.center());
|
||
let layout = h.layout();
|
||
let window = floating_window(&layout, "layers").expect("Layers floats");
|
||
assert_eq!(window_panels(window), ["layers"]);
|
||
let rect = window.rect;
|
||
assert_eq!(main_panels(&layout), ["properties", "libraries"]);
|
||
let close = h.hit(&Hit::Close("layers".into())).expect("the floating window has a close button");
|
||
h.click(close.center());
|
||
assert!(!h.layout().contains(&"layers".to_string()), "× hides its panels");
|
||
assert!(h.app.ui.docking_hidden.contains_key("layers"));
|
||
h.app.run("window.panel", json!({"panel": "layers"})).unwrap();
|
||
h.frame(vec![]);
|
||
let layout = h.layout();
|
||
let window = floating_window(&layout, "layers").expect("Window › Layers brings it back where it was");
|
||
assert_eq!(window.rect, rect);
|
||
assert!(h.tab("layers").is_some());
|
||
}
|
||
|
||
#[test]
|
||
fn the_dock_collapses_to_icons_and_back() {
|
||
let mut h = Harness::new();
|
||
let collapse = h.hit(&Hit::Collapse("properties".into())).expect("the main column's double arrow");
|
||
h.click(collapse.center());
|
||
assert!(dock_collapsed(&h.app.ui) && h.app.ui.dock_collapsed);
|
||
assert!(h.app.ui.docking.is_none(), "collapsing keeps the workspace's default layout");
|
||
assert!(h.tab("properties").is_none() && h.hit(&Hit::Icon("properties".into())).is_some());
|
||
assert!(h.app.run("window.collapseDock", json!({"collapsed": false})).is_ok());
|
||
h.frame(vec![]);
|
||
assert!(!dock_collapsed(&h.app.ui));
|
||
assert!(h.tab("properties").is_some());
|
||
}
|
||
|
||
#[test]
|
||
fn tab_hides_the_panels_and_the_canvas_keeps_drawing() {
|
||
let mut h = Harness::new();
|
||
let narrow = h.app.canvas_rect.unwrap();
|
||
h.app.run("window.dock", json!({})).unwrap();
|
||
h.frame(vec![]);
|
||
h.frame(vec![]);
|
||
assert!(h.tab("properties").is_none() && h.hit(&Hit::Icon("swatches".into())).is_none());
|
||
let wide = h.app.canvas_rect.expect("the canvas still draws");
|
||
assert!(wide.width() > narrow.width(), "the canvas takes the panels' room");
|
||
h.app.run("window.dock", json!({})).unwrap();
|
||
h.frame(vec![]);
|
||
h.frame(vec![]);
|
||
assert!(h.tab("properties").is_some());
|
||
}
|
||
|
||
#[test]
|
||
fn switching_workspace_during_a_tab_drag_keeps_the_new_workspace_layout() {
|
||
let mut h = Harness::new();
|
||
let tab = h.tab("layers").unwrap();
|
||
let canvas = h.app.canvas_rect.unwrap();
|
||
h.press_and_move(tab.center(), canvas.center());
|
||
h.app.run("window.workspace", json!({"name": "Layout"})).unwrap();
|
||
h.frame(vec![Event::PointerMoved(canvas.center())]);
|
||
h.frame(vec![button(canvas.center(), false)]);
|
||
h.frame(vec![]);
|
||
assert!(h.app.ui.docking.is_none(), "the drag ended with the switch");
|
||
assert!(h.layout().floating.is_empty());
|
||
assert_eq!(main_panels(&h.layout()), ["properties", "layers", "libraries"]);
|
||
}
|
||
|
||
#[test]
|
||
fn the_tools_panel_floats_by_its_title_bar_and_docks_on_the_left_edge() {
|
||
let mut h = Harness::new();
|
||
let zone = h.ctx.data(|d| d.get_temp::<Rect>(crate::tools_float::tools_zone_id())).expect("the Tools panel's docking strip");
|
||
let title = zone.min + vec2(20.0, 12.0);
|
||
h.drag(title, pos2(600.0, 400.0));
|
||
let [x, y] = h.app.ui.toolbar_pos.expect("the Tools panel floats");
|
||
assert!((300.0..700.0).contains(&x) && (250.0..450.0).contains(&y), "{x} {y}");
|
||
let title = pos2(x + 20.0, y + 7.0);
|
||
h.drag(title, zone.center());
|
||
assert_eq!(h.app.ui.toolbar_pos, None, "dropped on the left edge, it docks");
|
||
}
|
||
|
||
#[test]
|
||
fn a_library_panel_opens_renames_and_closes() {
|
||
let mut h = Harness::new();
|
||
h.app.run("window.swatchLibrary", json!({"library": "pastels"})).unwrap();
|
||
let pastels = "library:swatches:pastels".to_string();
|
||
h.frame(vec![]);
|
||
assert!(floating_window(&h.layout(), &pastels).is_some(), "a library panel floats");
|
||
assert!(h.tab(&pastels).is_some());
|
||
h.app.run("ui.libraryPanel", json!({"panel": pastels, "library": "web-safe-216"})).unwrap();
|
||
let web = "library:swatches:web-safe-216".to_string();
|
||
h.frame(vec![]);
|
||
assert!(h.layout().contains(&web) && !h.layout().contains(&pastels));
|
||
assert!(h.tab(&web).is_some());
|
||
h.app.run("window.panel.close", json!({"panel": web})).unwrap();
|
||
h.frame(vec![]);
|
||
assert!(!h.layout().contains(&web));
|
||
}
|
||
|
||
/// The floating window holding `panel`, by its panels.
|
||
fn window_of(layout: &Layout<String>, panel: &str) -> Vec<String> {
|
||
floating_window(layout, panel).map(window_panels).unwrap_or_default()
|
||
}
|
||
|
||
#[test]
|
||
fn float_onto_above_and_below_stack_with_a_floating_group() {
|
||
let mut h = Harness::new();
|
||
h.app.run("window.panel.float", json!({"panel": "swatches"})).unwrap();
|
||
h.app.run("window.panel.float", json!({"panel": "stroke", "onto": "Swatches"})).unwrap();
|
||
let layout = h.layout();
|
||
assert_eq!(group_of(&layout, &"stroke".into()), vec!["swatches".to_string(), "stroke".into()], "a tab of its group");
|
||
|
||
h.app.run("window.panel.float", json!({"panel": "color", "below": "swatches"})).unwrap();
|
||
h.app.run("window.panel.float", json!({"panel": "gradient", "above": "swatches"})).unwrap();
|
||
let layout = h.layout();
|
||
let window = window_of(&layout, "swatches");
|
||
for panel in ["color", "gradient", "stroke"] {
|
||
assert!(window.contains(&panel.to_string()), "{panel} in the window: {window:?}");
|
||
}
|
||
assert_eq!(group_of(&layout, &"color".into()), vec!["color".to_string()], "a group of its own below");
|
||
assert_eq!(group_of(&layout, &"gradient".into()), vec!["gradient".to_string()]);
|
||
h.frame(vec![]);
|
||
let (top, middle, bottom) = (h.tab("gradient").unwrap(), h.tab("swatches").unwrap(), h.tab("color").unwrap());
|
||
assert!(top.top() < middle.top() && middle.top() < bottom.top(), "gradient above, color below");
|
||
|
||
// `onto` names a floating group; a docked one is refused.
|
||
assert!(h.app.run("window.panel.float", json!({"panel": "stroke", "onto": "layers"})).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn float_collapsed_minimizes_its_window() {
|
||
let mut h = Harness::new();
|
||
h.app.run("window.panel.float", json!({"panel": "swatches", "collapsed": true})).unwrap();
|
||
assert!(floating_window(&h.layout(), "swatches").unwrap().minimized);
|
||
h.app.run("window.panel.float", json!({"panel": "swatches", "collapsed": false})).unwrap();
|
||
assert!(!floating_window(&h.layout(), "swatches").unwrap().minimized);
|
||
let r = h.app.run("window.panel.collapse", json!({"panel": "Layers"})).unwrap();
|
||
assert_eq!((r["panel"].clone(), r["collapsed"].clone()), (json!("layers"), json!(true)), "a docked group minimizes too");
|
||
}
|
||
|
||
#[test]
|
||
fn dock_column_n_docks_a_column_at_that_place() {
|
||
let mut h = Harness::new();
|
||
let before = end_columns(&h.layout()).len();
|
||
h.app.run("window.panel.float", json!({"panel": "swatches"})).unwrap();
|
||
h.app.run("window.panel.float", json!({"panel": "color", "below": "swatches"})).unwrap();
|
||
h.app.run("window.panel.dock", json!({"panel": "swatches", "column": 1})).unwrap();
|
||
let layout = h.layout();
|
||
let columns = end_columns(&layout);
|
||
assert_eq!(columns.len(), before + 1);
|
||
let panels: Vec<&String> = columns[2].groups.iter().flat_map(|g| &g.panels).collect();
|
||
assert_eq!(panels, ["swatches", "color"], "the whole floating window, next to the icon column");
|
||
assert!(layout.floating.is_empty());
|
||
|
||
// `true` docks at the innermost place; without `group`, the panel leaves its group.
|
||
h.app.run("window.panel.dock", json!({"panel": "layers", "column": true})).unwrap();
|
||
let layout = h.layout();
|
||
let columns = end_columns(&layout);
|
||
assert_eq!(columns.len(), before + 2);
|
||
assert_eq!(columns.last().unwrap().groups[0].panels, vec!["layers".to_string()]);
|
||
assert_eq!(main_panels(&layout), ["properties", "libraries"]);
|
||
}
|
||
|
||
#[test]
|
||
fn dock_icon_puts_a_panel_in_the_icon_column() {
|
||
let mut h = Harness::new();
|
||
h.app.run("window.panel.dock", json!({"panel": "layers", "icon": true})).unwrap();
|
||
let layout = h.layout();
|
||
assert!(in_iconic_column(&layout, "layers"));
|
||
assert!(!main_panels(&layout).contains(&"layers".to_string()));
|
||
assert_eq!(group_of(&layout, &"layers".into()), vec!["layers".to_string()], "a row of its own");
|
||
}
|
||
|
||
fn column_panels(layout: &Layout<String>) -> Vec<Vec<String>> {
|
||
end_columns(layout).iter().map(|c| c.groups.iter().flat_map(|g| g.panels.clone()).collect()).collect()
|
||
}
|
||
|
||
#[test]
|
||
fn a_docked_column_moves_to_a_later_place_and_back() {
|
||
let mut h = Harness::new();
|
||
h.app.run("window.panel.dock", json!({"panel": "swatches", "column": 1})).unwrap();
|
||
h.app.run("window.panel.dock", json!({"panel": "color", "column": 2})).unwrap();
|
||
let columns = column_panels(&h.layout());
|
||
assert_eq!(columns[2..], [vec!["swatches".to_string()], vec!["color".to_string()]]);
|
||
h.app.run("window.panel.dock", json!({"panel": "swatches", "column": 2})).unwrap();
|
||
let columns = column_panels(&h.layout());
|
||
assert_eq!(columns[2..], [vec!["color".to_string()], vec!["swatches".to_string()]], "swatches moved inwards");
|
||
h.app.run("window.panel.dock", json!({"panel": "swatches", "column": 1})).unwrap();
|
||
let columns = column_panels(&h.layout());
|
||
assert_eq!(columns[2..], [vec!["swatches".to_string()], vec!["color".to_string()]], "and back");
|
||
let before = h.layout();
|
||
h.app.run("window.panel.dock", json!({"panel": "swatches", "column": 1})).unwrap();
|
||
assert_eq!(h.layout(), before, "already there");
|
||
}
|
||
|
||
/// A layout converted from an old dock split side by side, with the dock collapsed: two
|
||
/// collapsed tree columns before the icon column.
|
||
#[test]
|
||
fn column_places_and_icon_count_from_the_icon_column() {
|
||
let mut h = Harness::new();
|
||
let mut layout = h.layout();
|
||
if let Some(Node::Split { second, .. }) = layout.root.as_mut()
|
||
&& let Node::Dock { columns, .. } = second.as_mut()
|
||
{
|
||
let libraries = columns[0].groups[0].panels.pop().unwrap();
|
||
assert_eq!(libraries, "libraries");
|
||
columns.insert(1, Column { groups: vec![Group::new(vec![libraries], 0)], width: 200.0, iconic: true, iconic_width: Some(ICON_WIDTH) });
|
||
columns[0].iconic = true;
|
||
}
|
||
let width: f32 = end_columns(&layout).iter().map(|c| c.iconic_width.unwrap_or(ICON_WIDTH)).sum();
|
||
if let Some(Node::Split { size, .. }) = layout.root.as_mut() {
|
||
*size = craft_ui::layout::SplitSize::FixedSecond(width);
|
||
}
|
||
assert!(valid(&layout));
|
||
h.app.ui.docking = Some(layout);
|
||
h.app.run("window.panel.dock", json!({"panel": "layers", "icon": true})).unwrap();
|
||
let layout = h.layout();
|
||
let columns = column_panels(&layout);
|
||
assert!(columns[2].contains(&"layers".to_string()), "{columns:?}");
|
||
h.app.run("window.panel.dock", json!({"panel": "swatches", "column": 1})).unwrap();
|
||
let columns = column_panels(&h.layout());
|
||
assert_eq!(columns[..2], [vec!["properties".to_string()], vec!["libraries".to_string()]]);
|
||
assert_eq!(columns[3], ["swatches"], "next to the icon column: {columns:?}");
|
||
|
||
// No icon column: `icon` makes one.
|
||
let mut layout = h.layout();
|
||
if let Some(Node::Split { second, .. }) = layout.root.as_mut()
|
||
&& let Node::Dock { columns, .. } = second.as_mut()
|
||
{
|
||
columns.remove(2);
|
||
}
|
||
h.app.ui.docking = Some(layout);
|
||
assert_eq!(icon_column(&end_columns(&h.layout())), None);
|
||
h.app.run("window.panel.dock", json!({"panel": "properties", "icon": true})).unwrap();
|
||
let layout = h.layout();
|
||
assert!(in_iconic_column(&layout, "properties"));
|
||
assert!(icon_column(&end_columns(&layout)).is_none_or(|k| column_panels(&layout)[k].contains(&"properties".to_string())));
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_docking_parameters_change_nothing() {
|
||
let mut h = Harness::new();
|
||
h.app.run("window.panel.float", json!({"panel": "swatches"})).unwrap();
|
||
let before = h.layout();
|
||
for (id, params) in [
|
||
("window.panel.float", json!({"panel": "layers", "onto": "nope"})),
|
||
("window.panel.float", json!({"panel": "layers", "onto": "swatches", "below": "swatches"})),
|
||
("window.panel.float", json!({"panel": "layers", "onto": "swatches", "x": 10, "y": 10})),
|
||
("window.panel.float", json!({"panel": "swatches", "onto": "swatches"})),
|
||
("window.panel.float", json!({"panel": "layers", "icon": true})),
|
||
("window.panel.float", json!({"panel": "layers", "collapsed": "yes"})),
|
||
("window.panel.dock", json!({"panel": "layers", "icon": true, "anchor": "swatches"})),
|
||
("window.panel.dock", json!({"panel": "layers", "column": 0})),
|
||
("window.panel.dock", json!({"panel": "layers", "column": "left"})),
|
||
("window.panel.dock", json!({"panel": "layers", "icon": "yes"})),
|
||
] {
|
||
assert!(h.app.run(id, params.clone()).is_err(), "{id} {params}");
|
||
assert_eq!(h.layout(), before, "{id} {params} changed the layout");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn ui_docking_reports_the_layout_and_hit_rects() {
|
||
let mut h = Harness::new();
|
||
h.app.run("window.panel.float", json!({"panel": "swatches"})).unwrap();
|
||
h.frame(vec![]);
|
||
let tab = h.tab("layers").unwrap();
|
||
h.click(tab.center());
|
||
let report = inspect(&h.app, &h.ctx);
|
||
assert_eq!(report["layout"], serde_json::to_value(h.layout()).unwrap());
|
||
assert_eq!(report["focused"], json!("layers"));
|
||
let hits = report["hitRects"].as_array().unwrap();
|
||
for hit in [json!({"Tab": "layers"}), json!({"Title": "swatches"})] {
|
||
let found = hits.iter().find(|h| h["hit"] == hit).unwrap_or_else(|| panic!("{hit} in {hits:?}"));
|
||
assert_eq!(found["rect"].as_array().unwrap().len(), 4);
|
||
}
|
||
}
|
||
|
||
/// Press on the main column's divider and drag it 60 pt toward the canvas, holding the button.
|
||
fn press_on_the_main_column_divider(h: &mut Harness) -> f32 {
|
||
let edge =
|
||
hit_rects(&h.ctx).into_iter().filter(|(hit, _)| matches!(hit, Hit::Divider(_))).map(|(_, r)| r.center().x).fold(f32::NEG_INFINITY, f32::max);
|
||
h.press_and_move(pos2(edge, 600.0), pos2(edge - 60.0, 600.0));
|
||
dock_columns(&h.layout())[0].width
|
||
}
|
||
|
||
#[test]
|
||
fn a_column_resize_whose_release_was_lost_ends_when_the_system_reports_no_button_down() {
|
||
let mut h = Harness::new();
|
||
let width = press_on_the_main_column_divider(&mut h);
|
||
for x in [900.0, 1000.0, 850.0, 1100.0] {
|
||
h.frame_with_system_buttons(vec![Event::PointerMoved(pos2(x, 600.0))], false);
|
||
assert_eq!(dock_columns(&h.layout())[0].width, width, "the column followed the pointer to {x}");
|
||
}
|
||
assert!(!h.ctx.input(|i| i.pointer.any_down()));
|
||
}
|
||
|
||
#[test]
|
||
fn a_column_resize_continues_while_the_system_reports_the_button_down() {
|
||
let mut h = Harness::new();
|
||
let width = press_on_the_main_column_divider(&mut h);
|
||
h.frame_with_system_buttons(vec![Event::PointerMoved(pos2(900.0, 600.0))], true);
|
||
assert_ne!(dock_columns(&h.layout())[0].width, width);
|
||
assert!(h.ctx.input(|i| i.pointer.primary_down()));
|
||
}
|
||
|
||
#[test]
|
||
fn a_lost_release_after_the_pointer_moved_away_is_not_a_click() {
|
||
let mut h = Harness::new();
|
||
h.frame(vec![Event::PointerMoved(pos2(800.0, 600.0)), button(pos2(800.0, 600.0), true)]);
|
||
h.frame_with_system_buttons(vec![Event::PointerMoved(pos2(1000.0, 600.0))], false);
|
||
assert!(!h.ctx.input(|i| i.pointer.any_down()));
|
||
assert!(!h.ctx.input(|i| i.pointer.any_click()), "the release at the press position made a click");
|
||
}
|
||
|
||
#[test]
|
||
fn control_channel_drags_keep_their_button_between_steps() {
|
||
let mut h = Harness::new();
|
||
h.frame(vec![Event::PointerMoved(pos2(800.0, 600.0)), button(pos2(800.0, 600.0), true)]);
|
||
h.app.synthetic.push(button(pos2(820.0, 600.0), false));
|
||
let mut input = egui::RawInput { events: vec![Event::PointerMoved(pos2(810.0, 600.0))], ..Default::default() };
|
||
h.app.release_lost_buttons(&h.ctx, &mut input, false);
|
||
assert_eq!(input.events, [Event::PointerMoved(pos2(810.0, 600.0))]);
|
||
}
|
||
|
||
#[test]
|
||
fn window_menu_panels_without_a_place_open_in_their_icon_group_or_float() {
|
||
let mut h = Harness::new();
|
||
// A saved layout that lost Swatches without remembering where it was.
|
||
let mut layout = h.layout();
|
||
layout.apply(Action::Close { panel: "swatches".into() }).unwrap();
|
||
h.app.ui.docking = Some(layout);
|
||
h.app.ui.docking_hidden.clear();
|
||
h.app.run("window.panel", json!({"panel": "Swatches"})).unwrap();
|
||
let layout = h.layout();
|
||
assert!(in_iconic_column(&layout, "swatches"), "back in the icon column");
|
||
let group = dock_columns(&layout).iter().flat_map(|c| &c.groups).find(|g| g.panels.contains(&"swatches".to_string())).unwrap().panels.clone();
|
||
assert!(group.contains(&"brushes".to_string()), "with its icon group: {group:?}");
|
||
assert_eq!(h.app.ui.open_panel.as_deref(), Some("swatches"), "its flyout opens");
|
||
// A panel of no icon group floats.
|
||
h.app.run("window.panel", json!({"panel": "openType"})).unwrap();
|
||
assert!(floating_window(&h.layout(), "openType").is_some());
|
||
h.frame(vec![]);
|
||
assert!(h.tab("openType").is_some());
|
||
}
|