mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
Window: hidden panels show while the pointer rests on the window edge
With the panels hidden by Tab or Shift+Tab, resting the pointer for
0.3 s on a 6 pt band along the left or right edge shows the panels
docked on that side, over the canvas's edge. The canvas keeps its size,
so the art does not move, and it takes no input under the panels. They
hide again 0.3 s after the pointer leaves them; Tab or Shift+Tab shows
them for good. Floating panels, Tools and the Control bar stay hidden,
and nothing shows in Presentation Mode.
window.panels.peek {side: left|right|null} does the same from a script,
and ui.docking reports the side shown as peek.
Part of #5661
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -1797,6 +1797,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
|
||||
|
||||
@@ -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 |
|
||||
|---|---|---|
|
||||
|
||||
Reference in New Issue
Block a user