diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 0157f169..dc59baf6 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -716,7 +716,7 @@ pub struct Prefs { pub canvas_color: String, pub auto_collapse_icon_panels: bool, /// User Interface › Show Tool Group Labels: the toolbar's group names (Select, Shapes, Draw…); - /// off, a faint dash separates the groups instead (#663). + /// off (as it ships, #1736), a faint dash separates the groups instead (#663). pub tool_group_labels: bool, pub open_documents_as_tabs: bool, pub large_tabs: bool, @@ -942,7 +942,7 @@ impl Default for Prefs { ui_brightness: s("mediumDark"), canvas_color: s("matchUi"), auto_collapse_icon_panels: false, - tool_group_labels: true, + tool_group_labels: false, open_documents_as_tabs: true, large_tabs: false, ui_scaling: 1.0, diff --git a/crates/ui-egui/src/edit_toolbar.rs b/crates/ui-egui/src/edit_toolbar.rs index 121641ba..cad227ac 100644 --- a/crates/ui-egui/src/edit_toolbar.rs +++ b/crates/ui-egui/src/edit_toolbar.rs @@ -24,11 +24,11 @@ use crate::state::{ToolbarCategory, ToolbarEdits}; use crate::theme::Tokens; use crate::{VectorcraftApp, icons, widgets}; -/// A tool's cell in the drawer: its icon and name. -const CELL: egui::Vec2 = vec2(176.0, 28.0); -const ICON: f32 = 20.0; -/// The drawer's width: two cells, the gap between them and room for the scroll bar. -const WIDTH: f32 = 2.0 * CELL.x + 24.0; +/// A tool's row in the drawer: its icon, name and shortcut. +const ROW: f32 = 26.0; +const ICON: f32 = 18.0; +/// The drawer's width: one column of rows, framed in groups, and room for the scroll bar. +const WIDTH: f32 = 268.0; /// Gap between the toolbar and the drawer, and the drawer's least distance from the window's edges. const GAP: f32 = 4.0; /// Size of the tool following the pointer while it is dragged. @@ -244,6 +244,19 @@ pub(crate) fn command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option (|| { + let on = crate::menus::opt_bool(p, "on")?; + if let Some(name) = p.get("control").filter(|v| !v.is_null()) { + let name = name.as_str().ok_or("control must be fillStroke, coloring, drawMode or screenMode")?; + let flag = app + .ui + .toolbar_show + .get_mut(name) + .ok_or_else(|| format!("unknown control `{name}`: fillStroke, coloring, drawMode or screenMode"))?; + *flag = on.unwrap_or(!*flag); + } + Ok(app.ui.toolbar_show.json()) + })(), "toolbar.reset" => { *current(&mut app.ui.toolbar_edits, app.ui.toolbar_advanced) = None; Ok(slots_json(app)) @@ -362,6 +375,27 @@ fn sections() -> &'static [(&'static str, Vec<&'static ToolInfo>)] { }) } +/// The headings in order, each with the flyout groups listed under it (a group under the heading +/// most of its tools have, the first tool's on a tie), each in its tools' order: what the drawer +/// frames together. +fn groups() -> &'static [(&'static str, Vec<&'static [ToolInfo]>)] { + static GROUPS: OnceLock)>> = OnceLock::new(); + GROUPS.get_or_init(|| { + let mut out: Vec<(&'static str, Vec<&'static [ToolInfo]>)> = sections().iter().map(|(cat, _)| (*cat, vec![])).collect(); + for g in TOOL_GROUPS { + let Some(first) = g.first() else { continue }; + let count = |h: &str| g.iter().filter(|t| heading(t.id) == h).count(); + let h = g.iter().map(|t| heading(t.id)).fold(heading(first.id), |best, h| if count(h) > count(best) { h } else { best }); + match out.iter_mut().find(|(c, _)| *c == h) { + Some((_, gs)) => gs.push(g), + None => out.push((h, vec![g])), + } + } + out.retain(|(_, gs)| !gs.is_empty()); + out + }) +} + /// The All Tools drawer beside the toolbar, its top by the ••• button (higher when that would put /// it past the window's bottom), while `tools_drawer` is on. pub(crate) fn drawer(app: &mut VectorcraftApp, ctx: &egui::Context) { @@ -383,29 +417,69 @@ pub(crate) fn drawer(app: &mut VectorcraftApp, ctx: &egui::Context) { let removing = dragged.is_some_and(|d| d.from_toolbar) && ctx.memory(|m| m.area_rect(id)).is_some_and(over); let mut picked = None; let mut reset = false; + let mut advanced = None; + let mut show_toggle = None; let resp = egui::Area::new(id).order(egui::Order::Foreground).fixed_pos(pos2(x, y)).show(ctx, |ui| { crate::toolbar::flyout_frame(&t, removing).inner_margin(egui::Margin::same(8)).show(ui, |ui| { - ui.set_width(WIDTH.min(screen.width() - 2.0 * GAP - 16.0).max(CELL.x)); + let width = WIDTH.min(screen.width() - 2.0 * GAP - 16.0).max(160.0); + ui.set_width(width); + // All Tools, and the drawer's flyout menu: Reset, and the Basic or Advanced toolbar. ui.horizontal(|ui| { ui.label(egui::RichText::new(tl!("All Tools")).size(13.0).color(t.text_strong)); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| reset = widgets::flat_button(ui, "Reset", 64.0).clicked()); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let (r, menu) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::click()); + icons::paint(ui, "menu", r.shrink(1.0), if menu.hovered() { t.text_strong } else { t.text_dim }); + menu.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, tl!("Drawer menu"))); + let menu = menu.on_hover_text(tl!("Drawer menu")); + egui::Popup::menu(&menu).id(menu_id()).show(|ui| { + ui.set_min_width(160.0); + if widgets::menu_item(ui, "Reset", true, false) { + reset = true; + } + ui.separator(); + for (label, adv) in [("Basic", false), ("Advanced", true)] { + if widgets::menu_item(ui, label, true, app.ui.toolbar_advanced == adv) { + advanced = Some(adv); + } + } + }); + }); }); ui.add_space(4.0); // As tall as its tools, up to the window's height (it scrolls then); the Area's size // from the last frame mustn't cap it. - let room = (screen.height() - 2.0 * GAP - 68.0).max(120.0); + let room = (screen.height() - 2.0 * GAP - 104.0).max(120.0); egui::ScrollArea::vertical().max_height(room).min_scrolled_height(room).auto_shrink([false, true]).show(ui, |ui| { - for (cat, tools) in sections() { - widgets::subheader(ui, tl!(cat)); - ui.horizontal_wrapped(|ui| { - ui.spacing_mut().item_spacing = vec2(4.0, 2.0); - for tool in tools { - if cell(ui, &t, tool, tool.id == active, in_toolbar.contains(tool.id)) { - picked = Some(tool.id); - } - } - }); + ui.spacing_mut().item_spacing = vec2(0.0, 0.0); + let inner = ui.available_width() - 10.0; + for (cat, gs) in groups() { ui.add_space(6.0); + ui.label(egui::RichText::new(format!("{}:", tl!(cat))).size(12.0).color(t.text_strong)); + ui.add_space(4.0); + for g in gs { + // A flyout group: its tools framed together, as one toolbar button holds them. + egui::Frame::NONE.stroke(Stroke::new(1.0, t.divider)).corner_radius(2.0).show(ui, |ui| { + ui.set_width(inner); + for tool in g.iter() { + if row(ui, &t, tool, tool.id == active, in_toolbar.contains(tool.id)) { + picked = Some(tool.id); + } + } + }); + ui.add_space(5.0); + } + } + }); + // Show: the controls under the toolbar's tools. + ui.add_space(4.0); + ui.separator(); + ui.horizontal(|ui| { + ui.label(egui::RichText::new(tl!("Show:")).size(12.0).color(t.text)); + ui.add_space(4.0); + for (name, label, icon) in crate::state::ToolbarShow::CONTROLS { + if show_button(ui, &t, name, label, icon, app.ui.toolbar_show.get(name)) { + show_toggle = Some(name); + } } }); }); @@ -423,10 +497,21 @@ pub(crate) fn drawer(app: &mut VectorcraftApp, ctx: &egui::Context) { if reset && let Err(e) = app.run("toolbar.reset", json!({})) { app.ui.status = e; } + // Basic or Advanced: the menu item of the other toolbar switches to it. + if advanced.is_some_and(|a| a != app.ui.toolbar_advanced) + && let Err(e) = app.run("window.toolbarAdvanced", json!({})) + { + app.ui.status = e; + } + if let Some(name) = show_toggle + && let Err(e) = app.run("toolbar.show", json!({ "control": name })) + { + app.ui.status = e; + } if let Some(id) = picked { app.select_tool(id); app.ui.tools_drawer = false; - } else if resp.response.clicked_elsewhere() && !pointer.is_some_and(|p| bar.contains(p)) { + } else if resp.response.clicked_elsewhere() && !pointer.is_some_and(|p| bar.contains(p)) && !egui::Popup::is_id_open(ctx, menu_id()) { // A click in the toolbar (picking a tool, or the ••• button that toggles the drawer) leaves // it open. app.ui.tools_drawer = false; @@ -434,19 +519,68 @@ pub(crate) fn drawer(app: &mut VectorcraftApp, ctx: &egui::Context) { ghost(ctx, &t); } -/// A tool's cell in the drawer: icon and name, dimmed when the tool is in the toolbar already (it -/// can't be dragged in again). Returns whether it was clicked. -fn cell(ui: &mut Ui, t: &Tokens, tool: &'static ToolInfo, on: bool, dimmed: bool) -> bool { - let (rect, resp) = ui.allocate_exact_size(CELL, if dimmed { Sense::click() } else { Sense::click_and_drag() }); +/// A Show section toggle: lit while its control shows under the toolbar's tools. Fill and Stroke +/// and Coloring are drawn as small copies of those controls, the modes with their buttons' icons. +fn show_button(ui: &mut Ui, t: &Tokens, name: &str, label: &str, icon: &str, on: bool) -> bool { + let (r, resp) = ui.allocate_exact_size(vec2(26.0, 26.0), Sense::click()); + resp.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, true, on, tl!(label))); + let p = ui.painter(); + if on { + p.rect_filled(r, 3.0, t.tool_active); + } else if resp.hovered() { + p.rect_filled(r, 3.0, t.hover); + } + let ink = if on { t.text_strong } else { t.icon }; + let c = r.center(); + match name { + "fillStroke" => { + // The proxy: a filled square in front of a stroked one. + let back = Rect::from_center_size(c + vec2(3.0, 3.0), vec2(10.0, 10.0)); + p.rect_stroke(back, 0.0, Stroke::new(2.0, ink), StrokeKind::Inside); + p.rect_filled(Rect::from_center_size(c - vec2(3.0, 3.0), vec2(10.0, 10.0)), 0.0, ink); + } + "coloring" => { + // Color, Gradient and None. + for (k, x) in [-6.5f32, 0.0, 6.5].into_iter().enumerate() { + let chip = Rect::from_center_size(c + vec2(x, 0.0), vec2(5.5, 9.0)); + match k { + 0 => { + p.rect_filled(chip, 0.0, ink); + } + 1 => { + p.rect_filled(chip, 0.0, ink.gamma_multiply(0.5)); + } + _ => { + p.rect_stroke(chip, 0.0, Stroke::new(1.0, ink), StrokeKind::Inside); + p.line_segment([chip.left_bottom(), chip.right_top()], Stroke::new(1.0, ink)); + } + } + } + } + _ => icons::paint(ui, icon, r.shrink(5.0), ink), + } + resp.on_hover_text(tl!(label)).clicked() +} + +/// The drawer's flyout menu. +fn menu_id() -> egui::Id { + egui::Id::new("tools-drawer-menu") +} + +/// A tool's row in the drawer: icon, name and shortcut, dimmed when the tool is in the toolbar +/// already (it can't be dragged in again). Returns whether it was clicked. +fn row(ui: &mut Ui, t: &Tokens, tool: &'static ToolInfo, on: bool, dimmed: bool) -> bool { + let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), ROW), if dimmed { Sense::click() } else { Sense::click_and_drag() }); if !dimmed { resp.dnd_set_drag_payload(ToolDrag { tool: tool.id, from_toolbar: false }); } + let fill = rect.shrink(1.0); if on { - ui.painter().rect_filled(rect, 3.0, t.selection); + ui.painter().rect_filled(fill, 2.0, t.selection); } else if resp.hovered() { - ui.painter().rect_filled(rect, 3.0, t.hover); + ui.painter().rect_filled(fill, 2.0, t.hover); } - // The active tool's cell is lit, dimmed or not. + // The active tool's row is lit, dimmed or not. let color = if on { t.text_strong } else if dimmed { @@ -454,12 +588,31 @@ fn cell(ui: &mut Ui, t: &Tokens, tool: &'static ToolInfo, on: bool, dimmed: bool } else { t.text }; - let icon = Rect::from_center_size(rect.left_center() + vec2(6.0 + ICON / 2.0, 0.0), vec2(ICON, ICON)); + let icon = Rect::from_center_size(rect.left_center() + vec2(8.0 + ICON / 2.0, 0.0), vec2(ICON, ICON)); icons::paint(ui, icons::tool_icon(tool.icon), icon, if dimmed || on { color } else { t.icon }); - let name = tl!(tool.label); - let name = if crate::i18n::current() == crate::i18n::Lang::EN { name.trim_end_matches(" Tool") } else { name }; - let text_at = pos2(icon.right() + 8.0, rect.center().y); - ui.painter().with_clip_rect(rect).text(text_at, egui::Align2::LEFT_CENTER, name, egui::FontId::proportional(12.0), color); + let painter = ui.painter().with_clip_rect(rect); + let shortcut = crate::shortcut_editor::tool_shortcut(tool.id); + let keys_w = shortcut.as_ref().map_or(0.0, |k| { + let keys = if on { + t.text_strong + } else if dimmed { + t.text_disabled + } else { + t.text_dim + }; + let g = painter.layout_no_wrap(k.to_string(), egui::FontId::proportional(11.5), keys); + let w = g.size().x; + painter.galley(pos2(rect.right() - 10.0 - w, rect.center().y - g.size().y / 2.0), g, keys); + w + 12.0 + }); + let text_at = pos2(icon.right() + 10.0, rect.center().y); + painter.with_clip_rect(Rect::from_min_max(rect.min, pos2(rect.right() - 10.0 - keys_w, rect.bottom()))).text( + text_at, + egui::Align2::LEFT_CENTER, + tl!(tool.label), + egui::FontId::proportional(12.5), + color, + ); resp.on_hover_text(crate::toolbar::tip(tool)).clicked() } @@ -483,7 +636,7 @@ mod tests { use super::*; /// Tall enough for the whole Basic toolbar and the whole drawer. - const SCREEN: egui::Vec2 = vec2(1000.0, 2400.0); + const SCREEN: egui::Vec2 = vec2(1000.0, 4000.0); /// A toolbar tool button. const BUTTON: egui::Vec2 = vec2(36.0, 29.0); @@ -561,7 +714,7 @@ mod tests { ctx.memory(|m| m.areas().visible_last_frame(&egui::LayerId::new(egui::Order::Foreground, drawer_id()))) } - /// A point inside the drawer's cell of the tool named `name` (as the drawer shows it), and the + /// A point inside the drawer's row of the tool named `name` (as the drawer shows it), and the /// colour of its name. fn cell_at(shown: &[(String, Pos2, Color32)], drawer: Rect, name: &str) -> (Pos2, Color32) { shown @@ -571,6 +724,40 @@ mod tests { .unwrap_or_else(|| panic!("no {name} in the drawer {drawer:?}")) } + /// The drawer frames each flyout group whole, under the heading most of its tools have: every + /// tool once, the Pencil's group (led by the Shaper) under Draw. + #[test] + fn the_drawer_frames_every_flyout_group_once() { + let listed: Vec<&str> = groups().iter().flat_map(|(_, gs)| gs.iter().flat_map(|g| g.iter().map(|t| t.id))).collect(); + let all: Vec<&str> = TOOL_GROUPS.iter().flat_map(|g| g.iter().map(|t| t.id)).collect(); + assert_eq!(listed.len(), all.len()); + assert!(all.iter().all(|id| listed.contains(id))); + let under = |id: &str| groups().iter().find(|(_, gs)| gs.iter().any(|g| g.iter().any(|t| t.id == id))).map(|(c, _)| *c); + assert_eq!(under("pencil"), Some("Draw")); + assert_eq!(under("shaper"), Some("Draw"), "framed with the Pencil"); + assert_eq!(under("rectangle"), Some("Shapes")); + } + + /// The Show section's controls (#1736): each shows (true), hides (false) or toggles, saved with + /// the preferences; an unknown control is an error naming them. + #[test] + fn toolbar_show_turns_the_controls_under_the_tools_on_and_off() { + let mut app = app(); + let all_on = json!({"fillStroke": true, "coloring": true, "drawMode": true, "screenMode": true}); + assert_eq!(app.run("toolbar.show", json!({})).unwrap(), all_on); + assert_eq!(app.run("toolbar.show", json!({"control": "coloring"})).unwrap()["coloring"], json!(false)); + assert_eq!(app.run("toolbar.show", json!({"control": "screenMode", "on": false})).unwrap()["screenMode"], json!(false)); + assert!(!app.ui.toolbar_show.coloring && !app.ui.toolbar_show.screen_mode && app.ui.toolbar_show.fill_stroke); + let err = app.run("toolbar.show", json!({"control": "swatches"})).unwrap_err(); + assert!(err.contains("swatches") && err.contains("fillStroke"), "{err}"); + // Saved with the preferences; older preferences show them all. + let saved = serde_json::to_value(&app.ui).unwrap(); + assert_eq!(serde_json::from_value::(saved).unwrap().toolbar_show, app.ui.toolbar_show); + let mut old = serde_json::to_value(&app.ui).unwrap(); + old.as_object_mut().unwrap().remove("toolbar_show"); + assert_eq!(serde_json::from_value::(old).unwrap().toolbar_show, crate::state::ToolbarShow::default()); + } + /// Every tool is listed once, under a heading; the Basic toolbar's groups come first, in its order. #[test] fn every_tool_is_listed_once_under_a_heading() { @@ -699,7 +886,7 @@ mod tests { assert!((drawer.left() - bar.right() - GAP).abs() < 1.0, "beside the toolbar: {drawer:?} {bar:?}"); assert!((drawer.top() - edit.top()).abs() < 1.0 || drawer.bottom() >= SCREEN.y - GAP - 1.0, "by the button: {drawer:?} {edit:?}"); assert!(drawer.y_range().contains(edit.center().y), "by the button: {drawer:?} {edit:?}"); - assert!(drawer.width() < 3.0 * CELL.x, "two tools wide: {drawer:?}"); + assert!(drawer.width() < WIDTH + 24.0, "one row of tools wide: {drawer:?}"); // A click on a toolbar tool picks it and leaves the drawer open. let tools = crate::toolbar::tests::widget_rects(&ctx, BUTTON); let ellipse = tools[slot_of(&app, "ellipse").unwrap()].center(); @@ -724,7 +911,7 @@ mod tests { app.run("window.editToolbar", json!({})).unwrap(); frame(&mut app, &ctx, 7.0, vec![]); let shown = frame(&mut app, &ctx, 7.1, vec![]); - let (at, _) = cell_at(&shown, drawer_rect(&ctx).unwrap(), "Artboard"); + let (at, _) = cell_at(&shown, drawer_rect(&ctx).unwrap(), "Artboard Tool"); click(&mut app, &ctx, 8.0, at); assert!(!app.ui.tools_drawer && app.session.tool_id() == "artboard"); } @@ -739,13 +926,13 @@ mod tests { let shown = frame(&mut app, &ctx, 0.1, vec![]); let drawer = drawer_rect(&ctx).unwrap(); let dim = Tokens::get(&ctx).text_disabled; - assert_eq!(cell_at(&shown, drawer, "Ellipse").1, dim); - assert_ne!(cell_at(&shown, drawer, "Artboard").1, dim); + assert_eq!(cell_at(&shown, drawer, "Ellipse Tool").1, dim); + assert_ne!(cell_at(&shown, drawer, "Artboard Tool").1, dim); app.run("toolbar.addTool", json!({"tool": "artboard"})).unwrap(); app.run("toolbar.removeTool", json!({"tool": "ellipse"})).unwrap(); let shown = frame(&mut app, &ctx, 0.2, vec![]); - assert_ne!(cell_at(&shown, drawer, "Ellipse").1, dim); - assert_eq!(cell_at(&shown, drawer, "Artboard").1, dim); + assert_ne!(cell_at(&shown, drawer, "Ellipse Tool").1, dim); + assert_eq!(cell_at(&shown, drawer, "Artboard Tool").1, dim); } /// #1018: tools drag from the drawer onto the toolbar (between two tools, or onto one to join @@ -761,19 +948,24 @@ mod tests { let drawer = drawer_rect(&ctx).unwrap(); let tools = crate::toolbar::tests::widget_rects(&ctx, BUTTON); // Onto the divider under the Selection tool: its own slot, second. - let (artboard, _) = cell_at(&shown, drawer, "Artboard"); + let (artboard, _) = cell_at(&shown, drawer, "Artboard Tool"); drag(&mut app, &ctx, 1.0, artboard, tools[0].center_bottom() - vec2(0.0, 3.0)); assert_eq!(&slots(&app)[..2], [&["selection"][..], &["artboard"]]); assert!(app.ui.tools_drawer, "the drawer stays open"); // Onto the middle of the Ellipse tool: into its group. let tools = crate::toolbar::tests::widget_rects(&ctx, BUTTON); let ellipse = slot_of(&app, "ellipse").unwrap(); - let (slice, _) = cell_at(&shown, drawer, "Slice"); + // The drawer moves with the ••• button the new slot pushed down: read it again. + let shown = frame(&mut app, &ctx, 1.9, vec![]); + let drawer = drawer_rect(&ctx).unwrap(); + let (slice, _) = cell_at(&shown, drawer, "Slice Tool"); drag(&mut app, &ctx, 2.0, slice, tools[ellipse].center()); assert_eq!(slots(&app)[ellipse], ["ellipse", "slice"]); // A dimmed tool (in the toolbar already) doesn't drag. let before = slots(&app); - let (rectangle, _) = cell_at(&shown, drawer, "Rectangle"); + let shown = frame(&mut app, &ctx, 2.9, vec![]); + let drawer = drawer_rect(&ctx).unwrap(); + let (rectangle, _) = cell_at(&shown, drawer, "Rectangle Tool"); drag(&mut app, &ctx, 3.0, rectangle, tools[0].center_bottom() - vec2(0.0, 3.0)); assert_eq!(slots(&app), before); // From the toolbar into the drawer: out of the toolbar. diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 522b6fce..4b35d262 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4747,6 +4747,14 @@ mark-style Japanese Japonské @msg unknown parameter `{key}`; the command takes: {keys} neznámý parametr `{key}`; příkaz přijímá: {keys} Load EPS into Clipboard Načíst EPS do schránky @msg an image of {w} × {h} pixels is too large to draw obraz o velikosti {w} × {h} obrazových bodů je příliš velký na vykreslení + Fill and Stroke Controls Ovládací prvky výplně a tahu + Coloring Controls Ovládací prvky barev + Draw Mode Controls Ovládací prvky režimu kreslení + Screen Mode Controls Ovládací prvky režimu zobrazení + Drawer menu Nabídka zásuvky + Toolbar: Show Controls Panel nástrojů: Zobrazit ovládací prvky +@msg control must be fillStroke, coloring, drawMode or screenMode control musí být fillStroke, coloring, drawMode nebo screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode neznámý ovládací prvek `{name}`: fillStroke, coloring, drawMode nebo screenMode @msg give a fraction `t` from 0 to 1 zadejte hodnotu `t` od 0 do 1 @msg give the path `id` zadejte `id` cesty @msg invalid stroke widths neplatné šířky tahu diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index bda4f451..51b06bad 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4271,6 +4271,15 @@ theme Appearance Erscheinungsbild @msg unknown parameter `{key}`; the command takes: {keys} unbekannter Parameter `{key}`; der Befehl akzeptiert: {keys} Load EPS into Clipboard EPS in die Zwischenablage laden @msg an image of {w} × {h} pixels is too large to draw ein Bild mit {w} × {h} Pixeln ist zu groß, um gezeichnet zu werden + Basic Basis + Fill and Stroke Controls Steuerelemente für Fläche und Kontur + Coloring Controls Steuerelemente für Farbe + Draw Mode Controls Steuerelemente für Zeichenmodus + Screen Mode Controls Steuerelemente für Bildschirmmodus + Drawer menu Menü der Werkzeugschublade + Toolbar: Show Controls Werkzeugleiste: Steuerelemente einblenden +@msg control must be fillStroke, coloring, drawMode or screenMode control muss fillStroke, coloring, drawMode oder screenMode sein +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode unbekanntes Steuerelement `{name}`: fillStroke, coloring, drawMode oder screenMode @msg give a fraction `t` from 0 to 1 geben Sie einen Wert `t` von 0 bis 1 an @msg give the path `id` geben Sie die `id` des Pfads an @msg invalid stroke widths ungültige Konturbreiten diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index c9c36d04..c9dba867 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4688,6 +4688,14 @@ mark-style Japanese Japonesas @msg unknown parameter `{key}`; the command takes: {keys} parámetro desconocido `{key}`; el comando acepta: {keys} Load EPS into Clipboard Cargar EPS en el portapapeles @msg an image of {w} × {h} pixels is too large to draw una imagen de {w} × {h} píxeles es demasiado grande para dibujarse + Fill and Stroke Controls Controles de relleno y trazo + Coloring Controls Controles de color + Draw Mode Controls Controles de modo de dibujo + Screen Mode Controls Controles de modo de pantalla + Drawer menu Menú del cajón + Toolbar: Show Controls Barra de herramientas: Mostrar controles +@msg control must be fillStroke, coloring, drawMode or screenMode control debe ser fillStroke, coloring, drawMode o screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode control desconocido `{name}`: fillStroke, coloring, drawMode o screenMode @msg give a fraction `t` from 0 to 1 indica un valor `t` de 0 a 1 @msg give the path `id` indica el `id` del trazado @msg invalid stroke widths anchuras de trazo no válidas diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index de2b62f6..cccd38e7 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4747,6 +4747,14 @@ mark-style Japanese Japonais theme Appearance Apparence Load EPS into Clipboard Charger un EPS dans le presse-papiers @msg an image of {w} × {h} pixels is too large to draw une image de {w} × {h} pixels est trop grande pour être dessinée + Fill and Stroke Controls Commandes de fond et de contour + Coloring Controls Commandes de couleur + Draw Mode Controls Commandes de mode de dessin + Screen Mode Controls Commandes de mode d'affichage + Drawer menu Menu du tiroir + Toolbar: Show Controls Barre d’outils : Afficher les commandes +@msg control must be fillStroke, coloring, drawMode or screenMode control doit être fillStroke, coloring, drawMode ou screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode commande inconnue `{name}` : fillStroke, coloring, drawMode ou screenMode @msg give a fraction `t` from 0 to 1 indiquez une valeur `t` comprise entre 0 et 1 @msg give the path `id` indiquez l’`id` du tracé @msg invalid stroke widths largeurs de contour non valides diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 488b5a9f..89d39aae 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4714,6 +4714,14 @@ imageTrace Outline Körvonal @msg unknown parameter `{key}`; the command takes: {keys} ismeretlen paraméter: `{key}`; a parancs ezeket fogadja el: {keys} Load EPS into Clipboard EPS betöltése a vágólapra @msg an image of {w} × {h} pixels is too large to draw egy {w} × {h} pixeles kép túl nagy a kirajzoláshoz + Fill and Stroke Controls Kitöltés és körvonal vezérlői + Coloring Controls Színezés vezérlői + Draw Mode Controls Rajzolási mód vezérlői + Screen Mode Controls Képernyőmód vezérlői + Drawer menu Fiók menüje + Toolbar: Show Controls Eszköztár: Vezérlők megjelenítése +@msg control must be fillStroke, coloring, drawMode or screenMode a control értéke fillStroke, coloring, drawMode vagy screenMode lehet +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode ismeretlen vezérlő: `{name}`; fillStroke, coloring, drawMode vagy screenMode @msg give a fraction `t` from 0 to 1 add meg a `t` értékét 0 és 1 között @msg give the path `id` add meg az útvonal `id` értékét @msg invalid stroke widths érvénytelen körvonalszélességek diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 1facacf4..494252d0 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4316,6 +4316,15 @@ lighting Light Luce @msg unknown parameter `{key}`; the command takes: {keys} parametro sconosciuto `{key}`; il comando accetta: {keys} Load EPS into Clipboard Carica EPS negli appunti @msg an image of {w} × {h} pixels is too large to draw un'immagine di {w} × {h} pixel è troppo grande per essere disegnata + Basic Base + Fill and Stroke Controls Controlli di riempimento e traccia + Coloring Controls Controlli colore + Draw Mode Controls Controlli modalità di disegno + Screen Mode Controls Controlli modalità schermo + Drawer menu Menu del cassetto + Toolbar: Show Controls Barra degli strumenti: Mostra controlli +@msg control must be fillStroke, coloring, drawMode or screenMode control deve essere fillStroke, coloring, drawMode o screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode controllo sconosciuto `{name}`: fillStroke, coloring, drawMode o screenMode @msg give a fraction `t` from 0 to 1 specifica un valore `t` da 0 a 1 @msg give the path `id` specifica l'`id` del tracciato @msg invalid stroke widths larghezze della traccia non valide diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 8eef3ae6..edb50b54 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4310,6 +4310,15 @@ lighting Light ライト @msg unknown parameter `{key}`; the command takes: {keys} `{key}` は不明なパラメーターです。このコマンドで使えるパラメーター: {keys} Load EPS into Clipboard EPS をクリップボードに読み込む @msg an image of {w} × {h} pixels is too large to draw {w} × {h} ピクセルの画像は大きすぎて描画できません + Basic 基本 + Fill and Stroke Controls 塗りと線のコントロール + Coloring Controls カラーのコントロール + Draw Mode Controls 描画モードのコントロール + Screen Mode Controls スクリーンモードのコントロール + Drawer menu ドロワーメニュー + Toolbar: Show Controls ツールバー: コントロールを表示 +@msg control must be fillStroke, coloring, drawMode or screenMode control は fillStroke、coloring、drawMode、screenMode のいずれかです +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode 不明なコントロール `{name}`:fillStroke、coloring、drawMode、screenMode のいずれかです @msg give a fraction `t` from 0 to 1 0 から 1 までの値 `t` を指定してください @msg give the path `id` パスの `id` を指定してください @msg invalid stroke widths 線幅が無効です diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 2b944e13..14070de7 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4280,6 +4280,15 @@ lighting Light 광원 @msg unknown parameter `{key}`; the command takes: {keys} 알 수 없는 매개변수 `{key}`: 이 명령이 받는 매개변수: {keys} Load EPS into Clipboard EPS를 클립보드로 불러오기 @msg an image of {w} × {h} pixels is too large to draw {w} × {h}픽셀 이미지는 너무 커서 그릴 수 없습니다 + Basic 기본 + Fill and Stroke Controls 칠 및 획 컨트롤 + Coloring Controls 색상 컨트롤 + Draw Mode Controls 그리기 모드 컨트롤 + Screen Mode Controls 화면 모드 컨트롤 + Drawer menu 서랍 메뉴 + Toolbar: Show Controls 도구 막대: 컨트롤 표시 +@msg control must be fillStroke, coloring, drawMode or screenMode control은 fillStroke, coloring, drawMode, screenMode 중 하나여야 합니다 +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode 알 수 없는 컨트롤 `{name}`: fillStroke, coloring, drawMode, screenMode 중 하나 @msg give a fraction `t` from 0 to 1 0에서 1 사이의 값 `t`를 지정하세요 @msg give the path `id` 패스 `id`를 지정하세요 @msg invalid stroke widths 선 너비가 유효하지 않습니다 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 4c26ef01..869e2a9b 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4589,6 +4589,15 @@ mark-style Japanese Japońskie @msg unknown parameter `{key}`; the command takes: {keys} nieznany parametr `{key}`; polecenie przyjmuje: {keys} Load EPS into Clipboard Wczytaj EPS do schowka @msg an image of {w} × {h} pixels is too large to draw obraz o wymiarach {w} × {h} pikseli jest zbyt duży, aby go narysować + Basic Podstawowy + Fill and Stroke Controls Elementy sterujące wypełnieniem i obrysem + Coloring Controls Elementy sterujące kolorem + Draw Mode Controls Elementy sterujące trybem rysowania + Screen Mode Controls Elementy sterujące trybem ekranu + Drawer menu Menu szuflady + Toolbar: Show Controls Pasek narzędzi: Pokaż elementy sterujące +@msg control must be fillStroke, coloring, drawMode or screenMode control musi mieć wartość fillStroke, coloring, drawMode lub screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode nieznany element sterujący `{name}`: fillStroke, coloring, drawMode lub screenMode @msg give a fraction `t` from 0 to 1 podaj wartość `t` od 0 do 1 @msg give the path `id` podaj `id` ścieżki @msg invalid stroke widths nieprawidłowe szerokości obrysu diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index a5c87a70..e5b3e964 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3750,7 +3750,8 @@ axis Both Ambos @msg unknown parameter `{key}`; the command takes no parameters parâmetro desconhecido `{key}`; o comando não tem parâmetros @msg unknown parameter `{key}`; the command takes: {keys} parâmetro desconhecido `{key}`; o comando aceita: {keys} Load EPS into Clipboard Carregar EPS na Área de Transferência - + Drawer menu Menu da gaveta + Toolbar: Show Controls Barra de ferramentas: Mostrar controles # Corner Radius: a field per corner of a live rectangle. Single Corner Radius Raio de Canto Único Top Left: Superior Esquerdo: diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 80d8d8c8..1b92d00d 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4312,6 +4312,15 @@ lighting Light Свет @msg unknown parameter `{key}`; the command takes: {keys} неизвестный параметр `{key}`; команда принимает: {keys} Load EPS into Clipboard Загрузить EPS в буфер обмена @msg an image of {w} × {h} pixels is too large to draw изображение размером {w} × {h} пикселей слишком велико для отрисовки + Basic Базовый + Fill and Stroke Controls Элементы управления заливкой и обводкой + Coloring Controls Элементы управления цветом + Draw Mode Controls Элементы управления режимом рисования + Screen Mode Controls Элементы управления режимом экрана + Drawer menu Меню ящика + Toolbar: Show Controls Панель инструментов: Показать элементы управления +@msg control must be fillStroke, coloring, drawMode or screenMode control должен быть fillStroke, coloring, drawMode или screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode неизвестный элемент управления `{name}`: fillStroke, coloring, drawMode или screenMode @msg give a fraction `t` from 0 to 1 укажите значение `t` от 0 до 1 @msg give the path `id` укажите `id` контура @msg invalid stroke widths недопустимые значения ширины обводки diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 82efe7e1..91999762 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4296,6 +4296,15 @@ lighting Light Світло @msg unknown parameter `{key}`; the command takes: {keys} невідомий параметр `{key}`; команда приймає: {keys} Load EPS into Clipboard Завантажити EPS у буфер обміну @msg an image of {w} × {h} pixels is too large to draw зображення розміром {w} × {h} пікселів завелике для відображення + Basic Базовий + Fill and Stroke Controls Елементи керування заливкою та обведенням + Coloring Controls Елементи керування кольором + Draw Mode Controls Елементи керування режимом малювання + Screen Mode Controls Елементи керування режимом екрана + Drawer menu Меню шухляди + Toolbar: Show Controls Панель інструментів: Показати елементи керування +@msg control must be fillStroke, coloring, drawMode or screenMode control має бути fillStroke, coloring, drawMode або screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode невідомий елемент керування `{name}`: fillStroke, coloring, drawMode або screenMode @msg give a fraction `t` from 0 to 1 вкажіть значення `t` від 0 до 1 @msg give the path `id` вкажіть `id` контуру @msg invalid stroke widths неприпустимі значення ширини обведення diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index a9c00b35..44dfae0a 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4298,6 +4298,15 @@ theme Appearance Giao diện @msg {tool} isn't in a toolbar group of several tools {tool} không nằm trong nhóm thanh công cụ có nhiều công cụ Load EPS into Clipboard Tải EPS vào bảng tạm @msg an image of {w} × {h} pixels is too large to draw ảnh {w} × {h} điểm ảnh quá lớn nên không thể vẽ + Basic Cơ bản + Fill and Stroke Controls Điều khiển tô và nét + Coloring Controls Điều khiển màu + Draw Mode Controls Điều khiển chế độ vẽ + Screen Mode Controls Điều khiển chế độ màn hình + Drawer menu Menu ngăn kéo + Toolbar: Show Controls Thanh công cụ: Hiện điều khiển +@msg control must be fillStroke, coloring, drawMode or screenMode control phải là fillStroke, coloring, drawMode hoặc screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode điều khiển không xác định `{name}`: fillStroke, coloring, drawMode hoặc screenMode @msg give a fraction `t` from 0 to 1 hãy cung cấp giá trị `t` từ 0 đến 1 @msg give the path `id` hãy cung cấp `id` đường dẫn @msg invalid stroke widths chiều rộng nét không hợp lệ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index ff88ff20..0ed1c633 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3902,7 +3902,14 @@ lighting Light 光源 @msg unknown parameter `{key}`; the command takes: {keys} 未知参数 `{key}`;该命令接受:{keys} Load EPS into Clipboard 将 EPS 载入剪贴板 @msg an image of {w} × {h} pixels is too large to draw {w} × {h} 像素的图像过大,无法绘制 - + Fill and Stroke Controls 填色和描边控件 + Coloring Controls 颜色控件 + Draw Mode Controls 绘图模式控件 + Screen Mode Controls 屏幕模式控件 + Drawer menu 抽屉菜单 + Toolbar: Show Controls 工具栏:显示控件 +@msg control must be fillStroke, coloring, drawMode or screenMode control 必须是 fillStroke、coloring、drawMode 或 screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode 未知控件 `{name}`:fillStroke、coloring、drawMode 或 screenMode # Corner Radius: a field per corner of a live rectangle. Single Corner Radius 统一圆角半径 Top Left: 左上: diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 82d60dd0..70e2d1a0 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3683,7 +3683,15 @@ lighting Light 光源 @msg unknown parameter `{key}`; the command takes: {keys} 未知的參數 `{key}`;此指令接受:{keys} Load EPS into Clipboard 將 EPS 載入剪貼簿 @msg an image of {w} × {h} pixels is too large to draw {w} × {h} 像素的影像過大,無法繪製 - + Basic 基本 + Fill and Stroke Controls 填色和筆畫控制項 + Coloring Controls 顏色控制項 + Draw Mode Controls 繪圖模式控制項 + Screen Mode Controls 螢幕模式控制項 + Drawer menu 抽屜選單 + Toolbar: Show Controls 工具列:顯示控制項 +@msg control must be fillStroke, coloring, drawMode or screenMode control 必須是 fillStroke、coloring、drawMode 或 screenMode +@msg unknown control `{name}`: fillStroke, coloring, drawMode or screenMode 未知控制項 `{name}`:fillStroke、coloring、drawMode 或 screenMode # Corner Radius: a field per corner of a live rectangle. Single Corner Radius 統一圓角半徑 Top Left: 左上: diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 81bf5552..f3a7d2ec 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -262,6 +262,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{tool: id, floating?: bool} float the toolbar group holding `tool` (in the current Basic or Advanced layout) as its own strip of tool buttons (true), put it back in the toolbar (false) or toggle (omitted), as dragging or clicking a flyout's tear-off bar and the strip's × do; returns the new state", ), + ( + "toolbar.show", + "Toolbar: Show Controls", + "", + "{control?: \"fillStroke\"|\"coloring\"|\"drawMode\"|\"screenMode\", on?: bool} show (true), hide (false) or toggle (omitted) one of the controls under the toolbar's tools (Fill and Stroke, Coloring, Draw Mode, Screen Mode), as the All Tools drawer's Show section does; saved with the preferences; returns every control's state", + ), ( "window.editToolbar", "Edit Toolbar", diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 17ef9149..e4eb10a0 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -277,6 +277,10 @@ pub struct UiState { /// The Basic and Advanced toolbars as Edit Toolbar left them (none: as they ship). #[serde(default)] pub toolbar_edits: ToolbarEdits, + /// The controls under the toolbar's tools that the All Tools drawer's Show section shows + /// (`toolbar.show`). + #[serde(default)] + pub toolbar_show: ToolbarShow, /// Edit Toolbar's All Tools drawer is open beside the toolbar (`window.editToolbar`). #[serde(skip)] pub tools_drawer: bool, @@ -458,6 +462,57 @@ pub struct ToolbarCategory { pub slots: Vec>, } +/// The controls under the toolbar's tools, each shown or hidden from the All Tools drawer's Show +/// section: Fill and Stroke (the proxy), Coloring (Color, Gradient, None), Draw Mode and Screen +/// Mode. All shown as it ships. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ToolbarShow { + #[serde(default = "yes")] + pub fill_stroke: bool, + #[serde(default = "yes")] + pub coloring: bool, + #[serde(default = "yes")] + pub draw_mode: bool, + #[serde(default = "yes")] + pub screen_mode: bool, +} + +impl Default for ToolbarShow { + fn default() -> Self { + Self { fill_stroke: true, coloring: true, draw_mode: true, screen_mode: true } + } +} + +impl ToolbarShow { + /// The controls as (`toolbar.show` name, label, icon), in the Show section's order. + pub const CONTROLS: [(&'static str, &'static str, &'static str); 4] = [ + ("fillStroke", "Fill and Stroke Controls", "dc-stroke"), + ("coloring", "Coloring Controls", "dc-gradient"), + ("drawMode", "Draw Mode Controls", "dc-draw-normal"), + ("screenMode", "Screen Mode Controls", "dc-screen-mode"), + ]; + + /// The flag of the control named `name`. + pub fn get_mut(&mut self, name: &str) -> Option<&mut bool> { + match name { + "fillStroke" => Some(&mut self.fill_stroke), + "coloring" => Some(&mut self.coloring), + "drawMode" => Some(&mut self.draw_mode), + "screenMode" => Some(&mut self.screen_mode), + _ => None, + } + } + + pub fn get(self, name: &str) -> bool { + let mut s = self; + s.get_mut(name).is_some_and(|on| *on) + } + + pub fn json(self) -> serde_json::Value { + serde_json::json!({"fillStroke": self.fill_stroke, "coloring": self.coloring, "drawMode": self.draw_mode, "screenMode": self.screen_mode}) + } +} + /// The toolbars Edit Toolbar changed: each one is `None` while it is as it ships. #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct ToolbarEdits { @@ -614,6 +669,7 @@ impl Default for UiState { slot_tool: Default::default(), floating_flyouts: vec![], toolbar_edits: ToolbarEdits::default(), + toolbar_show: ToolbarShow::default(), tools_drawer: false, floating_panels: vec![], docking: None, diff --git a/crates/ui-egui/src/toolbar.rs b/crates/ui-egui/src/toolbar.rs index 2a2a9f7e..f8fd5f77 100644 --- a/crates/ui-egui/src/toolbar.rs +++ b/crates/ui-egui/src/toolbar.rs @@ -443,38 +443,44 @@ fn crop_options(app: &mut VectorcraftApp, ui: &mut Ui) { /// The Fill/Stroke proxy, Color/Gradient/None, the drawing modes, screen mode and Edit Toolbar, as /// large as the toolbar's `cols` (one or two) leave room for (#1129): in two columns the proxy and /// buttons grow and the three drawing modes show side by side. +/// The All Tools drawer's Show section hides any of them but Edit Toolbar. fn bottom_controls(app: &mut VectorcraftApp, ui: &mut Ui, t: &Tokens, cols: usize) { let two = cols == 2; - ui.vertical_centered(|ui| crate::panels::proxy(app, ui, if two { 54.0 } else { 36.0 })); - ui.add_space(5.0); + let show = app.ui.toolbar_show; + if show.fill_stroke { + ui.vertical_centered(|ui| crate::panels::proxy(app, ui, if two { 54.0 } else { 36.0 })); + ui.add_space(5.0); + } // Color (the last solid colour), Gradient (the last gradient) and None, as commands. let (chip, gap) = if two { (17.0, 4.0) } else { (11.0, 2.0) }; - ui.horizontal(|ui| { - ui.add_space((ui.available_width() - 3.0 * chip - 2.0 * gap) / 2.0); - ui.spacing_mut().item_spacing.x = gap; - let mut clicked = None; - for (tip, cmd) in [("Color (,)", "paint.lastColor"), ("Gradient (.)", "paint.lastGradient"), ("None (/)", "paint.none")] { - let (r, resp) = ui.allocate_exact_size(vec2(chip, chip), Sense::click()); - match cmd { - "paint.lastColor" => widgets::paint_chip(ui, r, &Paint::solid(app.session.last_solid)), - "paint.lastGradient" => widgets::gradient_chip(ui, r, &app.session.last_gradient.gradient), - _ => widgets::paint_chip(ui, r, &Paint::None), + if show.coloring { + ui.horizontal(|ui| { + ui.add_space((ui.available_width() - 3.0 * chip - 2.0 * gap) / 2.0); + ui.spacing_mut().item_spacing.x = gap; + let mut clicked = None; + for (tip, cmd) in [("Color (,)", "paint.lastColor"), ("Gradient (.)", "paint.lastGradient"), ("None (/)", "paint.none")] { + let (r, resp) = ui.allocate_exact_size(vec2(chip, chip), Sense::click()); + match cmd { + "paint.lastColor" => widgets::paint_chip(ui, r, &Paint::solid(app.session.last_solid)), + "paint.lastGradient" => widgets::gradient_chip(ui, r, &app.session.last_gradient.gradient), + _ => widgets::paint_chip(ui, r, &Paint::None), + } + if resp.on_hover_text(tl!(tip)).clicked() { + clicked = Some(cmd); + } } - if resp.on_hover_text(tl!(tip)).clicked() { - clicked = Some(cmd); + if let Some(cmd) = clicked { + app.run(cmd, json!({})).ok(); } - } - if let Some(cmd) = clicked { - app.run(cmd, json!({})).ok(); - } - }); - ui.add_space(6.0); + }); + ui.add_space(6.0); + } let modes = [ (vectorcraft_engine::DrawMode::Normal, "dc-draw-normal", "Draw Normal", "normal"), (vectorcraft_engine::DrawMode::Behind, "dc-draw-behind", "Draw Behind", "behind"), (vectorcraft_engine::DrawMode::Inside, "dc-draw-inside", "Draw Inside", "inside"), ]; - if two { + if two && show.draw_mode { // Side by side, each one a click away. ui.horizontal(|ui| { ui.add_space((ui.available_width() - 3.0 * 22.0) / 2.0); @@ -488,12 +494,13 @@ fn bottom_controls(app: &mut VectorcraftApp, ui: &mut Ui, t: &Tokens, cols: usiz ui.vertical_centered(|ui| { // One column: the current mode's button, which cycles them. if !two + && show.draw_mode && let Some((mode, icon, name, _)) = modes.into_iter().find(|m| m.0 == app.session.draw_mode) && widgets::icon_button(ui, icon, &format!("{} (Shift+D)", tl!(name)), mode != vectorcraft_engine::DrawMode::Normal, 26.0).clicked() { app.run("view.drawMode", json!({})).ok(); } - if widgets::icon_button(ui, "dc-screen-mode", "Change Screen Mode (F)", false, 26.0).clicked() { + if show.screen_mode && widgets::icon_button(ui, "dc-screen-mode", "Change Screen Mode (F)", false, 26.0).clicked() { app.run("view.screenMode", json!({})).ok(); } // Edit Toolbar: the All Tools drawer opens beside the toolbar, by this button. @@ -1433,8 +1440,8 @@ pub(crate) mod tests { assert!(r.min.x.is_finite() && r.min.y.is_finite() && r.left() >= 0.0, "on screen: {r:?}"); } - /// User Interface › Show Tool Group Labels (#663): on, the toolbar names its groups; off, it - /// shows none of the names, a faint dash between the groups instead. + /// User Interface › Show Tool Group Labels (#663): off as it ships (#1736), the toolbar shows + /// none of its groups' names, a faint dash between the groups instead; on, it names them. #[test] fn tool_group_labels_can_be_hidden() { fn texts(s: &egui::Shape, out: &mut Vec) { @@ -1455,11 +1462,11 @@ pub(crate) mod tests { out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown)); shown }; - let with = draw(&mut app); - assert!(with.iter().any(|t| t == "Shapes") && with.iter().any(|t| t == "Draw"), "{with:?}"); - app.run("prefs.set", json!({"key": "toolGroupLabels", "value": false})).unwrap(); let without = draw(&mut app); assert!(!without.iter().any(|t| ["Select", "Shapes", "Draw", "Modify", "Type"].contains(&t.as_str())), "{without:?}"); + app.run("prefs.set", json!({"key": "toolGroupLabels", "value": true})).unwrap(); + let with = draw(&mut app); + assert!(with.iter().any(|t| t == "Shapes") && with.iter().any(|t| t == "Draw"), "{with:?}"); } /// Hover `at` for two seconds, coming from elsewhere: the texts painted meanwhile. diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 6ce86af2..bbbaf648 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -286,7 +286,12 @@ drawer beside the toolbar; Escape or a click outside closes it. While it is open toolbar to add it (onto a button's top or bottom edge: its own slot before or after; onto its middle: into its group), from the toolbar into it to remove it, or within the toolbar to move it. `toolbar.addTool {tool, slot?, place?}`, `toolbar.removeTool {tool, group?}` and `toolbar.reset` do the same for the current Basic or Advanced -toolbar and return its slots; the edits (`ui.toolbar_edits`) are saved with the preferences. +toolbar and return its slots; the edits (`ui.toolbar_edits`) are saved with the preferences. The drawer lists +the tools one per row (icon, name, shortcut), each flyout group framed together under its heading; its ☰ menu +has Reset and the Basic and Advanced toolbars. Its Show section (`toolbar.show {control?, on?}`, `control` one +of `fillStroke`, `coloring`, `drawMode`, `screenMode`) shows or hides the controls under the toolbar's tools, +saved with the preferences (`ui.toolbar_show`). The toolbar names its groups only with Preferences › User +Interface › Show Tool Group Labels, off as it ships (#1736). The Contextual Task Bar: its handle (the grip at its left end) drags it anywhere on the canvas, which always holds it whole, also when the window shrinks. Unpinned, the bar keeps that offset as it follows the selection, until