Rulers: the corner menu sets both rulers' units; ruler menus as InDesign's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
moppeeer
2026-10-15 21:15:15 +02:00
co-authored by Claude Opus 5.5
parent a930276fd7
commit 0e5a6bd9d1
10 changed files with 90 additions and 16 deletions
+1
View File
@@ -347,6 +347,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
Newest first.
- 2026-10-15: Ruler menus as InDesign's: right-clicking where the rulers meet sets the units of both; each ruler's menu lists InDesign's units and Hide Rulers, the horizontal one Delete All Guides on Spread.
- **2026-10-14** · The Type tool draws a new text frame over a locked empty frame, as InDesign does, instead of turning the locked frame into a text frame. No readiness percentage change.
- **2026-10-14** · 100% black shows as pure black on screen by default, as InDesign's Appearance of Black does out of the box (Display All Blacks as Rich Black); Preferences › Appearance of Black › Display All Blacks Accurately still shows it as the dark grey it prints, and a saved choice is kept. No readiness percentage change.
- **2026-10-14** · Pathfinder keeps the frontmost object, as InDesign does: a photo placed over shapes and intersected with them shows through the shapes (it was dropped, and the shapes kept their black fill); Add, Exclude Overlap and Minus Back take the front object's fill too, Subtract still keeps the back one. No readiness percentage change.
+80 -16
View File
@@ -1583,33 +1583,78 @@ fn ruler_rects(full: Rect, rect: Rect) -> (Rect, Rect) {
)
}
/// Right-clicking a ruler opens a menu of units for that ruler.
/// Right-clicking a ruler opens its menu, as InDesign's: units for that ruler, Hide Rulers and
/// (the horizontal ruler) Delete All Guides on Spread. Right-clicking the corner where they meet
/// opens the units for both rulers at once.
fn ruler_units_menus(app: &mut DesignApp, resp: &egui::Response, full: Rect, rect: Rect, doc: &Document) {
let (top, left) = ruler_rects(full, rect);
let corner = Rect::from_min_max(full.min, rect.min);
let at = resp.interact_pointer_pos();
for (ruler, key, cur) in [(top, "horizontalUnits", doc.settings.horizontal_units), (left, "verticalUnits", doc.settings.vertical_units)] {
let (h, v) = (doc.settings.horizontal_units, doc.settings.vertical_units);
// (where, which units it sets, the unit checked)
let menus = [(top, RulerMenu::Horizontal, Some(h)), (left, RulerMenu::Vertical, Some(v)), (corner, RulerMenu::Both, (h == v).then_some(h))];
for (area, which, cur) in menus {
let open = if resp.secondary_clicked() {
Some(egui::SetOpenCommand::Bool(at.is_some_and(|p| ruler.contains(p))))
Some(egui::SetOpenCommand::Bool(at.is_some_and(|p| area.contains(p))))
} else if resp.clicked() {
Some(egui::SetOpenCommand::Bool(false))
} else {
None
};
egui::Popup::context_menu(resp).id(resp.id.with(("ruler_units", key))).open_memory(open).show(|ui| ruler_units_menu(app, ui, key, cur));
egui::Popup::context_menu(resp)
.id(resp.id.with(("ruler_units", which as u8)))
.open_memory(open)
.show(|ui| ruler_units_menu(app, ui, which, cur));
}
}
/// Every unit, the current one checked; picking one sets `key` (`horizontalUnits` or
/// `verticalUnits`) through `document.preferences`.
fn ruler_units_menu(app: &mut DesignApp, ui: &mut egui::Ui, key: &str, cur: designcraft_geom::Unit) {
for u in designcraft_geom::Unit::ALL {
let label = crate::i18n::tr(&app.ui.language, u.label());
let text = if u == cur { format!("✓ {label}") } else { format!(" {label}") };
#[derive(Clone, Copy, PartialEq)]
enum RulerMenu {
Horizontal,
Vertical,
Both,
}
/// The units a ruler's menu offers, in InDesign's order.
const RULER_UNITS: [designcraft_geom::Unit; 9] = {
use designcraft_geom::Unit::*;
[Points, Picas, Inches, InchesDecimal, Millimeters, Centimeters, Ciceros, Agates, Pixels]
};
/// A ruler's menu: the units (the current one checked), then Hide Rulers and, on the horizontal
/// ruler, Delete All Guides on Spread. Picking a unit sets `horizontalUnits`, `verticalUnits` or
/// both through `document.preferences`.
fn ruler_units_menu(app: &mut DesignApp, ui: &mut egui::Ui, which: RulerMenu, cur: Option<designcraft_geom::Unit>) {
let lang = app.ui.language.clone();
let tr = |s: &str| crate::i18n::tr(&lang, s).to_string();
// A document in Q or Ha keeps its unit on the list.
let extra = cur.filter(|u| !RULER_UNITS.contains(u));
for u in RULER_UNITS.into_iter().chain(extra) {
let label = tr(u.label());
let text = if Some(u) == cur { format!("✓ {label}") } else { format!(" {label}") };
if ui.button(crate::rtl::widget(ui, text)).clicked() {
let _ = app.run("document.preferences", json!({ key: u }));
let p = match which {
RulerMenu::Horizontal => json!({"horizontalUnits": u}),
RulerMenu::Vertical => json!({"verticalUnits": u}),
RulerMenu::Both => json!({"horizontalUnits": u, "verticalUnits": u}),
};
let _ = app.run("document.preferences", p);
ui.close();
}
}
ui.separator();
let hide = format!(" {}", tr("Hide Rulers"));
if ui
.add(egui::Button::new(crate::rtl::widget(ui, hide)).shortcut_text(crate::menus::shortcut_of(app, "view.rulers").unwrap_or_default()))
.clicked()
{
let _ = app.run("view.rulers", json!({}));
ui.close();
}
if which == RulerMenu::Horizontal && ui.button(crate::rtl::widget(ui, format!(" {}", tr("Delete All Guides on Spread")))).clicked() {
crate::menus::activate(app, "app.deleteAllGuides", &Value::Null);
ui.close();
}
}
fn fmt_tick(v: f64) -> String {
@@ -2500,11 +2545,30 @@ mod tests {
h.get_by_label(" Inches").click();
h.run_steps(3);
assert_eq!(units(&h), (Unit::Inches, Unit::Millimeters));
// The page and the ruler corner open no units menu.
for p in [rect.center(), rect.min - vec2(RULER / 2.0, RULER / 2.0)] {
right_click(&mut h, p);
assert!(h.query_by_label(" Points").is_none());
}
// The corner where the rulers meet sets both, as InDesign's: nothing is checked while
// they differ.
let corner = rect.min - vec2(RULER / 2.0, RULER / 2.0);
right_click(&mut h, corner);
assert!(h.query_by_label("✓ Inches").is_none() && h.query_by_label("✓ Millimeters").is_none());
let _ = h.get_by_label_contains("Hide Rulers");
assert!(h.query_by_label(" Delete All Guides on Spread").is_none(), "only on the horizontal ruler");
h.get_by_label(" Points").click();
h.run_steps(3);
assert_eq!(units(&h), (Unit::Points, Unit::Points));
// InDesign's list: no Q or Ha.
right_click(&mut h, corner);
h.get_by_label("✓ Points");
assert!(h.query_by_label(" Q").is_none() && h.query_by_label(" Ha").is_none());
let _ = h.get_by_label(" Inches Decimal");
// The horizontal ruler's menu also deletes the spread's guides; Hide Rulers hides them.
right_click(&mut h, pos2(rect.center().x, rect.min.y - RULER / 2.0));
let _ = h.get_by_label(" Delete All Guides on Spread");
h.get_by_label_contains("Hide Rulers").click();
h.run_steps(3);
assert!(!h.state().ui.rulers);
// The page opens no units menu.
right_click(&mut h, rect.center());
assert!(h.query_by_label(" Points").is_none());
}
/// A text frame holding `text`, the Type tool, and a harness stepping at 60 fps (clicks in a
+2
View File
@@ -58,6 +58,8 @@ const TABLE: &[(&str, [&str; 5])] = &[
("Cut", ["Ausschneiden", "Couper", "Cortar", "カット", "剪切"]),
("Copy", ["Kopieren", "Copier", "Copiar", "コピー", "复制"]),
("Paste", ["Einfügen", "Coller", "Pegar", "ペースト", "粘贴"]),
("Hide Rulers", ["Lineale ausblenden", "Masquer les règles", "Ocultar reglas", "定規を隠す", "隐藏标尺"]),
("Inches Decimal", ["Zoll dezimal", "Pouces décimaux", "Pulgadas decimales", "インチ(10 進)", "十进制英寸"]),
(
"Bulleted & Numbered Lists",
[
+1
View File
@@ -30,6 +30,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "قص"),
("Copy", "نسخ"),
("Paste", "لصق"),
("Inches Decimal", "بوصات عشرية"),
("Bulleted & Numbered Lists", "القوائم ذات التعداد النقطي والرقمي"),
("Current Page Number", "رقم الصفحة الحالية"),
("Apply Color (,)", "تطبيق اللون (,)"),
+1
View File
@@ -30,6 +30,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Ausschneiden"),
("Copy", "Kopieren"),
("Paste", "Einfügen"),
("Inches Decimal", "Zoll dezimal"),
("Clear", "Löschen"),
("Duplicate", "Duplizieren"),
("Select All", "Alles auswählen"),
+1
View File
@@ -28,6 +28,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Taglia"),
("Copy", "Copia"),
("Paste", "Incolla"),
("Inches Decimal", "Pollici decimali"),
("Bulleted & Numbered Lists", "Elenchi puntati e numerati"),
("Current Page Number", "Numero di pagina corrente"),
("Apply Color (,)", "Applica colore (,)"),
+1
View File
@@ -30,6 +30,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "カット"),
("Copy", "コピー"),
("Paste", "ペースト"),
("Inches Decimal", "インチ(10 進)"),
("Bulleted & Numbered Lists", "箇条書き"),
("Apply Color (,)", "カラーを適用 (,)"),
("Apply Gradient (.)", "グラデーションを適用 (.)"),
+1
View File
@@ -31,6 +31,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "ამოჭრა"),
("Copy", "კოპირება"),
("Paste", "ჩასმა"),
("Inches Decimal", "ინჩები (ათობითი)"),
("Reverse Path", "ტრაექტორიის შებრუნება"),
("Set Anchor", "საყრდენი წერტილის დაყენება"),
("Clear", "გასუფთავება"),
+1
View File
@@ -31,6 +31,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Recortar"),
("Copy", "Copiar"),
("Paste", "Colar"),
("Inches Decimal", "Polegadas decimais"),
("Bulleted & Numbered Lists", "Listas com marcadores e numeradas"),
("Show Hidden Characters", "Mostrar caracteres ocultos"),
("Current Page Number", "Número da página atual"),
+1
View File
@@ -31,6 +31,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Cut", "Вирізати"),
("Copy", "Копіювати"),
("Paste", "Вставити"),
("Inches Decimal", "Дюйми (десяткові)"),
("Bulleted & Numbered Lists", "Марковані й нумеровані списки"),
("Current Page Number", "Номер поточної сторінки"),
("Apply Color (,)", "Застосувати колір (,)"),