M3: line-end previews in the stroke menus; scroll events over the control channel (#76)
The Start point and End point menus draw each line end beside its name (none, round, square, line
arrow, triangle, reversed triangle, circle, diamond; mirrored for start points). `ui.input` takes
`{scroll: [dx, dy]}` mouse-wheel events, so scrolled panels can be driven and checked.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
50517a6431
commit
a344b7a9a6
|
|
@ -239,7 +239,7 @@ Scores by behaviour, 0–100%. Hours are what remains (one agent).
|
|||
| Layers panel | 5 | 83% | Tab to rename the next layer, visual QA of long lists | 1 |
|
||||
| Shapes & vector | 8 | 74% | live boolean editing, arc handles on canvas, shape builder on strokes and open paths, one-sided widths | 4–8 |
|
||||
| Paint | 7 | 78% | a wide-gamut canvas (egui draws sRGB, so Display P3 colours outside sRGB clip on screen; exports keep them), images' own embedded colour profiles (images are taken as sRGB) | 1–3 |
|
||||
| Strokes | 3 | 80% | stroke styles, arrowhead previews in the menus | 2–3 |
|
||||
| Strokes | 3 | 82% | stroke styles | 1–2 |
|
||||
| Effects | 4 | 70% | glass/texture fidelity, shadow spread on non-rects exactness | 4–7 |
|
||||
| Frames, groups, sections, masks | 5 | 75% | hiding section contents UI, section-level prototype/dev affordances | 2–4 |
|
||||
| Auto layout & constraints | 9 | 72% | visual QA of nested edge cases, arrow-key moves across nested auto layout frames | 2–4 |
|
||||
|
|
|
|||
|
|
@ -223,6 +223,14 @@ pub fn handle(app: &mut FigApp, ctx: &Context, method: &str, p: &Value) -> Resul
|
|||
}
|
||||
} else if let Some(t) = e.get("text").and_then(Value::as_str) {
|
||||
events.push(egui::Event::Text(t.to_string()));
|
||||
} else if let Some(d) = e.get("scroll").and_then(pos) {
|
||||
// Content moves by `d` points (negative y scrolls down, like a wheel turned towards you).
|
||||
events.push(egui::Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Point,
|
||||
delta: d.to_vec2(),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: mods,
|
||||
});
|
||||
} else if b(e, "leave") {
|
||||
events.push(egui::Event::PointerGone);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! The right panel: Design, Prototype and Inspect tabs.
|
||||
|
||||
use egui::{Align2, Color32, CornerRadius, Id, Rect, Sense, Stroke, Ui, pos2, vec2};
|
||||
use egui::{Align2, Color32, CornerRadius, Id, Pos2, Rect, Sense, Stroke, Ui, pos2, vec2};
|
||||
use figcraft_color::{BlendMode, Paint, PaintKind};
|
||||
use figcraft_doc::*;
|
||||
use serde_json::{Value, json};
|
||||
|
|
@ -1080,7 +1080,7 @@ fn stroke_settings(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId]
|
|||
let resp = widgets::dropdown(ui, cap.map(StrokeCap::label).unwrap_or("Mixed"), half, None);
|
||||
egui::Popup::menu(&resp).show(|ui| {
|
||||
for c in StrokeCap::ALL {
|
||||
if menu_item(ui, c.label(), "") {
|
||||
if menu_item_with(ui, c.label(), "", |p, r, col| cap_art(p, r, col, c, key == "startCap")) {
|
||||
app.set_props(&ids, json!({key: format!("{c:?}")}));
|
||||
ui.close();
|
||||
}
|
||||
|
|
@ -1092,6 +1092,50 @@ fn stroke_settings(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId]
|
|||
ui.add_space(4.0);
|
||||
}
|
||||
|
||||
/// A short stroke ending in `cap` (on its left end for a start point), drawn at the right of a menu
|
||||
/// row.
|
||||
fn cap_art(p: &egui::Painter, r: Rect, col: Color32, cap: StrokeCap, start: bool) {
|
||||
let y = r.center().y;
|
||||
let (left, right) = (r.max.x - 58.0, r.max.x - 14.0);
|
||||
// The capped end, the plain end, and the direction from the stroke into its cap.
|
||||
let (tip, tail, d) = if start { (left, right, -1.0) } else { (right, left, 1.0) };
|
||||
let at = |back: f32, dy: f32| pos2(tip - d * back, y + dy);
|
||||
let line = |to: Pos2, w: f32| {
|
||||
p.line_segment([pos2(tail, y), to], Stroke::new(w, col));
|
||||
};
|
||||
let fill = |pts: Vec<Pos2>| {
|
||||
p.add(egui::Shape::convex_polygon(pts, col, Stroke::NONE));
|
||||
};
|
||||
match cap {
|
||||
StrokeCap::None => line(at(0.0, 0.0), 4.0),
|
||||
StrokeCap::Round => {
|
||||
line(at(2.0, 0.0), 4.0);
|
||||
p.circle_filled(at(2.0, 0.0), 2.0, col);
|
||||
}
|
||||
StrokeCap::Square => line(at(-2.0, 0.0), 4.0),
|
||||
StrokeCap::LineArrow => {
|
||||
line(at(0.0, 0.0), 1.5);
|
||||
p.add(egui::Shape::line(vec![at(5.5, -5.5), at(0.0, 0.0), at(5.5, 5.5)], Stroke::new(1.5, col)));
|
||||
}
|
||||
StrokeCap::TriangleArrow => {
|
||||
line(at(7.0, 0.0), 1.5);
|
||||
fill(vec![at(0.0, 0.0), at(8.0, -4.5), at(8.0, 4.5)]);
|
||||
}
|
||||
StrokeCap::ReversedTriangle => {
|
||||
line(at(8.0, 0.0), 1.5);
|
||||
fill(vec![at(8.0, 0.0), at(0.0, -4.5), at(0.0, 4.5)]);
|
||||
}
|
||||
StrokeCap::CircleFilled => {
|
||||
line(at(4.0, 0.0), 1.5);
|
||||
p.circle_filled(at(4.0, 0.0), 4.0, col);
|
||||
}
|
||||
StrokeCap::DiamondFilled => {
|
||||
line(at(5.0, 0.0), 1.5);
|
||||
fill(vec![at(0.0, 0.0), at(5.0, -5.0), at(10.0, 0.0), at(5.0, 5.0)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_row(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], i: usize, p: &Paint, fills: bool) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let ids = ids.to_vec();
|
||||
|
|
|
|||
|
|
@ -105,8 +105,9 @@ Raw window input for the next frame, as if from the mouse and keyboard: it reach
|
|||
popovers and fields (`ui.pointer` and `ui.key` drive the canvas tools and shortcuts instead).
|
||||
`events` is a list of `{move: [x, y]}`, `{down: [x, y], button?: "secondary"}`,
|
||||
`{up: [x, y], button?}`, `{key, shift?, alt?, cmd?}` (pressed and released; egui key names such as
|
||||
`ArrowLeft`, `Escape`, `A`), `{text}` (typed text) and `{leave: true}` (the pointer left the
|
||||
window); positions are window points. Events of one call arrive in the same frame, so send a press
|
||||
`ArrowLeft`, `Escape`, `A`), `{text}` (typed text), `{scroll: [dx, dy], shift?, alt?, cmd?}` (a
|
||||
mouse wheel at the pointer: the content moves by that many points, so a negative `dy` scrolls down)
|
||||
and `{leave: true}` (the pointer left the window); positions are window points. Events of one call arrive in the same frame, so send a press
|
||||
and its release (or a double-click's two clicks) as separate calls. Returns `{queued}`.
|
||||
|
||||
```json
|
||||
|
|
|
|||
Loading…
Reference in New Issue