mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 11:17:01 -06:00
Control bar: Transform link opens the Transform panel; reference point locator redrawn
The Control bar's underlined Transform link opens the Transform panel as a popover under it (reference point, X/Y/W/H, Rotate and Shear with their icons and angle presets, flips, shape properties, Scale Corners, Scale Strokes & Effects), as the Stroke link does the Stroke panel. X/Y/W/H stay inline while the bar has room. The reference point locator is now nine squares with the outer eight joined around the box; the old outline was inset off the squares' centres. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
99a5318fe4
commit
b5759662c1
@@ -111,6 +111,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist
|
||||
- Dialogs move by dragging their heading (the band down from the top edge, as a title bar), stay on screen and keep where they were moved for the session; the form dialogs' choices are dropdowns (Offset Path's Joins, Average's and Shear's and Reflect's axis), and Offset Path lists Offset, Joins, Miter Limit and previews (#423).
|
||||
- Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the focused field (#485: a notch a step; the panel under it then stays put, and an unfocused field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once.
|
||||
- Scrubby labels (#400, beyond Illustrator, as in Photoshop): dragging a numeric field's label left or right (the ↔ cursor shows over it) steps the value one unit every 2 points (Shift: ten, Ctrl/Cmd: a tenth; counts stay whole), in the field's unit and within its limits, applied live as the art follows; the whole drag is one undo step and Escape puts the value back. A click on the label focuses the field; the text box itself never scrubs, and a field with no label doesn't. Text labels before a field (Transform, Properties, Control bar, Stroke, Transparency, form rows in dialogs) and the Character and Paragraph panels' icons act as labels. Preferences › General › Scrub Numeric Fields by Dragging (on by default) turns it off.
|
||||
- The Control bar's underlined Transform link opens the Transform panel in a popover under it (reference point, X/Y/W/H, Rotate and Shear with their icons and angle presets, flips, shape properties, Scale Corners and Scale Strokes & Effects), as its Stroke link does the Stroke panel; X/Y/W/H also show inline while the bar has room. The reference point locator is nine squares, the outer eight joined around the box.
|
||||
- Tab shows and hides the panels (in the Type tool it types a tab) without moving the keyboard focus onto a field, so the next shortcut or letter still works; in dialogs and panel fields it moves between fields.
|
||||
- Cmd/Ctrl+A follows the keyboard: in a panel, dialog or search field it selects the field's text, while the Type tool edits it selects all of that text (also `select.all` over MCP), and only otherwise every object. On macOS the menu bar's Select All, Cut, Copy and Paste (keys or clicks) act on a focused field too. Not yet: Select All across threaded frames (one frame's text is selected).
|
||||
- On Windows and Linux the app bar is the window's title bar (its own minimize, maximize and close buttons, drag to move, edges to resize), with the dragon app icon as the brand mark; dialogs and menus size to their content. As on a native menu bar, once one menu is open, moving the pointer onto another title opens that one.
|
||||
|
||||
@@ -146,6 +146,9 @@ pub fn anchor_buttons(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The room the Control bar's inline X/Y/W/H fields need; with less, only the Transform link shows.
|
||||
const INLINE_TRANSFORM_WIDTH: f32 = 440.0;
|
||||
|
||||
/// The Control bar (Window → Control), context-sensitive like Illustrator's.
|
||||
pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
@@ -252,9 +255,13 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
// Transform fields.
|
||||
// The Transform link opens the whole Transform panel (reference point, rotate,
|
||||
// shear, options) in a popover; X/Y/W/H follow inline while the bar has room.
|
||||
crate::panels::transform::link(app, ui);
|
||||
// The bounding box, rotated with rotated objects: its centre and its own sides.
|
||||
if let Some(b) = app.selection_box() {
|
||||
if let Some(b) = app.selection_box()
|
||||
&& ui.available_width() >= INLINE_TRANSFORM_WIDTH
|
||||
{
|
||||
let c = b.center();
|
||||
let link = app.session.prefs.constrain_proportions;
|
||||
for (k, lbl, v) in [("x", "X:", c.x), ("y", "Y:", c.y), ("width", "W:", b.rect.width()), ("height", "H:", b.rect.height())] {
|
||||
|
||||
@@ -2546,6 +2546,7 @@
|
||||
Character: Carácter:
|
||||
Character options Opciones de carácter
|
||||
Stroke options Opciones de trazo
|
||||
Transform options Opciones de transformación
|
||||
Stroke Weight Grosor de trazo
|
||||
Stroke: Trazo:
|
||||
Stroke: click for swatches, Shift-click for the color mixer Trazo: clic para muestras, Shift+clic para el mezclador de colores
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2352,6 +2352,7 @@
|
||||
Character: 文字:
|
||||
Character options 文字オプション
|
||||
Stroke options 線オプション
|
||||
Transform options 変形オプション
|
||||
Stroke: 線:
|
||||
Style スタイル
|
||||
Style Name: スタイル名:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2248,6 +2248,7 @@
|
||||
Character: Caractere:
|
||||
Character options Opções de caractere
|
||||
Stroke options Opções de traçado
|
||||
Transform options Opções de transformação
|
||||
Stroke: Traçado:
|
||||
Style Estilo
|
||||
Style Name: Nome do Estilo:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2484,6 +2484,7 @@
|
||||
Character: 字符:
|
||||
Character options 字符选项
|
||||
Stroke options 描边选项
|
||||
Transform options 变换选项
|
||||
Stroke Weight 描边粗细
|
||||
Stroke: 描边:
|
||||
Stroke: click for swatches, Shift-click for the color mixer 描边:单击开启色板,按住 Shift 单击开启混色器
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2566,6 +2566,7 @@
|
||||
Character: 字元:
|
||||
Character options 字元選項
|
||||
Stroke options 筆畫選項
|
||||
Transform options 變形選項
|
||||
Stroke Weight 筆畫寬度
|
||||
Stroke: 筆畫:
|
||||
Stroke: click for swatches, Shift-click for the color mixer 筆畫:按一下開啟色票,按住 Shift 按一下開啟混色器
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -13,6 +13,8 @@ use crate::widgets::{self, menu_item};
|
||||
use crate::{VectorcraftApp, icons};
|
||||
|
||||
pub const ANGLE_PRESETS: [f64; 9] = [-180.0, -135.0, -90.0, -45.0, 0.0, 45.0, 90.0, 135.0, 180.0];
|
||||
/// The shear angle's presets: steep enough to be useful, short of the degenerate ±90°.
|
||||
pub const SHEAR_PRESETS: [f64; 9] = [-60.0, -45.0, -30.0, -15.0, 0.0, 15.0, 30.0, 45.0, 60.0];
|
||||
|
||||
/// Proportional size: the other dimension when one changes with the link on.
|
||||
pub fn constrained(w: f64, h: f64, new_w: Option<f64>, new_h: Option<f64>) -> (f64, f64) {
|
||||
@@ -151,8 +153,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
ui.add_space(4.0);
|
||||
let icon = icons::icon(ui, "dc-shear", 16.0, t.icon).on_hover_text(tl!("Shear"));
|
||||
crate::scrub::note_label(ui, icon.rect);
|
||||
if let Some(a) = widgets::plain_field(ui, "xfp-shear", 0.0, "°", 1, 56.0)
|
||||
// Shear is relative: the field rests at 0° and a new value shears by it.
|
||||
if let Some(a) = widgets::spin_plain(ui, "xfp-shear", 0.0, "°", 2, 96.0, 15.0, -89.0, &SHEAR_PRESETS)
|
||||
&& a != 0.0
|
||||
&& a.abs() < 90.0
|
||||
{
|
||||
app.run("object.shear", json!({"angle": a, "axis": "horizontal", "origin": origin})).ok();
|
||||
}
|
||||
@@ -209,6 +213,17 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Control bar's underlined "Transform" link: a click toggles the Transform panel in a
|
||||
/// popover under it, as the Stroke link does with the Stroke panel.
|
||||
pub fn link(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let resp = ui.link(egui::RichText::new(tl!("Transform")).size(12.0).color(t.text).underline()).on_hover_text(tl!("Transform options"));
|
||||
widgets::popover(&resp, resp.clicked(), |ui| {
|
||||
ui.set_width(300.0);
|
||||
show(app, ui);
|
||||
});
|
||||
}
|
||||
|
||||
/// The link between W and H (Transform panel, Properties panel, Control bar): one toggle, the
|
||||
/// `constrainProportions` preference, which the size fields pass on as `proportional`.
|
||||
pub fn constrain_link(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
|
||||
@@ -242,3 +242,31 @@ fn the_window_menu_panel_keys() {
|
||||
app.ui.shortcut_overrides.clear();
|
||||
shortcut_editor::sync(&app.ui);
|
||||
}
|
||||
|
||||
/// Where `shapes` paint the text `s`.
|
||||
fn text_rect(shapes: &[Shape], s: &str) -> Option<Rect> {
|
||||
shapes.iter().find_map(|sh| match sh {
|
||||
Shape::Text(t) if t.galley.text() == s => Some(t.visual_bounding_rect()),
|
||||
Shape::Vec(v) => text_rect(v, s),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_transform_link_opens_the_transform_panel() {
|
||||
let mut app = app();
|
||||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
|
||||
let ctx = context();
|
||||
frame(&mut app, &ctx, vec![], Modifiers::NONE, control_bar);
|
||||
let shapes = frame(&mut app, &ctx, vec![], Modifiers::NONE, control_bar);
|
||||
let link = text_rect(&shapes, "Transform").expect("the Control bar shows the Transform link");
|
||||
assert!(text_rect(&shapes, "Scale Corners").is_none());
|
||||
click(&mut app, &ctx, link.center(), Modifiers::NONE, control_bar);
|
||||
let shapes = frame(&mut app, &ctx, vec![], Modifiers::NONE, control_bar);
|
||||
for s in ["Scale Corners", "Scale Strokes & Effects", "0°"] {
|
||||
assert!(text_rect(&shapes, s).is_some(), "the popover shows {s}");
|
||||
}
|
||||
// A click outside closes it.
|
||||
click(&mut app, &ctx, Pos2::new(1300.0, 900.0), Modifiers::NONE, control_bar);
|
||||
assert!(text_rect(&frame(&mut app, &ctx, vec![], Modifiers::NONE, control_bar), "Scale Corners").is_none());
|
||||
}
|
||||
|
||||
@@ -806,18 +806,23 @@ pub fn opacity_blend(
|
||||
edit
|
||||
}
|
||||
|
||||
/// The 3×3 reference point locator. Returns a new index when clicked.
|
||||
/// The 3×3 reference point locator: nine squares, the eight outer ones joined by a line around
|
||||
/// the box (the centre one stands alone), the current one filled. Returns a new index when clicked.
|
||||
pub fn reference_point(ui: &mut Ui, current: usize) -> Option<usize> {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let size = 22.0;
|
||||
// 5 pt squares with 4 pt gaps; the origin is snapped to whole points so the 1 pt lines through
|
||||
// the squares' centres stay crisp.
|
||||
let (sq, step) = (5.0, 9.0);
|
||||
let size = 2.0 * step + sq;
|
||||
let (rect, _) = ui.allocate_exact_size(Vec2::splat(size), Sense::hover());
|
||||
let o = rect.min.round();
|
||||
let centre = |i: usize| o + Vec2::new(sq / 2.0 + (i % 3) as f32 * step, sq / 2.0 + (i / 3) as f32 * step);
|
||||
let line = Stroke::new(1.0, t.text_dim);
|
||||
ui.painter().rect_stroke(Rect::from_two_pos(centre(0), centre(8)), 0.0, line, StrokeKind::Middle);
|
||||
let mut out = None;
|
||||
let step = size / 2.0 - 2.5;
|
||||
ui.painter().rect_stroke(rect.shrink(4.5), 0.0, Stroke::new(1.0, t.text_dim), StrokeKind::Middle);
|
||||
for i in 0..9 {
|
||||
let c = Pos2::new(rect.left() + 3.0 + (i % 3) as f32 * step + 1.5, rect.top() + 3.0 + (i / 3) as f32 * step + 1.5);
|
||||
let r = Rect::from_center_size(c, Vec2::splat(5.0));
|
||||
let resp = ui.interact(r.expand(1.5), ui.id().with(("refpt", i)), Sense::click());
|
||||
let r = Rect::from_center_size(centre(i), Vec2::splat(sq));
|
||||
let resp = ui.interact(r.expand(2.0), ui.id().with(("refpt", i)), Sense::click());
|
||||
if resp.clicked() {
|
||||
out = Some(i);
|
||||
}
|
||||
@@ -825,7 +830,7 @@ pub fn reference_point(ui: &mut Ui, current: usize) -> Option<usize> {
|
||||
ui.painter().rect_filled(r, 0.0, t.text);
|
||||
} else {
|
||||
ui.painter().rect_filled(r, 0.0, t.panel);
|
||||
ui.painter().rect_stroke(r, 0.0, Stroke::new(1.0, t.text_dim), StrokeKind::Inside);
|
||||
ui.painter().rect_stroke(r, 0.0, Stroke::new(1.0, if resp.hovered() { t.text } else { t.text_dim }), StrokeKind::Inside);
|
||||
}
|
||||
}
|
||||
out
|
||||
|
||||
Reference in New Issue
Block a user