mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 09:39:52 +00:00
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:
@@ -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",
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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]
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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}}}
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user