Properties: selected ruler guides show their position in a Transform section

With ruler guides selected on their own (a click on one), the
Properties panel showed the Document sections, so a guide's position
could be typed only in the Transform panel.

The panel now has a Guides heading and a Transform section with the
Transform panel's guide fields (#1597): X moves the vertical guides and
Y the horizontal ones, together. With art selected too, it shows the
art's sections as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EjfPR4a1jPek7r3nyFFNYv
This commit is contained in:
nevarem
2026-10-29 18:46:47 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent d93657c631
commit a7db76bdfb
3 changed files with 82 additions and 14 deletions
+28 -8
View File
@@ -24,13 +24,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let first = first_selected(app);
// Edit Artboards (the Artboard tool): the active artboard, whatever is selected.
let editing_artboards = app.session.tool_id() == "artboard";
let label = match (&first, n_sel) {
_ if editing_artboards => tl!("Artboard").to_string(),
(None, _) => tl!("Document").to_string(),
(_, n) if n > 1 => crate::i18n::tn(n as u64, "{n} Object", "{n} Objects"),
(Some(n), _) if crate::panels::image_trace::is_trace(n) => tl!("Image Tracing").to_string(),
(Some(n), _) => tl!(n.kind_label()).to_string(),
};
// Ruler guides selected on their own (a click on one): their position.
let guides = super::transform::selected_guides(app);
let label = heading(first.as_ref(), n_sel, editing_artboards, guides.is_some());
ui.label(egui::RichText::new(label).size(11.5).color(t.text_dim));
ui.add_space(4.0);
if editing_artboards {
@@ -38,7 +34,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
return;
}
if first.is_none() {
document_sections(app, ui);
match guides {
Some(guides) => guide_sections(app, ui, guides),
None => document_sections(app, ui),
}
return;
}
if n_sel == 1
@@ -592,6 +591,27 @@ fn artboard_fill_row(app: &mut VectorcraftApp, ui: &mut Ui, i: usize, color: Opt
}
}
/// The panel's heading: what the sections are for (the artboards with the Artboard tool, the
/// selected ruler guides, the document with nothing selected), else the selection's kind.
fn heading(first: Option<&vectorcraft_doc::Node>, n_sel: usize, editing_artboards: bool, guides: bool) -> String {
match (first, n_sel) {
_ if editing_artboards => tl!("Artboard").to_string(),
(None, _) if guides => tl!("Guides").to_string(),
(None, _) => tl!("Document").to_string(),
(_, n) if n > 1 => crate::i18n::tn(n as u64, "{n} Object", "{n} Objects"),
(Some(n), _) if crate::panels::image_trace::is_trace(n) => tl!("Image Tracing").to_string(),
(Some(n), _) => tl!(n.kind_label()).to_string(),
}
}
/// Ruler guides selected on their own: a Transform section with the X of the vertical ones and
/// the Y of the horizontal ones, which move them, as the Transform panel shows them (#1597).
fn guide_sections(app: &mut VectorcraftApp, ui: &mut Ui, guides: (Option<f64>, Option<f64>)) {
section_header(ui, tl!("Transform"));
let fw = super::field_width(ui);
super::transform::guide_fields(app, ui, guides, fw);
}
fn document_sections(app: &mut VectorcraftApp, ui: &mut Ui) {
let units = app.session.general_unit();
section_header(ui, tl!("Document"));
@@ -1600,3 +1600,50 @@ fn a_symbol_instance_turns_about_its_registration_point() {
assert!((bx.angle - 45.0).abs() < 1e-6, "turned to {}", bx.angle);
assert!(near(bx.reference_point(8), vectorcraft_geom::Point::new(200.0, 160.0)), "the registration point moved to {:?}", bx.reference_point(8));
}
/// Ruler guides selected on their own (a click on one) show a Guides heading and a Transform
/// section, where X moves the vertical ones and Y the horizontal ones; with art selected too, the
/// panel shows the art's sections.
#[test]
fn selected_guides_show_their_position_in_a_transform_section() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
let v = app.run("guide.add", json!({"vertical": true, "pos": 30})).unwrap()["index"].as_u64().unwrap();
let h = app.run("guide.add", json!({"vertical": false, "pos": 20})).unwrap()["index"].as_u64().unwrap();
let pos = |app: &VectorcraftApp, i: u64| app.session.doc().unwrap().doc.guides[i as usize].pos;
let ctx = egui::Context::default();
// Steps the field labelled `label` up by one with ↑.
let step = |app: &mut VectorcraftApp, label: &str| {
let texts = frame(app, &ctx, vec![]);
let r = texts.iter().find(|(t, _)| t == label).unwrap_or_else(|| panic!("no {label:?} in {texts:?}")).1;
let at = egui::pos2(r.right() + 40.0, r.center().y);
let press = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() };
frame(app, &ctx, vec![Event::PointerMoved(at), press(true)]);
frame(app, &ctx, vec![press(false)]);
let up = Event::Key { key: egui::Key::ArrowUp, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() };
frame(app, &ctx, vec![up]);
frame(
app,
&ctx,
vec![Event::PointerButton { pos: egui::pos2(5.0, 690.0), button: PointerButton::Primary, pressed: true, modifiers: Default::default() }],
);
};
for (index, label, before) in [(v, "X:", 30.0), (h, "Y:", 20.0)] {
app.run("guide.select", json!({"indexes": [index]})).unwrap();
frame(&mut app, &ctx, vec![]);
let texts: Vec<String> = frame(&mut app, &ctx, vec![]).into_iter().map(|(t, _)| t).collect();
for shown in ["Guides", "Transform", "X:", "Y:"] {
assert!(texts.iter().any(|t| t == shown), "{shown:?} in {texts:?}");
}
assert!(!texts.iter().any(|t| t == "Document"), "{texts:?}");
step(&mut app, label);
assert_eq!((pos(&app, index) - before).abs(), 1.0, "{label} moves the selected guide");
}
// A guide and a rectangle: the rectangle's sections.
let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 40})).unwrap()["id"].as_u64().unwrap();
app.run("select.set", json!({"ids": [id]})).unwrap();
app.run("guide.select", json!({"indexes": [v], "toggle": true})).unwrap();
frame(&mut app, &ctx, vec![]);
let texts: Vec<String> = frame(&mut app, &ctx, vec![]).into_iter().map(|(t, _)| t).collect();
assert!(!texts.iter().any(|t| t == "Guides") && texts.iter().any(|t| t == "W:"), "{texts:?}");
}
+7 -6
View File
@@ -215,7 +215,7 @@ fn artboard_fields(app: &mut VectorcraftApp, ui: &mut Ui, index: usize, r: Rect)
/// With ruler guides selected and no art: the X of the vertical ones and the Y of the horizontal
/// ones (the least of each; none where no such guide is selected).
fn selected_guides(app: &VectorcraftApp) -> Option<(Option<f64>, Option<f64>)> {
pub(crate) fn selected_guides(app: &VectorcraftApp) -> Option<(Option<f64>, Option<f64>)> {
let st = app.session.active()?;
if !st.selection.objects.is_empty() || st.selection.guides.is_empty() {
return None;
@@ -226,16 +226,17 @@ fn selected_guides(app: &VectorcraftApp) -> Option<(Option<f64>, Option<f64>)> {
Some((least(true), least(false)))
}
/// The selected ruler guides' view of the panel (#1597): X moves the vertical ones and Y the
/// horizontal ones, together.
fn guide_fields(app: &mut VectorcraftApp, ui: &mut Ui, (x, y): (Option<f64>, Option<f64>)) {
/// The selected ruler guides' view of the panel (#1597), also the Properties panel's Transform
/// section for them: X moves the vertical ones and Y the horizontal ones, together. The fields
/// are `width` wide.
pub(crate) fn guide_fields(app: &mut VectorcraftApp, ui: &mut Ui, (x, y): (Option<f64>, Option<f64>), width: f32) {
let units = app.session.general_unit();
let origin = app.ruler_origin();
egui::Grid::new("xfp-guide-grid").num_columns(2).spacing([4.0, 6.0]).min_col_width(0.0).show(ui, |ui| {
for (label, id, pos, origin, by) in [("X:", "xfp-guide-x", x, origin.x, "dx"), ("Y:", "xfp-guide-y", y, origin.y, "dy")] {
widgets::dim_label(ui, label);
ui.add_enabled_ui(pos.is_some(), |ui| {
if let (Some(v), Some(pos)) = (widgets::num_field_named(ui, id, Some(tl!(label)), pos.map(|p| p - origin), units, 80.0), pos)
if let (Some(v), Some(pos)) = (widgets::num_field_named(ui, id, Some(tl!(label)), pos.map(|p| p - origin), units, width), pos)
&& let Err(e) = app.run("guide.move", json!({ by: v + origin - pos }))
{
app.status(e);
@@ -257,7 +258,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
return;
}
if let Some(guides) = selected_guides(app) {
guide_fields(app, ui, guides);
guide_fields(app, ui, guides, 80.0);
return;
}
let units = app.session.general_unit();