Keep committed line height and letter spacing in the text style editor

Fixes #438

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-15 12:26:22 +02:00
co-authored by Claude Haiku 5.5
parent 8482c3e85b
commit 43e6cba2ef
3 changed files with 57 additions and 2 deletions
+3 -2
View File
@@ -733,12 +733,13 @@ fn text_properties(ui: &mut Ui, id: &str, def: &TextStyle, out: &mut Frame) {
});
ui.horizontal(|ui| {
if let Some(v) = widgets::text_field(ui, Id::new(("ts_lh", id)), &def.line_height.label(), half, tr("Line height"))
&& let Some(lh) = crate::typography::parse_line_height(&v, Some(def.line_height)).and_then(|v| serde_json::from_value(v).ok())
&& let Some(lh) = crate::typography::parse_line_height(&v, Some(def.line_height)).and_then(|v| crate::typography::line_height_of(&v))
{
set(out, &|d| d.line_height = lh);
}
if let Some(v) = widgets::text_field(ui, Id::new(("ts_ls", id)), &def.letter_spacing.label(), half, tr("Letter spacing"))
&& let Some(ls) = crate::typography::parse_letter_spacing(&v, Some(def.letter_spacing)).and_then(|v| serde_json::from_value(v).ok())
&& let Some(ls) =
crate::typography::parse_letter_spacing(&v, Some(def.letter_spacing)).and_then(|v| crate::typography::letter_spacing_of(&v))
{
set(out, &|d| d.letter_spacing = ls);
}
+37
View File
@@ -211,3 +211,40 @@ fn edit_local_for_test(app: &mut FigApp, ctx: &egui::Context) {
let mut out = ctx.run_ui(RawInput::default(), |ui| edit_local(app, ui, StyleSlot::Fill, "Danger"));
out.textures_delta.clear();
}
/// Commits `typed` in the text style editor's field `field` ("ts_lh" / "ts_ls") with Enter and
/// returns the redefined style, if any.
fn commit_text_field(def: &TextStyle, field: &str, typed: &str) -> Option<TextStyle> {
let ctx = egui::Context::default();
ctx.data_mut(|d| d.insert_temp(Id::new((field, "S")).with("editing"), typed.to_string()));
let enter = egui::Event::Key { key: egui::Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() };
let input = RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(400.0, 400.0))), events: vec![enter], ..Default::default() };
let mut f = Frame { act: None, hover: None, budget: 0 };
let mut out = ctx.run_ui(input, |ui| text_properties(ui, "S", def, &mut f));
out.textures_delta.clear();
match f.act {
Some(Act::SetText(id, d)) if id == "S" => Some(d),
_ => None,
}
}
#[test]
fn text_style_editor_commits_line_height_and_letter_spacing() {
use figcraft_doc::{LetterSpacing, LineHeight};
let def = TextStyle { line_height: LineHeight::Auto, letter_spacing: LetterSpacing::Percent(0.0), ..Default::default() };
let lh = |def: &TextStyle, s: &str| commit_text_field(def, "ts_lh", s).map(|d| d.line_height);
let ls = |def: &TextStyle, s: &str| commit_text_field(def, "ts_ls", s).map(|d| d.letter_spacing);
assert_eq!(lh(&def, "150%"), Some(LineHeight::Percent(150.0)));
assert_eq!(lh(&def, "24px"), Some(LineHeight::Pixels(24.0)));
assert_eq!(lh(&def, "24"), Some(LineHeight::Pixels(24.0)));
let pct = TextStyle { line_height: LineHeight::Percent(120.0), ..def.clone() };
assert_eq!(lh(&pct, "130"), Some(LineHeight::Percent(130.0)), "a bare number keeps percent");
assert_eq!(lh(&pct, "Auto"), Some(LineHeight::Auto));
assert_eq!(ls(&def, "2%"), Some(LetterSpacing::Percent(2.0)));
assert_eq!(ls(&def, "1.5px"), Some(LetterSpacing::Pixels(1.5)));
assert_eq!(ls(&def, "3"), Some(LetterSpacing::Percent(3.0)));
// Only the edited property changes.
let d = commit_text_field(&def, "ts_lh", "150%").expect("redefined");
assert_eq!(TextStyle { line_height: def.line_height, ..d }, def);
assert_eq!(lh(&def, "abc"), None);
}
+17
View File
@@ -1026,6 +1026,23 @@ pub(crate) fn parse_letter_spacing(s: &str, cur: Option<LetterSpacing>) -> Optio
}
}
/// A [`parse_line_height`] value as a [`LineHeight`]: "auto", "150%" or a number of pixels.
pub(crate) fn line_height_of(v: &Value) -> Option<LineHeight> {
match v {
Value::String(s) if s.eq_ignore_ascii_case("auto") => Some(LineHeight::Auto),
Value::String(s) => s.strip_suffix('%')?.trim().parse().ok().filter(|p: &f64| p.is_finite()).map(LineHeight::Percent),
other => other.as_f64().filter(|v| v.is_finite()).map(LineHeight::Pixels),
}
}
/// A [`parse_letter_spacing`] value as a [`LetterSpacing`]: "2%" or a number of pixels.
pub(crate) fn letter_spacing_of(v: &Value) -> Option<LetterSpacing> {
match v {
Value::String(s) => s.strip_suffix('%')?.trim().parse().ok().filter(|p: &f64| p.is_finite()).map(LetterSpacing::Percent),
other => other.as_f64().filter(|v| v.is_finite()).map(LetterSpacing::Pixels),
}
}
// ---- Font picker ----
const RECENT_KEY: &str = "figcraft_recent_fonts";