mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-02 06:46:50 -06:00
Merge pull request #1521 from Carbocode/feat/corner-smoothing-scrub
M1: add a draggable corner smoothing icon
This commit is contained in:
@@ -70,6 +70,75 @@ fn ios_button_sets_corner_smoothing_to_60_percent() {
|
||||
assert_eq!(u.app.session.undo_depth().0, history + 1);
|
||||
}
|
||||
|
||||
/// The visible smoothing icon uses the same scrub gesture, cursor and modifier rates as the
|
||||
/// other numeric fields; the whole drag is one undo step, including when clamped at either end.
|
||||
#[test]
|
||||
fn corner_smoothing_icon_scrubs_with_modifiers_and_one_undo_step() {
|
||||
for (dx, modifiers, want) in
|
||||
[(16.0, Modifiers::NONE, 0.66), (8.0, Modifiers::SHIFT, 1.0), (16.0, Modifiers::ALT, 0.516), (-80.0, Modifiers::NONE, 0.0)]
|
||||
{
|
||||
let (mut u, r) = scene();
|
||||
u.app.run("node.set", json!({"ids": [r], "props": {"cornerSmoothing": 0.5}}));
|
||||
let toggle = named_button(&mut u, "Individual corners");
|
||||
click(&mut u, toggle);
|
||||
let lead = u.ctx.read_response(Id::unique("smoothing").with("lead")).expect("smoothing icon").rect;
|
||||
assert_eq!(lead.width(), 24.0, "the icon has the same drag target as other fields");
|
||||
let from = lead.center();
|
||||
let out = u.frame_output(vec![Event::PointerMoved(from)]);
|
||||
assert_eq!(out.platform_output.cursor_icon, egui::CursorIcon::ResizeHorizontal);
|
||||
let history = u.app.session.undo_depth().0;
|
||||
u.frame_with(vec![Event::PointerButton { pos: from, button: egui::PointerButton::Primary, pressed: true, modifiers }], modifiers);
|
||||
for step in 1..=4 {
|
||||
u.frame_with(vec![Event::PointerMoved(pos2(from.x + dx * step as f32 / 4.0, from.y))], modifiers);
|
||||
}
|
||||
let smoothing = |u: &Ui| u.app.session.doc().unwrap().node(r).unwrap().corners.smoothing;
|
||||
assert!((smoothing(&u) - want).abs() < 1e-9, "the drag updates the canvas before release");
|
||||
u.frame_with(
|
||||
vec![Event::PointerButton { pos: pos2(from.x + dx, from.y), button: egui::PointerButton::Primary, pressed: false, modifiers }],
|
||||
modifiers,
|
||||
);
|
||||
u.frame(vec![]);
|
||||
assert!((smoothing(&u) - want).abs() < 1e-9, "release keeps the scrubbed value");
|
||||
assert_eq!(u.app.session.undo_depth().0, history + 1);
|
||||
u.app.session.undo().unwrap();
|
||||
assert_eq!(smoothing(&u), 0.5);
|
||||
u.app.session.redo().unwrap();
|
||||
assert!((smoothing(&u) - want).abs() < 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fine drags keep their fractional baseline across gestures, including a later plain drag.
|
||||
#[test]
|
||||
fn consecutive_smoothing_drags_preserve_fractional_percentages() {
|
||||
let (mut u, r) = scene();
|
||||
u.app.run("node.set", json!({"ids": [r], "props": {"cornerSmoothing": 0.5}}));
|
||||
let toggle = named_button(&mut u, "Individual corners");
|
||||
click(&mut u, toggle);
|
||||
let history = u.app.session.undo_depth().0;
|
||||
let smoothing = |u: &Ui| u.app.session.doc().unwrap().node(r).unwrap().corners.smoothing;
|
||||
for (step, (dx, modifiers, want)) in
|
||||
[(16.0, Modifiers::ALT, 0.516), (16.0, Modifiers::ALT, 0.532), (8.0, Modifiers::NONE, 0.612)].into_iter().enumerate()
|
||||
{
|
||||
let from = u.ctx.read_response(Id::unique("smoothing").with("lead")).unwrap().rect.center();
|
||||
u.frame_with(vec![Event::PointerMoved(from)], modifiers);
|
||||
u.frame_with(vec![Event::PointerButton { pos: from, button: egui::PointerButton::Primary, pressed: true, modifiers }], modifiers);
|
||||
for k in 1..=4 {
|
||||
u.frame_with(vec![Event::PointerMoved(pos2(from.x + dx * k as f32 / 4.0, from.y))], modifiers);
|
||||
}
|
||||
u.frame_with(
|
||||
vec![Event::PointerButton { pos: pos2(from.x + dx, from.y), button: egui::PointerButton::Primary, pressed: false, modifiers }],
|
||||
modifiers,
|
||||
);
|
||||
u.frame(vec![]);
|
||||
assert!((smoothing(&u) - want).abs() < 1e-9, "gesture {step}: expected {want}, got {}", smoothing(&u));
|
||||
assert_eq!(u.app.session.undo_depth().0, history + step + 1);
|
||||
}
|
||||
for want in [0.532, 0.516, 0.5] {
|
||||
u.app.session.undo().unwrap();
|
||||
assert!((smoothing(&u) - want).abs() < 1e-9, "undo restores the precise value");
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]
|
||||
|
||||
@@ -89,6 +89,8 @@ icons! {
|
||||
FlipH,
|
||||
FlipV,
|
||||
CornerRadius,
|
||||
/// A smooth corner with its two tangent endpoints.
|
||||
CornerSmoothing,
|
||||
/// Individual corners: the rounded corner each field sets.
|
||||
CornerTopLeft,
|
||||
CornerTopRight,
|
||||
|
||||
@@ -510,6 +510,18 @@ fn draw(p: &P, icon: Icon) {
|
||||
p.fill(&[(3.0, 9.0), (13.0, 9.0), (13.0, 14.0)]);
|
||||
}
|
||||
CornerRadius | CornerTopLeft => p.corner(2.5, 2.5, 1.0, 1.0, 11.5, 6.0),
|
||||
CornerSmoothing => {
|
||||
// An original superellipse corner, with dots marking its tangent endpoints.
|
||||
let points: Vec<_> = (0..=24)
|
||||
.map(|i| {
|
||||
let a = i as f32 / 24.0 * FRAC_PI_2;
|
||||
(2.5 + 11.0 * (1.0 - a.cos().max(0.0).sqrt()), 2.5 + 11.0 * (1.0 - a.sin().max(0.0).sqrt()))
|
||||
})
|
||||
.collect();
|
||||
p.path(&points, false);
|
||||
p.dot(2.5, 13.5, 1.0);
|
||||
p.dot(13.5, 2.5, 1.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),
|
||||
|
||||
@@ -113,10 +113,10 @@ pub(super) fn appearance(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[N
|
||||
}
|
||||
});
|
||||
}
|
||||
let sm = common(doc, sel, |n| (n.corners.smoothing * 100.0).round());
|
||||
let sm = common(doc, sel, |n| n.corners.smoothing * 100.0);
|
||||
widgets::sublabels(ui, tr("Corner smoothing"), None);
|
||||
row(ui, 32.0, |ui| {
|
||||
let e = widgets::number_field(ui, Id::unique("smoothing"), Lead::None, sm, "%", col, 1.0);
|
||||
let e = widgets::number_field(ui, Id::unique("smoothing"), Lead::Icon(Icon::CornerSmoothing, tr("Corner smoothing")), sm, "%", col, 1.0);
|
||||
app.field(&ids, "cornerSmoothing", e, |v| json!((v / 100.0).clamp(0.0, 1.0)));
|
||||
if widgets::button(ui, tr("iOS"), false, true).on_hover_text(tr("Set corner smoothing to 60%")).clicked() {
|
||||
app.set_props(&ids, json!({"cornerSmoothing": 0.6}));
|
||||
|
||||
+3
-2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user