mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
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:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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