mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
#832: the per-side fields are two plain rows like Individual corners (toggle in the first); shut H/V fields read Mixed when their sides disagree; no comment naming the incumbent
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
//! alignment in its cell, and clip content. Everything goes through `node.set` props or `layout.*`
|
||||
//! commands.
|
||||
|
||||
use egui::{Align, Align2, CornerRadius, Id, Layout, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2};
|
||||
use egui::{Align2, CornerRadius, Id, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2};
|
||||
use figcraft_doc::*;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -401,97 +401,66 @@ fn alignment(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], l: &AutoLayout) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Spacing row: gap fields, then the H/V padding pair (or the 2x2 per-side
|
||||
/// grid while the individual-padding toggle is open).
|
||||
/// The Spacing row: the H/V padding pair, or, with Individual padding on, two rows of one field
|
||||
/// per side (like Individual corners).
|
||||
fn spacing(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], l: &AutoLayout) {
|
||||
let col = widgets::col_width(ui.available_width());
|
||||
let [pt, pr, pb, pl] = l.padding;
|
||||
let open = ui.data(|d| d.get_temp::<bool>(Id::new("indep_padding"))).unwrap_or(false);
|
||||
if open {
|
||||
// Each side on its own, like Figma's individual padding: a 2x2 grid of sides with
|
||||
// the toggle at its right, replacing the H/V row while open. Every measure copies
|
||||
// a plain row: 32px rows, the panel's own inter-row gap, 8px between the fields.
|
||||
let gap = ui.spacing().item_spacing.y;
|
||||
let total = 32.0 * 2.0 + gap;
|
||||
let fields_w = col * 2.0 + 8.0;
|
||||
let top = ("paddingTop", tr("Top padding"), Icon::PaddingTop, pt, 0);
|
||||
let right = ("paddingRight", tr("Right padding"), Icon::PaddingRight, pr, 1);
|
||||
let bottom = ("paddingBottom", tr("Bottom padding"), Icon::PaddingBottom, pb, 2);
|
||||
let left = ("paddingLeft", tr("Left padding"), Icon::PaddingLeft, pl, 3);
|
||||
block(ui, total, 0.0, |ui| {
|
||||
ui.allocate_ui_with_layout(vec2(fields_w, total), Layout::top_down(Align::LEFT), |ui| {
|
||||
ui.spacing_mut().item_spacing.y = 0.0;
|
||||
// The horizontal pair (left, right) on the left, the vertical pair on the right.
|
||||
for (r, pair) in [[&left, &top], [&right, &bottom]].iter().enumerate() {
|
||||
if r > 0 {
|
||||
ui.add_space(gap);
|
||||
}
|
||||
ui.allocate_ui_with_layout(vec2(fields_w, 32.0), Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.spacing_mut().item_spacing.x = 8.0;
|
||||
for (key, name, icon, v, k) in pair.iter() {
|
||||
let e = crate::variable_ui::number_field(
|
||||
app,
|
||||
ui,
|
||||
ids,
|
||||
key,
|
||||
Id::new(("padding", *k)),
|
||||
Lead::Icon(*icon, name),
|
||||
Some(*v),
|
||||
"",
|
||||
col,
|
||||
);
|
||||
app.field(ids, key, e, |v| json!(v.max(0.0)));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// The toggle keeps the shut row's rect: full-height strip, button 4px
|
||||
// down, centred in the top 32px exactly like the shut row.
|
||||
ui.allocate_ui_with_layout(vec2(24.0, total), Layout::top_down(Align::Center), |ui| {
|
||||
ui.add_space(4.0);
|
||||
if icons::button(ui, Icon::IndependentPadding, 24.0, true, tr("Individual padding")).clicked() {
|
||||
ui.data_mut(|d| d.insert_temp(Id::new("indep_padding"), false));
|
||||
}
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
row(ui, 32.0, |ui| {
|
||||
// Sides that disagree show both values ("16,54") instead of "Mixed".
|
||||
let h_pair = format!("{},{}", widgets::fmt_num(pl), widgets::fmt_num(pr));
|
||||
let h_empty = if pl == pr { tr("Mixed") } else { h_pair.as_str() };
|
||||
let e = crate::variable_ui::number_field_or(
|
||||
app,
|
||||
ui,
|
||||
ids,
|
||||
"paddingHorizontal",
|
||||
Id::new("al_padh"),
|
||||
(pl == pr).then_some(pl),
|
||||
col,
|
||||
h_empty,
|
||||
Lead::Icon(Icon::PaddingH, tr("Horizontal padding")),
|
||||
"",
|
||||
);
|
||||
app.field(ids, "paddingHorizontal", e, |v| json!(v.max(0.0)));
|
||||
let v_pair = format!("{},{}", widgets::fmt_num(pt), widgets::fmt_num(pb));
|
||||
let v_empty = if pt == pb { tr("Mixed") } else { v_pair.as_str() };
|
||||
let e = crate::variable_ui::number_field_or(
|
||||
app,
|
||||
ui,
|
||||
ids,
|
||||
"paddingVertical",
|
||||
Id::new("al_padv"),
|
||||
(pt == pb).then_some(pt),
|
||||
col,
|
||||
v_empty,
|
||||
Lead::Icon(Icon::PaddingV, tr("Vertical padding")),
|
||||
"",
|
||||
);
|
||||
app.field(ids, "paddingVertical", e, |v| json!(v.max(0.0)));
|
||||
if icons::button(ui, Icon::IndependentPadding, 24.0, false, tr("Individual padding")).clicked() {
|
||||
ui.data_mut(|d| d.insert_temp(Id::new("indep_padding"), true));
|
||||
let toggle = |ui: &mut Ui| {
|
||||
if icons::button(ui, Icon::IndependentPadding, 24.0, open, tr("Individual padding")).clicked() {
|
||||
ui.data_mut(|d| d.insert_temp(Id::new("indep_padding"), !open));
|
||||
}
|
||||
});
|
||||
};
|
||||
if open {
|
||||
// `l` is the selection's common layout, so its sides are common too. `k` indexes
|
||||
// [top, right, bottom, left].
|
||||
let sides = [
|
||||
("paddingLeft", tr("Left padding"), Icon::PaddingLeft, pl, 3),
|
||||
("paddingTop", tr("Top padding"), Icon::PaddingTop, pt, 0),
|
||||
("paddingRight", tr("Right padding"), Icon::PaddingRight, pr, 1),
|
||||
("paddingBottom", tr("Bottom padding"), Icon::PaddingBottom, pb, 2),
|
||||
];
|
||||
for (r, pair) in sides.chunks(2).enumerate() {
|
||||
row(ui, 32.0, |ui| {
|
||||
for (key, name, icon, v, k) in pair {
|
||||
let e = crate::variable_ui::number_field(app, ui, ids, key, Id::new(("padding", *k)), Lead::Icon(*icon, name), Some(*v), "", col);
|
||||
app.field(ids, key, e, |v| json!(v.max(0.0)));
|
||||
}
|
||||
if r == 0 {
|
||||
toggle(ui);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
row(ui, 32.0, |ui| {
|
||||
let e = crate::variable_ui::number_field(
|
||||
app,
|
||||
ui,
|
||||
ids,
|
||||
"paddingHorizontal",
|
||||
Id::new("al_padh"),
|
||||
Lead::Icon(Icon::PaddingH, tr("Horizontal padding")),
|
||||
(pl == pr).then_some(pl),
|
||||
"",
|
||||
col,
|
||||
);
|
||||
app.field(ids, "paddingHorizontal", e, |v| json!(v.max(0.0)));
|
||||
let e = crate::variable_ui::number_field(
|
||||
app,
|
||||
ui,
|
||||
ids,
|
||||
"paddingVertical",
|
||||
Id::new("al_padv"),
|
||||
Lead::Icon(Icon::PaddingV, tr("Vertical padding")),
|
||||
(pt == pb).then_some(pt),
|
||||
"",
|
||||
col,
|
||||
);
|
||||
app.field(ids, "paddingVertical", e, |v| json!(v.max(0.0)));
|
||||
toggle(ui);
|
||||
});
|
||||
}
|
||||
if l.mode != LayoutMode::Grid && l.wrap {
|
||||
// Wrapped rows (horizontal) or columns (vertical): the gap between them.
|
||||
let vertical = l.mode == LayoutMode::Vertical;
|
||||
|
||||
@@ -90,13 +90,15 @@ fn named_field(u: &mut Ui, label: &str) -> Pos2 {
|
||||
pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32)
|
||||
}
|
||||
|
||||
/// The shut pair fields edit their own axis: horizontal touches left and right
|
||||
/// only, vertical touches top and bottom only.
|
||||
/// The shut pair fields read Mixed when their sides disagree and edit their own axis: horizontal
|
||||
/// touches left and right only, vertical touches top and bottom only.
|
||||
#[test]
|
||||
fn shut_pair_fields_edit_their_own_axis() {
|
||||
let (mut u, f) = scene();
|
||||
u.app.run("node.set", json!({"ids": [f], "props": {"paddingLeft": 16.0, "paddingRight": 54.0}}));
|
||||
assert_eq!(padding(&u, f), [10.0, 54.0, 10.0, 16.0]);
|
||||
let shown = crate::live_tests::texts(&crate::live_tests::flat(&u.frame_output(vec![]).shapes));
|
||||
assert!(shown.iter().any(|(t, _, _)| t == "Mixed"), "sides that disagree read Mixed: {shown:?}");
|
||||
let h = named_field(&mut u, "Horizontal padding");
|
||||
click(&mut u, h);
|
||||
u.frame(vec![Event::Text("20".into())]);
|
||||
|
||||
Reference in New Issue
Block a user