feat(ui): Control panel for the Page tool; Controlled by Parent; Show Parent Page Overlay

Chris Cicchino's "Adobe InDesign – Page Tool and Liquid Layout" (1:02–
2:06, 13:47) sizes pages from the Control panel with the Page tool
selected: X/Y, W/H, the page-size preset ("US Business Card"),
orientation, Liquid Page Rule ("Controlled by Parent"), Objects Move with
Page and Show Parent Page Overlay. DesignCraft's Control panel showed the
(empty) object controls.

- ui: with the Page tool, the Control panel shows the selected page (or
  the page in view): the reference point (W/H, preset and orientation
  keep it in place via layout.pageSize's anchor), X/Y (read-only),
  W/H, the preset dropdown, portrait/landscape, Liquid Page Rule, Objects
  Move with Page (always on: pages don't move within spreads yet) and
  Show Parent Page Overlay.
- doc: LiquidRule::ControlledByParent (the parent page's rule, Off
  without one; resolved by liquid::effective_rule); Page.show_parent_overlay.
- engine: layout.pageOptions {pages, showParentOverlay}; liquid.pageRule
  takes controlledByParent.
- canvas: the parent page's size over a page with the overlay on.
- Liquid Layout panel and Control panel share InDesign's rule list
  (Off, Scale, Re-center, Object-based, Guide-based, Controlled by
  Parent); "US Business Card" preset; translations.

Builds on the Page tool PR (#862) for the selected page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
moppeeerandClaude Opus 5.5 committed 2026-10-14 00:51:47 +02:00
1 parent cc13e80c9d
commit 59b6125af1
18 files changed
+280 -25

No files matched your search

+1
View File
@@ -129,6 +129,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
Newest first.
- **2026-10-14** · With the Page tool, the Control panel shows the page as InDesign's does: W/H from a reference point, page-size preset (US Business Card added), orientation, Liquid Page Rule (now with Controlled by Parent) and Show Parent Page Overlay, which draws the parent page's size over a resized page. No readiness percentage change.
- **2026-10-14** · The Page tool works as InDesign's: a click selects a page and shows its handles and liquid page rule; dragging a handle previews the rule and springs back, Alt-drag resizes the page from the opposite edge, Shift keeps proportions, with a W/H readout. No readiness percentage change.
- **2026-10-12** · macOS Dock Quit uses the same unsaved-document prompts as window and menu Quit. Repeated requests keep the current answers; Cancel retains every tab and unsaved recovery checkpoint, and completed Save/Don't Save answers exit through the ordinary event loop. No readiness percentage change.
- **2026-10-12** · Orderly native exit writes the latest recovery state after finalizing current quit approvals; incomplete or stale plans keep their documents, and Undo to the saved state clears stale recovery. Unavailable or failed recovery is reported. Cancelable macOS Dock Quit remains open (#544). No readiness percentage change.
+1
View File
@@ -226,6 +226,7 @@ impl Document {
liquid: Default::default(),
transition: None,
view_rotation: 0,
show_parent_overlay: false,
}
}
+6
View File
@@ -114,6 +114,9 @@ pub struct Page {
/// Liquid page rule: how objects follow when the page changes size.
#[serde(default, skip_serializing_if = "LiquidRule::is_off")]
pub liquid: LiquidRule,
/// Page tool › Show Parent Page Overlay: the parent page's size drawn over this page.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub show_parent_overlay: bool,
/// Window › Interactive › Page Transitions (the spread's first page holds the spread's).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transition: Option<PageTransition>,
@@ -208,6 +211,8 @@ pub enum LiquidRule {
GuideBased,
/// Each object's pins and resize settings decide (`Item::liquid`).
ObjectBased,
/// The rule of the page's parent page (InDesign's "Controlled by Parent"; Off without one).
ControlledByParent,
}
impl LiquidRule {
@@ -367,6 +372,7 @@ mod tests {
liquid: Default::default(),
transition: None,
view_rotation: 0,
show_parent_overlay: false,
}
}
+29
View File
@@ -380,6 +380,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_doc,
page_size
),
cmd!(
"layout.pageOptions",
"Page Options",
[],
None,
"{pages: [1-based], showParentOverlay?: bool} — Page tool options (Show Parent Page Overlay: the parent page's size drawn over the page)",
has_doc,
page_options
),
cmd!(noundo "layout.selectPage", "Select Page", [], None, "{page: 1-based | null} — the page the Page tool selects (its handles, the Control panel)", has_doc, |s, p| {
let n = s.doc()?.doc.page_count();
let page = match p.get("page") {
@@ -664,6 +673,26 @@ fn page_size(s: &mut crate::Session, p: &Value) -> Result<Value> {
})
}
fn page_options(s: &mut crate::Session, p: &Value) -> Result<Value> {
let pages: Vec<usize> =
p.get("pages").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_u64).map(|n| n as usize).collect()).unwrap_or_default();
let n = s.doc()?.doc.page_count();
if pages.is_empty() || pages.iter().any(|x| *x == 0 || *x > n) {
return Err(bad("layout.pageOptions", "give `pages` (1-based)"));
}
let overlay = p.get("showParentOverlay").and_then(Value::as_bool);
s.edit(|d, _| {
for page in &pages {
let Some((si, pi)) = d.page_loc(page - 1) else { continue };
let pg = &mut std::sync::Arc::make_mut(&mut d.spreads[si]).pages[pi];
if let Some(v) = overlay {
pg.show_parent_overlay = v;
}
}
Ok(json!({"pages": pages.len()}))
})
}
/// Give `pages` (1-based) their own size. Objects stay on their page; pages with a liquid rule
/// re-flow their objects by it.
pub(crate) fn resize_pages(d: &mut designcraft_doc::Document, pages: &[usize], w: Option<f64>, h: Option<f64>) {
+20 -4
View File
@@ -35,7 +35,7 @@ fn axis(lo: f64, hi: f64, o: (f64, f64), n: (f64, f64), pin_lo: bool, pin_hi: bo
fn target(rule: LiquidRule, b: Rect, old: Rect, new: Rect, liquid: Option<ObjectLiquid>, vguides: &[f64], hguides: &[f64]) -> Rect {
let (ox, oy, nx, ny) = ((old.x0, old.x1), (old.y0, old.y1), (new.x0, new.x1), (new.y0, new.y1));
match rule {
LiquidRule::Off => b,
LiquidRule::Off | LiquidRule::ControlledByParent => b,
LiquidRule::ReCenter => b + (new.center() - old.center()),
LiquidRule::Scale => {
let k = (new.width() / old.width()).min(new.height() / old.height());
@@ -63,7 +63,7 @@ fn target(rule: LiquidRule, b: Rect, old: Rect, new: Rect, liquid: Option<Object
pub(crate) fn apply(d: &mut Document, sr: SpreadRef, pi: usize, old: Rect) {
let Some(sp) = d.spread(sr) else { return };
let Some(pg) = sp.pages.get(pi) else { return };
let (rule, new) = (pg.liquid, pg.bounds());
let (rule, new) = (effective_rule(d, sr, pi), pg.bounds());
if rule == LiquidRule::Off || old == new || old.width() < 1e-6 || old.height() < 1e-6 {
return;
}
@@ -108,8 +108,24 @@ pub(crate) fn apply(d: &mut Document, sr: SpreadRef, pi: usize, old: Rect) {
}
}
/// The rule page `pi` of spread `sr` follows: its own, or its parent page's when it's controlled
/// by the parent (Off when that's unset too).
pub(crate) fn effective_rule(d: &Document, sr: SpreadRef, pi: usize) -> LiquidRule {
let Some(pg) = d.spread(sr).and_then(|sp| sp.pages.get(pi)) else { return LiquidRule::Off };
if pg.liquid != LiquidRule::ControlledByParent {
return pg.liquid;
}
let SpreadRef::Doc(si) = sr else { return LiquidRule::Off };
let abs = d.first_page_of_spread(si) + pi;
match d.parent_page_for(abs).and_then(|(ppi, ppg)| d.parents.get(ppi)?.pages.get(ppg).map(|p| p.liquid)) {
Some(LiquidRule::ControlledByParent) | None => LiquidRule::Off,
Some(r) => r,
}
}
fn rule_param(p: &Value, id: &str) -> Result<LiquidRule> {
serde_json::from_value(p.get("rule").cloned().unwrap_or(json!("off"))).map_err(|_| bad(id, "`rule`: off|scale|reCenter|guideBased|objectBased"))
serde_json::from_value(p.get("rule").cloned().unwrap_or(json!("off")))
.map_err(|_| bad(id, "`rule`: off|scale|reCenter|guideBased|objectBased|controlledByParent"))
}
pub fn specs() -> Vec<CommandSpec> {
@@ -119,7 +135,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Liquid Page Rule",
["Layout", "Liquid Layout"],
None,
"{rule: off|scale|reCenter|guideBased|objectBased, pages?: [1-based] (default: all)}",
"{rule: off|scale|reCenter|guideBased|objectBased|controlledByParent (the parent page's rule), pages?: [1-based] (default: all)}",
has_doc,
|s, p| {
let rule = rule_param(p, "liquid.pageRule")?;
+25
View File
@@ -642,3 +642,28 @@ fn page_size_keeps_objects_by_its_anchor() {
assert!(s.execute("layout.selectPage", &json!({"page": 2})).is_err());
assert_eq!(s.execute("layout.selectPage", &json!({"page": 1})).unwrap()["page"], 1);
}
#[test]
fn controlled_by_parent_takes_the_parent_pages_rule() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 400, "height": 400, "facingPages": false})).unwrap();
let id = s.execute("frame.create", &json!({"rect": [100, 100, 200, 200]})).unwrap()["id"].as_u64().unwrap();
let frame = |s: &Session| s.doc().unwrap().doc.item(designcraft_doc::ItemId(id)).unwrap().bounds();
// The page follows its parent; the parent page re-centres.
s.execute("liquid.pageRule", &json!({"rule": "controlledByParent", "pages": [1]})).unwrap();
s.edit(|d, _| {
std::sync::Arc::make_mut(&mut d.parents[0]).pages[0].liquid = designcraft_doc::LiquidRule::ReCenter;
Ok(())
})
.unwrap();
s.execute("layout.pageSize", &json!({"pages": [1], "width": 600})).unwrap();
assert_eq!(frame(&s).x0, 200.0, "re-centred: {:?}", frame(&s));
// Without a parent (or a parent that's also controlled by its parent) nothing moves.
s.execute("layout.pages.applyParent", &json!({"pages": [0], "parent": null})).unwrap();
s.execute("layout.pageSize", &json!({"pages": [1], "width": 800})).unwrap();
assert_eq!(frame(&s).x0, 200.0);
// Show Parent Page Overlay is a page option.
s.execute("layout.pageOptions", &json!({"pages": [1], "showParentOverlay": true})).unwrap();
assert!(s.doc().unwrap().doc.spreads[0].pages[0].show_parent_overlay);
assert!(s.execute("layout.pageOptions", &json!({"pages": [2], "showParentOverlay": true})).is_err());
}
+1
View File
@@ -1963,6 +1963,7 @@ impl<'r> Importer<'r> {
liquid: Default::default(),
transition: None,
view_rotation: 0,
show_parent_overlay: false,
});
}
let overrides: Vec<Vec<String>> =
+1
View File
@@ -198,6 +198,7 @@ pub fn rule_label(rule: designcraft_doc::LiquidRule) -> &'static str {
R::ReCenter => "Re-center",
R::ObjectBased => "Object-Based",
R::GuideBased => "Guide-Based",
R::ControlledByParent => "Controlled by Parent",
}
}
+11 -1
View File
@@ -604,8 +604,18 @@ fn draw_guides(app: &DesignApp, painter: &egui::Painter, xf: &Xf, doc: &Document
painter.rect_stroke(rect(br), 0.0, Stroke::new(hair(painter), c32(s.bleed_color)), StrokeKind::Middle);
}
let pasteboard = m.inverse().transform_rect_bbox(layout.pasteboard);
for p in &sp.pages {
for (pi, p) in sp.pages.iter().enumerate() {
let pr = p.bounds();
// Page tool › Show Parent Page Overlay: the parent page's size over the page.
if p.show_parent_overlay
&& let SpreadRef::Doc(si) = slot.spread
&& let Some((ppi, ppg)) = doc.parent_page_for(doc.first_page_of_spread(si) + pi)
&& let Some(pp) = doc.parents.get(ppi).and_then(|ps| ps.pages.get(ppg))
{
let r = rect(DRect::new(pr.x0, pr.y0, pr.x0 + pp.width, pr.y0 + pp.height));
painter.rect_filled(r, 0.0, Color32::from_rgba_unmultiplied(110, 110, 230, 28));
painter.rect_stroke(r, 0.0, Stroke::new(1.0, Color32::from_rgb(110, 110, 230)), StrokeKind::Middle);
}
if !app.ui.guides {
continue;
}
+110 -1
View File
@@ -182,7 +182,9 @@ pub fn control_bar(app: &mut DesignApp, ui: &mut egui::Ui) {
ui.horizontal_top(|ui| {
let text_mode =
matches!(app.session.tool_id(), "type" | "verticalType") || app.session.active().is_some_and(|d| d.selection.text.is_some());
if text_mode {
if app.session.tool_id() == "page" {
control_page(app, ui);
} else if text_mode {
control_text(app, ui);
} else {
control_object(app, ui);
@@ -192,6 +194,96 @@ pub fn control_bar(app: &mut DesignApp, ui: &mut egui::Ui) {
});
}
/// The Control panel with the Page tool, as InDesign's: reference point, X/Y, W/H, page-size
/// preset, orientation, Liquid Page Rule, Objects Move with Page and Show Parent Page Overlay, for
/// the page the Page tool selected (else the page in view). W/H keep the reference point in place.
fn control_page(app: &mut DesignApp, ui: &mut egui::Ui) {
let lang = app.ui.language.clone();
let Some(st) = app.session.active() else { return };
let units = st.doc.settings.horizontal_units;
let Some(page) = st.selection.page.or_else(|| crate::canvas::current_page(app)) else { return };
let Some(st) = app.session.active() else { return };
let d = st.doc.clone();
let Some((si, pi)) = d.page_loc(page) else { return };
let pg = d.spreads[si].pages[pi].clone();
let n = page + 1;
// Reference point: W/H edits keep it in place.
let (r, _) = ui.allocate_exact_size(vec2(30.0, 44.0), Sense::hover());
if let Some(p) = panels::properties::ref_point_proxy(ui, egui::Rect::from_center_size(r.center(), vec2(27.0, 27.0)), app.ui.ref_point) {
app.ui.ref_point = p;
}
let anchor = app.ui.ref_point.min(8);
let (fx, fy) = ((anchor % 3) as f64 / 2.0, (anchor / 3) as f64 / 2.0);
// The reference point's position: from the spine across, from the top down.
let (x, y) = (pg.x - d.spreads[si].spine_x() + fx * pg.width, fy * pg.height);
egui::Grid::new("ctl_page_xywh").num_columns(4).spacing(vec2(4.0, 4.0)).show(ui, |ui| {
caption(ui, crate::i18n::tr(&lang, "X:"));
// Pages don't move within their spread yet.
ui.add_enabled_ui(false, |ui| measure(ui, "cpx", Some(x), units, 64.0));
caption(ui, crate::i18n::tr(&lang, "W:"));
if let Some(v) = measure(ui, "cpw", Some(pg.width), units, 64.0) {
let _ = app.run("layout.pageSize", json!({"pages": [n], "width": v, "anchor": anchor}));
}
ui.end_row();
caption(ui, crate::i18n::tr(&lang, "Y:"));
ui.add_enabled_ui(false, |ui| measure(ui, "cpy", Some(y), units, 64.0));
caption(ui, crate::i18n::tr(&lang, "H:"));
if let Some(v) = measure(ui, "cph", Some(pg.height), units, 64.0) {
let _ = app.run("layout.pageSize", json!({"pages": [n], "height": v, "anchor": anchor}));
}
ui.end_row();
});
vsep(ui);
// Page-size preset and orientation.
let landscape = pg.width > pg.height;
ui.vertical(|ui| {
let cur = panels::properties::preset_name(pg.width, pg.height);
egui::ComboBox::from_id_salt("ctl_page_preset").selected_text(crate::rtl::widget(ui, crate::i18n::tr(&lang, cur))).width(130.0).show_ui(
ui,
|ui| {
for (name, w, h) in panels::properties::PAGE_PRESETS {
if ui.selectable_label(cur == *name, crate::rtl::widget(ui, crate::i18n::tr(&lang, name))).clicked() {
let (w, h) = if landscape { (h, w) } else { (w, h) };
let _ = app.run("layout.pageSize", json!({"pages": [n], "width": w, "height": h, "anchor": anchor}));
}
}
},
);
ui.horizontal(|ui| {
for (icon, tip, want) in [("orient-portrait", "Portrait", false), ("orient-landscape", "Landscape", true)] {
if crate::widgets::icon_toggle(ui, icon, landscape == want, crate::i18n::tr(&lang, tip)).clicked() && landscape != want {
let _ = app.run("layout.pageSize", json!({"pages": [n], "width": pg.height, "height": pg.width, "anchor": anchor}));
}
}
});
});
vsep(ui);
ui.vertical(|ui| {
caption(ui, crate::i18n::tr(&lang, "Liquid Page Rule:"));
let label = panels::interactive::liquid_rule_label(pg.liquid);
egui::ComboBox::from_id_salt("ctl_page_rule").selected_text(crate::rtl::widget(ui, crate::i18n::tr(&lang, label))).width(150.0).show_ui(
ui,
|ui| {
for (r, l) in panels::interactive::LIQUID_RULES {
if ui.selectable_label(pg.liquid == r, crate::rtl::widget(ui, crate::i18n::tr(&lang, l))).clicked() && pg.liquid != r {
let _ = app.run("liquid.pageRule", json!({"rule": r, "pages": [n]}));
}
}
},
);
});
vsep(ui);
ui.vertical(|ui| {
// Objects follow a page that moves in its spread; pages don't move yet, so it's always on.
let mut follow = true;
ui.add_enabled(false, egui::Checkbox::new(&mut follow, crate::rtl::widget(ui, crate::i18n::tr(&lang, "Objects Move with Page"))));
let mut overlay = pg.show_parent_overlay;
if ui.checkbox(&mut overlay, crate::rtl::widget(ui, crate::i18n::tr(&lang, "Show Parent Page Overlay"))).changed() {
let _ = app.run("layout.pageOptions", json!({"pages": [n], "showParentOverlay": overlay}));
}
});
}
/// A field label drawn as a tool icon (hover for its name).
fn icon_caption(ui: &mut egui::Ui, icon: &str, tip: &str) {
let (r, resp) = ui.allocate_exact_size(vec2(16.0, 16.0), Sense::hover());
@@ -830,6 +922,23 @@ mod tests {
assert!(short.min.x - first.min.x <= 2.0 * super::DOC_TAB_MAX_W, "the last tab {short:?} starts too far right");
}
#[test]
fn the_page_tool_has_its_own_control_panel() {
// Chris Cicchino's Page Tool and Liquid Layout video (1:02): with the Page tool, the
// Control panel shows the page's size, preset, orientation and liquid page rule.
let mut app = app();
app.run("file.new", serde_json::json!({"width": 252, "height": 144, "facingPages": false})).unwrap();
app.session.set_tool("page");
let mut h = test_window::open(app, vec2(1800.0, 600.0));
for label in ["Liquid Page Rule:", "Objects Move with Page"] {
let _ = h.get_by_label(label);
}
assert_eq!(crate::panels::properties::preset_name(252.0, 144.0), "US Business Card");
h.get_by_label("Show Parent Page Overlay").click();
h.run_steps(4);
assert!(h.state().app.session.doc().unwrap().doc.spreads[0].pages[0].show_parent_overlay);
}
#[test]
fn a_narrow_window_scrolls_the_control_bar() {
let w = 700.0;
+29
View File
@@ -2501,6 +2501,35 @@ const TABLE: &[(&str, [&str; 5])] = &[
("Object-Based", ["Object-Based", "Object-Based", "Object-Based", "Object-Based", "基于对象"]),
("Guide-Based", ["Guide-Based", "Guide-Based", "Guide-Based", "Guide-Based", "基于参考线"]),
("Resize Page", ["Seitengröße ändern", "Redimensionner la page", "Cambiar tamaño de página", "ページのサイズを変更", "调整页面大小"]),
(
"Liquid Page Rule:",
["Regel für flüssige Seiten:", "Règle de page liquide :", "Regla de página líquida:", "リキッドページルール:", "自适应页面规则:"],
),
(
"Objects Move with Page",
[
"Objekte mit Seite verschieben",
"Déplacer les objets avec la page",
"Los objetos se mueven con la página",
"オブジェクトをページと共に移動",
"对象随页面移动",
],
),
(
"Show Parent Page Overlay",
[
"Überlagerung der übergeordneten Seite anzeigen",
"Afficher la superposition de la page parent",
"Mostrar superposición de página principal",
"親ページオーバーレイを表示",
"显示父页面叠加",
],
),
(
"Controlled by Parent",
["Durch übergeordnete Seite gesteuert", "Contrôlé par le parent", "Controlado por principal", "親ページで制御", "由父页面控制"],
),
("US Business Card", ["US-Visitenkarte", "Carte de visite US", "Tarjeta de visita EE. UU.", "US 名刺", "美国名片"]),
("Loop", ["Loop", "Loop", "Loop", "Loop", "循环播放"]),
("Object-based", ["Object-based", "Object-based", "Object-based", "Object-based", "基于对象"]),
("Off", ["Off", "Off", "Off", "Off", "关闭"]),
+5
View File
@@ -712,6 +712,11 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Object-Based", "استنادًا إلى الكائنات"),
("Guide-Based", "استنادًا إلى الأدلة"),
("Resize Page", "تغيير حجم الصفحة"),
("Liquid Page Rule:", "قاعدة الصفحة المرنة:"),
("Objects Move with Page", "تحريك الكائنات مع الصفحة"),
("Show Parent Page Overlay", "إظهار تراكب الصفحة الأصل"),
("Controlled by Parent", "تتحكم فيه الصفحة الأصل"),
("US Business Card", "بطاقة عمل أمريكية"),
("Loop", "تكرار"),
("Object-based", "استنادًا إلى الكائنات"),
("Play on Page Load", "تشغيل عند تحميل الصفحة"),
+5
View File
@@ -1298,6 +1298,11 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Object-Based", "Basato su oggetti"),
("Guide-Based", "Basato su guide"),
("Resize Page", "Ridimensiona pagina"),
("Liquid Page Rule:", "Regola pagina liquida:"),
("Objects Move with Page", "Sposta oggetti con la pagina"),
("Show Parent Page Overlay", "Mostra sovrapposizione pagina principale"),
("Controlled by Parent", "Controllato dalla pagina principale"),
("US Business Card", "Biglietto da visita USA"),
("Loop", "Ciclo continuo"),
("Object-based", "Basato su oggetti"),
("Off", "Disattivato"),
+5
View File
@@ -1294,6 +1294,11 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Object-Based", "オブジェクト基準"),
("Guide-Based", "ガイド基準"),
("Resize Page", "ページのサイズを変更"),
("Liquid Page Rule:", "リキッドページルール:"),
("Objects Move with Page", "オブジェクトをページと共に移動"),
("Show Parent Page Overlay", "親ページオーバーレイを表示"),
("Controlled by Parent", "親ページで制御"),
("US Business Card", "US 名刺"),
("Loop", "繰り返し再生"),
("Object-based", "オブジェクト基準"),
("Off", "オフ"),
+5
View File
@@ -711,6 +711,11 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Object-Based", "Baseado em objetos"),
("Guide-Based", "Baseado em guias"),
("Resize Page", "Redimensionar página"),
("Liquid Page Rule:", "Regra de página líquida:"),
("Objects Move with Page", "Objetos se movem com a página"),
("Show Parent Page Overlay", "Mostrar sobreposição da página principal"),
("Controlled by Parent", "Controlado pela página principal"),
("US Business Card", "Cartão de visita EUA"),
("Loop", "Repetir"),
("Object-based", "Baseado em objetos"),
("Play on Page Load", "Reproduzir ao carregar a página"),
+5
View File
@@ -1295,6 +1295,11 @@ pub(super) const TABLE: &[(&str, &str)] = &[
("Object-Based", "На основі об’єктів"),
("Guide-Based", "На основі напрямних"),
("Resize Page", "Змінити розмір сторінки"),
("Liquid Page Rule:", "Правило гнучкої сторінки:"),
("Objects Move with Page", "Об’єкти рухаються зі сторінкою"),
("Show Parent Page Overlay", "Показати накладення батьківської сторінки"),
("Controlled by Parent", "Керується батьківською сторінкою"),
("US Business Card", "Візитна картка (США)"),
("Loop", "Зациклити"),
("Object-based", "На основі об’єктів"),
("Off", "Вимкнено"),
+17 -16
View File
@@ -486,6 +486,21 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) {
}
}
/// Liquid page rules in InDesign's order, with their menu labels.
pub const LIQUID_RULES: [(designcraft_doc::LiquidRule, &str); 6] = [
(designcraft_doc::LiquidRule::Off, "Off"),
(designcraft_doc::LiquidRule::Scale, "Scale"),
(designcraft_doc::LiquidRule::ReCenter, "Re-center"),
(designcraft_doc::LiquidRule::ObjectBased, "Object-based"),
(designcraft_doc::LiquidRule::GuideBased, "Guide-based"),
(designcraft_doc::LiquidRule::ControlledByParent, "Controlled by Parent"),
];
/// A liquid page rule's menu label.
pub fn liquid_rule_label(rule: designcraft_doc::LiquidRule) -> &'static str {
LIQUID_RULES.iter().find(|(r, _)| *r == rule).map_or("Off", |(_, l)| l)
}
/// Liquid Layout panel: the current page's rule, the selection's pins (object-based), the page's
/// liquid guides, and Create Alternate Layout.
pub fn liquid(app: &mut DesignApp, ui: &mut egui::Ui) {
@@ -501,23 +516,9 @@ pub fn liquid(app: &mut DesignApp, ui: &mut egui::Ui) {
crate::rtl::label(ui, egui::RichText::new(format!("{} {}", crate::i18n::tr(&app.ui.language, "Page"), d.page_name(page))).strong());
let mut rule = pg.liquid;
egui::ComboBox::from_id_salt("liquid_rule")
.selected_text(crate::rtl::widget(
ui,
crate::i18n::tr(
&app.ui.language,
match rule {
L::Off => "Off",
L::Scale => "Scale",
L::ReCenter => "Re-center",
L::GuideBased => "Guide-based",
L::ObjectBased => "Object-based",
},
),
))
.selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, liquid_rule_label(rule))))
.show_ui(ui, |ui| {
for (r, label) in
[(L::Off, "Off"), (L::Scale, "Scale"), (L::ReCenter, "Re-center"), (L::GuideBased, "Guide-based"), (L::ObjectBased, "Object-based")]
{
for (r, label) in LIQUID_RULES {
ui.selectable_value(&mut rule, r, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label)));
}
});
+4 -3
View File
@@ -160,7 +160,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
// ---------------------------------------------------------------- No Selection
/// Page-size presets for the Document dropdown (portrait, points).
const PAGE_PRESETS: &[(&str, f64, f64)] = &[
pub(crate) const PAGE_PRESETS: &[(&str, f64, f64)] = &[
("Letter", 612.0, 792.0),
("Legal", 612.0, 1008.0),
("Tabloid", 792.0, 1224.0),
@@ -171,9 +171,10 @@ const PAGE_PRESETS: &[(&str, f64, f64)] = &[
("A5", 419.528, 595.276),
("A6", 297.638, 419.528),
("B5", 498.898, 708.661),
("US Business Card", 144.0, 252.0),
];
fn preset_name(w: f64, h: f64) -> &'static str {
pub(crate) fn preset_name(w: f64, h: f64) -> &'static str {
let (a, b) = (w.min(h), w.max(h));
PAGE_PRESETS.iter().find(|p| (p.1 - a).abs() < 0.6 && (p.2 - b).abs() < 0.6).map(|p| p.0).unwrap_or("Custom")
}
@@ -386,7 +387,7 @@ fn document_sections(app: &mut DesignApp, ui: &mut Ui) {
// ---------------------------------------------------------------- Objects
/// The 9-point reference-point proxy; returns a newly picked point (0..8, row-major).
fn ref_point_proxy(ui: &mut Ui, r: Rect, cur: u8) -> Option<u8> {
pub(crate) fn ref_point_proxy(ui: &mut Ui, r: Rect, cur: u8) -> Option<u8> {
let t = Tokens::get(ui.ctx());
let mut out = None;
let pitch = r.width() / 3.0;