Merge pull request #6737 from dotCooCoo/feat/control-bar-options-menu

Control bar: the panel menu turns each group of options on or off
This commit is contained in:
BFlat
2026-11-12 09:07:47 +07:00
committed by GitHub
42 changed files with 910 additions and 118 deletions
+6
View File
@@ -471,6 +471,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{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.control.options",
"Control Bar Options",
"",
"{group: \"snapping\"|\"anchors\"|\"toolOptions\"|\"objectOptions\"|\"imageOptions\"|\"fillStroke\"|\"opacity\"|\"graphicStyle\"|\"character\"|\"paragraph\"|\"verticalAlignment\"|\"alignDistribute\"|\"transform\"|\"artboardOptions\"|\"documentOptions\", visible?: bool} turn a group of the Control bar's options on (true), off (false) or toggle it (omitted), as the bar's panel menu does; a group that is off stays out of the bar even while its tool or object is selected, and one that is on shows only while the selection or the tool calls for it; every group starts on; kept with the workspace; another group or a `visible` that is not true or false is an error and changes nothing; returns {group, visible}",
),
("window.toolbar", "Tools", "", "{}"),
(
"window.toolbarColumns",
+1 -1
View File
@@ -20,7 +20,7 @@ mod tabs;
#[cfg(test)]
use control_bar::ISOLATION_BUTTON;
pub use control_bar::{AnchorControls, CONTROL_BAR_EDGES, anchor_buttons, anchor_controls, control_bar};
pub use control_bar::{AnchorControls, CONTROL_BAR_EDGES, CONTROL_BAR_GROUPS, anchor_buttons, anchor_controls, control_bar, read_hidden_groups};
#[cfg(test)]
use hints::hint_segments;
pub use hints::{hint_bar, hint_strings};
+189 -77
View File
@@ -218,55 +218,127 @@ fn separator_width(ui: &Ui) -> f32 {
}
/// The room the controls at the end of the Control bar need: the Transform link, or Document Setup
/// and Preferences with nothing selected. The groups ahead of them (the Paragraph section, area
/// type's vertical alignment, the Align and Distribute buttons) show only while it is left, so
/// the bar trims them from its right end (#5372), after the inline X/Y/W/H fields.
fn end_width(ui: &Ui, nothing_selected: bool) -> f32 {
if nothing_selected { DOCUMENT_SETUP_WIDTH + ui.spacing().item_spacing.x + PREFERENCES_WIDTH } else { crate::panels::transform::link_width(ui) }
/// and Preferences with nothing selected, or none while the panel menu has that group off. The
/// groups ahead of them (the Paragraph section, area type's vertical alignment, the Align and
/// Distribute buttons) show only while it is left, so the bar trims them from its right end
/// (#5372), after the inline X/Y/W/H fields.
fn end_width(app: &VectorcraftApp, ui: &Ui, nothing_selected: bool) -> f32 {
if nothing_selected {
if group_on(app, "documentOptions") { DOCUMENT_SETUP_WIDTH + ui.spacing().item_spacing.x + PREFERENCES_WIDTH } else { 0.0 }
} else if group_on(app, "transform") {
crate::panels::transform::link_width(ui)
} else {
0.0
}
}
/// Draws the Control bar's groups between its Character controls and the Transform link, and
/// returns whether the Transform link and X/Y/W/H follow (false: nothing is selected, and Document
/// Setup and Preferences, drawn here, end the bar). The groups are the Paragraph section and area
/// type's vertical alignment (#3759), then the Align buttons and, for two or more objects, the
/// Setup and Preferences, drawn here, end the bar, or the Transform group is off). The groups are
/// the Paragraph section and area type's vertical alignment (#3759), then the Align buttons and, for
/// two or more objects, the
/// Distribute buttons (a single object keeps the bar as it was); each shows only while the end of
/// the bar keeps its room.
/// the bar keeps its room. Groups the panel menu has off are left out, and take no room.
fn groups_before_transform(app: &mut VectorcraftApp, ui: &mut Ui, objects: usize) -> bool {
let nothing = objects == 0;
if crate::panels::paragraph::control_bar(app, ui, BUTTON, |ui| group_end_width(ui) + end_width(ui, nothing)) {
let end = end_width(app, ui, nothing);
if group_on(app, "paragraph") && crate::panels::paragraph::control_bar(app, ui, BUTTON, |ui| group_end_width(ui) + end) {
group_end(ui);
}
if crate::panels::properties::vertical_align::shown(app).is_some()
&& ui.available_width() >= buttons_width(ui, 4) + group_end_width(ui) + end_width(ui, nothing)
if group_on(app, "verticalAlignment")
&& crate::panels::properties::vertical_align::shown(app).is_some()
&& ui.available_width() >= buttons_width(ui, 4) + group_end_width(ui) + end
&& crate::panels::properties::vertical_align::buttons(app, ui, BUTTON)
{
group_end(ui);
}
if nothing {
if widgets::flat_button(ui, tl!("Document Setup"), DOCUMENT_SETUP_WIDTH).clicked() {
app.run("file.documentSetup", json!({})).ok();
}
if widgets::flat_button(ui, tl!("Preferences"), PREFERENCES_WIDTH).clicked() {
app.run("edit.preferences", json!({})).ok();
if group_on(app, "documentOptions") {
if widgets::flat_button(ui, tl!("Document Setup"), DOCUMENT_SETUP_WIDTH).clicked() {
app.run("file.documentSetup", json!({})).ok();
}
if widgets::flat_button(ui, tl!("Preferences"), PREFERENCES_WIDTH).clicked() {
app.run("edit.preferences", json!({})).ok();
}
}
return false;
}
let end = separator_width(ui) + end_width(ui, false);
if ui.available_width() >= buttons_width(ui, 7) + end {
let transform = group_on(app, "transform");
let end = if transform { separator_width(ui) + end } else { 0.0 };
if group_on(app, "alignDistribute") && ui.available_width() >= buttons_width(ui, 7) + end {
crate::panels::align::align_buttons(app, ui, BUTTON);
if objects > 1 && ui.available_width() >= GROUP_GAP + buttons_width(ui, 6) + end {
ui.add_space(GROUP_GAP);
crate::panels::align::distribute_buttons(app, ui, BUTTON);
}
separator(ui);
if transform {
separator(ui);
}
}
true
transform
}
/// 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 groups of the Control bar's options that its panel menu turns on or off
/// (`window.control.options {group, visible?}`), by id, in the menu's order. Every group is on
/// until it is turned off. A group that is off stays out of the bar even while its tool or object
/// is selected; one that is on still shows only while the selection or the tool calls for it.
pub const CONTROL_BAR_GROUPS: [&str; 15] = [
"snapping",
"anchors",
"toolOptions",
"objectOptions",
"imageOptions",
"fillStroke",
"opacity",
"graphicStyle",
"character",
"paragraph",
"verticalAlignment",
"alignDistribute",
"transform",
"artboardOptions",
"documentOptions",
];
/// The panel menu's label for group `group` of [`CONTROL_BAR_GROUPS`], translated.
fn group_label(group: &str) -> &'static str {
match group {
"snapping" => tl!("Snapping"),
"anchors" => tl!("Anchor Points"),
"toolOptions" => tl!("Tool Options"),
"objectOptions" => tl!("Object Options"),
"imageOptions" => tl!("Image Options"),
"fillStroke" => tl!("Fill and Stroke"),
"opacity" => tl!("Opacity"),
"graphicStyle" => tl!("Graphic Style"),
"character" => tl!("Character"),
"paragraph" => tl!("Paragraph"),
"verticalAlignment" => tl!("Vertical Alignment"),
"alignDistribute" => tl!("Align and Distribute"),
"transform" => tl!("Transform"),
"artboardOptions" => tl!("Artboard Options"),
_ => tl!("Document Options"),
}
}
/// Whether group `group` of [`CONTROL_BAR_GROUPS`] is on, so its controls may show in the bar.
pub(crate) fn group_on(app: &VectorcraftApp, group: &str) -> bool {
!app.ui.control_bar_hidden_groups.contains(group)
}
/// Reads the groups of [`CONTROL_BAR_GROUPS`] that are off, as kept in the preferences and in a
/// workspace. Ids the list does not hold are dropped, and a value that is not a list of strings
/// reads as no group off, so every group shows.
pub fn read_hidden_groups<'de, D: serde::Deserializer<'de>>(d: D) -> Result<std::collections::BTreeSet<String>, D::Error> {
let value = <serde_json::Value as serde::Deserialize>::deserialize(d)?;
let ids = value.as_array().into_iter().flatten().filter_map(serde_json::Value::as_str);
Ok(ids.filter(|id| CONTROL_BAR_GROUPS.contains(id)).map(str::to_string).collect())
}
/// The side of the Control bar's panel menu button, at the bar's right end.
const MENU_BUTTON: f32 = 16.0;
@@ -278,20 +350,84 @@ 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.
/// The Control bar's panel menu: an item for each group of [`CONTROL_BAR_GROUPS`], checked while
/// the group is on, which turns it on or off; then Dock to Top and Dock to Bottom, the edge the bar
/// is docked to checked. In a window too short for every item, the list scrolls.
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]);
widgets::menu_scroll(ui, |ui| {
for group in CONTROL_BAR_GROUPS {
if widgets::menu_item(ui, group_label(group), true, group_on(app, group)) {
crate::menus::invoke(app, "window.control.options", json!({ "group": group }));
}
}
}
ui.separator();
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 Fill and Stroke group: the fill and stroke chips, the Stroke link, the weight
/// and the width profile, and the separator after them.
fn fill_and_stroke(app: &mut VectorcraftApp, ui: &mut Ui) {
let shown_stroke = crate::panels::current_stroke(app);
let mixed = crate::panels::stroke_mixed(app, ui.ctx());
let weight = stroke_panel::shown_weight(app, shown_stroke.as_ref(), &mixed);
crate::panels::paint_chip(app, ui, false, 22.0, true);
crate::panels::paint_chip(app, ui, true, 22.0, true);
// The link opens the Stroke panel as a popover under it; then the weight spinner
// (with presets) and the width profile.
stroke_panel::link(app, ui, tl!("Stroke:"));
stroke_panel::weight_field(app, ui, "cb-stroke", weight, 100.0);
if let Some(id) = stroke_panel::profile_dropdown(app, ui, shown_stroke.as_ref().and_then(|s| s.profile.as_ref())) {
app.run("stroke.set", json!({"profile": id})).ok();
}
ui.add_space(4.0);
ui.separator();
}
/// The Control bar's Opacity group (its link and field, showing `opacity`) and, with something
/// `selected`, its Graphic Style group (Style, Recolor Artwork and Global Edit Options), each while
/// the panel menu has it on, and the separator after them.
fn opacity_and_style(app: &mut VectorcraftApp, ui: &mut Ui, t: &Tokens, opacity: f32, selected: bool) {
let (opacity_on, style_on) = (group_on(app, "opacity"), group_on(app, "graphicStyle") && selected);
if opacity_on {
// A click opens the Transparency panel; a drag scrubs the field (#4088).
if crate::scrub::link_label(ui, egui::RichText::new(tl!("Opacity:")).size(12.0).color(t.text).underline()).clicked() {
app.ui.open_panel = Some("transparency".into());
}
if let Some(o) = widgets::plain_field(ui, "cb-opacity", opacity as f64 * 100.0, "%", 0, 56.0)
&& selected
{
app.run("transparency.set", json!({"opacity": o.clamp(0.0, 100.0)})).ok();
}
}
if style_on {
// Style picker: the selection's graphic style; its menu applies another.
ui.add_space(4.0);
if ui.link(egui::RichText::new(tl!("Style:")).size(12.0).color(t.text).underline()).clicked() {
app.ui.open_panel = Some("graphicStyles".into());
}
let resp = widgets::chip_button_named(ui, 22.0, true, tl!("Graphic Style"), |ui, r| crate::panels::graphic_styles::paint_linked(app, ui, r));
egui::Popup::menu(&resp).show(|ui| crate::panels::graphic_styles::picker(app, ui));
// Recolor Artwork, when the selection has colours to recolour.
let colored = app.session.active().is_some_and(|st| crate::canvas::has_colors(&st.doc, &st.selection.objects));
if colored && widgets::icon_button(ui, "palette", tl!("Recolor Artwork"), false, 24.0).clicked() {
crate::menus::invoke(app, "ui.recolorDialog", json!({}));
}
global_edit_options(app, ui);
}
if opacity_on || style_on {
ui.separator();
}
}
/// 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) {
@@ -345,7 +481,7 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
return;
}
// The Artboard tool's controls fill the narrowest window, so they keep it to themselves there.
if app.session.tool_id() != "artboard" || ui.available_width() >= ARTBOARD_SNAPPING_WIDTH {
if group_on(app, "snapping") && (app.session.tool_id() != "artboard" || ui.available_width() >= ARTBOARD_SNAPPING_WIDTH) {
snapping_menu(app, ui);
}
let Some(st) = app.session.active() else { return };
@@ -374,64 +510,40 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
ui.label(egui::RichText::new(label).font(theme::semibold(12.0)).color(t.text));
// With the Artboard tool in use, the bar shows the active artboard's controls in place
// of the object controls.
if crate::panels::artboards::control_bar(app, ui) {
let (artboard_options, transform) = (group_on(app, "artboardOptions"), group_on(app, "transform"));
if crate::panels::artboards::control_bar(app, ui, artboard_options, transform) {
return;
}
if anchors != AnchorControls::None {
if anchors != AnchorControls::None && group_on(app, "anchors") {
anchor_buttons(app, ui, anchors);
}
isolation_button(app, ui);
let object_options = group_on(app, "objectOptions");
if object_options {
isolation_button(app, ui);
}
ui.add_space(6.0);
// An image or an Image Trace object shows its own controls in place of Fill and Stroke.
let image = crate::place::control_bar_details(app, ui) || crate::panels::image_trace::control_bar(app, ui);
crate::toolbar::control_bar_options(app, ui);
crate::dialogs::envelope::control_bar(app, ui);
crate::panels::symbols::control_bar(app, ui);
// An image or an Image Trace object shows its own controls in place of Fill and Stroke,
// and leaves Fill and Stroke out while the Image Options group is off too.
let image = if group_on(app, "imageOptions") {
crate::place::control_bar_details(app, ui) || crate::panels::image_trace::control_bar(app, ui)
} else {
crate::place::selected_image_info(app).is_some() || crate::panels::image_trace::selected_trace(app).is_some()
};
if group_on(app, "toolOptions") {
crate::toolbar::control_bar_options(app, ui);
}
if object_options {
crate::dialogs::envelope::control_bar(app, ui);
crate::panels::symbols::control_bar(app, ui);
}
let opacity = if has_first { crate::panels::current_transparency(app).map_or(1.0, |t| t.0) } else { 1.0 };
if !image {
let shown_stroke = crate::panels::current_stroke(app);
let mixed = crate::panels::stroke_mixed(app, ui.ctx());
let weight = stroke_panel::shown_weight(app, shown_stroke.as_ref(), &mixed);
crate::panels::paint_chip(app, ui, false, 22.0, true);
crate::panels::paint_chip(app, ui, true, 22.0, true);
// The link opens the Stroke panel as a popover under it; then the weight spinner
// (with presets) and the width profile.
stroke_panel::link(app, ui, tl!("Stroke:"));
stroke_panel::weight_field(app, ui, "cb-stroke", weight, 100.0);
if let Some(id) = stroke_panel::profile_dropdown(app, ui, shown_stroke.as_ref().and_then(|s| s.profile.as_ref())) {
app.run("stroke.set", json!({"profile": id})).ok();
}
ui.add_space(4.0);
ui.separator();
if !image && group_on(app, "fillStroke") {
fill_and_stroke(app, ui);
}
// A click opens the Transparency panel; a drag scrubs the field (#4088).
if crate::scrub::link_label(ui, egui::RichText::new(tl!("Opacity:")).size(12.0).color(t.text).underline()).clicked() {
app.ui.open_panel = Some("transparency".into());
opacity_and_style(app, ui, &t, opacity, !sel.is_empty());
if group_on(app, "character") {
crate::panels::character::control_bar(app, ui);
}
if let Some(o) = widgets::plain_field(ui, "cb-opacity", opacity as f64 * 100.0, "%", 0, 56.0)
&& !sel.is_empty()
{
app.run("transparency.set", json!({"opacity": o.clamp(0.0, 100.0)})).ok();
}
if !sel.is_empty() {
// Style picker: the selection's graphic style; its menu applies another.
ui.add_space(4.0);
if ui.link(egui::RichText::new(tl!("Style:")).size(12.0).color(t.text).underline()).clicked() {
app.ui.open_panel = Some("graphicStyles".into());
}
let resp = widgets::chip_button_named(ui, 22.0, true, tl!("Graphic Style"), |ui, r| {
crate::panels::graphic_styles::paint_linked(app, ui, r)
});
egui::Popup::menu(&resp).show(|ui| crate::panels::graphic_styles::picker(app, ui));
// Recolor Artwork, when the selection has colours to recolour.
let colored = app.session.active().is_some_and(|st| crate::canvas::has_colors(&st.doc, &st.selection.objects));
if colored && widgets::icon_button(ui, "palette", tl!("Recolor Artwork"), false, 24.0).clicked() {
crate::menus::invoke(app, "ui.recolorDialog", json!({}));
}
global_edit_options(app, ui);
}
ui.separator();
crate::panels::character::control_bar(app, ui);
if !groups_before_transform(app, ui, sel.len()) {
return;
}
+301 -13
View File
@@ -2191,9 +2191,22 @@ fn the_control_bar_snapping_menu_shares_glyph_and_alignment_settings() {
/// 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) };
/// The window the Control bar panel menu tests draw the bar in, tall enough for the whole menu.
const MENU_SCREEN: egui::Rect = egui::Rect { min: egui::Pos2::ZERO, max: egui::pos2(1200.0, 700.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));
control_bar_frame_in(app, ctx, DOCK_SCREEN, events)
}
/// [`control_bar_frame`] in window `screen`.
fn control_bar_frame_in(
app: &mut crate::VectorcraftApp,
ctx: &egui::Context,
screen: egui::Rect,
events: Vec<egui::Event>,
) -> Vec<egui::accesskit::Node> {
let mut out = ctx.run_ui(egui::RawInput { events, screen_rect: Some(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()
}
@@ -2219,31 +2232,69 @@ fn the_control_bar_panel_menu_docks_it_to_the_top_or_the_bottom() {
let ctx = egui::Context::default();
ctx.enable_accesskit();
crate::theme::install_fonts(&ctx);
let frame = |app: &mut crate::VectorcraftApp, events| control_bar_frame_in(app, &ctx, MENU_SCREEN, events);
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();
frame(app, vec![]);
let menu = node_center(&frame(app, vec![]), "Panel menu").unwrap_or_else(|| panic!("the bar has a panel menu"));
frame(app, vec![egui::Event::PointerMoved(menu), press_at(menu, true)]);
frame(app, vec![press_at(menu, false)]);
let nodes = frame(app, vec![]);
// The groups of options are listed first, every one on; the edge items follow.
let checked: Vec<String> =
nodes.iter().filter_map(|n| n.label()).filter(|l| l.starts_with('✓') && l.contains("Dock")).map(str::to_string).collect();
let groups = nodes.iter().filter_map(|n| n.label()).filter(|l| CONTROL_BAR_GROUP_LABELS.iter().any(|g| *l == format!("✓ {g}"))).count();
assert_eq!(groups, CONTROL_BAR_GROUP_LABELS.len(), "every group is listed, on");
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"));
assert!(MENU_SCREEN.contains(at) && (at.y < menu.y) == app.ui.control_bar_bottom, "{item} at {at:?}, the button at {menu:?}");
frame(app, vec![egui::Event::PointerMoved(at), press_at(at, true)]);
frame(app, vec![press_at(at, false)]);
frame(app, vec![]);
let menu = node_center(&frame(app, 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}");
assert!(y > MENU_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}");
}
/// In a window too short for the whole Control bar panel menu, the menu stays inside the window and
/// scrolls, so Dock to Bottom, its last item, can still be reached and clicked.
#[test]
fn the_control_bar_panel_menu_scrolls_in_a_short_window() {
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);
control_bar_frame(&mut app, &ctx, vec![]);
let menu = node_center(&control_bar_frame(&mut app, &ctx, vec![]), "Panel menu").unwrap_or_else(|| panic!("the bar has a panel menu"));
control_bar_frame(&mut app, &ctx, vec![egui::Event::PointerMoved(menu), press_at(menu, true)]);
control_bar_frame(&mut app, &ctx, vec![press_at(menu, false)]);
let nodes = control_bar_frame(&mut app, &ctx, vec![]);
let first = node_center(&nodes, "Snapping").filter(|p| p.y > 34.0).unwrap_or_else(|| panic!("the menu lists Snapping"));
let wheel = egui::Event::MouseWheel {
unit: egui::MouseWheelUnit::Point,
delta: egui::vec2(0.0, -200.0),
phase: egui::TouchPhase::Move,
modifiers: Default::default(),
source: egui::MouseWheelSource::Unknown,
};
for _ in 0..6 {
control_bar_frame(&mut app, &ctx, vec![egui::Event::PointerMoved(first), wheel.clone()]);
}
let nodes = control_bar_frame(&mut app, &ctx, vec![]);
let at = node_center(&nodes, "Dock to Bottom").unwrap_or_else(|| panic!("the menu lists Dock to Bottom"));
assert!(DOCK_SCREEN.shrink(1.0).contains(at), "scrolled into the window: {at:?}");
control_bar_frame(&mut app, &ctx, vec![egui::Event::PointerMoved(at), press_at(at, true)]);
control_bar_frame(&mut app, &ctx, vec![press_at(at, false)]);
assert!(app.ui.control_bar_bottom, "Dock to Bottom docks the bar at the bottom");
}
/// 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]
@@ -2289,6 +2340,243 @@ fn the_control_bar_dock_command_takes_top_or_bottom() {
assert!(!app.ui.control_bar_bottom);
}
/// The labels of the Control bar panel menu's groups of options, in the menu's order.
const CONTROL_BAR_GROUP_LABELS: [&str; 15] = [
"Snapping",
"Anchor Points",
"Tool Options",
"Object Options",
"Image Options",
"Fill and Stroke",
"Opacity",
"Graphic Style",
"Character",
"Paragraph",
"Vertical Alignment",
"Align and Distribute",
"Transform",
"Artboard Options",
"Document Options",
];
/// Opens the Control bar's panel menu, at the right end of the bar `control_frame` draws, unless
/// it is open, and returns the text the bar and the menu show.
fn open_control_bar_menu(app: &mut crate::VectorcraftApp, ctx: &egui::Context) -> Vec<(String, egui::Rect)> {
use crate::tests_removeanchors::{click_control, control_frame};
let open = |texts: &[(String, egui::Rect)]| texts.iter().any(|(t, _)| t.ends_with("Dock to Bottom"));
let texts = control_frame(app, ctx, vec![]);
if open(&texts) {
return texts;
}
click_control(app, ctx, egui::pos2(1387.0, 17.0));
let texts = control_frame(app, ctx, vec![]);
assert!(open(&texts), "the panel menu opens: {texts:?}");
texts
}
/// Turns group `label` of the Control bar's panel menu on or off with a click on its item, which
/// shows checked while the group is on.
fn toggle_control_bar_group(app: &mut crate::VectorcraftApp, ctx: &egui::Context, label: &str, on: bool) {
let menu = open_control_bar_menu(app, ctx);
let item = if on { format!("✓ {label}") } else { format!(" {label}") };
crate::tests_removeanchors::click_control(app, ctx, crate::tests_removeanchors::at(&menu, &item));
}
/// The Control bar's panel menu lists every group of options, each checked while it is on, and a
/// click on one turns that group off or on again. A group that is off stays out of the bar when
/// the selection changes, the other groups and the menu button stay, and with no document the
/// menu still lists every group.
#[test]
fn the_control_bar_panel_menu_turns_each_group_on_or_off() {
use crate::tests_removeanchors::{control_frame, has};
crate::i18n::set_current(crate::i18n::Lang::EN);
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let on = |app: &crate::VectorcraftApp, group: &str| crate::menus::checked(app, "window.control.options", &json!({ "group": group }));
let menu = open_control_bar_menu(&mut app, &ctx);
for label in CONTROL_BAR_GROUP_LABELS {
assert!(has(&menu, &format!("✓ {label}")), "{label} is listed, on");
}
assert!(has(&menu, "✓ Dock to Top") && has(&menu, " Dock to Bottom"), "the edge items stay: {menu:?}");
toggle_control_bar_group(&mut app, &ctx, "Fill and Stroke", true);
assert_eq!(on(&app, "fillStroke"), Some(false));
assert!(has(&open_control_bar_menu(&mut app, &ctx), " Fill and Stroke"), "listed, off, with no document");
toggle_control_bar_group(&mut app, &ctx, "Fill and Stroke", false);
assert_eq!(on(&app, "fillStroke"), Some(true));
app.run("file.new", json!({"width": 400, "height": 200})).unwrap();
let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap()["id"].clone();
app.run("select.set", json!({ "ids": [id] })).unwrap();
let bar = control_frame(&mut app, &ctx, vec![]);
for s in ["Snapping", "Stroke:", "Opacity:", "Style:", "Transform"] {
assert!(has(&bar, s), "{s} shows with every group on");
}
toggle_control_bar_group(&mut app, &ctx, "Fill and Stroke", true);
let bar = control_frame(&mut app, &ctx, vec![]);
assert!(!has(&bar, "Stroke:"), "Fill and Stroke is off");
for s in ["Snapping", "Opacity:", "Style:", "Transform"] {
assert!(has(&bar, s), "{s} stays");
}
app.run("select.set", json!({ "ids": [] })).unwrap();
let bar = control_frame(&mut app, &ctx, vec![]);
assert!(has(&bar, "Document Setup") && !has(&bar, "Stroke:"), "still off with nothing selected");
app.run("select.set", json!({ "ids": [id] })).unwrap();
assert!(!has(&control_frame(&mut app, &ctx, vec![]), "Stroke:"), "still off with the rectangle selected again");
toggle_control_bar_group(&mut app, &ctx, "Fill and Stroke", false);
assert!(has(&control_frame(&mut app, &ctx, vec![]), "Stroke:"), "on again");
}
/// With every group of options off, the Control bar keeps its gripper, the selection's label and
/// its panel menu, which lists every group off; nothing selected shows no Document Setup either.
#[test]
fn the_control_bar_keeps_its_label_and_menu_with_every_group_off() {
use crate::tests_removeanchors::{control_frame, has};
crate::i18n::set_current(crate::i18n::Lang::EN);
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 200})).unwrap();
let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap()["id"].clone();
app.run("select.set", json!({ "ids": [id] })).unwrap();
for group in crate::chrome::CONTROL_BAR_GROUPS {
app.run("window.control.options", json!({ "group": group, "visible": false })).unwrap();
}
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let bar = control_frame(&mut app, &ctx, vec![]);
assert!(has(&bar, crate::panels::appearance::object_label(&app)), "the label stays: {bar:?}");
for s in ["Snapping", "Stroke:", "Opacity:", "Style:", "Transform", "X:"] {
assert!(!has(&bar, s), "{s} is off");
}
let menu = open_control_bar_menu(&mut app, &ctx);
for label in CONTROL_BAR_GROUP_LABELS {
assert!(has(&menu, &format!(" {label}")), "{label} is listed, off");
}
app.run("select.set", json!({ "ids": [] })).unwrap();
let bar = control_frame(&mut app, &ctx, vec![]);
assert!(!has(&bar, "Document Setup") && !has(&bar, "Preferences"), "{bar:?}");
}
/// `window.control.options {group, visible?}` turns a group of the Control bar's options on or
/// off, or toggles it, and the panel menu's item shows it; an unknown or missing group, or a
/// `visible` that is not true or false, is an error and changes nothing.
#[test]
fn the_control_bar_options_command_turns_a_group_on_or_off() {
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
let checked = |app: &crate::VectorcraftApp, group: &str| crate::menus::checked(app, "window.control.options", &json!({ "group": group }));
for group in crate::chrome::CONTROL_BAR_GROUPS {
assert_eq!(checked(&app, group), Some(true), "{group} starts on");
}
let run = |app: &mut crate::VectorcraftApp, p: serde_json::Value| app.run("window.control.options", p);
assert_eq!(run(&mut app, json!({"group": "opacity", "visible": false})).unwrap(), json!({"group": "opacity", "visible": false}));
assert_eq!(checked(&app, "opacity"), Some(false));
assert_eq!(run(&mut app, json!({"group": "opacity", "visible": false})).unwrap(), json!({"group": "opacity", "visible": false}), "off stays off");
assert_eq!(run(&mut app, json!({"group": "opacity"})).unwrap(), json!({"group": "opacity", "visible": true}), "toggled on");
assert_eq!(run(&mut app, json!({"group": "transform"})).unwrap(), json!({"group": "transform", "visible": false}), "toggled off");
assert_eq!(run(&mut app, json!({"group": "transform", "visible": null})).unwrap(), json!({"group": "transform", "visible": true}));
assert_eq!(run(&mut app, json!({"group": "paragraph", "visible": false})).unwrap()["visible"], json!(false));
let before = app.ui.control_bar_hidden_groups.clone();
for (p, err) in [
(json!({"group": "stroke"}), "`group` must be one of"),
(json!({"group": "Opacity"}), "`group` must be one of"),
(json!({"group": 1}), "`group` must be one of"),
(json!({}), "`group` must be one of"),
(json!({"group": "paragraph", "visible": "true"}), "`visible` must be true or false"),
(json!({"group": "opacity", "visible": 0}), "`visible` must be true or false"),
] {
let e = run(&mut app, p.clone()).unwrap_err();
assert!(e.starts_with(err), "{p}: {e}");
assert_eq!(app.ui.control_bar_hidden_groups, before, "{p} changes nothing");
}
assert_eq!(checked(&app, "paragraph"), Some(false));
assert_eq!(checked(&app, "stroke"), Some(false), "an unknown group is never checked");
}
/// A group of options that is off takes no room in the Control bar: with Fill and Stroke, Opacity
/// and Graphic Style off, or Transform off, the Align buttons fit a narrower window, and the panel
/// menu button still shows there.
#[test]
fn groups_that_are_off_leave_their_room_to_the_later_groups() {
crate::i18n::set_current(crate::i18n::Lang::EN);
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 200})).unwrap();
let ids: Vec<serde_json::Value> =
[10, 100].iter().map(|x| app.run("shape.rectangle", json!({"x": x, "y": 10, "width": 30, "height": 20})).unwrap()["id"].clone()).collect();
app.run("select.set", json!({ "ids": ids })).unwrap();
let ctx = egui::Context::default();
ctx.enable_accesskit();
crate::theme::install_fonts(&ctx);
// The narrowest window, in steps of 10 points, whose bar shows the Align buttons.
let narrowest = |app: &mut crate::VectorcraftApp| {
(40..=240).map(|w| w as f32 * 10.0).find(|&w| {
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(w, 300.0));
control_bar_frame_in(app, &ctx, screen, vec![]);
let nodes = control_bar_frame_in(app, &ctx, screen, vec![]);
node_center(&nodes, "Horizontal Align Left").is_some() && node_center(&nodes, "Panel menu").is_some()
})
};
let all = narrowest(&mut app).expect("the Align buttons show in a wide window");
for group in ["fillStroke", "opacity", "graphicStyle"] {
app.run("window.control.options", json!({ "group": group, "visible": false })).unwrap();
}
let fewer = narrowest(&mut app).expect("they show with fewer groups");
assert!(fewer < all, "the Align buttons fit {fewer} wide with three groups off, {all} with every group on");
for group in ["fillStroke", "opacity", "graphicStyle"] {
app.run("window.control.options", json!({ "group": group, "visible": true })).unwrap();
}
app.run("window.control.options", json!({"group": "transform", "visible": false})).unwrap();
let no_transform = narrowest(&mut app).expect("they show without Transform");
assert!(no_transform < all, "without the Transform link's room the Align buttons fit {no_transform} wide, {all} with it");
}
/// With the Artboard tool, the Artboard Options group holds the artboard's own controls and the
/// Transform group the Transform link and the X/Y/W/H fields; each one off leaves the other, and
/// with Artboard Options off the fields find room in a window that is too narrow for them otherwise.
#[test]
fn the_artboard_tool_s_control_bar_follows_its_two_groups() {
use crate::tests_removeanchors::{control_frame, has};
crate::i18n::set_current(crate::i18n::Lang::EN);
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 200})).unwrap();
app.select_tool("artboard");
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let bar = control_frame(&mut app, &ctx, vec![]);
for s in ["Artboard", "Name:", "Transform", "Move Artwork with Artboard"] {
assert!(has(&bar, s), "{s} shows with every group on: {bar:?}");
}
assert!(!has(&bar, "X:"), "the fields need more room than is left: {bar:?}");
app.run("window.control.options", json!({"group": "artboardOptions", "visible": false})).unwrap();
let bar = control_frame(&mut app, &ctx, vec![]);
assert!(!has(&bar, "Name:") && !has(&bar, "Move Artwork with Artboard"), "{bar:?}");
assert!(has(&bar, "Artboard") && has(&bar, "Transform") && has(&bar, "X:"), "{bar:?}");
app.run("window.control.options", json!({"group": "artboardOptions", "visible": true})).unwrap();
app.run("window.control.options", json!({"group": "transform", "visible": false})).unwrap();
let bar = control_frame(&mut app, &ctx, vec![]);
assert!(!has(&bar, "Transform") && !has(&bar, "X:"), "{bar:?}");
assert!(has(&bar, "Name:") && has(&bar, "Move Artwork with Artboard"), "{bar:?}");
}
/// The Anchor Points group holds Convert and Anchors for direct-selected anchors; off, the bar keeps
/// its Anchor Point label.
#[test]
fn the_anchor_points_group_holds_convert_and_anchors() {
use crate::tests_removeanchors::{control_frame, has};
crate::i18n::set_current(crate::i18n::Lang::EN);
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 200})).unwrap();
let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap()["id"].as_u64().unwrap();
app.select_tool("directSelection");
app.run("select.anchors", json!({"id": id, "anchors": [[0, 0]]})).unwrap();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let bar = control_frame(&mut app, &ctx, vec![]);
assert!(has(&bar, "Convert:") && has(&bar, "Anchors:"), "{bar:?}");
app.run("window.control.options", json!({"group": "anchors", "visible": false})).unwrap();
let bar = control_frame(&mut app, &ctx, vec![]);
assert!(!has(&bar, "Convert:") && !has(&bar, "Anchors:"), "{bar:?}");
assert!(has(&bar, "Anchor Point") && has(&bar, "Stroke:"), "{bar:?}");
}
/// 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.
+7
View File
@@ -3955,6 +3955,13 @@ axis Both И двете
Dock Panel Закотвен панел
Dock to Top Закотвяне горе
Dock to Bottom Закотвяне долу
Control Bar Options Опции на контролната лента
Tool Options Опции за инструмент
Object Options Опции за обект
Image Options Опции за изображение
Vertical Alignment Вертикално подравняване
Align and Distribute Подравняване и разпределение
Document Options Опции за документ
Dock Control Bar Закотвяне на контролната лента
Drag to float the toolbar Плъзнете, за да направите лентата с инструменти плаваща
Drag to the window's left or right edge to dock Плъзнете до левия или десния ръб на прозореца за закотвяне
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -864,6 +864,13 @@
Dock Panel Usidri panel
Dock to Top Usidri na vrh
Dock to Bottom Usidri na dno
Control Bar Options Opcije kontrolne trake
Tool Options Opcije alata
Object Options Opcije objekta
Image Options Opcije slike
Vertical Alignment Okomito poravnanje
Align and Distribute Poravnanje i raspored
Document Options Opcije dokumenta
Dock Control Bar Usidri kontrolnu traku
Float Tool Group Odvoji grupu alata
Services Usluge
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -1215,6 +1215,13 @@ antiAlias Sharp Nítid
Dock Panel Acobla el panell
Dock to Top Acobla a dalt
Dock to Bottom Acobla a baix
Control Bar Options Opcions de la barra de control
Tool Options Opcions de l’eina
Object Options Opcions de l’objecte
Image Options Opcions de la imatge
Vertical Alignment Alineació vertical
Align and Distribute Alinea i distribueix
Document Options Opcions del document
Dock Control Bar Acobla la barra de control
Document Document
Document Color Mode Mode de color del document
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -874,6 +874,13 @@
Dock Panel Ukotvit panel
Dock to Top Ukotvit nahoru
Dock to Bottom Ukotvit dolů
Control Bar Options Možnosti ovládacího panelu
Tool Options Možnosti nástroje
Object Options Možnosti objektu
Image Options Možnosti obrazu
Vertical Alignment Svislé zarovnání
Align and Distribute Zarovnat a rozmístit
Document Options Možnosti dokumentu
Dock Control Bar Ukotvit ovládací panel
Float Tool Group Plovoucí skupina nástrojů
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -809,6 +809,13 @@
Dock Panel Dok panel
Dock to Top Dok øverst
Dock to Bottom Dok nederst
Control Bar Options Indstillinger for kontrollinjen
Tool Options Værktøjsindstillinger
Object Options Objektindstillinger
Image Options Billedindstillinger
Vertical Alignment Lodret justering
Align and Distribute Justér og fordel
Document Options Dokumentindstillinger
Dock Control Bar Dok kontrollinjen
Float Tool Group Flydende værktøjsgruppe
Services Tjenester
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -3964,6 +3964,13 @@ theme Appearance Erscheinungsbild
Dock Panel Bedienfeld andocken
Dock to Top Oben andocken
Dock to Bottom Unten andocken
Control Bar Options Optionen der Steuerungsleiste
Tool Options Werkzeugoptionen
Object Options Objektoptionen
Image Options Bildoptionen
Vertical Alignment Vertikale Ausrichtung
Align and Distribute Ausrichten und verteilen
Document Options Dokumentoptionen
Dock Control Bar Steuerungsleiste andocken
Drag to float the toolbar Ziehen, um die Werkzeugleiste abzulösen
Drag to the window's left or right edge to dock Zum Andocken an den linken oder rechten Fensterrand ziehen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -2204,6 +2204,13 @@ axis Both Και τα δύο
Dock Panel Αγκύρωση πάνελ
Dock to Top Αγκύρωση επάνω
Dock to Bottom Αγκύρωση κάτω
Control Bar Options Επιλογές γραμμής ελέγχου
Tool Options Επιλογές εργαλείου
Object Options Επιλογές αντικειμένου
Image Options Επιλογές εικόνας
Vertical Alignment Κατακόρυφη στοίχιση
Align and Distribute Στοίχιση και κατανομή
Document Options Επιλογές εγγράφου
Dock Control Bar Αγκύρωση γραμμής ελέγχου
@msg no library `{id}` Δεν υπάρχει βιβλιοθήκη με αναγνωριστικό `{id}`
@msg select some type first Επιλέξτε πρώτα κάποιο κείμενο
Can't render this file because it is too large.
+7
View File
@@ -4048,6 +4048,13 @@ axis Both Ambos
Dock Panel Anclar panel
Dock to Top Anclar arriba
Dock to Bottom Anclar abajo
Control Bar Options Opciones de la barra de control
Tool Options Opciones de herramienta
Object Options Opciones de objeto
Image Options Opciones de imagen
Vertical Alignment Alineación vertical
Align and Distribute Alinear y distribuir
Document Options Opciones de documento
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 or right edge to dock Arrastrar al borde izquierdo o derecho de la ventana para anclar
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -862,6 +862,13 @@
Dock Panel Telakoi paneeli
Dock to Top Telakoi ylös
Dock to Bottom Telakoi alas
Control Bar Options Ohjauspalkin asetukset
Tool Options Työkalun asetukset
Object Options Objektin asetukset
Image Options Kuvan asetukset
Vertical Alignment Pystysuora tasaus
Align and Distribute Tasaa ja jaa
Document Options Asiakirjan asetukset
Dock Control Bar Telakoi ohjauspalkki
Float Tool Group Kelluta työkaluryhmä
Services Palvelut
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -4037,6 +4037,13 @@ axis Both Les deux
Dock Panel Ancrer le panneau
Dock to Top Ancrer en haut
Dock to Bottom Ancrer en bas
Control Bar Options Options de la barre de contrôle
Tool Options Options de l’outil
Object Options Options de l’objet
Image Options Options de l’image
Vertical Alignment Alignement vertical
Align and Distribute Aligner et répartir
Document Options Options du document
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 or right edge to dock Faire glisser vers le bord gauche ou droit de la fenêtre pour ancrer
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -1350,6 +1350,13 @@ antiAlias Sharp शार्प
Dock Panel पैनल डॉक करें
Dock to Top ऊपर डॉक करें
Dock to Bottom नीचे डॉक करें
Control Bar Options नियंत्रण बार ऑप्शन्स
Tool Options टूल ऑप्शन्स
Object Options ऑब्जेक्ट ऑप्शन्स
Image Options इमेज ऑप्शन्स
Vertical Alignment वर्टिकल अलाइनमेंट
Align and Distribute संरेखित और वितरित करें
Document Options डॉक्यूमेंट ऑप्शन्स
Dock Control Bar नियंत्रण बार डॉक करें
Document डॉक्यूमेंट
Document Color Mode डॉक्यूमेंट रंग मोड
Can't render this file because it is too large.
+7
View File
@@ -1098,6 +1098,13 @@ font-technology Variable Változó
Dock Panel Panel dokkolása
Dock to Top Dokkolás felülre
Dock to Bottom Dokkolás alulra
Control Bar Options Vezérlősáv beállításai
Tool Options Eszközbeállítások
Object Options Objektumbeállítások
Image Options Képbeállítások
Vertical Alignment Függőleges igazítás
Align and Distribute Igazítás és elosztás
Document Options Dokumentumbeállítások
Dock Control Bar Vezérlősáv dokkolása
Document Dokumentum
Document Color Mode Dokumentum színmódja
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -809,6 +809,13 @@
Dock Panel Tambatkan panel
Dock to Top Tambatkan ke atas
Dock to Bottom Tambatkan ke bawah
Control Bar Options Opsi bilah kontrol
Tool Options Opsi alat
Object Options Opsi objek
Image Options Opsi gambar
Vertical Alignment Perataan vertikal
Align and Distribute Sejajarkan dan distribusikan
Document Options Opsi dokumen
Dock Control Bar Tambatkan bilah kontrol
Float Tool Group Mengambangkan grup alat
Services Layanan
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
809
810
811
812
813
814
815
816
817
818
819
820
821
+7
View File
@@ -4091,6 +4091,13 @@ axis Both Entrambi
Dock Panel Ancora pannello
Dock to Top Ancora in alto
Dock to Bottom Ancora in basso
Control Bar Options Opzioni barra di controllo
Tool Options Opzioni strumento
Object Options Opzioni oggetto
Image Options Opzioni immagine
Vertical Alignment Allineamento verticale
Align and Distribute Allinea e distribuisci
Document Options Opzioni documento
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 or right edge to dock Trascina sul bordo sinistro o destro della finestra per ancorare
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -4033,6 +4033,13 @@ axis Both 両方
Dock Panel パネルをドッキング
Dock to Top 上部にドッキング
Dock to Bottom 下部にドッキング
Control Bar Options コントロールバーオプション
Tool Options ツールオプション
Object Options オブジェクトオプション
Image Options 画像オプション
Vertical Alignment 垂直方向の配置
Align and Distribute 整列と分布
Document Options ドキュメントオプション
Dock Control Bar コントロールバーをドッキング
Drag to float the toolbar ドラッグしてツールバーをフローティング
Drag to the window's left or right edge to dock ウィンドウの左端または右端までドラッグしてドッキング
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -767,6 +767,13 @@
Dock Panel პანელის მიმაგრება
Dock to Top ზემოთ მიმაგრება
Dock to Bottom ქვემოთ მიმაგრება
Control Bar Options მართვის ზოლის პარამეტრები
Tool Options ხელსაწყოს პარამეტრები
Object Options ობიექტის პარამეტრები
Image Options გამოსახულების პარამეტრები
Vertical Alignment ვერტიკალური სწორება
Align and Distribute სწორება და განაწილება
Document Options დოკუმენტის პარამეტრები
Dock Control Bar მართვის ზოლის მიმაგრება
Float Tool Group მცურავი ხელსაწყოთა ჯგუფი
Services სერვისები
Can't render this file because it is too large.
+7
View File
@@ -4017,6 +4017,13 @@ axis Both 양쪽
Dock Panel 패널 고정
Dock to Top 위쪽에 고정
Dock to Bottom 아래쪽에 고정
Control Bar Options 컨트롤 막대 옵션
Tool Options 도구 옵션
Object Options 개체 옵션
Image Options 이미지 옵션
Vertical Alignment 세로 정렬
Align and Distribute 정렬 및 분포
Document Options 문서 옵션
Dock Control Bar 컨트롤 막대 고정
Drag to float the toolbar 드래그하여 도구 모음 분리
Drag to the window's left or right edge to dock 창 왼쪽 또는 오른쪽 가장자리로 드래그하여 고정
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -823,6 +823,13 @@
Dock Panel Paneel dokken
Dock to Top Bovenaan dokken
Dock to Bottom Onderaan dokken
Control Bar Options Opties voor bedieningsbalk
Tool Options Gereedschapsopties
Object Options Objectopties
Image Options Afbeeldingsopties
Vertical Alignment Verticale uitlijning
Align and Distribute Uitlijnen en verdelen
Document Options Documentopties
Dock Control Bar Bedieningsbalk dokken
Float Tool Group Gereedschapsgroep laten zweven
Services Diensten
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -819,6 +819,13 @@
Dock Panel Zadokuj panel
Dock to Top Zadokuj u góry
Dock to Bottom Zadokuj u dołu
Control Bar Options Opcje paska sterowania
Tool Options Opcje narzędzia
Object Options Opcje obiektu
Image Options Opcje obrazu
Vertical Alignment Wyrównanie w pionie
Align and Distribute Wyrównaj i rozmieść
Document Options Opcje dokumentu
Dock Control Bar Zadokuj pasek sterowania
Float Tool Group Odepnij grupę narzędzi
Services Usługi
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -3286,6 +3286,13 @@ imageTrace Outline Contorno
Dock Panel Encaixar Painel
Dock to Top Encaixar na Parte Superior
Dock to Bottom Encaixar na Parte Inferior
Control Bar Options Opções da Barra de Controle
Tool Options Opções de Ferramenta
Object Options Opções de Objeto
Image Options Opções de Imagem
Vertical Alignment Alinhamento Vertical
Align and Distribute Alinhar e Distribuir
Document Options Opções de Documento
Dock Control Bar Encaixar Barra de Controle
Float Tool Group Flutuar Grupo de Ferramentas
Services Serviços
Can't render this file because it contains an unexpected character in line 6 and column 47.
+7
View File
@@ -4085,6 +4085,13 @@ axis Both Оба
Dock Panel Прикрепить панель
Dock to Top Прикрепить сверху
Dock to Bottom Прикрепить снизу
Control Bar Options Параметры панели управления
Tool Options Параметры инструмента
Object Options Параметры объекта
Image Options Параметры изображения
Vertical Alignment Вертикальное выравнивание
Align and Distribute Выровнять и распределить
Document Options Параметры документа
Dock Control Bar Прикрепить панель управления
Drag to float the toolbar Перетащите, чтобы сделать панель инструментов плавающей
Drag to the window's left or right edge to dock Перетащите к левому или правому краю окна для прикрепления
Can't render this file because it is too large.
+7
View File
@@ -3964,6 +3964,13 @@ axis Both Båda
Dock Panel Docka panel
Dock to Top Docka överst
Dock to Bottom Docka nederst
Control Bar Options Alternativ för kontrollfältet
Tool Options Verktygsalternativ
Object Options Objektalternativ
Image Options Bildalternativ
Vertical Alignment Vertikal justering
Align and Distribute Justera och fördela
Document Options Dokumentalternativ
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 or right edge to dock Dra till fönstrets vänster- eller högerkant för att docka
Can't render this file because it contains an unexpected character in line 5 and column 25.
+31
View File
@@ -194,6 +194,37 @@ fn marquee_selection_labels_are_in_complete_catalogs() {
}
}
/// The Control bar panel menu's items, its groups of options and the edges, and the label of the
/// command that turns a group on or off.
#[test]
fn control_bar_panel_menu_labels_are_in_complete_catalogs() {
const LABELS: [&str; 18] = [
"Snapping",
"Anchor Points",
"Tool Options",
"Object Options",
"Image Options",
"Fill and Stroke",
"Opacity",
"Graphic Style",
"Character",
"Paragraph",
"Vertical Alignment",
"Align and Distribute",
"Transform",
"Artboard Options",
"Document Options",
"Dock to Top",
"Dock to Bottom",
"Control Bar Options",
];
for lang in LANGUAGES.iter().filter(|lang| lang.complete_menus) {
for label in LABELS {
assert!(lang.catalog().plain(label).is_some(), "{} is missing {label}", lang.code);
}
}
}
/// The Character panel's Anti-aliasing Method label and its choices, with the Sharp method's
/// own `antiAlias` row apart from the plain "Sharp" corner row.
#[test]
+7
View File
@@ -3850,6 +3850,13 @@ axis Both Her İkisi
Dock Panel Bağlantı İstasyonu Paneli
Dock to Top Üste yerleştir
Dock to Bottom Alta yerleştir
Control Bar Options Kontrol Çubuğu Seçenekleri
Tool Options Araç Seçenekleri
Object Options Nesne Seçenekleri
Image Options Resim Seçenekleri
Vertical Alignment Dikey Hizalama
Align and Distribute Hizala ve Dağıt
Document Options Belge Seçenekleri
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 or right edge to dock Sabitlemek için pencerenin sol veya sağ kenarına sürükleyin
Can't render this file because it contains an unexpected character in line 5 and column 25.
+7
View File
@@ -4080,6 +4080,13 @@ axis Both Обидві
Dock Panel Прикріпити панель
Dock to Top Прикріпити вгорі
Dock to Bottom Прикріпити внизу
Control Bar Options Параметри панелі керування
Tool Options Параметри інструменту
Object Options Параметри об’єкта
Image Options Параметри зображення
Vertical Alignment Вертикальне вирівнювання
Align and Distribute Вирівняти та розподілити
Document Options Параметри документа
Dock Control Bar Прикріпити панель керування
Drag to float the toolbar Перетягніть, щоб від’єднати панель інструментів
Drag to the window's left or right edge to dock Перетягніть до лівого або правого краю вікна для прикріплення
Can't render this file because it contains an unexpected character in line 182 and column 5.
+7
View File
@@ -988,6 +988,13 @@ font-technology Variable Biến thiên
Dock Panel Neo bảng
Dock to Top Neo lên trên
Dock to Bottom Neo xuống dưới
Control Bar Options Tùy chọn thanh điều khiển
Tool Options Tùy chọn công cụ
Object Options Tùy chọn đối tượng
Image Options Tùy chọn hình ảnh
Vertical Alignment Căn chỉnh dọc
Align and Distribute Căn chỉnh và phân bố
Document Options Tùy chọn tài liệu
Dock Control Bar Neo thanh điều khiển
Document Tài liệu
Document Color Mode Chế độ màu tài liệu
Can't render this file because it contains an unexpected character in line 204 and column 5.
+7
View File
@@ -5225,6 +5225,13 @@ axis Both 两者
Dock Panel 停放面板
Dock to Top 停放到顶部
Dock to Bottom 停放到底部
Control Bar Options 控制栏选项
Tool Options 工具选项
Object Options 对象选项
Image Options 图像选项
Vertical Alignment 垂直对齐
Align and Distribute 对齐和分布
Document Options 文档选项
Dock Control Bar 停放控制栏
Drag to float the toolbar 拖动以浮动工具栏
Drag to the window's left or right edge to dock 拖到窗口左边缘或右边缘以停放
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -5046,6 +5046,13 @@ axis Both 兩者
Dock Panel 停駐面板
Dock to Top 停駐到頂部
Dock to Bottom 停駐到底部
Control Bar Options 控制列選項
Tool Options 工具選項
Object Options 物件選項
Image Options 影像選項
Vertical Alignment 垂直對齊
Align and Distribute 對齊與均分
Document Options 文件選項
Dock Control Bar 停駐控制列
Drag to float the toolbar 拖曳以浮動工具列
Drag to the window's left or right edge to dock 拖曳到視窗左側或右側邊緣以停駐
Can't render this file because it contains an unexpected character in line 6 and column 47.
+18
View File
@@ -477,6 +477,24 @@ fn run_ui_part5(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<
None => Err(format!("`{key}` must be one of {}", edges.join(", "))),
}
}
// The Control bar's panel menu: a group of the bar's options on or off.
"window.control.options" => {
let groups = crate::chrome::CONTROL_BAR_GROUPS;
match (groups.iter().find(|g| s("group").as_deref() == Some(**g)), opt_bool(p, "visible")) {
(None, _) => Err(format!("`group` must be one of {}", groups.join(", "))),
(_, Err(e)) => Err(e),
(Some(&group), Ok(visible)) => {
let hidden = &mut app.ui.control_bar_hidden_groups;
let visible = visible.unwrap_or_else(|| hidden.contains(group));
if visible {
hidden.remove(group);
} else {
hidden.insert(group.to_string());
}
Ok(json!({ "group": group, "visible": visible }))
}
}
}
"window.toolbar" => flag(&mut app.ui.toolbar),
"window.toolbarColumns" => {
let toolbar = match crate::custom_toolbars::target(&app.ui, p) {
+4
View File
@@ -66,6 +66,10 @@ pub fn checked(app: &VectorcraftApp, id: &str, p: &Value) -> Option<bool> {
"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.control.options" => p
.get("group")
.and_then(Value::as_str)
.is_some_and(|g| crate::chrome::CONTROL_BAR_GROUPS.contains(&g) && !app.ui.control_bar_hidden_groups.contains(g)),
"window.toolbar" => app.ui.toolbar,
"window.toolbarAdvanced" => app.ui.toolbar_advanced,
"window.toolsBesideCanvas" => app.ui.tools_beside_canvas,
+6
View File
@@ -472,6 +472,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{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.control.options",
"Control Bar Options",
"",
"{group: \"snapping\"|\"anchors\"|\"toolOptions\"|\"objectOptions\"|\"imageOptions\"|\"fillStroke\"|\"opacity\"|\"graphicStyle\"|\"character\"|\"paragraph\"|\"verticalAlignment\"|\"alignDistribute\"|\"transform\"|\"artboardOptions\"|\"documentOptions\", visible?: bool} turn a group of the Control bar's options on (true), off (false) or toggle it (omitted), as the bar's panel menu does; a group that is off stays out of the bar even while its tool or object is selected, and one that is on shows only while the selection or the tool calls for it; every group starts on; kept with the workspace; another group or a `visible` that is not true or false is an error and changes nothing; returns {group, visible}",
),
("window.toolbar", "Tools", "", "{}"),
(
"window.toolbarColumns",
+37 -27
View File
@@ -60,9 +60,11 @@ const NAME_FIELD_ROOM: f32 = 330.0;
/// 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
/// or Transform panel counterpart runs. Returns false, and draws nothing, while another tool is in
/// use.
pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) -> bool {
/// or Transform panel counterpart runs. With `options` false (the bar's panel menu has its Artboard
/// Options group off), the controls other than the Transform link and the fields are left out; with
/// `transform` false (its Transform group off), the Transform link and the fields are. Returns
/// false, and draws nothing, while another tool is in use.
pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui, options: bool, transform: bool) -> bool {
if app.session.tool_id() != "artboard" {
return false;
}
@@ -71,35 +73,43 @@ pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) -> bool {
let i = selected(app, n);
let Some(ab) = app.session.active().and_then(|d| d.doc.artboards.get(i)).cloned() else { return true };
let r = ab.rect;
super::properties::artboard_size_row(app, ui, i, r.width(), r.height());
if widgets::icon_button(ui, "dc-new-item", tl!("New Artboard"), false, 24.0).clicked() {
add_new(app);
if options {
super::properties::artboard_size_row(app, ui, i, r.width(), r.height());
if widgets::icon_button(ui, "dc-new-item", tl!("New Artboard"), false, 24.0).clicked() {
add_new(app);
}
if widgets::icon_button_enabled(ui, "trash-2", tl!("Delete Artboard"), false, n > 1, 24.0).clicked() {
delete_selected(app);
}
ui.separator();
// 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));
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() {
app.run("tool.options", json!({"tool": "artboard"})).ok();
}
if widgets::icon_button_enabled(ui, "dc-rearrange", tl!("Rearrange All Artboards"), false, n > 1, 24.0).clicked() {
open_rearrange(app);
}
ui.separator();
}
if widgets::icon_button_enabled(ui, "trash-2", tl!("Delete Artboard"), false, n > 1, 24.0).clicked() {
delete_selected(app);
if transform {
super::transform::link(app, ui);
ui.separator();
}
ui.separator();
// 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));
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 options {
art_option_boxes(app, ui, ui.available_width() < ART_OPTIONS_WIDTH);
}
if widgets::icon_button(ui, "dc-artboard-options", tl!("Artboard Options"), false, 24.0).clicked() {
app.run("tool.options", json!({"tool": "artboard"})).ok();
}
if widgets::icon_button_enabled(ui, "dc-rearrange", tl!("Rearrange All Artboards"), false, n > 1, 24.0).clicked() {
open_rearrange(app);
}
ui.separator();
super::transform::link(app, ui);
ui.separator();
art_option_boxes(app, ui, ui.available_width() < ART_OPTIONS_WIDTH);
if ui.available_width() < INLINE_FIELDS_WIDTH {
if !transform || ui.available_width() < INLINE_FIELDS_WIDTH {
return true;
}
ui.separator();
if options {
ui.separator();
}
let units = app.session.general_unit();
let origin = app.ruler_origin();
let refi = super::transform::reference_index(ui);
@@ -698,6 +698,37 @@ fn the_control_bar_shows_the_paragraph_alignment_of_the_selected_type() {
assert!(control_bar_node(&nodes, "Justify all lines").is_none(), "a second click closes it");
}
/// The Control bar's Character and Paragraph groups hold the type controls; each off leaves the
/// other, and with Paragraph off the Align buttons fit a narrower window.
#[test]
fn the_character_and_paragraph_groups_hold_the_type_controls() {
let ctx = control_bar_ctx();
let mut app = control_bar_app(&[json!({"x": 20, "y": 50, "text": "Point"})]);
let set = |app: &mut VectorcraftApp, group: &str, visible: bool| {
app.run("window.control.options", json!({ "group": group, "visible": visible })).unwrap();
};
// The narrowest window, in steps of 10 points, whose bar shows the Align buttons.
let narrowest = |app: &mut VectorcraftApp| {
(40..=240).map(|w| w as f32 * 10.0).find(|&w| control_bar_node(&control_bar_nodes(&ctx, app, w, vec![]), "Horizontal Align Left").is_some())
};
let nodes = control_bar_nodes(&ctx, &mut app, 1920.0, vec![]);
for name in ["Character:", "Paragraph:", "Align left"] {
assert!(control_bar_node(&nodes, name).is_some(), "{name} shows");
}
let all = narrowest(&mut app).expect("the Align buttons show in a wide window");
set(&mut app, "paragraph", false);
let nodes = control_bar_nodes(&ctx, &mut app, 1920.0, vec![]);
assert!(control_bar_node(&nodes, "Paragraph:").is_none() && control_bar_node(&nodes, "Align left").is_none(), "Paragraph is off");
assert!(control_bar_node(&nodes, "Character:").is_some(), "Character stays");
let fewer = narrowest(&mut app).expect("the Align buttons show without Paragraph");
assert!(fewer < all, "the Align buttons fit {fewer} wide without Paragraph, {all} with it");
set(&mut app, "paragraph", true);
set(&mut app, "character", false);
let nodes = control_bar_nodes(&ctx, &mut app, 1920.0, vec![]);
assert!(control_bar_node(&nodes, "Character:").is_none(), "Character is off");
assert!(control_bar_node(&nodes, "Paragraph:").is_some(), "Paragraph stays");
}
/// #5372: at any width the Control bar trims itself from its right end, ending inside its margin.
/// With type selected the inline X/Y/W/H fields go first, then the Distribute and Align buttons,
/// area type's vertical alignment, the Paragraph buttons and last the Paragraph: link, while the
+5
View File
@@ -405,6 +405,10 @@ pub struct UiState {
/// The Control bar is docked to the bottom of the window instead of the top
/// (`window.control.dock`).
pub control_bar_bottom: bool,
/// The groups of the Control bar's options (`chrome::CONTROL_BAR_GROUPS`) that its panel menu
/// has off (`window.control.options`); none: every group shows.
#[serde(deserialize_with = "crate::chrome::read_hidden_groups")]
pub control_bar_hidden_groups: std::collections::BTreeSet<String>,
pub toolbar: bool,
pub toolbar_double: bool,
/// Advanced toolbar (every tool group) instead of the categorized Basic toolbar.
@@ -1019,6 +1023,7 @@ impl Default for UiState {
find_font_in: String::new(),
control_bar: false,
control_bar_bottom: false,
control_bar_hidden_groups: Default::default(),
toolbar: true,
toolbar_double: false,
toolbar_advanced: false,
+28
View File
@@ -606,6 +606,34 @@ fn the_control_bar_and_properties_trace_a_selected_image() {
assert!(selected_trace(&app).is_none() && g.children().unwrap().iter().all(|c| !matches!(c.kind, NodeKind::Image(_))), "expanded");
}
/// With the Control bar's Image Options group off, a selected image or Image Trace object shows
/// neither its own controls nor Fill and Stroke in their place; its label and Opacity stay.
#[test]
fn the_image_options_group_holds_the_image_and_trace_controls() {
use crate::tests_removeanchors::{control_frame, has};
let mut app = app();
let data = vectorcraft_format::base64_encode(&png(30, 30, 72.0));
app.run("file.place", json!({"name": "red.png", "dataBase64": data, "link": false})).unwrap();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let set = |app: &mut VectorcraftApp, on: bool| app.run("window.control.options", json!({"group": "imageOptions", "visible": on})).unwrap();
set(&mut app, false);
let bar = control_frame(&mut app, &ctx, vec![]);
for s in ["Unembed…", "Image Trace", "Crop Image", "Stroke:"] {
assert!(!has(&bar, s), "{s}: {bar:?}");
}
assert!(has(&bar, "Embedded") && has(&bar, "Opacity:"), "{bar:?}");
set(&mut app, true);
click_label(&mut app, &ctx, "Image Trace");
assert!(has(&control_frame(&mut app, &ctx, vec![]), "Expand"), "traced");
set(&mut app, false);
let bar = control_frame(&mut app, &ctx, vec![]);
for s in ["Preset:", "Expand", "Stroke:"] {
assert!(!has(&bar, s), "{s}: {bar:?}");
}
assert!(has(&bar, "Image Tracing") && has(&bar, "Opacity:"), "{bar:?}");
}
#[test]
fn the_control_bars_mask_clips_the_image_and_selects_the_clipping_path() {
let mut app = app();
+46
View File
@@ -24,6 +24,10 @@ pub struct Workspace {
/// 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,
/// The groups of the Control bar's options its panel menu has off (built-in workspaces, and
/// workspaces saved without this field: none).
#[serde(skip_serializing_if = "std::collections::BTreeSet::is_empty", deserialize_with = "crate::chrome::read_hidden_groups")]
pub control_bar_hidden_groups: std::collections::BTreeSet<String>,
pub rulers: bool,
pub toolbar: bool,
pub toolbar_double: bool,
@@ -186,6 +190,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace {
name: name.into(),
control_bar: ui.control_bar,
control_bar_bottom: ui.control_bar_bottom,
control_bar_hidden_groups: ui.control_bar_hidden_groups.clone(),
rulers: ui.view.rulers,
toolbar: ui.toolbar,
toolbar_double: ui.toolbar_double,
@@ -216,6 +221,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace {
pub fn apply(ui: &mut UiState, w: &Workspace) {
ui.control_bar = w.control_bar;
ui.control_bar_bottom = w.control_bar_bottom;
ui.control_bar_hidden_groups = w.control_bar_hidden_groups.clone();
ui.view.rulers = w.rulers;
ui.toolbar = w.toolbar;
ui.toolbar_double = w.toolbar_double;
@@ -806,4 +812,44 @@ mod tests {
apply(&mut ui, &old);
assert!(ui.control_bar && !ui.control_bar_bottom, "an older workspace docks the bar at the top");
}
/// A workspace keeps the groups of Control bar options its panel menu has off: switching
/// brings each workspace's back, Reset turns a built-in workspace's on again, and they survive
/// the preferences' JSON. A workspace or preferences saved without them, or with groups the bar
/// does not have, show every group, or the ones they name that it has.
#[test]
fn saved_workspaces_restore_the_control_bar_groups() {
let hidden = |ids: &[&str]| ids.iter().map(|s| s.to_string()).collect::<std::collections::BTreeSet<String>>();
let mut ui = UiState { control_bar: true, control_bar_hidden_groups: hidden(&["opacity", "transform"]), ..Default::default() };
save_as(&mut ui, "Lean").unwrap();
switch(&mut ui, ESSENTIALS).unwrap();
assert!(ui.control_bar_hidden_groups.is_empty(), "Essentials shows every group");
ui.control_bar_hidden_groups = hidden(&["snapping"]);
switch(&mut ui, "Lean").unwrap();
assert_eq!(ui.control_bar_hidden_groups, hidden(&["opacity", "transform"]), "the saved workspace's groups");
switch(&mut ui, ESSENTIALS).unwrap();
assert_eq!(ui.control_bar_hidden_groups, hidden(&["snapping"]), "Essentials as it was left");
reset(&mut ui);
assert!(ui.control_bar_hidden_groups.is_empty(), "Reset Essentials shows every group again");
switch(&mut ui, "Lean").unwrap();
let json = serde_json::to_value(&ui).unwrap();
assert_eq!(json["custom_workspaces"][0]["controlBarHiddenGroups"], serde_json::json!(["opacity", "transform"]));
let back: UiState = serde_json::from_value(json).unwrap();
assert_eq!(back.control_bar_hidden_groups, hidden(&["opacity", "transform"]), "kept in the preferences");
assert_eq!(back.custom_workspaces[0].control_bar_hidden_groups, hidden(&["opacity", "transform"]), "kept with the workspace");
let mut old = serde_json::to_value(&back.custom_workspaces[0]).unwrap();
assert!(old.as_object_mut().unwrap().remove("controlBarHiddenGroups").is_some());
let old: Workspace = serde_json::from_value(old).unwrap();
apply(&mut ui, &old);
assert!(ui.control_bar_hidden_groups.is_empty(), "an older workspace shows every group");
let mut prefs = serde_json::to_value(&back).unwrap();
prefs["control_bar_hidden_groups"] = serde_json::json!(["opacity", "stroke", 3, "paragraph"]);
prefs["custom_workspaces"][0]["controlBarHiddenGroups"] = serde_json::json!("opacity");
let read: UiState = serde_json::from_value(prefs.clone()).unwrap();
assert_eq!(read.control_bar_hidden_groups, hidden(&["opacity", "paragraph"]), "groups the bar does not have are dropped");
assert!(read.custom_workspaces[0].control_bar_hidden_groups.is_empty(), "a value that is not a list shows every group");
prefs.as_object_mut().unwrap().remove("control_bar_hidden_groups");
let read: UiState = serde_json::from_value(prefs).unwrap();
assert!(read.control_bar_hidden_groups.is_empty(), "older preferences show every group");
}
}
+17
View File
@@ -5064,6 +5064,23 @@ Dock to Top and Dock to Bottom, the edge in use checked, and so does a drag of t
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.
`window.control.options {group, visible?}` (desktop app) turns a group of the Control bar's options on (`visible:
true`), off (`false`) or toggles it (omitted), and returns `{group, visible}`. The groups are `snapping` (the Snapping
menu), `anchors` (Convert and Anchors), `toolOptions` (the active tool's options), `objectOptions` (Isolate Selected
Object or Exit Isolation Mode, the envelope controls and the symbol instance controls), `imageOptions` (a selected
image's or Image Trace object's controls), `fillStroke` (the fill and stroke chips, Stroke and its weight and width
profile), `opacity`, `graphicStyle` (Style, Recolor Artwork and Global Edit Options), `character`, `paragraph`,
`verticalAlignment` (area type's vertical alignment), `alignDistribute`, `transform` (the Transform link and the X/Y/W/H
fields, with the Artboard tool too), `artboardOptions` (the Artboard tool's other controls) and `documentOptions`
(Document Setup and Preferences, with nothing selected). The bar's panel menu lists the groups above Dock to Top and Dock
to Bottom, each checked while it is on, and runs the command for the group clicked. A group that is off stays out of
the bar even while its tool or object is selected, takes no room, and leaves the room to the groups after it; a group
that is on shows only while the selection or the tool calls for it. With an image or an Image Trace object selected and
`imageOptions` off, the bar shows neither its controls nor Fill and Stroke. Every group starts on. Which groups are off
is kept with the preferences and with the workspace; a workspace or preferences saved without them show every group,
and a group id the bar does not have is dropped when they are read. Another `group`, or none, fails with "`group` must
be one of …", and a `visible` that is not true or false fails with "`visible` must be true or false"; either leaves
every group as it is. The groups and their contents are VectorCraft's own.
```json
{"name":"run_command","arguments":{"command":"file.new","params":{"preset":"A4","orientation":"landscape","artboards":4,"artboardLayout":{"columns":2},"bleed":9}}}
+1
View File
@@ -600,6 +600,7 @@ since 2026-10-01.
- **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`.
- **Control bar panel menu turns groups of options on or off:** the Control bar's panel menu lists the bar's groups of options (Snapping, Anchor Points, Tool Options, Object Options, Image Options, Fill and Stroke, Opacity, Graphic Style, Character, Paragraph, Vertical Alignment, Align and Distribute, Transform, Artboard Options and Document Options) above Dock to Top and Dock to Bottom, each checked while it is on; a click turns that group off or on (`window.control.options {group, visible?}`), and a group that is off stays out of the bar even while its tool or object is selected, as the help page describes ([Control panel overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/control-panel-overview.html)). The list of groups and what each holds, every group on to start, a group that is off leaving its room to the groups after it, and keeping the groups with the preferences and the workspace are VectorCraft's own rules. Regression tests: `the_control_bar_panel_menu_turns_each_group_on_or_off`, `the_control_bar_keeps_its_label_and_menu_with_every_group_off`, `the_control_bar_panel_menu_scrolls_in_a_short_window`, `the_control_bar_options_command_turns_a_group_on_or_off`, `groups_that_are_off_leave_their_room_to_the_later_groups`, `the_artboard_tool_s_control_bar_follows_its_two_groups` and `the_anchor_points_group_holds_convert_and_anchors` in `crates/ui-egui/src/chrome/tests.rs`, `the_image_options_group_holds_the_image_and_trace_controls` in `crates/ui-egui/src/tests/place.rs`, `the_character_and_paragraph_groups_hold_the_type_controls` in `crates/ui-egui/src/panels/paragraph/tests.rs`, `saved_workspaces_restore_the_control_bar_groups` 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`.