mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-11 13:02:25 +00:00
docking: test dock columns, seams, column drags and RTL docks
Behaviour tests with real egui input: seam, band, column edge and new dock drops with their line previews, tab drops, a column taking groups anywhere, scrolling, column bar drags and their cancellation, dividers, minimize, iconic toggles, occlusion by floating windows and the mirrored right-to-left layout. Behaviour follows vectorcraft crates/ui-egui/src/floating.rs tests.
This commit is contained in:
1 parent
f1a3683cc9
commit
c07a0debb4
2 files changed
+473
No files matched your search
@@ -0,0 +1,471 @@
|
||||
//! [D] Dock profile: columns, stacked groups, seams, column bars and their drops.
|
||||
|
||||
use super::*;
|
||||
use crate::layout::{SplitAxis, SplitSize};
|
||||
use egui::{Context, Event, Id, Modifiers, PointerButton, Pos2, RawInput, Rect, UiBuilder, pos2, vec2};
|
||||
|
||||
const AREA: &str = "dock-area";
|
||||
|
||||
fn group(panels: &[u32]) -> Group<u32> {
|
||||
Group::new(panels.to_vec(), 0)
|
||||
}
|
||||
|
||||
/// A dock on `side` with `columns` (each a list of groups, 240 wide) beside canvas 0.
|
||||
fn docked(side: Side, columns: &[&[&[u32]]]) -> Layout<u32> {
|
||||
let dock = Node::Dock { side, columns: columns.iter().map(|groups| Column::new(groups.iter().map(|g| group(g)).collect(), 240.0)).collect() };
|
||||
let canvas = Node::Tabs { panels: vec![0], active: 0 };
|
||||
let (first, second, size) = match side {
|
||||
Side::Start => (dock, canvas, SplitSize::FixedFirst(240.0)),
|
||||
Side::End => (canvas, dock, SplitSize::FixedSecond(240.0)),
|
||||
};
|
||||
Layout { root: Some(Node::Split { axis: SplitAxis::Horizontal, size, first: Box::new(first), second: Box::new(second) }), floating: Vec::new() }
|
||||
}
|
||||
|
||||
fn with_floating(mut layout: Layout<u32>, windows: &[(u32, [f32; 4])]) -> Layout<u32> {
|
||||
layout.floating.extend(windows.iter().map(|(panel, rect)| Floating::single(vec![*panel], 0, *rect)));
|
||||
layout
|
||||
}
|
||||
|
||||
fn columns(layout: &Layout<u32>, side: Side) -> Vec<Vec<Vec<u32>>> {
|
||||
fn find(node: &Node<u32>, side: Side) -> Option<Vec<Vec<Vec<u32>>>> {
|
||||
match node {
|
||||
Node::Dock { side: s, columns } if *s == side => {
|
||||
Some(columns.iter().map(|c| c.groups.iter().map(|g| g.panels.clone()).collect()).collect())
|
||||
}
|
||||
Node::Split { first, second, .. } => find(first, side).or_else(|| find(second, side)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
layout.root.as_ref().and_then(|root| find(root, side)).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn column(layout: &Layout<u32>, index: usize) -> Column<u32> {
|
||||
fn find(node: &Node<u32>) -> Option<&Vec<Column<u32>>> {
|
||||
match node {
|
||||
Node::Dock { columns, .. } => Some(columns),
|
||||
Node::Split { first, second, .. } => find(first).or_else(|| find(second)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
layout.root.as_ref().and_then(find).and_then(|c| c.get(index)).cloned().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestContent {
|
||||
min_height: f32,
|
||||
}
|
||||
|
||||
impl DockContent<u32> for TestContent {
|
||||
fn body(&mut self, ui: &mut egui::Ui, _: &u32) {
|
||||
ui.label("Body");
|
||||
}
|
||||
fn permissions(&self, panel: &u32) -> Permissions {
|
||||
if *panel == 0 { Permissions::PROTECTED } else { Permissions::default() }
|
||||
}
|
||||
fn limits(&self, _: &u32) -> PanelLimits {
|
||||
PanelLimits { min: vec2(0.0, self.min_height), ..PanelLimits::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a layout in a 600x600 area of an 800x600 window and applies what it emits.
|
||||
struct Driver {
|
||||
ctx: Context,
|
||||
layout: Layout<u32>,
|
||||
content: TestContent,
|
||||
focused: bool,
|
||||
right_to_left: bool,
|
||||
shapes: Vec<egui::epaint::ClippedShape>,
|
||||
time: f64,
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
fn new(layout: Layout<u32>) -> Self {
|
||||
Self::with(layout, TestContent::default(), false)
|
||||
}
|
||||
|
||||
fn with(layout: Layout<u32>, content: TestContent, right_to_left: bool) -> Self {
|
||||
let mut driver = Self { ctx: Context::default(), layout, content, focused: true, right_to_left, shapes: Vec::new(), time: 0.0 };
|
||||
// Floating areas settle after their sizing pass.
|
||||
for _ in 0..3 {
|
||||
driver.step(vec![]);
|
||||
}
|
||||
driver
|
||||
}
|
||||
|
||||
fn frame(&mut self, events: Vec<Event>) -> DockOutput<u32> {
|
||||
let mut output = DockOutput::default();
|
||||
self.time += 1.0 / 60.0;
|
||||
let input = RawInput {
|
||||
screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(800.0, 600.0))),
|
||||
events,
|
||||
focused: self.focused,
|
||||
time: Some(self.time),
|
||||
..Default::default()
|
||||
};
|
||||
let (layout, content, rtl) = (&self.layout, &mut self.content, self.right_to_left);
|
||||
let mut full = self.ctx.run_ui(input, |ui| {
|
||||
let mut style = DockStyle::from_ui(ui);
|
||||
style.right_to_left = rtl;
|
||||
ui.scope_builder(UiBuilder::new().max_rect(Rect::from_min_size(Pos2::ZERO, vec2(600.0, 600.0))), |ui| {
|
||||
output = DockArea::new(Id::new(AREA)).show_content(ui, layout, &style, content);
|
||||
});
|
||||
});
|
||||
full.textures_delta.clear();
|
||||
self.shapes = full.shapes;
|
||||
output
|
||||
}
|
||||
|
||||
fn step(&mut self, events: Vec<Event>) -> DockOutput<u32> {
|
||||
let output = self.frame(events);
|
||||
assert!(output.error.is_none(), "{output:?}");
|
||||
let permissions = |p: &u32| self.content.permissions(p);
|
||||
for action in &output.actions {
|
||||
self.layout.apply_with_permissions(action.clone(), permissions).unwrap();
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn rect(&self, name: &str, panel: u32) -> Rect {
|
||||
self.ctx.read_response(Id::new(AREA).with((name, Some(&panel)))).unwrap().rect
|
||||
}
|
||||
|
||||
fn tab(&self, panel: u32) -> Rect {
|
||||
self.ctx.read_response(Id::new(AREA).with(("tab", &panel))).unwrap().rect
|
||||
}
|
||||
|
||||
fn hold(&mut self, from: Pos2, to: Pos2) -> DockOutput<u32> {
|
||||
self.step(vec![Event::PointerMoved(from)]);
|
||||
self.step(vec![button(from, true)]);
|
||||
self.step(vec![Event::PointerMoved(from + vec2(8.0, 6.0))]);
|
||||
self.step(vec![Event::PointerMoved(to)])
|
||||
}
|
||||
|
||||
fn drag(&mut self, from: Pos2, to: Pos2) -> DockOutput<u32> {
|
||||
self.hold(from, to);
|
||||
self.step(vec![button(to, false)])
|
||||
}
|
||||
|
||||
fn click(&mut self, at: Pos2) -> DockOutput<u32> {
|
||||
self.step(vec![Event::PointerMoved(at), button(at, true)]);
|
||||
self.step(vec![button(at, false)])
|
||||
}
|
||||
|
||||
/// Two frames: egui reports a moved widget's rect a frame late.
|
||||
fn settle(&mut self) {
|
||||
self.step(vec![]);
|
||||
self.step(vec![]);
|
||||
}
|
||||
|
||||
fn wait(&mut self) {
|
||||
for _ in 0..45 {
|
||||
self.step(vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the last frame painted the filled insertion line `line`.
|
||||
fn painted(&self, line: Rect) -> bool {
|
||||
self.shapes.iter().any(|clipped| match &clipped.shape {
|
||||
egui::Shape::Rect(shape) => shape.rect == line && shape.fill != egui::Color32::TRANSPARENT,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn button(pos: Pos2, pressed: bool) -> Event {
|
||||
Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE }
|
||||
}
|
||||
|
||||
fn key(key: egui::Key) -> Event {
|
||||
Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }
|
||||
}
|
||||
|
||||
fn moved(actions: &[Action<u32>]) -> Option<(u32, &Target<u32>)> {
|
||||
actions.iter().find_map(|a| match a {
|
||||
Action::MoveGroup { panel, to } | Action::MoveColumn { panel, to } => Some((*panel, to)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn hline(y: f32, x: std::ops::RangeInclusive<f32>) -> Rect {
|
||||
Rect::from_x_y_ranges(x, (y - 2.0)..=(y + 2.0))
|
||||
}
|
||||
|
||||
fn vline(x: f32, y: std::ops::RangeInclusive<f32>) -> Rect {
|
||||
Rect::from_x_y_ranges((x - 2.0)..=(x + 2.0), y)
|
||||
}
|
||||
|
||||
/// A floating group's drag bar.
|
||||
fn bar(d: &Driver, panel: u32) -> Pos2 {
|
||||
d.rect("drag-bar", panel).center()
|
||||
}
|
||||
|
||||
// Geometry of `docked(Start, ..)`: the dock is 0..240 wide, its column bar 14 high, and two open
|
||||
// groups share 14..600, meeting at y = 307.
|
||||
|
||||
#[test]
|
||||
fn group_dropped_on_a_seam_inserts_between_and_shows_a_horizontal_line() {
|
||||
let layout = with_floating(docked(Side::Start, &[&[&[2], &[3]]]), &[(5, [400.0, 300.0, 150.0, 150.0])]);
|
||||
let mut d = Driver::new(layout);
|
||||
assert_eq!(d.tab(3).top(), 307.0);
|
||||
let seam = pos2(120.0, 309.0);
|
||||
d.hold(bar(&d, 5), seam);
|
||||
assert!(d.painted(hline(307.0, 0.0..=240.0)), "a horizontal line across the column at the seam");
|
||||
let out = d.step(vec![button(seam, false)]);
|
||||
assert_eq!(moved(&out.actions), Some((5, &Target::At { anchor: 2, placement: Placement::Group { below: true } })));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2], vec![5], vec![3]]]);
|
||||
assert!(d.layout.floating.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tab_dropped_on_a_seam_or_column_edge_moves_only_that_panel() {
|
||||
let mut d = Driver::new(docked(Side::Start, &[&[&[2, 3], &[4]]]));
|
||||
let out = d.drag(d.tab(3).center(), pos2(120.0, 309.0));
|
||||
assert_eq!(out.actions, vec![Action::Move { panel: 3, anchor: 2, placement: Placement::Group { below: true } }]);
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2], vec![3], vec![4]]]);
|
||||
d.settle();
|
||||
d.drag(d.tab(4).center(), pos2(246.0, 200.0));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2], vec![3]], vec![vec![4]]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_dropped_on_a_top_or_bottom_band_stacks_above_or_below() {
|
||||
for (at, expected) in [
|
||||
// The top quarter of group 2's body, and the bottom quarter of group 3's.
|
||||
(pos2(120.0, 14.0 + 26.0 + 20.0), vec![vec![5], vec![2], vec![3]]),
|
||||
(pos2(120.0, 590.0), vec![vec![2], vec![3], vec![5]]),
|
||||
// The middle of a body joins as tabs.
|
||||
(pos2(120.0, 450.0), vec![vec![2], vec![3, 5]]),
|
||||
] {
|
||||
let layout = with_floating(docked(Side::Start, &[&[&[2], &[3]]]), &[(5, [400.0, 300.0, 150.0, 150.0])]);
|
||||
let mut d = Driver::new(layout);
|
||||
d.drag(bar(&d, 5), at);
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![expected], "{at:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_dropped_on_a_column_edge_makes_a_new_column() {
|
||||
// The dock's inner edge: a column after the existing one, previewed full height.
|
||||
let layout = with_floating(docked(Side::Start, &[&[&[2], &[3]]]), &[(5, [400.0, 300.0, 150.0, 150.0])]);
|
||||
let mut d = Driver::new(layout.clone());
|
||||
let edge = pos2(246.0, 200.0);
|
||||
d.hold(bar(&d, 5), edge);
|
||||
assert!(d.painted(vline(240.0, 0.0..=600.0)), "a vertical line the full height of the dock");
|
||||
let out = d.step(vec![button(edge, false)]);
|
||||
assert_eq!(moved(&out.actions), Some((5, &Target::At { anchor: 2, placement: Placement::Column { after: true } })));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2], vec![3]], vec![vec![5]]]);
|
||||
assert_eq!(column(&d.layout, 1).width, 150.0, "the new column keeps the window's width");
|
||||
|
||||
// The window edge of the outermost column: a column before it.
|
||||
let mut d = Driver::new(layout);
|
||||
d.drag(bar(&d, 5), pos2(3.0, 200.0));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![5]], vec![vec![2], vec![3]]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_dropped_on_the_window_edge_without_a_dock_makes_a_new_dock() {
|
||||
for (side, at, new) in [(Side::Start, pos2(595.0, 200.0), Side::End), (Side::End, pos2(4.0, 200.0), Side::Start)] {
|
||||
let layout = with_floating(docked(side, &[&[&[2]]]), &[(5, [300.0, 300.0, 150.0, 150.0])]);
|
||||
let mut d = Driver::new(layout);
|
||||
d.hold(bar(&d, 5), at);
|
||||
let x = if new == Side::End { 598.0 } else { 2.0 };
|
||||
assert!(d.painted(vline(x, 0.0..=600.0)), "{new:?}: a vertical line at the window edge");
|
||||
let out = d.step(vec![button(at, false)]);
|
||||
assert_eq!(moved(&out.actions), Some((5, &Target::NewDock { side: new })));
|
||||
assert_eq!(columns(&d.layout, new), vec![vec![vec![5]]]);
|
||||
assert_eq!(columns(&d.layout, side), vec![vec![vec![2]]]);
|
||||
// The new dock is drawn on its edge, its »/« button at the window edge.
|
||||
d.settle();
|
||||
let button = d.rect("column-collapse", 5);
|
||||
assert!(if new == Side::End { button.right() == 600.0 } else { button.left() == 0.0 }, "{button:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_column_takes_groups_anywhere_in_it_and_as_tabs() {
|
||||
let windows = [(5, [400.0, 40.0, 150.0, 120.0]), (6, [400.0, 220.0, 150.0, 120.0]), (7, [400.0, 400.0, 150.0, 120.0])];
|
||||
let mut d = Driver::new(with_floating(docked(Side::Start, &[&[&[2], &[3]]]), &windows));
|
||||
// Onto group 3's tab strip: joins as a tab.
|
||||
d.drag(bar(&d, 5), pos2(d.tab(3).right() + 20.0, d.tab(3).center().y));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2], vec![3, 5]]]);
|
||||
// Onto the middle of group 2's body: joins as the last tab.
|
||||
d.drag(bar(&d, 6), pos2(120.0, 160.0));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2, 6], vec![3, 5]]]);
|
||||
// Into the room under the groups, every group minimized: below the last group.
|
||||
d.step(vec![Event::PointerMoved(pos2(700.0, 10.0))]);
|
||||
let mut minimized = d.layout.clone();
|
||||
for panel in [2, 3] {
|
||||
minimized.apply(Action::SetMinimized { panel, minimized: true }).unwrap();
|
||||
}
|
||||
d.layout = minimized;
|
||||
d.step(vec![]);
|
||||
d.drag(bar(&d, 7), pos2(120.0, 500.0));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2, 6], vec![3, 5], vec![7]]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_column_too_tall_for_the_window_scrolls() {
|
||||
let groups: [&[u32]; 5] = [&[2], &[3], &[4], &[5], &[6]];
|
||||
let mut d = Driver::with(docked(Side::Start, &[&groups]), TestContent { min_height: 160.0 }, false);
|
||||
let last = d.tab(6);
|
||||
assert!(last.top() > 600.0, "the last group starts below the window: {last:?}");
|
||||
let over = d.tab(2).center();
|
||||
for _ in 0..6 {
|
||||
d.step(vec![
|
||||
Event::PointerMoved(over),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Point,
|
||||
delta: vec2(0.0, -200.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Modifiers::NONE,
|
||||
},
|
||||
]);
|
||||
}
|
||||
d.wait();
|
||||
let moved = d.tab(6);
|
||||
assert!(moved.top() < last.top() - 100.0, "scrolled up: {moved:?} from {last:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_bar_drag_moves_the_column_and_away_from_a_dock_floats_its_groups() {
|
||||
let mut d = Driver::new(docked(Side::Start, &[&[&[2], &[3]], &[&[5]]]));
|
||||
// The second column to the window edge: it becomes the first.
|
||||
let out = d.drag(d.rect("column-bar", 5).center(), pos2(3.0, 200.0));
|
||||
assert_eq!(moved(&out.actions), Some((5, &Target::At { anchor: 2, placement: Placement::Column { after: false } })));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![5]], vec![vec![2], vec![3]]]);
|
||||
d.settle();
|
||||
// Away from the dock: a floating window holding the column's groups.
|
||||
let source = Rect::from_min_max(pos2(240.0, 0.0), pos2(480.0, 600.0));
|
||||
assert_eq!(d.rect("column-bar", 2).left(), 240.0 + 18.0);
|
||||
let grab = d.rect("column-bar", 2).center();
|
||||
d.hold(grab, pos2(560.0, 300.0));
|
||||
let ghost = source.translate(pos2(560.0, 300.0) - grab);
|
||||
let ghost = egui::Rect::from_min_size(ghost.min.min(pos2(800.0, 600.0) - source.size()).max(Pos2::ZERO), source.size());
|
||||
assert!(
|
||||
d.shapes.iter().any(|s| matches!(&s.shape, egui::Shape::Rect(r) if r.rect == ghost && r.fill == egui::Color32::TRANSPARENT)),
|
||||
"a ghost of the column follows the pointer"
|
||||
);
|
||||
let out = d.step(vec![button(pos2(560.0, 300.0), false)]);
|
||||
assert!(matches!(moved(&out.actions), Some((2, Target::Float { .. }))), "{:?}", out.actions);
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![5]]]);
|
||||
assert_eq!(d.layout.floating[0].groups, vec![group(&[2]), group(&[3])]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_drag_escape_focus_loss_pointer_exit_and_cancel_drag_leave_the_layout_unchanged() {
|
||||
let layout = docked(Side::Start, &[&[&[2], &[3]], &[&[5]]]);
|
||||
for case in ["escape", "focus", "exit", "cancel"] {
|
||||
let mut d = Driver::new(layout.clone());
|
||||
let target = pos2(3.0, 200.0);
|
||||
let held = d.hold(d.rect("column-bar", 5).center(), target);
|
||||
assert_eq!(held.group_drag, Some(5), "{case}");
|
||||
let out = match case {
|
||||
"escape" => d.step(vec![key(egui::Key::Escape)]),
|
||||
"focus" => {
|
||||
d.focused = false;
|
||||
let out = d.step(vec![]);
|
||||
d.focused = true;
|
||||
out
|
||||
}
|
||||
"exit" => d.step(vec![Event::PointerGone]),
|
||||
_ => {
|
||||
cancel_drag::<u32>(&d.ctx, Id::new(AREA));
|
||||
d.step(vec![Event::PointerMoved(target + vec2(1.0, 0.0))])
|
||||
}
|
||||
};
|
||||
assert!(out.actions.is_empty(), "{case}: {out:?}");
|
||||
let out = d.step(vec![Event::PointerMoved(target), button(target, false)]);
|
||||
assert!(out.actions.is_empty() && out.group_drag.is_none(), "{case}: {out:?}");
|
||||
assert_eq!(d.layout, layout, "{case}");
|
||||
assert!(!is_dragging::<u32>(&d.ctx, Id::new(AREA)), "{case}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_and_group_dividers_resize_and_minimized_groups_keep_their_height() {
|
||||
let mut d = Driver::new(docked(Side::Start, &[&[&[2], &[4]]]));
|
||||
// The dock's inner edge widens its column; the canvas gives way.
|
||||
d.drag(pos2(240.0, 300.0), pos2(280.0, 300.0));
|
||||
assert_eq!(column(&d.layout, 0).width, 280.0);
|
||||
d.settle();
|
||||
assert_eq!(d.rect("column-bar", 2).right(), 280.0);
|
||||
// The divider between the groups saves both heights.
|
||||
d.wait();
|
||||
d.drag(pos2(120.0, 307.0), pos2(120.0, 357.0));
|
||||
let groups = column(&d.layout, 0).groups;
|
||||
assert_eq!((groups[0].height, groups[1].height), (Some(343.0), Some(243.0)));
|
||||
d.settle();
|
||||
assert_eq!(d.tab(4).top(), 357.0);
|
||||
// Minimized, the lower group keeps its saved height and the upper one takes the room.
|
||||
d.layout.apply(Action::SetMinimized { panel: 4, minimized: true }).unwrap();
|
||||
d.settle();
|
||||
assert_eq!(column(&d.layout, 0).groups[1].height, Some(243.0));
|
||||
assert_eq!(d.tab(4).top(), 600.0 - 26.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_click_on_a_docked_tab_minimizes_and_clicking_another_tab_expands() {
|
||||
let mut d = Driver::new(docked(Side::Start, &[&[&[2, 3], &[4]]]));
|
||||
let minimized = |d: &Driver| column(&d.layout, 0).groups[0].minimized;
|
||||
let tab = d.tab(2).center();
|
||||
d.click(tab);
|
||||
d.click(tab);
|
||||
assert!(minimized(&d));
|
||||
d.settle();
|
||||
assert_eq!(d.tab(4).top(), 14.0 + 26.0, "the minimized group takes its strip");
|
||||
d.wait();
|
||||
d.click(d.tab(3).center());
|
||||
assert!(!minimized(&d));
|
||||
assert_eq!(column(&d.layout, 0).groups[0].active_panel(), Some(&3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collapse_button_and_column_bar_double_click_emit_set_iconic() {
|
||||
let mut d = Driver::new(docked(Side::Start, &[&[&[2], &[3]]]));
|
||||
let iconic = |d: &Driver| column(&d.layout, 0).iconic;
|
||||
let button = d.rect("column-collapse", 2).center();
|
||||
let out = d.click(button);
|
||||
assert_eq!(out.actions, vec![Action::SetIconic { panel: 2, iconic: true }]);
|
||||
assert!(iconic(&d));
|
||||
d.wait();
|
||||
d.click(d.rect("column-collapse", 2).center());
|
||||
assert!(!iconic(&d));
|
||||
d.wait();
|
||||
let bar = d.rect("column-bar", 2).center();
|
||||
d.click(bar);
|
||||
d.click(bar);
|
||||
assert!(iconic(&d));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dock_targets_respect_occlusion_by_floating_windows() {
|
||||
// Window 5 covers the column's seam at x 150..240; window 6 is dragged.
|
||||
let windows = [(5, [150.0, 250.0, 200.0, 150.0]), (6, [400.0, 450.0, 150.0, 120.0])];
|
||||
let layout = with_floating(docked(Side::Start, &[&[&[2], &[3]]]), &windows);
|
||||
let mut d = Driver::new(layout.clone());
|
||||
let out = d.drag(bar(&d, 6), pos2(200.0, 309.0));
|
||||
assert!(matches!(moved(&out.actions), Some((6, Target::At { anchor: 5, .. }))), "joins the window in front: {:?}", out.actions);
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2], vec![3]]]);
|
||||
// Left of the window, the seam takes it.
|
||||
let mut d = Driver::new(layout);
|
||||
d.drag(bar(&d, 6), pos2(100.0, 309.0));
|
||||
assert_eq!(columns(&d.layout, Side::Start), vec![vec![vec![2], vec![6], vec![3]]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_to_left_mirrors_docks_their_edges_and_new_docks() {
|
||||
let layout = with_floating(docked(Side::Start, &[&[&[2], &[3]]]), &[(5, [100.0, 300.0, 150.0, 150.0])]);
|
||||
let mut d = Driver::with(layout.clone(), TestContent::default(), true);
|
||||
// The start dock is on the right, its »/« button at the window edge.
|
||||
let column_bar = d.rect("column-bar", 2);
|
||||
assert_eq!((column_bar.left(), column_bar.right()), (360.0, 600.0 - 18.0));
|
||||
// Its inner edge is on the left: dragging it left widens the column.
|
||||
d.drag(pos2(360.0, 300.0), pos2(320.0, 300.0));
|
||||
assert_eq!(column(&d.layout, 0).width, 280.0);
|
||||
// The left window edge has no dock: a new end dock.
|
||||
d.wait();
|
||||
let out = d.drag(bar(&d, 5), pos2(4.0, 200.0));
|
||||
assert_eq!(moved(&out.actions), Some((5, &Target::NewDock { side: Side::End })));
|
||||
d.settle();
|
||||
assert_eq!(d.rect("column-collapse", 5).left(), 0.0);
|
||||
}
|
||||
@@ -21,6 +21,8 @@ pub use workspace::Workspace;
|
||||
|
||||
use surface::{Drag, GroupDrag, GroupGesture};
|
||||
|
||||
#[cfg(test)]
|
||||
mod dock_tests;
|
||||
#[cfg(test)]
|
||||
mod group_tests;
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user