Toolbox: Shift+X, D, X and / shortcuts for the Fill/Stroke proxy
The proxy's tooltips named keys that did nothing. color.defaultFillStroke (D), color.swapFillStroke (Shift+X), color.toggleFillStroke (X) and color.applyNone (/) are UI commands; the proxy's buttons run the same commands. Swap keeps the fill and stroke tints. Which of fill and stroke is in front lives in UiState, shared by the Tools panel proxy, the Color panel and the commands. The single-key shortcuts stay out of text being typed, like the tool shortcuts.
This commit is contained in:
parent
3384224419
commit
b21111f232
|
|
@ -129,6 +129,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
|||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-12** · The Tools panel's Fill/Stroke keys work: D (default fill and stroke), X (fill or stroke in front), Shift+X (swap, tints included) and / (Apply None), as `color.*` commands; the Color panel follows the proxy's fill/stroke choice. No readiness percentage change.
|
||||
- **2026-10-12** · Words in capitals hyphenate like capitalized words (FERRUM COM-MODI), as in InDesign; Hyphenate Capitalized Words off keeps them whole. No readiness percentage change.
|
||||
- **2026-10-11** · `transform.move {copy}`, Edit › Duplicate and Step and Repeat return the copies' ids, so a script can go on with them. No readiness percentage change.
|
||||
- **2026-10-11** · `object.fill` and `object.stroke` refuse a swatch name the document doesn't have (it painted nothing) and suggest the name meant (`Paper` → `[Paper]`). No readiness percentage change.
|
||||
|
|
|
|||
|
|
@ -646,6 +646,15 @@ const TABLE: &[(&str, [&str; 5])] = &[
|
|||
("Fill (X)", ["Fill (X)", "Fill (X)", "Fill (X)", "Fill (X)", "填色 (X)"]),
|
||||
("Stroke (X)", ["Stroke (X)", "Stroke (X)", "Stroke (X)", "Stroke (X)", "描边 (X)"]),
|
||||
("Apply None (/)", ["Apply None (/)", "Apply None (/)", "Apply None (/)", "Apply None (/)", "应用无 (/)"]),
|
||||
(
|
||||
"Default Fill and Stroke",
|
||||
["Default Fill and Stroke", "Default Fill and Stroke", "Default Fill and Stroke", "Default Fill and Stroke", "默认填色和描边"],
|
||||
),
|
||||
("Swap Fill and Stroke", ["Swap Fill and Stroke", "Swap Fill and Stroke", "Swap Fill and Stroke", "Swap Fill and Stroke", "交换填色和描边"]),
|
||||
(
|
||||
"Toggle Fill and Stroke",
|
||||
["Toggle Fill and Stroke", "Toggle Fill and Stroke", "Toggle Fill and Stroke", "Toggle Fill and Stroke", "切换填色和描边"],
|
||||
),
|
||||
("Reverse the gradient", ["Reverse the gradient", "Reverse the gradient", "Reverse the gradient", "Reverse the gradient", "反转渐变"]),
|
||||
("Style source", ["Style source", "Style source", "Style source", "Style source", "样式源"]),
|
||||
("Open", ["Open", "Open", "Open", "Open", "打开"]),
|
||||
|
|
|
|||
|
|
@ -520,6 +520,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Superscript", "النص المرتفع"),
|
||||
("Tag the selection", "إضافة وسم للتحديد"),
|
||||
("The structure is valid.", "البنية صالحة."),
|
||||
("Toggle Fill and Stroke", "التبديل بين التعبئة والحد"),
|
||||
("Toggle double column", "تبديل التخطيط بعمودين"),
|
||||
("Type:", "النوع:"),
|
||||
("Underline", "تسطير"),
|
||||
|
|
@ -844,6 +845,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Crop to:", "القص إلى:"),
|
||||
("Cross-Reference Format", "تنسيق المرجع المتقاطع"),
|
||||
("Cursor Key:", "مفتاح المؤشر:"),
|
||||
("Default Fill and Stroke", "التعبئة والحد الافتراضيان"),
|
||||
("Default Fill and Stroke (D)", "التعبئة والحد الافتراضيان (D)"),
|
||||
("Default View:", "العرض الافتراضي:"),
|
||||
("Delete Column", "حذف العمود"),
|
||||
|
|
@ -985,6 +987,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Style Name Conflicts", "تعارض أسماء الأنماط"),
|
||||
("Stylistic Sets", "المجموعات الأسلوبية"),
|
||||
("Subdivisions:", "التقسيمات الفرعية:"),
|
||||
("Swap Fill and Stroke", "تبديل التعبئة والحد"),
|
||||
("Swap Fill and Stroke (Shift+X)", "تبديل التعبئة والحد (Shift+X)"),
|
||||
("Synchronize", "مزامنة"),
|
||||
("System default", "افتراضي النظام"),
|
||||
|
|
|
|||
|
|
@ -376,6 +376,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Fill (X)", "Riempimento (X)"),
|
||||
("Stroke (X)", "Traccia (X)"),
|
||||
("Apply None (/)", "Applica nessuno (/)"),
|
||||
("Default Fill and Stroke", "Riempimento e traccia predefiniti"),
|
||||
("Swap Fill and Stroke", "Scambia riempimento e traccia"),
|
||||
("Toggle Fill and Stroke", "Alterna riempimento e traccia"),
|
||||
("Reverse the gradient", "Inverti la sfumatura"),
|
||||
("Style source", "Origine stile"),
|
||||
("Open", "Apri"),
|
||||
|
|
|
|||
|
|
@ -378,6 +378,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Fill (X)", "塗り (X)"),
|
||||
("Stroke (X)", "線 (X)"),
|
||||
("Apply None (/)", "カラーなしを適用 (/)"),
|
||||
("Default Fill and Stroke", "塗りと線をデフォルトに戻す"),
|
||||
("Swap Fill and Stroke", "塗りと線を入れ替え"),
|
||||
("Toggle Fill and Stroke", "塗りと線を切り替え"),
|
||||
("Reverse the gradient", "グラデーションを反転"),
|
||||
("Style source", "スタイルの同期元"),
|
||||
("Open", "開く"),
|
||||
|
|
|
|||
|
|
@ -519,6 +519,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Superscript", "Sobrescrito"),
|
||||
("Tag the selection", "Marcar a seleção"),
|
||||
("The structure is valid.", "A estrutura é válida."),
|
||||
("Toggle Fill and Stroke", "Alternar entre preenchimento e traçado"),
|
||||
("Toggle double column", "Alternar coluna dupla"),
|
||||
("Type:", "Tipo:"),
|
||||
("Underline", "Sublinhado"),
|
||||
|
|
@ -846,6 +847,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Crop to:", "Cortar para:"),
|
||||
("Cross-Reference Format", "Formato de referência cruzada"),
|
||||
("Cursor Key:", "Tecla do cursor:"),
|
||||
("Default Fill and Stroke", "Preenchimento e traçado padrão"),
|
||||
("Default Fill and Stroke (D)", "Preenchimento e traçado padrão (D)"),
|
||||
("Default View:", "Exibição padrão:"),
|
||||
("Delete Column", "Excluir coluna"),
|
||||
|
|
@ -992,6 +994,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Style Name Conflicts", "Conflitos de nomes de estilos"),
|
||||
("Stylistic Sets", "Conjuntos estilísticos"),
|
||||
("Subdivisions:", "Subdivisões:"),
|
||||
("Swap Fill and Stroke", "Alternar preenchimento e traçado"),
|
||||
("Swap Fill and Stroke (Shift+X)", "Alternar preenchimento e traçado (Shift+X)"),
|
||||
("Synchronize", "Sincronizar"),
|
||||
("System default", "Padrão do sistema"),
|
||||
|
|
|
|||
|
|
@ -379,6 +379,9 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
|||
("Fill (X)", "Заливка (X)"),
|
||||
("Stroke (X)", "Обведення (X)"),
|
||||
("Apply None (/)", "Без кольору (/)"),
|
||||
("Default Fill and Stroke", "Заливка та обведення за замовчуванням"),
|
||||
("Swap Fill and Stroke", "Змінити заливку та обведення"),
|
||||
("Toggle Fill and Stroke", "Перемкнути заливку та обведення"),
|
||||
("Reverse the gradient", "Змінити напрямок градієнта"),
|
||||
("Style source", "Джерело стилів"),
|
||||
("Open", "Відкрити"),
|
||||
|
|
|
|||
|
|
@ -287,6 +287,10 @@ pub struct UiState {
|
|||
pub palette: Option<String>,
|
||||
#[serde(skip)]
|
||||
pub flyout: Option<usize>,
|
||||
/// Fill/Stroke proxy (Tools and Color panels): the stroke is in front, so Apply None and the
|
||||
/// Color panel act on it (X toggles).
|
||||
#[serde(skip)]
|
||||
pub stroke_front: bool,
|
||||
}
|
||||
|
||||
impl Default for UiState {
|
||||
|
|
@ -362,6 +366,7 @@ impl Default for UiState {
|
|||
dialog: None,
|
||||
palette: None,
|
||||
flyout: None,
|
||||
stroke_front: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
|
|||
("view.zoom", "Zoom To", None, "{zoom: 1.0 = 100%}"),
|
||||
("view.screenMode", "Screen Mode", None, "{mode: normal|preview|bleed|slug|presentation}"),
|
||||
("view.togglePreview", "Toggle Normal/Preview", Some("W"), "{}"),
|
||||
("color.defaultFillStroke", "Default Fill and Stroke", Some("D"), "{} — the selection gets no fill and a black stroke"),
|
||||
("color.swapFillStroke", "Swap Fill and Stroke", Some("Shift+X"), "{} — the selection's fill and stroke colours (and tints) trade places"),
|
||||
("color.toggleFillStroke", "Toggle Fill and Stroke", Some("X"), "{} → {strokeFront} — which of fill and stroke the proxy has in front"),
|
||||
("color.applyNone", "Apply None", Some("/"), "{} — [None] for the fill or stroke in front of the proxy"),
|
||||
("view.frameEdges", "Show/Hide Frame Edges", Some("Cmd+H"), "{}"),
|
||||
("view.rulers", "Show/Hide Rulers", Some("Cmd+R"), "{}"),
|
||||
("view.guides", "Show/Hide Guides", Some("Cmd+;"), "{}"),
|
||||
|
|
@ -1109,6 +1113,10 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
|
|||
app.ui.screen_mode = if app.ui.screen_mode == ScreenMode::Normal { ScreenMode::Preview } else { ScreenMode::Normal };
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"color.defaultFillStroke" => crate::toolbar::default_fill_stroke(app),
|
||||
"color.swapFillStroke" => crate::toolbar::swap_fill_stroke(app),
|
||||
"color.toggleFillStroke" => crate::toolbar::toggle_fill_stroke(app),
|
||||
"color.applyNone" => crate::toolbar::apply_none(app),
|
||||
"view.frameEdges" => flag(&mut app.ui.frame_edges),
|
||||
"view.overprintPreview" => {
|
||||
app.canvas.shown = None;
|
||||
|
|
@ -2097,6 +2105,7 @@ fn parse_shortcut(sc: &str) -> Option<(egui::Modifiers, egui::Key)> {
|
|||
"," => key = Some(egui::Key::Comma),
|
||||
"." => key = Some(egui::Key::Period),
|
||||
"\\" => key = Some(egui::Key::Backslash),
|
||||
"/" => key = Some(egui::Key::Slash),
|
||||
"Delete" => key = Some(egui::Key::Delete),
|
||||
k => key = egui::Key::from_name(k),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,9 +209,8 @@ pub fn color_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
|||
return;
|
||||
};
|
||||
let Some(doc) = app.session.active().map(|d| d.doc.clone()) else { return };
|
||||
let stroke_id = egui::Id::new("color_panel_stroke");
|
||||
let mode_id = egui::Id::new("color_panel_mode");
|
||||
let mut stroke: bool = ui.data(|d| d.get_temp(stroke_id)).unwrap_or(false);
|
||||
let mut stroke = app.ui.stroke_front;
|
||||
let default_mode = if doc.settings.intent == designcraft_doc::Intent::Print { ColorMode::Cmyk } else { ColorMode::Rgb };
|
||||
let mut mode: ColorMode = ui.data(|d| d.get_temp(mode_id)).unwrap_or(default_mode);
|
||||
let target = if stroke { "stroke" } else { "fill" };
|
||||
|
|
@ -253,10 +252,8 @@ pub fn color_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
|||
});
|
||||
});
|
||||
});
|
||||
ui.data_mut(|d| {
|
||||
d.insert_temp(stroke_id, stroke);
|
||||
d.insert_temp(mode_id, mode);
|
||||
});
|
||||
app.ui.stroke_front = stroke;
|
||||
ui.data_mut(|d| d.insert_temp(mode_id, mode));
|
||||
let sw = doc.swatch(&name).cloned();
|
||||
let Some(c) = doc.resolve_color(&name, 1.0) else {
|
||||
ui.label(crate::rtl::widget(
|
||||
|
|
|
|||
|
|
@ -196,9 +196,8 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) {
|
|||
let t = Tokens::get(ui.ctx());
|
||||
let info = crate::panels::sel_info(app);
|
||||
let doc = app.session.active().map(|d| d.doc.clone());
|
||||
let stroke_front_id = egui::Id::new("proxy_stroke_front");
|
||||
let text_id = egui::Id::new("proxy_text");
|
||||
let stroke_front: bool = ui.data(|d| d.get_temp(stroke_front_id)).unwrap_or(false);
|
||||
let stroke_front = app.ui.stroke_front;
|
||||
let text_mode: bool = ui.data(|d| d.get_temp(text_id)).unwrap_or(false);
|
||||
let (fill_sw, stroke_sw) = match &info {
|
||||
Some(i) => (i.fill.clone(), i.stroke.clone()),
|
||||
|
|
@ -216,8 +215,7 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) {
|
|||
})
|
||||
.clicked()
|
||||
{
|
||||
let _ = app.run("object.fill", json!({"swatch": "[None]"}));
|
||||
let _ = app.run("object.stroke", json!({"swatch": "[Black]"}));
|
||||
let _ = app.run("color.defaultFillStroke", json!({}));
|
||||
}
|
||||
let swap = egui::Rect::from_min_size(egui::pos2(x0 + 19.0, row.min.y), vec2(11.0, 11.0));
|
||||
icons::paint(ui.painter(), swap, "swap", t.icon);
|
||||
|
|
@ -228,8 +226,7 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) {
|
|||
})
|
||||
.clicked()
|
||||
{
|
||||
let _ = app.run("object.fill", json!({"swatch": stroke_sw}));
|
||||
let _ = app.run("object.stroke", json!({"swatch": fill_sw}));
|
||||
let _ = app.run("color.swapFillStroke", json!({}));
|
||||
}
|
||||
ui.add_space(2.0);
|
||||
// Proxy: 19.5 pt squares, fill top-left, stroke bottom-right.
|
||||
|
|
@ -278,7 +275,7 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) {
|
|||
if fresp.clicked() || sresp.clicked() {
|
||||
let front =
|
||||
if stroke_front { !fresp.clicked() } else { sresp.clicked() && !fill_r.contains(sresp.interact_pointer_pos().unwrap_or_default()) };
|
||||
ui.data_mut(|d| d.insert_temp(stroke_front_id, front));
|
||||
app.ui.stroke_front = front;
|
||||
}
|
||||
if fresp.double_clicked() || sresp.double_clicked() {
|
||||
let _ = app.run("window.panel", json!({"panel": "swatches"}));
|
||||
|
|
@ -318,7 +315,7 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) {
|
|||
flyout_triangle(ui.painter(), well, t.icon);
|
||||
let target = if stroke_front { "object.stroke" } else { "object.fill" };
|
||||
if resp.clicked() {
|
||||
let _ = app.run(target, json!({"swatch": "[None]"}));
|
||||
let _ = app.run("color.applyNone", json!({}));
|
||||
}
|
||||
egui::Popup::context_menu(&resp).show(|ui| {
|
||||
for (label, sw) in [("Apply Color", "[Black]"), ("Apply None", "[None]")] {
|
||||
|
|
@ -411,6 +408,33 @@ fn tool_button(ui: &mut egui::Ui, icon: &str, active: bool, tip: &str) -> egui::
|
|||
})
|
||||
}
|
||||
|
||||
/// Default Fill and Stroke (D): no fill, a black stroke.
|
||||
pub(crate) fn default_fill_stroke(app: &mut DesignApp) -> Result<Value, String> {
|
||||
app.run("object.fill", json!({"swatch": "[None]", "tint": 1.0}))?;
|
||||
app.run("object.stroke", json!({"swatch": "[Black]", "tint": 1.0}))
|
||||
}
|
||||
|
||||
/// Swap Fill and Stroke (Shift+X): the selection's fill and stroke colours trade places, tints
|
||||
/// included.
|
||||
pub(crate) fn swap_fill_stroke(app: &mut DesignApp) -> Result<Value, String> {
|
||||
let Some(info) = crate::panels::sel_info(app) else { return Err("nothing selected".into()) };
|
||||
let (fill_tint, stroke_tint) = (crate::panels::sel_tint(app, false), crate::panels::sel_tint(app, true));
|
||||
app.run("object.fill", json!({"swatch": info.stroke, "tint": stroke_tint}))?;
|
||||
app.run("object.stroke", json!({"swatch": info.fill, "tint": fill_tint}))
|
||||
}
|
||||
|
||||
/// Fill/Stroke toggle (X): brings the other of fill and stroke to the front of the proxy.
|
||||
pub(crate) fn toggle_fill_stroke(app: &mut DesignApp) -> Result<Value, String> {
|
||||
app.ui.stroke_front = !app.ui.stroke_front;
|
||||
Ok(json!({"strokeFront": app.ui.stroke_front}))
|
||||
}
|
||||
|
||||
/// Apply None (/): to the fill or the stroke, whichever is in front of the proxy.
|
||||
pub(crate) fn apply_none(app: &mut DesignApp) -> Result<Value, String> {
|
||||
let target = if app.ui.stroke_front { "object.stroke" } else { "object.fill" };
|
||||
app.run(target, json!({"swatch": "[None]"}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use egui::{pos2, vec2};
|
||||
|
|
@ -482,4 +506,42 @@ mod tests {
|
|||
wheel(&mut h, panel.center(), vec2(0.0, -2000.0));
|
||||
assert_eq!((rect(&h, LAST_TOOL), rect(&h, FILL)), before);
|
||||
}
|
||||
/// The Fill/Stroke proxy's keys run on the selection and stay out of text being typed.
|
||||
#[test]
|
||||
fn fill_stroke_proxy_keys() {
|
||||
use egui::{Event, Key, Modifiers};
|
||||
use serde_json::json;
|
||||
fn press(app: &mut crate::DesignApp, ctx: &egui::Context, key: Key, modifiers: Modifiers) {
|
||||
let events = [true, false].map(|pressed| Event::Key { key, physical_key: None, pressed, repeat: false, modifiers }).to_vec();
|
||||
ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| crate::menus::shortcuts(app, ui.ctx())).textures_delta.clear();
|
||||
}
|
||||
fn colors(app: &crate::DesignApp) -> (String, f32, String, f32) {
|
||||
let st = app.session.active().unwrap();
|
||||
let it = st.doc.item(st.selection.items[0]).unwrap();
|
||||
(it.fill.swatch.clone(), it.fill.tint, it.stroke.swatch.clone(), it.stroke.tint)
|
||||
}
|
||||
let mut app = app(false);
|
||||
let ctx = egui::Context::default();
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let f = app.run("frame.create", json!({"rect": [72, 72, 300, 200], "content": "text", "text": "Hello"})).unwrap();
|
||||
app.run("selection.set", json!({"ids": [f["id"]]})).unwrap();
|
||||
app.run("object.fill", json!({"swatch": "[Paper]", "tint": 0.5})).unwrap();
|
||||
app.run("object.stroke", json!({"swatch": "[Black]", "tint": 1.0})).unwrap();
|
||||
|
||||
press(&mut app, &ctx, Key::X, Modifiers::SHIFT);
|
||||
assert_eq!(colors(&app), ("[Black]".into(), 1.0, "[Paper]".into(), 0.5), "Shift+X swaps, tints included");
|
||||
press(&mut app, &ctx, Key::D, Modifiers::NONE);
|
||||
assert_eq!(colors(&app), ("[None]".into(), 1.0, "[Black]".into(), 1.0), "D: no fill, black stroke");
|
||||
press(&mut app, &ctx, Key::X, Modifiers::NONE);
|
||||
assert!(app.ui.stroke_front, "X brings the stroke to the front");
|
||||
press(&mut app, &ctx, Key::Slash, Modifiers::NONE);
|
||||
assert_eq!(colors(&app).2, "[None]", "/ applies None to the stroke in front");
|
||||
|
||||
// Typing in a story: the keys are text, not shortcuts.
|
||||
app.session.execute("text.select", &json!({"story": f["story"], "anchor": 5})).unwrap();
|
||||
app.select_tool("type");
|
||||
assert!(app.session.wants_text());
|
||||
press(&mut app, &ctx, Key::X, Modifiers::NONE);
|
||||
assert!(app.ui.stroke_front, "X while typing leaves the proxy alone");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ It is evidence of breadth: each item exists. It is not a claim of InDesign-level
|
|||
- Text engine: Knuth–Plass paragraph composer and single-line composer, dictionary hyphenation (public-domain Moby list + our own trained Liang patterns), justification with word/letter/glyph-scaling ranges, keep options (keep with next, keep lines together, widows/orphans), balance ragged lines, optical margin alignment, hyphenation zone/limit, columns, threading across frames and spreads, tabs, bullets/numbering, rules, shading, baseline grid, first-baseline options, vertical justification, wrap exclusions, overset detection, caret/hit testing.
|
||||
- FID2: IDML underline/strikethrough Text Color resets survive style inheritance and roundtrip. Raster and PDF paint underlines behind glyphs, retain strikethrough above, and center rules on their offsets; synthetic overlap tests cover both backends.
|
||||
- Rendering: vello_cpu (SIMD, multithreaded) — pages, items, images with mip levels, gradients, composed text.
|
||||
- UI (egui): application bar + menus, Control panel (object and text modes), Tools panel with flyouts, document tabs, rulers, pasteboard, guides (margins, columns, bleed, baseline grid), frame edges, selection handles, text ports and threads, Properties / Pages / Layers dock, Swatches, Styles, Character, Paragraph, Stroke, Text Wrap panels, New Document and Preferences dialogs, Quick Apply (⌘Return), 4 brightness themes.
|
||||
- UI (egui): application bar + menus, Control panel (object and text modes), Tools panel with flyouts and the Fill/Stroke proxy (D, X, Shift+X, / as `color.*` commands), document tabs, rulers, pasteboard, guides (margins, columns, bleed, baseline grid), frame edges, selection handles, text ports and threads, Properties / Pages / Layers dock, Swatches, Styles, Character, Paragraph, Stroke, Text Wrap panels, New Document and Preferences dialogs, Quick Apply (⌘Return), 4 brightness themes.
|
||||
- Pen tool, Direct Selection anchor/handle editing, rotate from corners, Rotate/Scale/Shear tools, Eyedropper, snapping & smart guides, Align/Distribute, Find/Change (text + GREP), Story Editor, Paragraph Style Options, Effects panel, hidden characters, native macOS menu bar, measured InDesign 2026 look (Medium Dark): Properties sections per selection state, 21 pt spinner fields with arithmetic, Contextual Task Bar, inverse (black) text selection, Pages panel with drag-and-drop reorder / parent apply, Layers panel with per-object rows.
|
||||
- Tools: Selection (click, marquee, move, Alt-duplicate, resize handles), Direct Selection, Type (draw frame, click caret, select, type), Rectangle/Ellipse/Polygon (+ frame variants), Line, Hand, Zoom.
|
||||
- PDF export (krilla): real selectable text with embedded font subsets, DeviceCMYK/RGB + spot Separations, bleed boxes, crop/bleed marks + page info, pages or spreads, PDF/A-2b (PDF/X-4 output intent pending) — `file.exportPdf`, File › Export PDF…, `designcraft-cli run --export out.pdf`. File › Export PDF… now opens an "Export PDF" options dialog (General / Compression / Marks and Bleeds / Advanced) and remembers the last-used settings across exports and sessions.
|
||||
|
|
|
|||
Loading…
Reference in New Issue