mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 02:19:56 -06:00
Merge pull request #6090 from dotCooCoo/feat/control-bar-dock
Control bar: the panel menu and the gripper dock it to the top or the bottom of the window
This commit is contained in:
@@ -534,8 +534,9 @@ impl VectorcraftApp {
|
||||
if self.ui.status_bar && self.ui.screen_mode < 3 {
|
||||
chrome::status_bar(self, ui);
|
||||
chrome::hint_bar(self, ui);
|
||||
} else {
|
||||
// Keeps the previous frame's value valid for menus drawn above, before the bars.
|
||||
} else if !(self.ui.control_bar && self.ui.control_bar_bottom && self.ui.screen_mode < 2) {
|
||||
// Keeps the previous frame's value valid for menus drawn above, before the bars. A
|
||||
// Control bar docked at the bottom has set it to the bar's top.
|
||||
widgets::set_menu_floor(&ctx, ctx.content_rect().bottom());
|
||||
}
|
||||
if self.ui.toolbar && self.ui.screen_mode < 3 {
|
||||
|
||||
@@ -20,7 +20,7 @@ mod tabs;
|
||||
|
||||
#[cfg(test)]
|
||||
use control_bar::ISOLATION_BUTTON;
|
||||
pub use control_bar::{AnchorControls, anchor_buttons, anchor_controls, control_bar};
|
||||
pub use control_bar::{AnchorControls, CONTROL_BAR_EDGES, anchor_buttons, anchor_controls, control_bar};
|
||||
#[cfg(test)]
|
||||
use hints::hint_segments;
|
||||
pub use hints::{hint_bar, hint_strings};
|
||||
|
||||
@@ -261,19 +261,83 @@ fn groups_before_transform(app: &mut VectorcraftApp, ui: &mut Ui, objects: usize
|
||||
true
|
||||
}
|
||||
|
||||
/// The Control bar (Window → Control), context-sensitive like Illustrator's.
|
||||
/// The edges of the window the Control bar docks to (`window.control.dock {edge}`): the top, then
|
||||
/// the bottom.
|
||||
pub const CONTROL_BAR_EDGES: [&str; 2] = ["top", "bottom"];
|
||||
|
||||
/// The side of the Control bar's panel menu button, at the bar's right end.
|
||||
const MENU_BUTTON: f32 = 16.0;
|
||||
|
||||
/// The height of the band drawn along the edge of the window that a gripper drag docks the bar to.
|
||||
const DOCK_BAND: f32 = 4.0;
|
||||
|
||||
/// Docks the Control bar to `edge` of [`CONTROL_BAR_EDGES`], through its command.
|
||||
fn dock(app: &mut VectorcraftApp, edge: &str) {
|
||||
crate::menus::invoke(app, "window.control.dock", json!({ "edge": edge }));
|
||||
}
|
||||
|
||||
/// The Control bar's panel menu: Dock to Top and Dock to Bottom, the edge the bar is docked to
|
||||
/// checked.
|
||||
fn panel_menu(app: &mut VectorcraftApp, ui: &mut Ui, rect: egui::Rect) {
|
||||
let resp = crate::panels::menu_button(ui, "control", rect);
|
||||
egui::Popup::menu(&resp).show(|ui| {
|
||||
ui.set_min_width(160.0);
|
||||
for (i, label) in [tl!("Dock to Top"), tl!("Dock to Bottom")].into_iter().enumerate() {
|
||||
if widgets::menu_item(ui, label, true, usize::from(app.ui.control_bar_bottom) == i) {
|
||||
dock(app, CONTROL_BAR_EDGES[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The Control bar's gripper. Dragging it shows a band along the top or the bottom edge of the
|
||||
/// window, whichever the pointer is nearer, and docks the bar there on release.
|
||||
fn gripper(app: &mut VectorcraftApp, ui: &mut Ui, t: &Tokens) {
|
||||
let (g, resp) = ui.allocate_exact_size(vec2(6.0, 20.0), Sense::drag());
|
||||
for i in 0..5 {
|
||||
ui.painter().circle_filled(g.center_top() + vec2(0.0, 2.0 + i as f32 * 4.0), 0.9, t.text_disabled);
|
||||
}
|
||||
let resp = resp.on_hover_cursor(egui::CursorIcon::Grab);
|
||||
if !resp.dragged() && !resp.drag_stopped() {
|
||||
return;
|
||||
}
|
||||
let Some(pos) = resp.interact_pointer_pos().or_else(|| ui.ctx().pointer_latest_pos()) else { return };
|
||||
let window = ui.ctx().content_rect();
|
||||
let bottom = pos.y > window.center().y;
|
||||
if resp.drag_stopped() {
|
||||
if bottom != app.ui.control_bar_bottom {
|
||||
dock(app, CONTROL_BAR_EDGES[usize::from(bottom)]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
|
||||
let band = if bottom {
|
||||
egui::Rect::from_min_max(egui::pos2(window.left(), window.bottom() - DOCK_BAND), window.max)
|
||||
} else {
|
||||
egui::Rect::from_min_max(window.min, egui::pos2(window.right(), window.top() + DOCK_BAND))
|
||||
};
|
||||
let layer = egui::LayerId::new(egui::Order::Foreground, egui::Id::unique("control_bar_dock_band"));
|
||||
ui.ctx().layer_painter(layer).rect_filled(band, 0.0, t.accent);
|
||||
}
|
||||
|
||||
/// The Control bar (Window → Control), context-sensitive like Illustrator's, docked to the top or
|
||||
/// the bottom of the window.
|
||||
pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
egui::Panel::top("control_bar")
|
||||
let bottom = app.ui.control_bar_bottom;
|
||||
let panel = if bottom { egui::Panel::bottom("control_bar") } else { egui::Panel::top("control_bar") };
|
||||
let bar = panel
|
||||
.exact_size(34.0)
|
||||
.frame(egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin::symmetric(10, 0)).stroke(Stroke::new(1.0, t.border)))
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal_centered(|ui| {
|
||||
// Gripper
|
||||
let (g, _) = ui.allocate_exact_size(vec2(6.0, 20.0), Sense::hover());
|
||||
for i in 0..5 {
|
||||
ui.painter().circle_filled(g.center_top() + vec2(0.0, 2.0 + i as f32 * 4.0), 0.9, t.text_disabled);
|
||||
}
|
||||
// The panel menu button takes the right end, and the controls trim to the room left of it:
|
||||
// they are laid out in that room, so every room check below leaves the button's width.
|
||||
let full = ui.max_rect();
|
||||
let menu = egui::Rect::from_center_size(egui::pos2(full.right() - MENU_BUTTON / 2.0, full.center().y), vec2(MENU_BUTTON, MENU_BUTTON));
|
||||
let room = egui::Rect::from_min_max(full.min, egui::pos2(menu.left() - ui.spacing().item_spacing.x, full.bottom()));
|
||||
let layout = egui::Layout::left_to_right(egui::Align::Center);
|
||||
let controls = ui.scope_builder(egui::UiBuilder::new().max_rect(room).layout(layout), |ui| {
|
||||
gripper(app, ui, &t);
|
||||
if app.session.active().is_none() {
|
||||
ui.label(egui::RichText::new(tl!("No Document")).color(t.text_dim));
|
||||
return;
|
||||
@@ -405,5 +469,14 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
});
|
||||
// Controls that keep their size can still run into the button's room in a narrow window;
|
||||
// the button is left out then.
|
||||
if controls.response.rect.right() <= menu.left() {
|
||||
panel_menu(app, ui, menu);
|
||||
}
|
||||
});
|
||||
// Docked at the bottom, the bar is a bottom bar that menus stop above.
|
||||
if bottom {
|
||||
widgets::set_menu_floor(ui.ctx(), bar.response.rect.top());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1801,6 +1801,139 @@ fn full_screen_modes_fill_the_screen_and_normal_mode_restores_the_window() {
|
||||
assert!(sent(&mut app, true).is_empty(), "still full screen, as the user had it");
|
||||
}
|
||||
|
||||
/// The window the Control bar docking tests draw the bar in.
|
||||
const DOCK_SCREEN: egui::Rect = egui::Rect { min: egui::Pos2::ZERO, max: egui::pos2(1200.0, 300.0) };
|
||||
|
||||
/// One headless frame of the Control bar alone; returns its accessibility nodes.
|
||||
fn control_bar_frame(app: &mut crate::VectorcraftApp, ctx: &egui::Context, events: Vec<egui::Event>) -> Vec<egui::accesskit::Node> {
|
||||
let mut out = ctx.run_ui(egui::RawInput { events, screen_rect: Some(DOCK_SCREEN), ..Default::default() }, |ui| super::control_bar(app, ui));
|
||||
out.textures_delta.clear();
|
||||
out.platform_output.accesskit_update.map(|u| u.nodes.into_iter().map(|(_, n)| n).collect()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The center of the bounds of the node whose label, without a check mark, is `name`.
|
||||
fn node_center(nodes: &[egui::accesskit::Node], name: &str) -> Option<egui::Pos2> {
|
||||
let n = nodes.iter().find(|n| n.label().is_some_and(|l| l.trim_start_matches('✓').trim() == name))?;
|
||||
let b = n.bounds()?;
|
||||
Some(egui::pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32))
|
||||
}
|
||||
|
||||
/// A primary-button press or release at `pos`.
|
||||
fn press_at(pos: egui::Pos2, pressed: bool) -> egui::Event {
|
||||
egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }
|
||||
}
|
||||
|
||||
/// The Control bar's panel menu has Dock to Top and Dock to Bottom, the edge in use checked, and
|
||||
/// each docks the bar to that edge of the window.
|
||||
#[test]
|
||||
fn the_control_bar_panel_menu_docks_it_to_the_top_or_the_bottom() {
|
||||
crate::i18n::set_current(crate::i18n::Lang::EN);
|
||||
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
|
||||
let ctx = egui::Context::default();
|
||||
ctx.enable_accesskit();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let pick = |app: &mut crate::VectorcraftApp, item: &str| {
|
||||
control_bar_frame(app, &ctx, vec![]);
|
||||
let menu = node_center(&control_bar_frame(app, &ctx, vec![]), "Panel menu").unwrap_or_else(|| panic!("the bar has a panel menu"));
|
||||
control_bar_frame(app, &ctx, vec![egui::Event::PointerMoved(menu), press_at(menu, true)]);
|
||||
control_bar_frame(app, &ctx, vec![press_at(menu, false)]);
|
||||
let nodes = control_bar_frame(app, &ctx, vec![]);
|
||||
let checked: Vec<String> = nodes.iter().filter_map(|n| n.label()).filter(|l| l.starts_with('✓')).map(str::to_string).collect();
|
||||
let at = node_center(&nodes, item).unwrap_or_else(|| panic!("the menu lists {item}"));
|
||||
// Below the button at the top, and above it at the bottom: the menu opens inside the window.
|
||||
assert!(DOCK_SCREEN.contains(at) && (at.y < menu.y) == app.ui.control_bar_bottom, "{item} at {at:?}, the button at {menu:?}");
|
||||
control_bar_frame(app, &ctx, vec![egui::Event::PointerMoved(at), press_at(at, true)]);
|
||||
control_bar_frame(app, &ctx, vec![press_at(at, false)]);
|
||||
control_bar_frame(app, &ctx, vec![]);
|
||||
let menu = node_center(&control_bar_frame(app, &ctx, vec![]), "Panel menu").unwrap_or_else(|| panic!("the bar still shows"));
|
||||
(checked, menu.y)
|
||||
};
|
||||
let (checked, y) = pick(&mut app, "Dock to Bottom");
|
||||
assert_eq!(checked, ["✓ Dock to Top"], "the bar starts docked at the top");
|
||||
assert!(app.ui.control_bar_bottom, "Dock to Bottom docks it at the bottom");
|
||||
assert!(y > DOCK_SCREEN.bottom() - 34.0, "the bar is drawn along the bottom of the window: {y}");
|
||||
let (checked, y) = pick(&mut app, "Dock to Top");
|
||||
assert_eq!(checked, ["✓ Dock to Bottom"]);
|
||||
assert!(!app.ui.control_bar_bottom && y < 34.0, "Dock to Top docks it at the top again: {y}");
|
||||
}
|
||||
|
||||
/// Dragging the Control bar's gripper docks the bar at the edge of the window the pointer is
|
||||
/// released nearer; a drag released nearer the edge it is at leaves it there.
|
||||
#[test]
|
||||
fn dragging_the_control_bar_gripper_docks_it_at_the_nearer_edge() {
|
||||
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
// The gripper is the bar's first item, inside its 10-point margin, centered in the 34-point bar.
|
||||
let grip = |bottom: bool| egui::pos2(13.0, if bottom { DOCK_SCREEN.bottom() - 17.0 } else { 17.0 });
|
||||
let drag = |app: &mut crate::VectorcraftApp, from: egui::Pos2, to: egui::Pos2| {
|
||||
control_bar_frame(app, &ctx, vec![]);
|
||||
control_bar_frame(app, &ctx, vec![egui::Event::PointerMoved(from), press_at(from, true)]);
|
||||
for k in 1..=4 {
|
||||
control_bar_frame(app, &ctx, vec![egui::Event::PointerMoved(from.lerp(to, k as f32 / 4.0))]);
|
||||
}
|
||||
control_bar_frame(app, &ctx, vec![press_at(to, false)]);
|
||||
};
|
||||
drag(&mut app, grip(false), egui::pos2(400.0, 200.0));
|
||||
assert!(app.ui.control_bar_bottom, "released nearer the bottom: docked at the bottom");
|
||||
drag(&mut app, grip(true), egui::pos2(400.0, 160.0));
|
||||
assert!(app.ui.control_bar_bottom, "released nearer the bottom again: still at the bottom");
|
||||
drag(&mut app, grip(true), egui::pos2(400.0, 120.0));
|
||||
assert!(!app.ui.control_bar_bottom, "released nearer the top: docked at the top");
|
||||
drag(&mut app, grip(false), egui::pos2(400.0, 140.0));
|
||||
assert!(!app.ui.control_bar_bottom, "released nearer the top again: still at the top");
|
||||
}
|
||||
|
||||
/// `window.control.dock {edge}` docks the Control bar to the top or the bottom of the window, and
|
||||
/// the panel menu checks the edge in use; another edge, or none, is an error and changes nothing.
|
||||
#[test]
|
||||
fn the_control_bar_dock_command_takes_top_or_bottom() {
|
||||
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
|
||||
assert_eq!(app.run("window.control.dock", json!({"edge": "bottom"})).unwrap(), json!({"edge": "bottom"}));
|
||||
assert!(app.ui.control_bar_bottom);
|
||||
assert_eq!(crate::menus::checked(&app, "window.control.dock", &json!({"edge": "bottom"})), Some(true));
|
||||
assert_eq!(crate::menus::checked(&app, "window.control.dock", &json!({"edge": "top"})), Some(false));
|
||||
for p in [json!({"edge": "left"}), json!({"edge": "Bottom"}), json!({"edge": 1}), json!({})] {
|
||||
let err = app.run("window.control.dock", p.clone()).unwrap_err();
|
||||
assert_eq!(err, "`edge` must be one of top, bottom", "{p}");
|
||||
assert!(app.ui.control_bar_bottom, "{p} leaves the bar at the bottom");
|
||||
}
|
||||
assert_eq!(app.run("window.control.dock", json!({"edge": "top"})).unwrap(), json!({"edge": "top"}));
|
||||
assert!(!app.ui.control_bar_bottom);
|
||||
}
|
||||
|
||||
/// Menus stop above a Control bar docked at the bottom when the status bar is hidden, above the
|
||||
/// status and hint bars when they show, and at the bottom of the window when the bar is docked at
|
||||
/// the top or a screen mode hides it.
|
||||
#[test]
|
||||
fn menus_stop_above_a_control_bar_docked_at_the_bottom() {
|
||||
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
|
||||
let ctx = egui::Context::default();
|
||||
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0));
|
||||
let floor = |app: &mut crate::VectorcraftApp, status: bool, bottom: bool, screen_mode: u8| {
|
||||
(app.ui.status_bar, app.ui.control_bar, app.ui.control_bar_bottom, app.ui.screen_mode) = (status, true, bottom, screen_mode);
|
||||
for _ in 0..3 {
|
||||
let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), ..Default::default() }, |ui| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
crate::widgets::menu_floor(&ctx)
|
||||
};
|
||||
let bar_top = screen.bottom() - 34.0;
|
||||
let f = floor(&mut app, false, true, 0);
|
||||
assert!((f - bar_top).abs() < 1.0, "status bar hidden: menus stop at the Control bar's top, {f}");
|
||||
let f = floor(&mut app, false, true, 2);
|
||||
assert_eq!(f, screen.bottom(), "screen mode 2 hides the Control bar: menus stop at the window's bottom");
|
||||
let f = floor(&mut app, false, true, 0);
|
||||
assert!((f - bar_top).abs() < 1.0, "shown again: {f}");
|
||||
let f = floor(&mut app, false, false, 0);
|
||||
assert_eq!(f, screen.bottom(), "docked at the top: menus stop at the window's bottom");
|
||||
let f = floor(&mut app, true, true, 0);
|
||||
assert!(f < bar_top - 1.0, "the status and hint bars show above the Control bar: menus stop above them, {f}");
|
||||
}
|
||||
|
||||
/// Two or more selected objects show all six Distribute Objects buttons in the Control bar;
|
||||
/// a single object keeps the bar as it was. A click distributes as the Align panel's button does.
|
||||
#[test]
|
||||
@@ -1955,6 +2088,7 @@ fn the_control_bar_shows_the_inline_transform_fields_only_when_they_fit() {
|
||||
ctx.enable_accesskit();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut seen = false;
|
||||
let mut menu_seen = false;
|
||||
// The Align group, then for two objects the Distribute group (#5372): each by its controls.
|
||||
let mut groups: Vec<(&str, Vec<&str>)> = vec![(
|
||||
"Align",
|
||||
@@ -2002,16 +2136,22 @@ fn the_control_bar_shows_the_inline_transform_fields_only_when_they_fit() {
|
||||
let fields = ["X:", "Y:", "W:", "H:"].map(right);
|
||||
assert!(fields.iter().all(Option::is_some) || fields.iter().all(Option::is_none), "{count} objects, {width}: {fields:?}");
|
||||
assert!(nodes.iter().any(|(_, n)| n.label() == Some("Transform")), "{count} objects, {width}: the Transform link shows");
|
||||
// The bar's content ends at its 10-point inner margin.
|
||||
// The panel menu button takes the bar's right end, and the bar's content ends before it
|
||||
// (without the button, at the bar's 10-point inner margin). Once a bar shows the button,
|
||||
// a wider one keeps it: the fields and groups trim to the room left of it.
|
||||
let menu = nodes.iter().find(|(_, n)| n.label() == Some("Panel menu")).and_then(|(_, n)| n.bounds());
|
||||
assert!(!menu_seen || menu.is_some(), "{count} objects, {width}: the panel menu button left out after a narrower bar showed it");
|
||||
menu_seen |= menu.is_some();
|
||||
let edge = menu.map_or(f64::from(width) - 10.0, |m| m.x0);
|
||||
if let Some(h) = fields[3] {
|
||||
assert!(h <= f64::from(width) - 10.0, "{count} objects, {width}: H ends at {h}");
|
||||
assert!(h <= edge, "{count} objects, {width}: H ends at {h}, the content's room at {edge}");
|
||||
}
|
||||
assert!(!seen || fields[3].is_some(), "{count} objects, {width}: left out after a narrower bar showed them");
|
||||
seen |= fields[3].is_some();
|
||||
// The Align and Distribute buttons show whole inside the margin, with the Transform
|
||||
// link after them, or are left out; once shown, a wider bar keeps them.
|
||||
let end = |name: &str| nodes.iter().find(|(_, n)| n.label() == Some(name)).and_then(|(_, n)| n.bounds()).map(|b| b.x1);
|
||||
let inside = |x: f64| x <= f64::from(width) - 10.0;
|
||||
let inside = |x: f64| x <= edge;
|
||||
for ((group, names), was) in groups.iter().zip(groups_seen.iter_mut()) {
|
||||
let ends: Vec<Option<f64>> = names.iter().map(|n| end(n)).collect();
|
||||
let shown = ends.iter().all(Option::is_some);
|
||||
@@ -2026,6 +2166,7 @@ fn the_control_bar_shows_the_inline_transform_fields_only_when_they_fit() {
|
||||
}
|
||||
}
|
||||
assert!(seen, "{count} objects: a wide bar has room for the fields");
|
||||
assert!(menu_seen, "{count} objects: a wide bar has room for the panel menu button");
|
||||
assert!(groups_seen.iter().all(|s| *s), "{count} objects: a wide bar shows every group: {groups_seen:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3848,6 +3848,9 @@ axis Both И двете
|
||||
Put back in the toolbar Връщане в лентата с инструменти
|
||||
Float Panel Плаващ панел
|
||||
Dock Panel Закотвен панел
|
||||
Dock to Top Закотвяне горе
|
||||
Dock to Bottom Закотвяне долу
|
||||
Dock Control Bar Закотвяне на контролната лента
|
||||
Drag to float the toolbar Плъзнете, за да направите лентата с инструменти плаваща
|
||||
Drag to the window's left edge to dock Плъзнете до левия ръб на прозореца за закотвяне
|
||||
Float Tool Group Плаваща група инструменти
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -809,6 +809,9 @@
|
||||
Put back in the toolbar Vrati u traku s alatima
|
||||
Float Panel Odvoji panel
|
||||
Dock Panel Usidri panel
|
||||
Dock to Top Usidri na vrh
|
||||
Dock to Bottom Usidri na dno
|
||||
Dock Control Bar Usidri kontrolnu traku
|
||||
Float Tool Group Odvoji grupu alata
|
||||
Services Usluge
|
||||
Hide VectorCraft Sakrij VectorCraft
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1166,6 +1166,9 @@
|
||||
Do Not Scale No escalis
|
||||
Do you want to save the changes you made to “{name}”? Voleu desar els canvis que heu fet a “{name}”?
|
||||
Dock Panel Acobla el panell
|
||||
Dock to Top Acobla a dalt
|
||||
Dock to Bottom Acobla a baix
|
||||
Dock Control Bar Acobla la barra de control
|
||||
Document Document
|
||||
Document Color Mode Mode de color del document
|
||||
Document Info Informació del document
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -819,6 +819,9 @@
|
||||
Put back in the toolbar Vrátit do panelu nástrojů
|
||||
Float Panel Plovoucí panel
|
||||
Dock Panel Ukotvit panel
|
||||
Dock to Top Ukotvit nahoru
|
||||
Dock to Bottom Ukotvit dolů
|
||||
Dock Control Bar Ukotvit ovládací panel
|
||||
Float Tool Group Plovoucí skupina nástrojů
|
||||
|
||||
# The macOS menu bar (native_menu): the app and Window menus' system items, Settings pages.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -753,6 +753,9 @@
|
||||
Put back in the toolbar Sæt tilbage på værktøjslinjen
|
||||
Float Panel Flydende panel
|
||||
Dock Panel Dok panel
|
||||
Dock to Top Dok øverst
|
||||
Dock to Bottom Dok nederst
|
||||
Dock Control Bar Dok kontrollinjen
|
||||
Float Tool Group Flydende værktøjsgruppe
|
||||
Services Tjenester
|
||||
Hide VectorCraft Skjul VectorCraft
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3851,6 +3851,9 @@ theme Appearance Erscheinungsbild
|
||||
Put back in the toolbar Zurück in die Werkzeugleiste setzen
|
||||
Float Panel Bedienfeld ablösen
|
||||
Dock Panel Bedienfeld andocken
|
||||
Dock to Top Oben andocken
|
||||
Dock to Bottom Unten andocken
|
||||
Dock Control Bar Steuerungsleiste andocken
|
||||
Drag to float the toolbar Ziehen, um die Werkzeugleiste abzulösen
|
||||
Drag to the window's left edge to dock Zum Andocken an den linken Fensterrand ziehen
|
||||
Float Tool Group Werkzeuggruppe ablösen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2139,6 +2139,9 @@ axis Vertical Κατακόρυφα
|
||||
axis Both Και τα δύο
|
||||
Float Panel Αιωρούμενο πάνελ
|
||||
Dock Panel Αγκύρωση πάνελ
|
||||
Dock to Top Αγκύρωση επάνω
|
||||
Dock to Bottom Αγκύρωση κάτω
|
||||
Dock Control Bar Αγκύρωση γραμμής ελέγχου
|
||||
@msg no library `{id}` Δεν υπάρχει βιβλιοθήκη με αναγνωριστικό `{id}`
|
||||
@msg select some type first Επιλέξτε πρώτα κάποιο κείμενο
|
||||
@msg kerning must be a number, "auto", or "optical" Η διαγραμμάτωση πρέπει να είναι αριθμός, «auto» ή «optical»
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 163 and column 5.
|
@@ -3936,6 +3936,9 @@ axis Both Ambos
|
||||
Put back in the toolbar Devolver a la barra de herramientas
|
||||
Float Panel Hacer flotar el panel
|
||||
Dock Panel Anclar panel
|
||||
Dock to Top Anclar arriba
|
||||
Dock to Bottom Anclar abajo
|
||||
Dock Control Bar Anclar barra de control
|
||||
Drag to float the toolbar Arrastrar para hacer flotar la barra de herramientas
|
||||
Drag to the window's left edge to dock Arrastrar al borde izquierdo de la ventana para anclar
|
||||
Float Tool Group Hacer flotar el grupo de herramientas
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -807,6 +807,9 @@
|
||||
Put back in the toolbar Palauta työkaluriviin
|
||||
Float Panel Kelluta paneeli
|
||||
Dock Panel Telakoi paneeli
|
||||
Dock to Top Telakoi ylös
|
||||
Dock to Bottom Telakoi alas
|
||||
Dock Control Bar Telakoi ohjauspalkki
|
||||
Float Tool Group Kelluta työkaluryhmä
|
||||
Services Palvelut
|
||||
Hide VectorCraft Piilota VectorCraft
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3926,6 +3926,9 @@ axis Both Les deux
|
||||
Put back in the toolbar Remettre dans la barre d’outils
|
||||
Float Panel Rendre le panneau flottant
|
||||
Dock Panel Ancrer le panneau
|
||||
Dock to Top Ancrer en haut
|
||||
Dock to Bottom Ancrer en bas
|
||||
Dock Control Bar Ancrer la barre de contrôle
|
||||
Drag to float the toolbar Faire glisser pour rendre la barre d’outils flottante
|
||||
Drag to the window's left edge to dock Faire glisser vers le bord gauche de la fenêtre pour ancrer
|
||||
Float Tool Group Rendre le groupe d’outils flottant
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1300,6 +1300,9 @@
|
||||
Do Not Scale स्केल न करें
|
||||
Do you want to save the changes you made to “{name}”? क्या आप "{name}" में किए गए परिवर्तनों को सेव करना चाहते हैं?
|
||||
Dock Panel पैनल डॉक करें
|
||||
Dock to Top ऊपर डॉक करें
|
||||
Dock to Bottom नीचे डॉक करें
|
||||
Dock Control Bar नियंत्रण बार डॉक करें
|
||||
Document डॉक्यूमेंट
|
||||
Document Color Mode डॉक्यूमेंट रंग मोड
|
||||
Document Health Check डॉक्यूमेंट स्वास्थ्य जांच
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1073,6 +1073,9 @@ font-technology Variable Változó
|
||||
Do Not Scale Nincs méretezés
|
||||
Do you want to save the changes you made to “{name}”? Mented a módosításokat ebben: „{name}”?
|
||||
Dock Panel Panel dokkolása
|
||||
Dock to Top Dokkolás felülre
|
||||
Dock to Bottom Dokkolás alulra
|
||||
Dock Control Bar Vezérlősáv dokkolása
|
||||
Document Dokumentum
|
||||
Document Color Mode Dokumentum színmódja
|
||||
Document Info Dokumentuminformáció
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -753,6 +753,9 @@
|
||||
Put back in the toolbar Kembalikan ke bilah alat
|
||||
Float Panel Mengambangkan panel
|
||||
Dock Panel Tambatkan panel
|
||||
Dock to Top Tambatkan ke atas
|
||||
Dock to Bottom Tambatkan ke bawah
|
||||
Dock Control Bar Tambatkan bilah kontrol
|
||||
Float Tool Group Mengambangkan grup alat
|
||||
Services Layanan
|
||||
Hide VectorCraft Sembunyikan VectorCraft
|
||||
|
||||
|
@@ -3978,6 +3978,9 @@ axis Both Entrambi
|
||||
Put back in the toolbar Riporta nella barra degli strumenti
|
||||
Float Panel Rendi mobile pannello
|
||||
Dock Panel Ancora pannello
|
||||
Dock to Top Ancora in alto
|
||||
Dock to Bottom Ancora in basso
|
||||
Dock Control Bar Ancora barra di controllo
|
||||
Drag to float the toolbar Trascina per rendere mobile la barra degli strumenti
|
||||
Drag to the window's left edge to dock Trascina sul bordo sinistro della finestra per ancorare
|
||||
Float Tool Group Rendi mobile gruppo strumenti
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3920,6 +3920,9 @@ axis Both 両方
|
||||
Put back in the toolbar ツールバーに戻す
|
||||
Float Panel パネルをフローティング
|
||||
Dock Panel パネルをドッキング
|
||||
Dock to Top 上部にドッキング
|
||||
Dock to Bottom 下部にドッキング
|
||||
Dock Control Bar コントロールバーをドッキング
|
||||
Drag to float the toolbar ドラッグしてツールバーをフローティング
|
||||
Drag to the window's left edge to dock ウィンドウの左端までドラッグしてドッキング
|
||||
Float Tool Group ツールグループをフローティング
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -703,6 +703,9 @@
|
||||
Put back in the toolbar ხელსაწყოთა ზოლში დაბრუნება
|
||||
Float Panel მცურავი პანელი
|
||||
Dock Panel პანელის მიმაგრება
|
||||
Dock to Top ზემოთ მიმაგრება
|
||||
Dock to Bottom ქვემოთ მიმაგრება
|
||||
Dock Control Bar მართვის ზოლის მიმაგრება
|
||||
Float Tool Group მცურავი ხელსაწყოთა ჯგუფი
|
||||
Services სერვისები
|
||||
Hide VectorCraft VectorCraft-ის დამალვა
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -3904,6 +3904,9 @@ axis Both 양쪽
|
||||
Put back in the toolbar 도구 모음으로 되돌리기
|
||||
Float Panel 패널 분리
|
||||
Dock Panel 패널 고정
|
||||
Dock to Top 위쪽에 고정
|
||||
Dock to Bottom 아래쪽에 고정
|
||||
Dock Control Bar 컨트롤 막대 고정
|
||||
Drag to float the toolbar 드래그하여 도구 모음 분리
|
||||
Drag to the window's left edge to dock 창 왼쪽 가장자리로 드래그하여 고정
|
||||
Float Tool Group 도구 그룹 분리
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -767,6 +767,9 @@
|
||||
Put back in the toolbar Terug in de werkbalk zetten
|
||||
Float Panel Paneel laten zweven
|
||||
Dock Panel Paneel dokken
|
||||
Dock to Top Bovenaan dokken
|
||||
Dock to Bottom Onderaan dokken
|
||||
Dock Control Bar Bedieningsbalk dokken
|
||||
Float Tool Group Gereedschapsgroep laten zweven
|
||||
Services Diensten
|
||||
Hide VectorCraft VectorCraft verbergen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -765,6 +765,9 @@
|
||||
Put back in the toolbar Odłóż z powrotem na pasek narzędzi
|
||||
Float Panel Odepnij panel
|
||||
Dock Panel Zadokuj panel
|
||||
Dock to Top Zadokuj u góry
|
||||
Dock to Bottom Zadokuj u dołu
|
||||
Dock Control Bar Zadokuj pasek sterowania
|
||||
Float Tool Group Odepnij grupę narzędzi
|
||||
Services Usługi
|
||||
Hide VectorCraft Ukryj VectorCraft
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3192,6 +3192,9 @@ imageTrace Outline Contorno
|
||||
Put back in the toolbar Devolver à barra de ferramentas
|
||||
Float Panel Flutuar Painel
|
||||
Dock Panel Encaixar Painel
|
||||
Dock to Top Encaixar na Parte Superior
|
||||
Dock to Bottom Encaixar na Parte Inferior
|
||||
Dock Control Bar Encaixar Barra de Controle
|
||||
Float Tool Group Flutuar Grupo de Ferramentas
|
||||
Services Serviços
|
||||
Hide VectorCraft Ocultar o VectorCraft
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3973,6 +3973,9 @@ axis Both Оба
|
||||
Put back in the toolbar Вернуть на панель инструментов
|
||||
Float Panel Сделать панель плавающей
|
||||
Dock Panel Прикрепить панель
|
||||
Dock to Top Прикрепить сверху
|
||||
Dock to Bottom Прикрепить снизу
|
||||
Dock Control Bar Прикрепить панель управления
|
||||
Drag to float the toolbar Перетащите, чтобы сделать панель инструментов плавающей
|
||||
Drag to the window's left edge to dock Перетащите к левому краю окна для прикрепления
|
||||
Float Tool Group Сделать группу инструментов плавающей
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3853,6 +3853,9 @@ axis Both Båda
|
||||
Put back in the toolbar Lägg tillbaka i verktygsfältet
|
||||
Float Panel Gör panelen flytande
|
||||
Dock Panel Docka panel
|
||||
Dock to Top Docka överst
|
||||
Dock to Bottom Docka nederst
|
||||
Dock Control Bar Docka kontrollfältet
|
||||
Drag to float the toolbar Dra för att göra verktygsfältet flytande
|
||||
Drag to the window's left edge to dock Dra till fönstrets vänsterkant för att docka
|
||||
Float Tool Group Gör verktygsgruppen flytande
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -3741,6 +3741,9 @@ axis Both Her İkisi
|
||||
Put back in the toolbar Araç çubuğuna geri yerleştir
|
||||
Float Panel Paneli Yüzer Yap
|
||||
Dock Panel Bağlantı İstasyonu Paneli
|
||||
Dock to Top Üste yerleştir
|
||||
Dock to Bottom Alta yerleştir
|
||||
Dock Control Bar Kontrol çubuğunu yerleştir
|
||||
Drag to float the toolbar Araç çubuğunu ayırmak için sürükleyin
|
||||
Drag to the window's left edge to dock Sabitlemek için pencerenin sol kenarına sürükleyin
|
||||
Float Tool Group Araç Grubunu Yüzer Yap
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -3967,6 +3967,9 @@ axis Both Обидві
|
||||
Put back in the toolbar Повернути на панель інструментів
|
||||
Float Panel Від’єднати панель
|
||||
Dock Panel Прикріпити панель
|
||||
Dock to Top Прикріпити вгорі
|
||||
Dock to Bottom Прикріпити внизу
|
||||
Dock Control Bar Прикріпити панель керування
|
||||
Drag to float the toolbar Перетягніть, щоб від’єднати панель інструментів
|
||||
Drag to the window's left edge to dock Перетягніть до лівого краю вікна для прикріплення
|
||||
Float Tool Group Від’єднати групу інструментів
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 177 and column 5.
|
@@ -963,6 +963,9 @@ font-technology Variable Biến thiên
|
||||
Do Not Move Không di chuyển
|
||||
Do you want to save the changes you made to “{name}”? Bạn có muốn lưu các thay đổi đã thực hiện với “{name}”?
|
||||
Dock Panel Neo bảng
|
||||
Dock to Top Neo lên trên
|
||||
Dock to Bottom Neo xuống dưới
|
||||
Dock Control Bar Neo thanh điều khiển
|
||||
Document Tài liệu
|
||||
Document Color Mode Chế độ màu tài liệu
|
||||
Document Info Thông tin tài liệu
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 199 and column 5.
|
@@ -5042,6 +5042,9 @@ axis Both 两者
|
||||
Put back in the toolbar 放回工具栏
|
||||
Float Panel 浮动面板
|
||||
Dock Panel 停放面板
|
||||
Dock to Top 停放到顶部
|
||||
Dock to Bottom 停放到底部
|
||||
Dock Control Bar 停放控制栏
|
||||
Drag to float the toolbar 拖动以浮动工具栏
|
||||
Drag to the window's left edge to dock 拖到窗口左边缘以停放
|
||||
Float Tool Group 浮动工具组
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4853,6 +4853,9 @@ axis Both 兩者
|
||||
Put back in the toolbar 放回工具列
|
||||
Float Panel 浮動面板
|
||||
Dock Panel 停駐面板
|
||||
Dock to Top 停駐到頂部
|
||||
Dock to Bottom 停駐到底部
|
||||
Dock Control Bar 停駐控制列
|
||||
Drag to float the toolbar 拖曳以浮動工具列
|
||||
Drag to the window's left edge to dock 拖曳到視窗左側邊緣以停駐
|
||||
Float Tool Group 浮動工具組
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -448,6 +448,18 @@ fn run_ui_part5(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"window.control" => flag(&mut app.ui.control_bar),
|
||||
// The Control bar's panel menu and gripper: the edge of the window the bar docks to.
|
||||
"window.control.dock" => {
|
||||
let key = "edge";
|
||||
let edges = crate::chrome::CONTROL_BAR_EDGES;
|
||||
match edges.iter().position(|e| s(key).as_deref() == Some(*e)) {
|
||||
Some(i) => {
|
||||
app.ui.control_bar_bottom = i == 1;
|
||||
Ok(json!({ key: edges[i] }))
|
||||
}
|
||||
None => Err(format!("`{key}` must be one of {}", edges.join(", "))),
|
||||
}
|
||||
}
|
||||
"window.toolbar" => flag(&mut app.ui.toolbar),
|
||||
"window.toolbarColumns" => {
|
||||
let toolbar = match crate::custom_toolbars::target(&app.ui, p) {
|
||||
|
||||
@@ -59,6 +59,9 @@ pub fn checked(app: &VectorcraftApp, id: &str, p: &Value) -> Option<bool> {
|
||||
// a document no item is checked.
|
||||
"view.rotate" => app.view().zip(p.get("angle").and_then(Value::as_f64)).is_some_and(|(view, a)| (view.rotation - a).abs() < 1e-9),
|
||||
"window.control" => app.ui.control_bar,
|
||||
"window.control.dock" => {
|
||||
p.get("edge").and_then(Value::as_str) == Some(crate::chrome::CONTROL_BAR_EDGES[usize::from(app.ui.control_bar_bottom)])
|
||||
}
|
||||
"window.toolbar" => app.ui.toolbar,
|
||||
"window.toolbarAdvanced" => app.ui.toolbar_advanced,
|
||||
"window.toolbarColumns" => match p.get("toolbar").and_then(Value::as_str) {
|
||||
|
||||
@@ -447,6 +447,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
),
|
||||
("view.rotateReset", "Reset Rotate View", "Cmd+Shift+1", "{}"),
|
||||
("window.control", "Control", "", "{}"),
|
||||
(
|
||||
"window.control.dock",
|
||||
"Dock Control Bar",
|
||||
"",
|
||||
"{edge: \"top\"|\"bottom\"} dock the Control bar to the top of the window, under the menu bar, or to its bottom, as the bar's panel menu (Dock to Top, Dock to Bottom) and a drag of its gripper do; kept with the workspace; another edge is an error and changes nothing; returns {edge}",
|
||||
),
|
||||
("window.toolbar", "Tools", "", "{}"),
|
||||
(
|
||||
"window.toolbarColumns",
|
||||
|
||||
@@ -242,7 +242,7 @@ pub fn panel_menu_items(app: &mut VectorcraftApp, ui: &mut Ui, id: &str) -> bool
|
||||
}
|
||||
|
||||
/// The ≡ button drawn into `rect` that opens a panel menu.
|
||||
fn menu_button(ui: &mut Ui, id: &str, rect: Rect) -> egui::Response {
|
||||
pub(crate) fn menu_button(ui: &mut Ui, id: &str, rect: Rect) -> egui::Response {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let resp = ui.interact(rect, ui.scope_id().with(("panel-menu", id)), Sense::click());
|
||||
icons::paint(ui, "menu", rect.shrink(1.0), if resp.hovered() { t.text_strong } else { t.text_dim });
|
||||
|
||||
@@ -48,10 +48,15 @@ const ART_OPTIONS_WIDTH: f32 = 370.0;
|
||||
/// less, they show only in the Transform link's popover.
|
||||
const INLINE_FIELDS_WIDTH: f32 = 480.0;
|
||||
|
||||
/// The room the Control bar needs from the artboard's name field to Move and Scale Artwork with
|
||||
/// Artboard shown as their boxes; with less, the name field narrows from 120 to 100 points.
|
||||
const NAME_FIELD_ROOM: f32 = 330.0;
|
||||
|
||||
/// The Control bar while the Artboard tool is in use, in place of the object controls: the active
|
||||
/// artboard's preset and orientation, New Artboard and Delete Artboard, its name, Artboard Options,
|
||||
/// Rearrange All Artboards and the Transform link (the Transform panel in a popover), which all fit
|
||||
/// in the narrowest window; then Move and Scale Artwork with Artboard (the boxes alone, with their
|
||||
/// artboard's preset and orientation, New Artboard and Delete Artboard, its name (in a narrower
|
||||
/// field when the bar is short of room), Artboard Options, Rearrange All Artboards and the Transform
|
||||
/// link (the Transform panel in a popover), which all fit in the narrowest window, left of the bar's
|
||||
/// panel menu button; then Move and Scale Artwork with Artboard (the boxes alone, with their
|
||||
/// names in tooltips, when the bar is short of room) and, while the bar has room, the reference
|
||||
/// point the Transform panel also uses with the artboard's X and Y, and its W and H with the
|
||||
/// Constrain Width and Height Proportions link. Each control runs the command its Properties panel
|
||||
@@ -77,7 +82,8 @@ pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) -> bool {
|
||||
// The field shows the name as it is (names are never translated). A new name is applied on Enter
|
||||
// or a click elsewhere, and an empty field or Escape keeps the old one, as in the Properties panel.
|
||||
ui.label(egui::RichText::new(tl!("Name:")).size(12.0).color(t.text));
|
||||
if let Some(name) = widgets::text_field_named(ui, ("cb-ab", "name"), Some(tl!("Name:")), Some(&ab.name), 120.0, 1).filter(|n| !n.is_empty()) {
|
||||
let width = if ui.available_width() < NAME_FIELD_ROOM { 100.0 } else { 120.0 };
|
||||
if let Some(name) = widgets::text_field_named(ui, ("cb-ab", "name"), Some(tl!("Name:")), Some(&ab.name), width, 1).filter(|n| !n.is_empty()) {
|
||||
app.run("artboard.setProps", json!({"index": i, "name": name})).ok();
|
||||
}
|
||||
if widgets::icon_button(ui, "dc-artboard-options", tl!("Artboard Options"), false, 24.0).clicked() {
|
||||
|
||||
@@ -849,11 +849,15 @@ fn the_control_bar_shows_the_artboard_tools_controls() {
|
||||
assert!(!has(&texts, "Snapping"), "{texts:?}");
|
||||
let link = text_rect(&texts, "Transform");
|
||||
assert!(link.right() < 790.0 && text_rect(&texts, "Artboard 1").right() < link.left(), "{texts:?}");
|
||||
// Move and Scale Artwork with Artboard show as their boxes alone, inside the window, and the
|
||||
// second one turns Scale Artwork with Artboard on.
|
||||
// Move and Scale Artwork with Artboard show as their boxes alone, inside the window and left of
|
||||
// the panel menu button at the bar's right end, and the second one turns Scale Artwork with
|
||||
// Artboard on.
|
||||
assert!(!has(&texts, "Move Artwork with Artboard"), "{texts:?}");
|
||||
let boxes = bar_buttons(&ctx, link.right(), 790.0);
|
||||
let mut boxes = bar_buttons(&ctx, link.right(), 790.0);
|
||||
let menu = boxes.pop().filter(|r| (r.width() - 16.0).abs() < 0.5 && r.right() > 785.0);
|
||||
let menu = menu.unwrap_or_else(|| panic!("the panel menu button ends the bar: {boxes:?}"));
|
||||
assert_eq!(boxes.len(), 2, "{boxes:?}");
|
||||
assert!(boxes[1].right() <= menu.left(), "{boxes:?} and the panel menu button at {menu:?}");
|
||||
let scale = |app: &VectorcraftApp| app.session.tool_options()["scaleArt"].as_bool();
|
||||
assert_eq!(scale(&app), Some(false));
|
||||
let press =
|
||||
|
||||
@@ -599,8 +599,9 @@ const BAR_ALIGNMENTS: [&str; 3] = ["Align left", "Align center", "Align right"];
|
||||
fn the_control_bar_shows_the_paragraph_alignment_of_the_selected_type() {
|
||||
let ctx = control_bar_ctx();
|
||||
let mut app = control_bar_app(&[json!({"x": 20, "y": 50, "text": "Point"})]);
|
||||
// Pinned, so that the room the buttons need can't creep up past common window widths.
|
||||
for width in [1440.0, 1920.0] {
|
||||
// Pinned, so that the room the buttons need can't creep up past common window widths. The
|
||||
// bar's right end keeps the panel menu button's room.
|
||||
for width in [1536.0, 1920.0] {
|
||||
let nodes = control_bar_nodes(&ctx, &mut app, width, vec![]);
|
||||
let ends = BAR_ALIGNMENTS.map(|n| control_bar_end(&nodes, n));
|
||||
assert!(ends.iter().all(|e| e.is_some_and(|x| x <= f64::from(width) - 10.0)), "pinned: the buttons show on a {width} pt window: {ends:?}");
|
||||
|
||||
@@ -370,6 +370,9 @@ pub struct UiState {
|
||||
#[serde(skip)]
|
||||
pub transient_focus: bool,
|
||||
pub control_bar: bool,
|
||||
/// The Control bar is docked to the bottom of the window instead of the top
|
||||
/// (`window.control.dock`).
|
||||
pub control_bar_bottom: bool,
|
||||
pub toolbar: bool,
|
||||
pub toolbar_double: bool,
|
||||
/// Advanced toolbar (every tool group) instead of the categorized Basic toolbar.
|
||||
@@ -961,6 +964,7 @@ impl Default for UiState {
|
||||
transient_panel: None,
|
||||
transient_focus: false,
|
||||
control_bar: false,
|
||||
control_bar_bottom: false,
|
||||
toolbar: true,
|
||||
toolbar_double: false,
|
||||
toolbar_advanced: false,
|
||||
|
||||
@@ -43,6 +43,8 @@ pub use drag::{
|
||||
};
|
||||
#[cfg(test)]
|
||||
use dropdowns::combo_button_id;
|
||||
#[cfg(test)]
|
||||
pub(crate) use dropdowns::menu_floor;
|
||||
pub(crate) use dropdowns::{combo, combo_menu, combo_menu_field, combo_named, popover_is_open};
|
||||
pub use dropdowns::{
|
||||
dropdown, dropdown_named, dropdown_names, dropdown_names_named, dropdown_with, dropdown_with_named, list_scroll_style, menu_room, menu_scroll,
|
||||
|
||||
@@ -252,8 +252,9 @@ pub(crate) fn popover_is_open(ctx: &egui::Context, anchor: egui::Id) -> bool {
|
||||
ctx.data(|d| d.get_temp::<u64>(anchor.with("popover"))).is_some_and(|f| f + 1 >= frame)
|
||||
}
|
||||
|
||||
/// Records where the bottom bars (status and hint bars) start this frame in the window being drawn,
|
||||
/// so its menus can stop above them (#2086). Without bars it is the bottom of the window.
|
||||
/// Records where the bottom bars (the status and hint bars, and the Control bar docked at the
|
||||
/// bottom) start this frame in the window being drawn, so its menus can stop above them (#2086).
|
||||
/// Without bars it is the bottom of the window.
|
||||
pub fn set_menu_floor(ctx: &egui::Context, y: f32) {
|
||||
let id = menu_floor_id(ctx);
|
||||
ctx.data_mut(|d| d.insert_temp(id, y));
|
||||
@@ -266,7 +267,7 @@ fn menu_floor_id(ctx: &egui::Context) -> egui::Id {
|
||||
|
||||
/// Where menus of the window being drawn stop: its recorded floor, or its bottom edge when nothing
|
||||
/// recorded one, as in a floating panel's OS window.
|
||||
pub(super) fn menu_floor(ctx: &egui::Context) -> f32 {
|
||||
pub(crate) fn menu_floor(ctx: &egui::Context) -> f32 {
|
||||
let id = menu_floor_id(ctx);
|
||||
ctx.data(|d| d.get_temp::<f32>(id)).unwrap_or_else(|| ctx.content_rect().bottom())
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ use crate::{VectorcraftApp, widgets};
|
||||
pub struct Workspace {
|
||||
pub name: String,
|
||||
pub control_bar: bool,
|
||||
/// The Control bar is docked to the bottom of the window (built-in workspaces, and workspaces
|
||||
/// saved without this field: the top).
|
||||
pub control_bar_bottom: bool,
|
||||
pub rulers: bool,
|
||||
pub toolbar: bool,
|
||||
pub toolbar_double: bool,
|
||||
@@ -175,6 +178,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace {
|
||||
Workspace {
|
||||
name: name.into(),
|
||||
control_bar: ui.control_bar,
|
||||
control_bar_bottom: ui.control_bar_bottom,
|
||||
rulers: ui.view.rulers,
|
||||
toolbar: ui.toolbar,
|
||||
toolbar_double: ui.toolbar_double,
|
||||
@@ -201,6 +205,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace {
|
||||
/// Apply a workspace's layout flags (brightness, prefs, shortcuts etc. untouched).
|
||||
pub fn apply(ui: &mut UiState, w: &Workspace) {
|
||||
ui.control_bar = w.control_bar;
|
||||
ui.control_bar_bottom = w.control_bar_bottom;
|
||||
ui.view.rulers = w.rulers;
|
||||
ui.toolbar = w.toolbar;
|
||||
ui.toolbar_double = w.toolbar_double;
|
||||
@@ -766,4 +771,23 @@ mod tests {
|
||||
let back: UiState = serde_json::from_value(serde_json::to_value(&ui).unwrap()).unwrap();
|
||||
assert_eq!(back.custom_workspaces[1].dock_width, Some(260.0));
|
||||
}
|
||||
|
||||
/// A workspace keeps the edge the Control bar is docked to, and one saved without it docks the
|
||||
/// bar at the top.
|
||||
#[test]
|
||||
fn saved_workspaces_restore_the_control_bar_edge() {
|
||||
let mut ui = UiState { control_bar: true, control_bar_bottom: true, ..Default::default() };
|
||||
save_as(&mut ui, "Bottom").unwrap();
|
||||
switch(&mut ui, ESSENTIALS).unwrap();
|
||||
assert!(!ui.control_bar_bottom, "Essentials docks the bar at the top");
|
||||
switch(&mut ui, "Bottom").unwrap();
|
||||
assert!(ui.control_bar_bottom, "the saved workspace docks it at the bottom");
|
||||
let back: UiState = serde_json::from_value(serde_json::to_value(&ui).unwrap()).unwrap();
|
||||
assert!(back.control_bar_bottom && back.custom_workspaces[0].control_bar_bottom, "both survive the preferences' JSON");
|
||||
let mut old = serde_json::to_value(&back.custom_workspaces[0]).unwrap();
|
||||
assert!(old.as_object_mut().unwrap().remove("controlBarBottom").is_some());
|
||||
let old: Workspace = serde_json::from_value(old).unwrap();
|
||||
apply(&mut ui, &old);
|
||||
assert!(ui.control_bar && !ui.control_bar_bottom, "an older workspace docks the bar at the top");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3955,6 +3955,12 @@ with an `index` it closes the document and all its views. `window.nextDocument`
|
||||
through the views in tab order and add `view` to their reply for a document with more than one view. `ui.inspect`
|
||||
reports `windows` (every window as `[document index, view number]`, in tab order), `currentWindow` and each pane's
|
||||
`view`. Views are not saved.
|
||||
`window.control.dock {edge}` (desktop app) docks the Control bar to the top of the window, under the menu bar
|
||||
(`edge: "top"`, the default), or to its bottom (`"bottom"`), and returns `{edge}`. The bar's panel menu runs it with
|
||||
Dock to Top and Dock to Bottom, the edge in use checked, and so does a drag of the bar's gripper, which docks the bar at
|
||||
the edge the pointer is released nearer and shows a band along that edge while it moves. The edge is kept with the
|
||||
workspace, and a workspace saved without it docks the bar at the top. Another `edge`, or none, fails with "`edge` must
|
||||
be one of top, bottom" and leaves the bar where it is.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"file.new","params":{"preset":"A4","orientation":"landscape","artboards":4,"artboardLayout":{"columns":2},"bleed":9}}}
|
||||
|
||||
@@ -546,6 +546,7 @@ since 2026-10-01.
|
||||
- **Global Edit Options in the Control bar:** while Select › Start Global Edit can run, the Control bar shows a Global Edit Options dropdown that opens the same options as the Properties panel's (Match: Appearance, Size; On Artboards; Include Objects On Canvas; Result(s)); both set the same options. The dropdown is off while Global Edit is on; it sits after Recolor Artwork, ahead of the X/Y/W/H fields ([Edit similar objects globally](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)). Regression test: `the_control_bar_shows_the_global_edit_options` in `crates/ui-egui/src/chrome/tests.rs`.
|
||||
- **Global Edit's enabled check on large paths:** the check that decides whether Select › Start Global Edit can run on the selected path, compound path or symbol instance (which the Control bar makes every frame) measures the box of its anchors and handles instead of building and normalizing a copy of every anchor, with the same answer. On a 200,000-anchor path the check takes 0.7 ms instead of 2.2 to 3.0 ms. Regression tests: `support_*` in `crates/engine/src/cmd/globaledit.rs`.
|
||||
- **Control bar's inline X, Y, W and H fields fit the bar:** the Control bar shows the inline X, Y, W and H fields only when all four, their labels as drawn in the current language and the W/H link fit inside the bar's margin; otherwise it shows the Transform link alone. This is VectorCraft's own layout rule. Regression test: `the_control_bar_shows_the_inline_transform_fields_only_when_they_fit` in `crates/ui-egui/src/chrome/tests.rs`.
|
||||
- **Control bar docks to the top or the bottom:** the Control bar's panel menu, at the bar's right end, has Dock to Top and Dock to Bottom, the edge in use checked, and dragging the bar's gripper docks it at the top or the bottom edge of the window, whichever the pointer is released nearer, with a band along that edge while it moves (`window.control.dock {edge}`), as the help page describes ([Control panel overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/control-panel-overview.html)). Keeping the edge with the workspace, with older workspaces docking the bar at the top, is VectorCraft's own rule. Not done: dragging the gripper away from both edges to float the bar. Regression tests: `the_control_bar_panel_menu_docks_it_to_the_top_or_the_bottom`, `dragging_the_control_bar_gripper_docks_it_at_the_nearer_edge`, `the_control_bar_dock_command_takes_top_or_bottom` and `menus_stop_above_a_control_bar_docked_at_the_bottom` in `crates/ui-egui/src/chrome/tests.rs`, `saved_workspaces_restore_the_control_bar_edge` in `crates/ui-egui/src/workspaces.rs`.
|
||||
- **Pattern-only backquote drags:** With Selection move, bounding-box scale or rotate, or the Rotate, Reflect, Scale or Shear tool, backquote held at mouse release applies the drag matrix to selected pattern paint while object geometry stays in place. The key may be pressed after mouse press; if released before the mouse button, the drag commits as an ordinary transform. Objects without pattern paint stay still; stroke-only patterns and patterned objects in mixed selections take the same matrix. Regression tests: `backquote_move_uses_release_state_and_one_commit` and `backquote_box_scale_and_rotate_use_pattern_matrix_at_release` in `select/tests.rs`, `backquote_drag_previews_and_commits_pattern_only_for_each_transform_tool` in `xform/transform.rs`, `selection_backquote_drag_changes_pattern_fill_and_stroke_without_moving_mixed_art` and `selection_backquote_drag_changes_stroke_only_pattern` in `tests/pattern.rs`, `egui_backtick_drag_uses_key_state_at_release` in `canvas/tests.rs`, and `synthetic_pointer_backquote_keeps_solid_object_in_place` in `control/tests.rs`. Scores and hours unchanged.
|
||||
- **Existing-guide Escape cancellation (G1):** Escape clears a pending guide press or drag through Selection and Direct/Group Selection, cancelling a begun preview. Eight app-input regressions cover both axes, move/copy, temporary selection-tool borrowing from idle Pen, preselected-guide document/interaction-start selection and Undo/Redo/saved-state preservation, later on-canvas motion/release, Shift-press cancellation, and ordinary-release/Enter/idle controls. Historical focused checks on `8ab574b7` recorded five expected RED failures with three passing controls, then all eight GREEN tests and the Direct Selection Escape control passed. All nine also passed in current full CI. A full CI retry on the five-unit combined source at `a0671777` passed all seven stages with 6,613 test outcomes on 2026-10-14. An earlier full-CI run on the identical source panicked in the randomized `ai_mutated_save_never_panics` test; the cause remains unresolved. This five-fix batch does not claim to repair that failure. The original 1280×960 headless app capture from `b076025a` was reviewed for one vertical Selection guide after Escape; source review found its scene unchanged on `a0671777`. Scores and hours unchanged.
|
||||
- **Escape in more tool drags:** Escape during an Anchor Point drag, a Mesh tool drag, a Direct Selection drag of a mesh point, a corner widget or the brackets of type on a path, or a Shape Builder drag cancels it: the art goes back to how the press found it, no undo step is added, the release does nothing, and the tool stays chosen. Escape during a Measure tool drag brings back the measurement before it. Regression tests: `escape_cancels_*` in `draw2/anchor/tests.rs`, `meshedit.rs`, `direct/tests.rs`, `builder/tests.rs` and `xform/measure.rs`, and `escape_cancels_an_anchor_point_mesh_shape_builder_or_measure_drag` in `tests/toolescape.rs`.
|
||||
|
||||
Reference in New Issue
Block a user