mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-24 10:16:59 -06:00
Control panel: font size, leading and tracking have a spinner and a list of values
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8dff7ecdb0
commit
606b28dbec
@@ -348,6 +348,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-20** · With the Type tool, the Control panel's font size, leading and tracking fields have a spinner and a list of common values, as in InDesign (auto leading in parentheses). No readiness percentage change.
|
||||
- **2026-10-16** · Type › Reload Fonts (`font.reload`): fonts installed, edited or removed while DesignCraft runs are read again without a restart, and the open documents recompose with them (#328, #547). An edited font file is noticed by its length and modification time; bundled, added and document fonts stay as they are. InDesign does this by itself, so noticing changes automatically is still open (gap 6). No readiness percentage change.
|
||||
- **2026-10-16** · Parents can be deleted from their Pages-panel context menu or `layout.parents.delete {id}`. Applied-page and based-on references are cleared, local override copies survive, and Undo restores the parent. Addresses the deletion portion of #956; no readiness percentage change.
|
||||
- **2026-10-17** · File › Package writes the document's and the IDML's links relative to the package (`Links/logo.png`, `file:Links/logo.png`), so a package holds no path from the machine that made it; opening a document resolves them to the files beside it. Links › Copy Link(s) To still links by full path. (#340). No readiness percentage change.
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde_json::json;
|
||||
|
||||
use crate::panels;
|
||||
use crate::theme::{Tokens, semibold};
|
||||
use crate::widgets::{caption, measure, number};
|
||||
use crate::widgets::{NumField, caption, measure, number};
|
||||
use crate::{DesignApp, icons};
|
||||
|
||||
/// Height of the application bar (InDesign 2026), then a 7 pt lower band and a 1 pt border. The
|
||||
@@ -564,6 +564,11 @@ fn control_page(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
});
|
||||
}
|
||||
|
||||
/// The Control panel's font sizes (and leadings), as InDesign lists them.
|
||||
const SIZES: &[f64] = &[6.0, 8.0, 9.0, 10.0, 11.0, 12.0, 14.0, 18.0, 24.0, 30.0, 36.0, 48.0, 60.0, 72.0];
|
||||
/// The Control panel's tracking values (1/1000 em).
|
||||
const TRACKING: &[f64] = &[-100.0, -75.0, -50.0, -25.0, -10.0, -5.0, 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 200.0];
|
||||
|
||||
fn control_text(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let attrs = panels::text_attrs(app);
|
||||
@@ -585,22 +590,36 @@ fn control_text(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
panels::font_style_picker(app, ui, &fam, &sty, 170.0);
|
||||
});
|
||||
vsep(ui);
|
||||
// Size, leading and tracking as InDesign's Control panel has them: a spinner on the left and
|
||||
// a list of common values on the right; automatic leading in parentheses.
|
||||
egui::Grid::new("ctl_char").num_columns(4).spacing(vec2(4.0, 3.0)).show(ui, |ui| {
|
||||
caption(ui, "𝐓");
|
||||
if let Some(v) = number(ui, "csize", chars["size"].as_f64(), " pt", 54.0, 2) {
|
||||
let (sz, _) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::hover());
|
||||
icons::paint(ui.painter(), sz, "font-size", t.icon);
|
||||
if let Some(v) = NumField::number("csize", chars["size"].as_f64(), " pt", 2).width(84.0).spinner().presets(SIZES).range(0.1, 1296.0).show(ui)
|
||||
{
|
||||
let _ = app.run("type.char", json!({"attrs": {"size": v}}));
|
||||
}
|
||||
caption(ui, crate::i18n::tr(&app.ui.language, "VA"));
|
||||
if let Some(v) = number(ui, "ctrack", chars["tracking"].as_f64(), "", 48.0, 0) {
|
||||
let (tr, _) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::hover());
|
||||
icons::paint(ui.painter(), tr, "tracking", t.icon);
|
||||
if let Some(v) = NumField::number("ctrack", chars["tracking"].as_f64(), "", 0)
|
||||
.width(76.0)
|
||||
.spinner()
|
||||
.presets(TRACKING)
|
||||
.step(5.0)
|
||||
.range(-1000.0, 10000.0)
|
||||
.show(ui)
|
||||
{
|
||||
let _ = app.run("type.char", json!({"attrs": {"tracking": v}}));
|
||||
}
|
||||
ui.end_row();
|
||||
caption(ui, "Ā");
|
||||
let lead = match chars["leading"]["kind"].as_str() {
|
||||
Some("points") => chars["leading"]["value"].as_f64(),
|
||||
_ => chars["size"].as_f64().map(|s| s * para["autoLeading"].as_f64().unwrap_or(1.2)),
|
||||
};
|
||||
if let Some(v) = number(ui, "clead", lead, " pt", 54.0, 2) {
|
||||
let (ld, _) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::hover());
|
||||
icons::paint(ui.painter(), ld, "leading", t.icon);
|
||||
let auto = chars["leading"]["kind"].as_str() != Some("points");
|
||||
let lead =
|
||||
if auto { chars["size"].as_f64().map(|s| s * para["autoLeading"].as_f64().unwrap_or(1.2)) } else { chars["leading"]["value"].as_f64() };
|
||||
let lead = lead.map(|v| (v * 100.0).round() / 100.0);
|
||||
let key = if auto { "cleadauto" } else { "clead" };
|
||||
if let Some(v) = NumField::number(key, lead, " pt", 2).width(84.0).spinner().presets(SIZES).range(0.0, 5000.0).parens(auto).show(ui) {
|
||||
let _ = app.run("type.char", json!({"attrs": {"leading": {"kind": "points", "value": v}}}));
|
||||
}
|
||||
caption(ui, crate::i18n::tr(&app.ui.language, "IT"));
|
||||
@@ -1304,6 +1323,55 @@ mod tests {
|
||||
assert!(short.min.x - first.min.x <= 2.0 * super::DOC_TAB_MAX_W, "the last tab {short:?} starts too far right");
|
||||
}
|
||||
|
||||
/// Zero2Graphic's page layout video (0:48): with the Type tool, the Control panel's font size
|
||||
/// has a list of sizes; the instructor picks 72 pt from it. Leading has the same list.
|
||||
#[test]
|
||||
fn the_type_control_panel_lists_font_sizes_and_leadings() {
|
||||
let mut app = app();
|
||||
app.run("file.new", serde_json::json!({})).unwrap();
|
||||
let f = app.run("frame.create", serde_json::json!({"rect": [72, 72, 400, 300], "content": "text", "text": "Headline"})).unwrap();
|
||||
app.run("text.select", serde_json::json!({"story": f["story"], "anchor": 0, "focus": 8})).unwrap();
|
||||
app.session.set_tool("type");
|
||||
let mut h = test_window::open(app, vec2(1800.0, 700.0));
|
||||
let bar = test_window::panel_rect(&h, "control_bar");
|
||||
let in_bar = |r: &egui::Rect| bar.y_range().contains(r.center().y);
|
||||
let field = |h: &egui_kittest::Harness<'static, test_window::Window>, value: &str| {
|
||||
h.query_all_by_value(value).map(|n| n.rect()).find(in_bar).unwrap_or_else(|| panic!("no {value:?} field in the Control panel"))
|
||||
};
|
||||
let press = |h: &mut egui_kittest::Harness<'static, test_window::Window>, p: egui::Pos2| {
|
||||
h.hover_at(p);
|
||||
h.drag_at(p);
|
||||
h.drop_at(p);
|
||||
};
|
||||
let size_of = |h: &egui_kittest::Harness<'static, test_window::Window>| {
|
||||
let st = h.state().app.session.doc().unwrap();
|
||||
let sid = designcraft_doc::StoryId(f["story"].as_u64().unwrap());
|
||||
st.doc.story(sid).unwrap().format_after(0).over.size
|
||||
};
|
||||
// The ⌄ segment right of the size field opens the list; choosing 72 pt sets the size.
|
||||
let size = field(&h, "12 pt");
|
||||
let chevron = pos2(size.max.x + 1.0 + crate::widgets::SEG_W / 2.0, size.center().y);
|
||||
press(&mut h, chevron);
|
||||
h.run_steps(3);
|
||||
h.get_by_label("72 pt").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(size_of(&h), Some(72.0));
|
||||
// Auto leading shows in parentheses, with the same list beside it.
|
||||
let lead = field(&h, "(86.4 pt)");
|
||||
press(&mut h, pos2(lead.max.x + 1.0 + crate::widgets::SEG_W / 2.0, lead.center().y));
|
||||
h.run_steps(3);
|
||||
h.get_by_label("60 pt").click();
|
||||
h.run_steps(3);
|
||||
let st = h.state().app.session.doc().unwrap();
|
||||
let sid = designcraft_doc::StoryId(f["story"].as_u64().unwrap());
|
||||
assert_eq!(st.doc.story(sid).unwrap().format_after(0).over.leading, Some(designcraft_doc::Leading::Points(60.0)));
|
||||
// The spinner left of the size steps it by a point.
|
||||
let size = field(&h, "72 pt");
|
||||
press(&mut h, pos2(size.min.x - 5.0 - crate::widgets::SEG_W / 2.0, size.min.y + 3.0));
|
||||
h.run_steps(3);
|
||||
assert_eq!(size_of(&h), Some(73.0));
|
||||
}
|
||||
|
||||
#[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
|
||||
|
||||
Reference in New Issue
Block a user