M7: Type settings keeps its width with long labels (#1029)

This commit is contained in:
AganzeFelicite
2026-10-22 09:21:24 +02:00
parent a86897dc91
commit e6067549bc
4 changed files with 54 additions and 25 deletions
+1
View File
@@ -316,6 +316,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | Type settings keeps its width (#1029): in languages whose labels run past the 110 px label column (French "Retrait de paragraphe", "Espacement de liste"), the List spacing, Paragraph indent, Max lines and decoration rows were wider than the popover, which then widened every frame while open. Those fields now take the width left after their label; English is unchanged. Regression test in `type_settings_tests`. Numbers unchanged. |
| 2026-10-20 | minor | A document dropped on the canvas opens (#916): a `.figcraft`, `.fig` or file JSON dropped on the canvas opens as a file of its own, as on the home screen, in the browser too; it used to add its pages to the file in front. Images and SVG still import at the drop point, File ▸ Import still adds pages, and dragging only documents over the canvas says "Drop to open" (not in the browser, which names no file until the drop). Numbers unchanged |
| 2026-10-20 | minor | Duplicating a main component makes an instance (#892): Duplicate and Alt-drag copied a main component as a second main; within its file the copy is now an instance of it, as Copy then Paste already was (a variant duplicated in its set stays a variant). Numbers unchanged. |
| 2026-10-20 | minor | Readable disabled and selected text (#890): the dim text colour (disabled buttons, placeholders, hints) is at least 3:1 against panels, fields and selected rows in both themes (it was 2.1:1 in the light theme), a disabled button is grey with that text instead of fading to 40%, and selected text (renaming a page or layer, any text box) takes the theme's readable green on the highlight (1.4:1 in the dark theme before, now 4.2:1 or more); checked with the colour contrast helper (`theme::tests`). Numbers unchanged |
+3
View File
@@ -1877,5 +1877,8 @@ pub(crate) fn drop_overlay(ctx: &egui::Context, rect: egui::Rect, label: &str) {
#[cfg(test)]
mod selection_spacing_tests;
#[cfg(test)]
mod type_settings_tests;
#[cfg(test)]
mod typography_scrub_tests;
+29
View File
@@ -0,0 +1,29 @@
//! Regression for #1029: a Type settings row whose label is wider than its column (French "Retrait
//! de paragraphe") stays inside the popover, which otherwise widened a little every frame.
use egui::{Area, Id, Pos2, RawInput, Rect, vec2};
#[test]
fn a_long_label_does_not_widen_the_type_settings_popover() {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx, false, 0);
let id = Id::new("ty_row_test");
let mut widths = Vec::new();
for i in 0..30 {
let raw =
RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1200.0, 900.0))), time: Some(i as f64 / 60.0), ..Default::default() };
ctx.run_ui(raw, |ui| {
// As `egui::Popup::width` shows it: a default width the area grows past to fit.
Area::new(id).fixed_pos(Pos2::ZERO).default_width(264.0).show(ui.ctx(), |ui| {
for label in ["List spacing", "Retrait de paragraphe, plus long que la colonne"] {
crate::typography::field_row(ui, label, |ui, fw| ui.allocate_exact_size(vec2(fw, 20.0), egui::Sense::hover()));
}
});
})
.textures_delta
.clear();
widths.push(ctx.memory(|m| m.area_rect(id)).expect("the area is shown").width());
}
let (first, last) = (widths[1], widths[widths.len() - 1]);
assert!((last - first).abs() < 0.01, "the row widens the popover every frame: {first} → {last}");
assert!((last - 264.0).abs() < 0.01, "{last}");
}
+21 -25
View File
@@ -468,17 +468,15 @@ fn type_settings(
// Indent applies to start-aligned text only; elsewhere the field stays visible but off.
let start_aligned =
common(doc, ids, |n| n.kind.text().map(|t| t.align_h != TextAlignH::Center && t.align_h != TextAlignH::Justified)).flatten();
ui.horizontal(|ui| {
ui.add_enabled_ui(start_aligned != Some(false), |ui| {
ui.add_sized(vec2(110.0, FIELD_H), egui::Label::new(egui::RichText::new(tr("Paragraph indent")).font(regular(11.0)).color(t.text)));
let field = widgets::number_field(ui, Id::new("ty_indent"), Lead::None, indent, "", w - 118.0, 1.0);
if let Some(FieldEdit::Set(v) | FieldEdit::Scrub(v, true)) = field {
app.set_props(ids, json!({"paragraphIndent": v.clamp(0.0, 10_000.0)}));
}
})
.response
.on_disabled_hover_text(tr("Paragraph indent applies to left-aligned text"));
});
ui.add_enabled_ui(start_aligned != Some(false), |ui| {
let field =
field_row(ui, tr("Paragraph indent"), |ui, fw| widgets::number_field(ui, Id::new("ty_indent"), Lead::None, indent, "", fw, 1.0));
if let Some(FieldEdit::Set(v) | FieldEdit::Scrub(v, true)) = field {
app.set_props(ids, json!({"paragraphIndent": v.clamp(0.0, 10_000.0)}));
}
})
.response
.on_disabled_hover_text(tr("Paragraph indent applies to left-aligned text"));
ui.add_space(4.0);
for (key, label) in [("hangingPunctuation", tr("Hanging punctuation")), ("hangingList", tr("Hanging list"))] {
let all_on = ids.iter().all(|i| {
@@ -508,16 +506,13 @@ fn type_settings(
if resize == Some(TextAutoResize::Truncate) {
let max = common(doc, ids, |n| n.kind.text().map(|t| t.max_lines)).flatten();
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.add_sized(vec2(110.0, FIELD_H), egui::Label::new(egui::RichText::new(tr("Max lines")).font(regular(11.0)).color(t.text)));
let shown = max.map(f64::from).filter(|m| *m > 0.0);
let empty = if max.is_some() { tr("Auto") } else { tr(MIXED) };
if let Some(FieldEdit::Set(v) | FieldEdit::Scrub(v, true)) =
widgets::number_field_or(ui, Id::new("ty_maxlines"), shown, empty, w - 118.0, 1.0)
{
app.run("text.maxLines", json!({"ids": ids, "lines": v.round().clamp(0.0, 10_000.0)}));
}
});
let shown = max.map(f64::from).filter(|m| *m > 0.0);
let empty = if max.is_some() { tr("Auto") } else { tr(MIXED) };
if let Some(FieldEdit::Set(v) | FieldEdit::Scrub(v, true)) =
field_row(ui, tr("Max lines"), |ui, fw| widgets::number_field_or(ui, Id::new("ty_maxlines"), shown, empty, fw, 1.0))
{
app.run("text.maxLines", json!({"ids": ids, "lines": v.round().clamp(0.0, 10_000.0)}));
}
}
} else if let (2, Some(face)) = (tab, &vf) {
variable_axes(app, ui, ids, styles, face);
@@ -542,13 +537,14 @@ fn type_settings(
}
}
/// A Type settings row: a label, then `add` with the rest of the width.
fn field_row<R>(ui: &mut Ui, label: &str, add: impl FnOnce(&mut Ui, f32) -> R) -> R {
/// A Type settings row: a label, then `add` with the rest of the width. A label wider than its
/// column (French, say) narrows the field rather than the row outgrowing the popover, which then
/// widened every frame (#1029).
pub(crate) fn field_row<R>(ui: &mut Ui, label: &str, add: impl FnOnce(&mut Ui, f32) -> R) -> R {
let t = Tokens::get(ui.ctx());
let w = ui.available_width();
ui.horizontal(|ui| {
ui.add_sized(vec2(110.0, FIELD_H), egui::Label::new(egui::RichText::new(label).font(regular(11.0)).color(t.text)));
add(ui, w - 118.0)
add(ui, ui.available_width())
})
.inner
}