Merge pull request #7763 from dotCooCoo/feat/hidden-panels-edge-reveal

Window: hidden panels show while the pointer rests on the window edge
This commit is contained in:
BFlat
2026-11-15 15:46:57 +07:00
committed by GitHub
7 changed files with 479 additions and 4 deletions
+6
View File
@@ -612,6 +612,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"Shift+Tab",
"{} show/hide the dock and the floating panels (hiding them closes a panel popped out of the icon column), leaving the toolbar and the Control bar as they are; returns whether the panels show",
),
(
"window.panels.peek",
"Show Hidden Panels at Edge",
"",
"{side: left|right|null} while Tab or Shift+Tab hides the panels, show the docked panels on that side of the canvas over its edge, as resting the pointer on that edge of the window for 0.3 s does; they hide again 0.3 s after the pointer moves away from them, and null hides them now. Floating panels, the toolbar and the Control bar stay hidden, and Tab or Shift+Tab shows the panels for good. Fails and changes nothing while the panels show, in Presentation Mode, for any other side, or when no panel is docked on that side; returns the side shown or null",
),
(
"window.panel",
"Show Panel",
+6
View File
@@ -613,6 +613,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"Shift+Tab",
"{} show/hide the dock and the floating panels (hiding them closes a panel popped out of the icon column), leaving the toolbar and the Control bar as they are; returns whether the panels show",
),
(
"window.panels.peek",
"Show Hidden Panels at Edge",
"",
"{side: left|right|null} while Tab or Shift+Tab hides the panels, show the docked panels on that side of the canvas over its edge, as resting the pointer on that edge of the window for 0.3 s does; they hide again 0.3 s after the pointer moves away from them, and null hides them now. Floating panels, the toolbar and the Control bar stay hidden, and Tab or Shift+Tab shows the panels for good. Fails and changes nothing while the panels show, in Presentation Mode, for any other side, or when no panel is docked on that side; returns the side shown or null",
),
(
"window.panel",
"Show Panel",
+15 -4
View File
@@ -51,6 +51,8 @@ pub(crate) struct Runtime {
raise: Option<String>,
/// The panel the user last clicked or pressed in (a tab, a panel body, a drop).
focused: Option<String>,
/// Hidden panels shown from the window's edges ([`peek`]).
peek: peek::State,
}
/// Every docking control the main window drew last frame with its rect. Controls in floating
@@ -72,6 +74,7 @@ pub(crate) fn inspect(app: &VectorcraftApp, ctx: &egui::Context) -> Value {
"hidden": app.ui.docking_hidden.keys().collect::<Vec<_>>(),
"autoCollapse": app.session.prefs.auto_collapse_icon_panels,
"panelsShown": app.ui.dock && app.ui.screen_mode < 3,
"peek": peek::shown(app),
"hitRects": hits,
})
}
@@ -322,6 +325,7 @@ pub(crate) fn command(app: &mut VectorcraftApp, id: &str, params: &Value) -> Opt
match id {
"window.panel" => return Some(show_panel(app, params)),
"window.collapseDock" => return Some(crate::menus::opt_bool(params, "collapsed").and_then(|c| collapse_dock(app, c))),
"window.panels.peek" => return Some(peek::command(app, params)),
"window.panel.collapse" => {
let minimized = match crate::menus::opt_bool(params, "collapsed") {
Ok(c) => c,
@@ -1167,6 +1171,9 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
if modal || egui::Popup::is_any_open(&ctx) {
blocked.push(ctx.content_rect());
}
// Panels hidden by Tab or Shift+Tab show on the side of the window edge the pointer rests on.
let peeking = peek::update(app, &ctx, ui.available_rect_before_wrap(), &layout, !blocked.is_empty()).map(|edge| peek::edge_panels(&layout, edge));
let shown_from_edge = |panel: &String| peeking.as_ref().is_some_and(|panels| panels.contains(panel));
let style = dock_style(ui);
let mut dock = craft_ui::docking::DockArea::new(area)
.blocked_rects(&blocked)
@@ -1187,7 +1194,7 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
// Tab and Shift+Tab (`window.dock`, `window.panels`) and the full screen modes hide the panels;
// the canvas stays.
if !app.ui.dock || app.ui.screen_mode >= 3 {
dock = dock.hide_panels(HidePanels::All);
dock = dock.hide_panels(if peeking.is_some() { HidePanels::Except(&shown_from_edge) } else { HidePanels::All });
}
if let Some(panel) = app.dock_rt.raise.take() {
dock = dock.raise_panel(panel);
@@ -1220,7 +1227,7 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
app.open_dropped_files(window.dropped, None, shift);
}
forward_keys(app, &ctx, output.forwarded_events, output.forwarded_modifiers);
if !thumbnail_panel_showing(&app.ui, &ctx) {
if peeking.is_none() && !thumbnail_panel_showing(&app.ui, &ctx) {
// No panel can ask for these rows again this pass. Keep the cache of open documents, but
// discard worker jobs queued behind the one already rendering.
crate::panels::layers::show_thumbs_of(None);
@@ -1383,8 +1390,10 @@ impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
/// Draw a registered panel body, fitting untouched flyouts and pinning sized panels' footers.
fn body(&mut self, ui: &mut egui::Ui, id: &String) {
if id == CANVAS {
crate::toolbar::show_beside_canvas(self.app, ui);
self.app.canvas_area(ui);
peek::canvas(self.app, ui, |app, ui| {
crate::toolbar::show_beside_canvas(app, ui);
app.canvas_area(ui);
});
return;
}
let floating = ui.ctx().viewport_id() != egui::ViewportId::ROOT;
@@ -1513,6 +1522,8 @@ impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
}
}
mod peek;
#[cfg(test)]
mod tests;
+248
View File
@@ -0,0 +1,248 @@
//! Panels hidden by Tab or Shift+Tab, shown for a while: resting the pointer on the left or right
//! edge of the dock area shows the docked panels on that side of the canvas
//! (`window.panels.peek`). They hide again once the pointer has been away from them for
//! [`GRACE`] seconds; Tab or Shift+Tab shows them for good. The canvas keeps the size it has with
//! every panel hidden, so the panels cover its edge and the artwork stays where it is. Floating
//! panels, the Tools panel and the Control bar stay hidden.
use craft_ui::docking::{Layout, Node};
use craft_ui::layout::SplitAxis;
use serde_json::{Value, json};
use super::CANVAS;
use crate::VectorcraftApp;
/// Width of the band along each edge of the dock area that shows the panels on that side.
pub(crate) const EDGE: f32 = 6.0;
/// How long the pointer rests on an edge band before the panels show, in seconds.
pub(crate) const DELAY: f64 = 0.3;
/// How long the pointer stays away from the panels shown before they hide, in seconds.
pub(crate) const GRACE: f64 = 0.3;
/// A side of the canvas.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Edge {
Left,
Right,
}
impl Edge {
fn id(self) -> &'static str {
match self {
Edge::Left => "left",
Edge::Right => "right",
}
}
}
/// The panels shown on one side.
#[derive(Clone, Copy, Debug)]
struct Shown {
edge: Edge,
/// When the pointer left them; `None` while it is over them or has not moved since they showed.
left_at: Option<f64>,
}
/// The session's state of the panels shown from the edges.
pub(crate) struct State {
/// Whether Tab or Shift+Tab hides the panels this frame.
hidden: bool,
/// The dock area this frame.
area: egui::Rect,
/// The edge band the pointer rests on, and since when.
resting: Option<(Edge, f64)>,
shown: Option<Shown>,
/// How far the canvas's corners lay from the dock area's with every panel hidden.
canvas: Option<(egui::Vec2, egui::Vec2)>,
/// The part of the canvas left uncovered while panels show.
uncovered: Option<egui::Rect>,
}
impl Default for State {
fn default() -> Self {
Self { hidden: false, area: egui::Rect::NOTHING, resting: None, shown: None, canvas: None, uncovered: None }
}
}
/// The docked panels on `edge`'s side of the canvas, at any depth of horizontal splits.
pub(crate) fn edge_panels(layout: &Layout<String>, edge: Edge) -> Vec<String> {
let canvas = CANVAS.to_string();
let tree = |node: &Node<String>| Layout { root: Some(node.clone()), floating: Vec::new() };
let mut out = Vec::new();
let mut node = layout.root.as_ref();
let mut depth = 0usize;
while let Some(Node::Split { axis, first, second, .. }) = node {
depth = depth.saturating_add(1);
if depth > 2 * craft_ui::docking::MAX_PANELS {
break;
}
let (inner, outer, side) = if tree(first).contains(&canvas) { (first, second, Edge::Right) } else { (second, first, Edge::Left) };
if matches!(axis, SplitAxis::Horizontal) && side == edge {
out.extend(tree(outer).panels().into_iter().filter(|p| **p != canvas).cloned());
}
node = Some(inner);
}
out
}
/// The side whose panels show this frame. `area` is the dock area and `blocked` whether a modal
/// dialog or a popup takes the input.
pub(super) fn update(app: &mut VectorcraftApp, ctx: &egui::Context, area: egui::Rect, layout: &Layout<String>, blocked: bool) -> Option<Edge> {
let hidden = !app.ui.dock && app.ui.screen_mode < 3;
let flyout = app.ui.open_panel.is_some();
// The docked Tools panel's rect, read only while Tools shows (Tab turns `toolbar` off).
let tools = app.ui.toolbar.then(|| ctx.data(|d| d.get_temp::<egui::Rect>(crate::toolbar::docked_rect_id()))).flatten();
let st = &mut app.dock_rt.peek;
st.hidden = hidden;
st.area = area;
if !hidden {
st.resting = None;
st.shown = None;
st.uncovered = None;
return None;
}
let (now, pointer, down, moved) = ctx.input(|i| {
// A release counts as moving away: a press may have held the panels while it left them.
let moved =
i.pointer.delta() != egui::Vec2::ZERO || i.pointer.any_released() || i.events.iter().any(|e| matches!(e, egui::Event::PointerGone));
(i.time, i.pointer.hover_pos(), i.pointer.any_down(), moved)
});
// A workspace switched to while panels show may dock none on that side.
if st.shown.is_some_and(|shown| edge_panels(layout, shown.edge).is_empty()) {
st.shown = None;
st.uncovered = None;
}
if let Some(shown) = st.shown.as_mut() {
// The panels shown sit beside the canvas, level with it.
let over = pointer.is_some_and(|p| {
let uncovered = st.uncovered.unwrap_or_else(|| area.shrink2(egui::vec2(EDGE, 0.0)));
area.contains(p)
&& (uncovered.top()..=uncovered.bottom()).contains(&p.y)
&& match shown.edge {
Edge::Left => p.x < uncovered.left(),
Edge::Right => p.x > uncovered.right(),
}
});
if over || down || blocked || flyout {
shown.left_at = None;
} else if moved || pointer.is_none() {
shown.left_at.get_or_insert(now);
}
match shown.left_at {
Some(left) if now - left >= GRACE => {
st.shown = None;
st.uncovered = None;
// A flyout opened from the panels shown closes with them.
app.ui.open_panel = None;
return None;
}
Some(left) => ctx.request_repaint_after_secs((GRACE - (now - left)) as f32),
None => {}
}
return Some(shown.edge);
}
let edge = pointer.filter(|p| area.contains(*p) && !tools.is_some_and(|r| r.contains(*p))).and_then(|p| {
if p.x <= area.left() + EDGE {
Some(Edge::Left)
} else if p.x >= area.right() - EDGE {
Some(Edge::Right)
} else {
None
}
});
let Some(edge) = edge.filter(|e| !down && !blocked && !edge_panels(layout, *e).is_empty()) else {
st.resting = None;
return None;
};
let since = match st.resting {
Some((rested, since)) if rested == edge => since,
_ => now,
};
if now - since >= DELAY {
st.resting = None;
st.shown = Some(Shown { edge, left_at: None });
return Some(edge);
}
st.resting = Some((edge, since));
ctx.request_repaint_after_secs((DELAY - (now - since)) as f32);
// The strip the pointer rests on.
let band = match edge {
Edge::Left => egui::Rect::from_min_max(area.left_top(), egui::pos2(area.left() + EDGE, area.bottom())),
Edge::Right => egui::Rect::from_min_max(egui::pos2(area.right() - EDGE, area.top()), area.right_bottom()),
};
let t = crate::theme::Tokens::get(ctx);
let layer = egui::LayerId::new(egui::Order::Foreground, super::area_id().with("peek-strip"));
ctx.layer_painter(layer).rect_filled(band, 0.0, t.accent.gamma_multiply(0.6));
None
}
/// The side shown, for `ui.docking`.
pub(super) fn shown(app: &VectorcraftApp) -> Option<&'static str> {
app.dock_rt.peek.shown.map(|s| s.edge.id())
}
/// Draw the canvas panel's body with `draw`. While panels show from an edge, the canvas keeps the
/// rect it has with every panel hidden, drawn under the panels and taking input only where they
/// leave it uncovered.
pub(super) fn canvas(app: &mut VectorcraftApp, ui: &mut egui::Ui, draw: impl FnOnce(&mut VectorcraftApp, &mut egui::Ui)) {
let uncovered = ui.max_rect();
let st = &mut app.dock_rt.peek;
let wide = if !st.hidden {
st.canvas = None;
None
} else if st.shown.is_none() {
st.canvas = Some((uncovered.min - st.area.min, uncovered.max - st.area.max));
None
} else {
st.uncovered = Some(uncovered);
// The canvas's insets in the dock area with every panel hidden, kept as the window resizes;
// none before such a frame was drawn, as it fills the area then.
let (min, max) = st.canvas.unwrap_or((egui::Vec2::ZERO, egui::Vec2::ZERO));
Some(egui::Rect::from_min_max(st.area.min + min, st.area.max + max))
};
match wide {
Some(full) if full != uncovered && full.is_positive() => {
let clip = uncovered.intersect(ui.clip_rect());
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(full));
child.set_clip_rect(clip);
draw(app, &mut child);
}
_ => draw(app, ui),
}
}
/// `window.panels.peek {side: left|right|null}`: show the docked panels on that side while Tab or
/// Shift+Tab hides the panels, as resting the pointer on that edge does, or (null) hide them.
pub(super) fn command(app: &mut VectorcraftApp, params: &Value) -> Result<Value, String> {
let edge = match params.get("side") {
None | Some(Value::Null) => None,
Some(Value::String(s)) if s == "left" => Some(Edge::Left),
Some(Value::String(s)) if s == "right" => Some(Edge::Right),
Some(_) => return Err("`side` must be left, right or null".into()),
};
if app.ui.screen_mode >= 3 {
return Err("panels can't be shown in Presentation Mode".into());
}
if app.ui.dock {
return Err("the panels are not hidden".into());
}
match edge {
Some(edge) => {
if edge_panels(&super::layout_of(&app.ui), edge).is_empty() {
return Err(format!("no panels are docked on the {} side", edge.id()));
}
let st = &mut app.dock_rt.peek;
st.resting = None;
st.shown = Some(Shown { edge, left_at: None });
Ok(json!(edge.id()))
}
None => {
let st = &mut app.dock_rt.peek;
if st.shown.take().is_some() {
st.uncovered = None;
app.ui.open_panel = None;
}
Ok(Value::Null)
}
}
}
+194
View File
@@ -468,6 +468,200 @@ fn tab_hides_the_panels_and_the_canvas_keeps_drawing() {
assert!(h.tab("properties").is_some());
}
/// Hide the panels with Tab and settle; returns the canvas rect with every panel hidden.
fn hide_with_tab(h: &mut Harness) -> Rect {
h.app.run("window.dock", json!({})).unwrap();
h.frame(vec![]);
h.frame(vec![]);
assert!(h.tab("properties").is_none());
h.app.canvas_rect.expect("the canvas draws")
}
/// Frames with the pointer still, for `secs` of the harness clock.
fn wait(h: &mut Harness, secs: f64) {
let until = h.time + secs;
while h.time < until {
h.frame(vec![]);
}
}
#[test]
fn resting_on_the_right_edge_shows_the_hidden_panels_over_the_canvas_until_the_pointer_leaves() {
let mut h = Harness::new();
let wide = hide_with_tab(&mut h);
let edge = pos2(SCREEN.right() - 2.0, 500.0);
h.frame(vec![Event::PointerMoved(edge)]);
wait(&mut h, peek::DELAY / 2.0);
assert!(h.tab("properties").is_none(), "the panels wait for the pointer to rest");
wait(&mut h, peek::DELAY);
let tab = h.tab("properties").expect("resting on the edge shows the right-hand panels");
assert!(h.hit(&Hit::Icon("swatches".into())).is_some(), "the icon column shows with them");
assert!(!h.app.ui.dock, "the panels stay hidden for good");
assert_eq!(h.app.canvas_rect, Some(wide), "the canvas keeps its size, so the art stays where it is");
assert_eq!(inspect(&h.app, &h.ctx)["peek"], json!("right"));
h.frame(vec![Event::PointerMoved(tab.center())]);
wait(&mut h, peek::GRACE * 2.0);
assert!(h.tab("properties").is_some(), "they stay while the pointer is over them");
h.frame(vec![Event::PointerMoved(wide.center())]);
wait(&mut h, peek::GRACE / 2.0);
assert!(h.tab("properties").is_some(), "a short trip away keeps them");
wait(&mut h, peek::GRACE);
assert!(h.tab("properties").is_none(), "away from them, they hide again");
assert_eq!(inspect(&h.app, &h.ctx)["peek"], Value::Null);
}
#[test]
fn a_press_on_the_canvas_while_the_panels_show_from_the_edge_does_not_reach_the_art_under_them() {
let mut h = Harness::new();
let wide = hide_with_tab(&mut h);
h.app.run("shape.rectangle", json!({"x": 100, "y": 100, "width": 100, "height": 100})).unwrap();
h.frame(vec![]);
fn selected(app: &VectorcraftApp) -> bool {
!app.session.active().unwrap().selection.is_empty()
}
assert!(selected(&h.app));
assert_eq!(h.app.run("window.panels.peek", json!({"side": "right"})).unwrap(), json!("right"));
h.frame(vec![]);
h.frame(vec![]);
let tab = h.tab("properties").expect("the right-hand panels show");
assert!(tab.left() < wide.right(), "they cover the canvas's right edge");
assert_eq!(h.app.canvas_rect, Some(wide));
// A click in the panels, where the canvas lies under them, leaves the selection.
h.click(pos2(tab.center().x, wide.bottom() - 40.0));
assert!(selected(&h.app), "the canvas under the panels takes no click");
// A click on the uncovered canvas away from the art deselects it, as it always does.
h.click(pos2(wide.left() + 40.0, wide.bottom() - 40.0));
assert!(!selected(&h.app));
}
#[test]
fn the_canvas_keeps_the_hidden_panels_size_when_the_window_resizes_or_the_panels_show_at_once() {
let mut h = Harness::new();
// Shown before any frame has drawn the panels hidden.
h.app.run("window.dock", json!({})).unwrap();
assert_eq!(h.app.run("window.panels.peek", json!({"side": "right"})).unwrap(), json!("right"));
h.frame(vec![]);
h.frame(vec![]);
assert!(h.tab("properties").is_some());
let wide = h.app.canvas_rect.unwrap();
h.app.run("window.panels.peek", json!({"side": null})).unwrap();
h.frame(vec![]);
h.frame(vec![]);
assert_eq!(h.app.canvas_rect, Some(wide), "the canvas had the size it has with every panel hidden");
// A narrower window while they show.
h.app.run("window.panels.peek", json!({"side": "right"})).unwrap();
let narrow = Rect::from_min_max(SCREEN.min, pos2(SCREEN.right() - 200.0, SCREEN.bottom()));
for _ in 0..3 {
h.time += 0.05;
h.run(egui::RawInput { time: Some(h.time), screen_rect: Some(narrow), ..Default::default() });
}
assert!(h.tab("properties").is_some());
let canvas = h.app.canvas_rect.unwrap();
assert!((canvas.width() - (wide.width() - 200.0)).abs() < 1.0, "{canvas:?} follows the window, under the panels");
}
#[test]
fn panels_held_by_a_press_hide_after_its_release_away_from_them() {
let mut h = Harness::new();
let wide = hide_with_tab(&mut h);
h.frame(vec![Event::PointerMoved(pos2(SCREEN.right() - 2.0, 500.0))]);
wait(&mut h, peek::DELAY * 2.0);
let tab = h.tab("properties").expect("the panels show");
let body = pos2(tab.center().x, wide.bottom() - 40.0);
h.frame(vec![Event::PointerMoved(body), button(body, true)]);
h.frame(vec![Event::PointerMoved(wide.center())]);
wait(&mut h, peek::GRACE * 2.0);
assert!(h.tab("properties").is_some(), "a held button keeps them");
h.frame(vec![button(wide.center(), false)]);
wait(&mut h, peek::GRACE * 2.0);
assert!(h.tab("properties").is_none(), "released away from them, they hide");
}
#[test]
fn a_drag_to_the_edge_or_a_pointer_on_the_left_edge_shows_nothing() {
let mut h = Harness::new();
let wide = hide_with_tab(&mut h);
// The default layout docks no panel on the left.
h.frame(vec![Event::PointerMoved(pos2(SCREEN.left() + 2.0, 500.0))]);
wait(&mut h, peek::DELAY * 2.0);
assert!(h.tab("properties").is_none());
// A drag that reaches the right edge holds a button down.
h.frame(vec![Event::PointerMoved(wide.center()), button(wide.center(), true)]);
h.frame(vec![Event::PointerMoved(pos2(SCREEN.right() - 2.0, 500.0))]);
wait(&mut h, peek::DELAY * 2.0);
assert!(h.tab("properties").is_none(), "a drag never shows them");
h.frame(vec![button(pos2(SCREEN.right() - 2.0, 500.0), false)]);
}
#[test]
fn tab_while_the_panels_show_from_the_edge_shows_them_for_good() {
let mut h = Harness::new();
let wide = hide_with_tab(&mut h);
h.frame(vec![Event::PointerMoved(pos2(SCREEN.right() - 2.0, 500.0))]);
wait(&mut h, peek::DELAY * 2.0);
assert!(h.tab("properties").is_some());
h.app.run("window.dock", json!({})).unwrap();
h.frame(vec![Event::PointerMoved(wide.center())]);
wait(&mut h, peek::GRACE * 2.0);
assert!(h.app.ui.dock && h.tab("properties").is_some());
assert_eq!(inspect(&h.app, &h.ctx)["peek"], Value::Null);
}
#[test]
fn shift_tab_hidden_panels_show_from_the_edge_too() {
let mut h = Harness::new();
h.app.run("window.panels", json!({})).unwrap();
h.frame(vec![]);
h.frame(vec![]);
assert!(h.tab("properties").is_none());
h.frame(vec![Event::PointerMoved(pos2(SCREEN.right() - 2.0, 500.0))]);
wait(&mut h, peek::DELAY * 2.0);
assert!(h.tab("properties").is_some());
}
#[test]
fn window_panels_peek_rejects_what_it_cannot_show_and_changes_nothing() {
let mut h = Harness::new();
assert_eq!(h.app.run("window.panels.peek", json!({"side": "right"})).unwrap_err(), "the panels are not hidden");
hide_with_tab(&mut h);
for side in [json!("up"), json!(1), json!(true), json!("Right")] {
assert_eq!(h.app.run("window.panels.peek", json!({"side": side})).unwrap_err(), "`side` must be left, right or null", "{side}");
}
assert_eq!(h.app.run("window.panels.peek", json!({"side": "left"})).unwrap_err(), "no panels are docked on the left side");
assert_eq!(h.app.run("window.panels.peek", json!({})).unwrap(), Value::Null, "nothing to hide");
h.frame(vec![]);
assert!(h.tab("properties").is_none() && !h.app.ui.dock);
assert_eq!(h.app.run("window.panels.peek", json!({"side": "right"})).unwrap(), json!("right"));
h.frame(vec![]);
assert!(h.tab("properties").is_some(), "the panels stay while the pointer has not moved");
assert_eq!(h.app.run("window.panels.peek", json!({"side": null})).unwrap(), Value::Null);
h.frame(vec![]);
assert!(h.tab("properties").is_none());
h.app.ui.screen_mode = 3;
assert_eq!(h.app.run("window.panels.peek", json!({"side": "right"})).unwrap_err(), "panels can't be shown in Presentation Mode");
h.app.ui.screen_mode = 0;
}
#[test]
fn edge_panels_are_the_docked_panels_on_each_side_of_the_canvas() {
let layout = default_from(DockTab::ALL[0], &[], false, None, None);
let right = peek::edge_panels(&layout, peek::Edge::Right);
assert!(right.contains(&"properties".to_string()) && right.contains(&"swatches".to_string()));
assert!(!right.contains(&CANVAS.to_string()));
assert!(peek::edge_panels(&layout, peek::Edge::Left).is_empty());
let mirrored = Layout {
root: Some(Node::Split {
axis: craft_ui::layout::SplitAxis::Horizontal,
size: craft_ui::layout::SplitSize::FixedFirst(310.0),
first: Box::new(Node::Tabs { panels: vec!["layers".to_string()], active: 0 }),
second: Box::new(Node::Tabs { panels: vec![CANVAS.to_string()], active: 0 }),
}),
floating: Vec::new(),
};
assert_eq!(peek::edge_panels(&mirrored, peek::Edge::Left), ["layers"]);
assert!(peek::edge_panels(&mirrored, peek::Edge::Right).is_empty());
}
#[test]
fn switching_workspace_during_a_tab_drag_keeps_the_new_workspace_layout() {
let mut h = Harness::new();
+9
View File
@@ -1832,6 +1832,15 @@ and the action still plays. `Session::replay` plays a journal the same way. The
hides the dock, no panel is checked, and the panel shows again with the dock in its earlier place; the toolbar and the
Control bar stay as they are.
While Tab or Shift+Tab hides the panels, resting the pointer for 0.3 s on a 6 pt band along the left or right edge of
the area below the bars shows the panels docked on that side of the canvas, over the canvas's edge; the canvas keeps its
size, so the art does not move. They hide again once the pointer has been away from them for 0.3 s, and stay while a
flyout opened from them, a menu or a pressed button is open. Floating panels, the toolbar and the Control bar stay
hidden, and Tab or Shift+Tab shows the panels for good. `window.panels.peek {side: "left" | "right"}` shows them as the
pointer does, `{side: null}` hides them, and `ui.docking`'s `peek` names the side shown (null for none). The command
refuses another side, a side with no docked panel, panels that are not hidden and Presentation Mode, and changes
nothing.
The export commands limit those keys. `document.export`, `document.exportSelection`, `document.serialize`, `app.export`
and `file.export` refuse a key that no format takes. They accept a key that only another format takes and do not use
it: `{"format": "pdf", "quality": 80}` writes the PDF, and the reply's `warnings` name the key
+1
View File
@@ -384,6 +384,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
| Selection artwork drag hovers a visible document tab to activate it, then drops editable artwork on its canvas | done | `tests_document_drag`, `edit.dropSelection`, lossless resource and one-step Undo regressions; overflow and cross-window drag remain separate |
| A document tab dragged along the tab strip moves its document; a line marks where it goes, the current document and every document's view stay as they were, and a drag off the strip still floats it | done | #5663, [public source](https://helpx.adobe.com/illustrator/using/customizing-workspace.html); `document.move`; `tests::tab_reorder`, `chrome::tests` (drop index with floating and view tabs), `engine` `tests::document_move` |
| A middle-click on a document tab or its × closes it as the × does; closing a modified document's last view asks to save first (VectorCraft addition, as in PhotoCraft) | done | #6506; `chrome::tests::a_middle_click_on_a_tab_closes_it_as_the_close_box_does`; not scored |
| Panels hidden by Tab or Shift+Tab show for a while when the pointer rests on the left or right edge of the window, over the canvas, and hide again when it moves away; Tab shows them for good | done | Part of #5661; `window.panels.peek` ([Workspace basics](https://web.archive.org/web/2014/https://helpx.adobe.com/illustrator/using/workspace-basics.html), "Hide or show all panels"); VectorCraft's own choices: the 6 pt band and the 0.3 s delays, only the docked panels on that side show (floating panels, Tools and the Control bar stay hidden), and nothing shows in Presentation Mode; `panel_docking::tests::resting_on_the_right_edge_shows_the_hidden_panels_over_the_canvas_until_the_pointer_leaves` and the tests after it |
| Behaviour | Status | Notes |
|---|---|---|