mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 02:16:56 -06:00
Merge #1030: M1: individual corner fields sit at their corners, each with its own icon (#1002) (@muamerETS)
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
//! Individual corners in the Design panel, driven headless: the four corner fields sit where
|
||||
//! their corners are (top left, top right / bottom left, bottom right) and each sets its own.
|
||||
|
||||
use egui::accesskit::Role;
|
||||
use egui::{Event, Id, Key, Modifiers, Pos2, Rect, pos2};
|
||||
use figcraft_doc::NodeId;
|
||||
use figcraft_engine::Session;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::layers_drag_tests::Ui;
|
||||
|
||||
/// A rectangle rounded only on its left corners (top left and bottom left 30), selected.
|
||||
fn scene() -> (Ui, NodeId) {
|
||||
let mut s = Session::new();
|
||||
let r = NodeId(s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 200, "height": 120})).unwrap()["id"].as_u64().unwrap());
|
||||
s.execute("node.set", &json!({"ids": [r], "props": {"topLeftRadius": 30, "bottomLeftRadius": 30}})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [r]})).unwrap();
|
||||
(Ui::new(s), r)
|
||||
}
|
||||
|
||||
/// The rectangle's radii as `[top left, top right, bottom right, bottom left]`.
|
||||
fn radii(u: &Ui, r: NodeId) -> [f64; 4] {
|
||||
u.app.session.doc().unwrap().node(r).unwrap().corners.radii
|
||||
}
|
||||
|
||||
/// A full press-and-release at `p`, then settling one frame.
|
||||
fn click(u: &mut Ui, p: Pos2) {
|
||||
u.button(p, true);
|
||||
u.button(p, false);
|
||||
u.frame(vec![]);
|
||||
}
|
||||
|
||||
/// A press-and-release of Enter, committing the focused number field.
|
||||
fn enter(u: &mut Ui) {
|
||||
u.frame(vec![
|
||||
Event::Key { key: Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE },
|
||||
Event::Key { key: Key::Enter, physical_key: None, pressed: false, repeat: false, modifiers: Modifiers::NONE },
|
||||
]);
|
||||
}
|
||||
|
||||
/// The middle of the toggle button named `label`, found in the AccessKit tree.
|
||||
fn named_button(u: &mut Ui, label: &str) -> Pos2 {
|
||||
u.ctx.enable_accesskit();
|
||||
u.frame(vec![]);
|
||||
let out = u.frame_output(vec![]);
|
||||
let nodes = out.platform_output.accesskit_update.expect("an AccessKit update").nodes;
|
||||
let n = nodes.iter().map(|(_, n)| n).find(|n| n.role() == Role::Button && n.label() == Some(label)).expect(label);
|
||||
let b = n.bounds().expect("bounds");
|
||||
pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32)
|
||||
}
|
||||
|
||||
/// Corner field `k` (an index into the radii).
|
||||
fn field(u: &Ui, k: usize) -> Rect {
|
||||
u.ctx.read_response(Id::new(("corner", k)).with("field")).expect("corner field shows").rect
|
||||
}
|
||||
|
||||
/// The fields sit at their corners, and typing in the bottom right one sets only that corner in a
|
||||
/// single undo step (#1002: the bottom row showed bottom right on the left).
|
||||
#[test]
|
||||
fn corner_fields_sit_at_their_corners_and_set_their_own() {
|
||||
let (mut u, r) = scene();
|
||||
let t = named_button(&mut u, "Individual corners");
|
||||
click(&mut u, t);
|
||||
let (tl, tr, br, bl) = (field(&u, 0).center(), field(&u, 1).center(), field(&u, 2).center(), field(&u, 3).center());
|
||||
assert!(tl.x < tr.x && (tl.y - tr.y).abs() < 1.0, "top row: top left, then top right");
|
||||
assert!(bl.x < br.x && (bl.y - br.y).abs() < 1.0, "bottom row: bottom left, then bottom right");
|
||||
assert!(bl.y > tl.y && (bl.x - tl.x).abs() < 1.0, "bottom left sits under top left");
|
||||
let history = u.app.session.undo_depth().0;
|
||||
click(&mut u, br);
|
||||
u.frame(vec![Event::Text("12".into())]);
|
||||
enter(&mut u);
|
||||
u.frame(vec![]);
|
||||
assert_eq!(radii(&u, r), [30.0, 0.0, 12.0, 30.0]);
|
||||
assert_eq!(u.app.session.undo_depth().0, history + 1);
|
||||
}
|
||||
@@ -83,6 +83,11 @@ icons! {
|
||||
FlipH,
|
||||
FlipV,
|
||||
CornerRadius,
|
||||
/// Individual corners: the rounded corner each field sets.
|
||||
CornerTopLeft,
|
||||
CornerTopRight,
|
||||
CornerBottomRight,
|
||||
CornerBottomLeft,
|
||||
Angle,
|
||||
IndependentCorners,
|
||||
/// Individual padding: a frame with one inward tick per side, each set alone.
|
||||
@@ -719,7 +724,10 @@ fn draw(p: &P, icon: Icon) {
|
||||
p.poly(&[(3.5, 5.5), (12.5, 5.5), (12.5, 1.5)], true);
|
||||
p.fill(&[(3.0, 9.0), (13.0, 9.0), (13.0, 14.0)]);
|
||||
}
|
||||
CornerRadius => p.corner(2.5, 2.5, 1.0, 1.0, 11.5, 6.0),
|
||||
CornerRadius | CornerTopLeft => p.corner(2.5, 2.5, 1.0, 1.0, 11.5, 6.0),
|
||||
CornerTopRight => p.corner(13.5, 2.5, -1.0, 1.0, 11.5, 6.0),
|
||||
CornerBottomRight => p.corner(13.5, 13.5, -1.0, -1.0, 11.5, 6.0),
|
||||
CornerBottomLeft => p.corner(2.5, 13.5, 1.0, -1.0, 11.5, 6.0),
|
||||
Angle => {
|
||||
// Two rays from a vertex, the arc between them marking the angle (all unhinted, so
|
||||
// the arc meets the rays at every size).
|
||||
@@ -1417,4 +1425,18 @@ mod tests {
|
||||
let inside = (0..h.len()).all(|i| side(i) >= 0.0) || (0..h.len()).all(|i| side(i) <= 0.0);
|
||||
assert!(inside, "the handle covers the blade's base at {base:?}");
|
||||
}
|
||||
|
||||
/// Each corner's icon shows its own corner: no two of the four draw alike.
|
||||
#[test]
|
||||
fn corner_icons_differ() {
|
||||
let drawn: Vec<String> = [Icon::CornerTopLeft, Icon::CornerTopRight, Icon::CornerBottomRight, Icon::CornerBottomLeft]
|
||||
.iter()
|
||||
.map(|&icon| format!("{:?}", shapes_at_32(icon)))
|
||||
.collect();
|
||||
for (i, a) in drawn.iter().enumerate() {
|
||||
for b in &drawn[i + 1..] {
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ mod comments_tests;
|
||||
#[cfg(test)]
|
||||
mod component_props_reorder_tests;
|
||||
#[cfg(test)]
|
||||
mod corner_individual_tests;
|
||||
#[cfg(test)]
|
||||
mod cursors_tests;
|
||||
#[cfg(test)]
|
||||
mod design_paste_tests;
|
||||
|
||||
+25
-37
@@ -881,44 +881,32 @@ fn appearance(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId]) {
|
||||
});
|
||||
if indep_ok && ui.data(|d| d.get_temp::<bool>(Id::new("indep_corners"))).unwrap_or(false) {
|
||||
let radii = common(doc, sel, |n| n.corners.radii);
|
||||
let keys = [
|
||||
("topLeftRadius", tr("Top left radius")),
|
||||
("topRightRadius", tr("Top right radius")),
|
||||
("bottomRightRadius", tr("Bottom right radius")),
|
||||
("bottomLeftRadius", tr("Bottom left radius")),
|
||||
// `k` indexes `radii` ([top left, top right, bottom right, bottom left]); the fields sit
|
||||
// where their corners are: top left, top right / bottom left, bottom right.
|
||||
let corners = [
|
||||
("topLeftRadius", tr("Top left radius"), Icon::CornerTopLeft, 0),
|
||||
("topRightRadius", tr("Top right radius"), Icon::CornerTopRight, 1),
|
||||
("bottomLeftRadius", tr("Bottom left radius"), Icon::CornerBottomLeft, 3),
|
||||
("bottomRightRadius", tr("Bottom right radius"), Icon::CornerBottomRight, 2),
|
||||
];
|
||||
row(ui, 32.0, |ui| {
|
||||
for (k, (key, name)) in keys.iter().enumerate().take(2) {
|
||||
let e = crate::variable_ui::number_field(
|
||||
app,
|
||||
ui,
|
||||
&ids,
|
||||
key,
|
||||
Id::new(("corner", k)),
|
||||
Lead::Icon(Icon::CornerRadius, name),
|
||||
radii.map(|r| r[k]),
|
||||
"",
|
||||
col,
|
||||
);
|
||||
app.field(&ids, key, e, |v| json!(v.max(0.0)));
|
||||
}
|
||||
});
|
||||
row(ui, 32.0, |ui| {
|
||||
for (k, (key, name)) in keys.iter().enumerate().skip(2) {
|
||||
let e = crate::variable_ui::number_field(
|
||||
app,
|
||||
ui,
|
||||
&ids,
|
||||
key,
|
||||
Id::new(("corner", k)),
|
||||
Lead::Icon(Icon::CornerRadius, name),
|
||||
radii.map(|r| r[k]),
|
||||
"",
|
||||
col,
|
||||
);
|
||||
app.field(&ids, key, e, |v| json!(v.max(0.0)));
|
||||
}
|
||||
});
|
||||
for pair in corners.chunks(2) {
|
||||
row(ui, 32.0, |ui| {
|
||||
for (key, name, icon, k) in pair {
|
||||
let e = crate::variable_ui::number_field(
|
||||
app,
|
||||
ui,
|
||||
&ids,
|
||||
key,
|
||||
Id::new(("corner", *k)),
|
||||
Lead::Icon(*icon, name),
|
||||
radii.map(|r| r[*k]),
|
||||
"",
|
||||
col,
|
||||
);
|
||||
app.field(&ids, key, e, |v| json!(v.max(0.0)));
|
||||
}
|
||||
});
|
||||
}
|
||||
let sm = common(doc, sel, |n| (n.corners.smoothing * 100.0).round());
|
||||
widgets::sublabels(ui, tr("Corner smoothing"), None);
|
||||
row(ui, 32.0, |ui| {
|
||||
|
||||
Reference in New Issue
Block a user