graphic design tool
git clone https://git.lucas.co/cce-designer.git
src/image_handles.rs (9.8K)
1 //! The viewer states of the 2D context: a shape and a run of text on an
2 //! image, placed by dragging where the image stands in the viewport.
3 //!
4 //! The third and fourth [`HandleSource`]s, and the first whose handles are
5 //! not where their parameters say. A shape's rows are in the IMAGE'S unit
6 //! from the image's top-left corner, y running down; its handles are world
7 //! positions on a quad standing in the scene. [`PageFrame`] is the
8 //! conversion, and the framework hands it over in the [`HandleCtx`], read
9 //! off the `page` node up the chain without composing anything.
10 //!
11 //! They are also the first whose handles hang off one another, which is
12 //! what [`HandleSource::drag`] is for:
13 //!
14 //! - **move** — the centre. The other handles go with it: a shape dragged
15 //! by its middle is moved, not resized.
16 //! - **size** — a corner of the box, in the shape's own turned frame. The
17 //! box grows about its centre, so the opposite corner moves the other way.
18 //! - **turn** — the middle of the box's right edge. Its DIRECTION from the
19 //! centre is the rotation; the corner is carried round with it.
20 //!
21 //! A line has two: its middle, and an end that sets its length and its
22 //! angle together, which is how a line is drawn.
23 //!
24 //! Every drag follows the cursor's ray to the image's plane
25 //! ([`HandleSource::plane`]), so a handle stays under the pointer from any
26 //! orbit, and not only while the view is square to the image.
27
28 use crate::app::FsNode;
29 use crate::geometry::{node_param_f32, node_param_str};
30 use crate::page::PageFrame;
31 use crate::viewer_state::{HandleCtx, HandleSource};
32 use glam::Vec3;
33
34 pub struct ShapeHandles;
35 pub struct TextHandles;
36
37 /// `v` turned by `degrees`, in the page's frame: y runs down, so a positive
38 /// angle is clockwise as the page is looked at — the way `Page::shape`
39 /// turns the shape itself.
40 fn turned(degrees: f32, v: [f32; 2]) -> [f32; 2] {
41 let (sin, cos) = degrees.to_radians().sin_cos();
42 [v[0] * cos - v[1] * sin, v[0] * sin + v[1] * cos]
43 }
44
45 fn add(a: [f32; 2], b: [f32; 2]) -> [f32; 2] {
46 [a[0] + b[0], a[1] + b[1]]
47 }
48
49 fn sub(a: [f32; 2], b: [f32; 2]) -> [f32; 2] {
50 [a[0] - b[0], a[1] - b[1]]
51 }
52
53 fn length(v: [f32; 2]) -> f32 {
54 v[0].hypot(v[1])
55 }
56
57 /// The angle of `v` in degrees, or None for a vector too short to have one.
58 fn angle_of(v: [f32; 2], frame: &PageFrame) -> Option<f32> {
59 (length(v) > one_pixel(frame) * 0.5).then(|| v[1].atan2(v[0]).to_degrees())
60 }
61
62 /// One pixel of the raster, in the page's unit: the least a size can be.
63 fn one_pixel(frame: &PageFrame) -> f32 {
64 frame.unit.from_inches(1.0 / frame.scale(), frame.scale())
65 }
66
67 fn set(node: &mut FsNode, name: &str, value: String) {
68 if let Some(p) = node.params.iter_mut().find(|p| p.name == name) {
69 p.set_text(value);
70 }
71 }
72
73 fn is_line(node: &FsNode) -> bool {
74 node_param_str(node, "shape", "Rectangle").eq_ignore_ascii_case("Line")
75 }
76
77 /// The shape's centre, box and turn, as its rows have them.
78 fn shape_rows(node: &FsNode) -> ([f32; 2], [f32; 2], f32) {
79 (
80 [node_param_f32(node, "x", 0.0), node_param_f32(node, "y", 0.0)],
81 [node_param_f32(node, "width", 1.0).abs(), node_param_f32(node, "height", 1.0).abs()],
82 node_param_f32(node, "rotation", 0.0),
83 )
84 }
85
86 impl HandleSource for ShapeHandles {
87 fn name(&self) -> &'static str {
88 "Image Shape"
89 }
90
91 fn accepts(&self, node_type: &str) -> bool {
92 node_type.eq_ignore_ascii_case("page_shape")
93 }
94
95 fn read(&self, node: &FsNode, ctx: &HandleCtx) -> Vec<Vec3> {
96 let Some(frame) = ctx.page else { return Vec::new() };
97 let (c, size, turn) = shape_rows(node);
98 let world = |at: [f32; 2]| frame.to_world(at, ctx.world_unit_mm);
99 let edge = add(c, turned(turn, [size[0] * 0.5, 0.0]));
100 if is_line(node) {
101 vec![world(c), world(edge)]
102 } else {
103 let corner = add(c, turned(turn, [size[0] * 0.5, size[1] * 0.5]));
104 vec![world(c), world(corner), world(edge)]
105 }
106 }
107
108 fn write(&self, node: &mut FsNode, handles: &[Vec3], ctx: &HandleCtx) {
109 let Some(frame) = ctx.page else { return };
110 let page = |p: &Vec3| frame.from_world(*p, ctx.world_unit_mm);
111 let least = one_pixel(&frame);
112 let kept = node_param_f32(node, "rotation", 0.0);
113 let (c, size, turn) = match handles {
114 [c, end] => {
115 let (c, along) = (page(c), sub(page(end), page(c)));
116 let turn = angle_of(along, &frame).unwrap_or(kept);
117 (c, [length(along) * 2.0, node_param_f32(node, "height", 1.0)], turn)
118 }
119 [c, corner, edge] => {
120 let c = page(c);
121 let turn = angle_of(sub(page(edge), c), &frame).unwrap_or(kept);
122 // The corner in the shape's own frame: the page turned back.
123 let half = turned(-turn, sub(page(corner), c));
124 (c, [half[0].abs() * 2.0, half[1].abs() * 2.0], turn)
125 }
126 _ => return,
127 };
128 set(node, "x", frame.row(c[0]));
129 set(node, "y", frame.row(c[1]));
130 set(node, "width", frame.row(size[0].max(least)));
131 if handles.len() == 3 {
132 set(node, "height", frame.row(size[1].max(least)));
133 }
134 set(node, "rotation", format!("{turn:.1}"));
135 }
136
137 fn drag(&self, handles: &mut Vec<Vec3>, moved: usize, to: Vec3, ctx: &HandleCtx) {
138 let Some(frame) = ctx.page else {
139 handles[moved] = to;
140 return;
141 };
142 let page = |p: Vec3| frame.from_world(p, ctx.world_unit_mm);
143 let world = |at: [f32; 2]| frame.to_world(at, ctx.world_unit_mm);
144 match (moved, handles.len()) {
145 // The middle carries the rest.
146 (0, _) => {
147 let by = to - handles[0];
148 for h in handles.iter_mut() {
149 *h += by;
150 }
151 }
152 // The corner sets the box; the turn handle sits on the box's
153 // edge, so it follows the width.
154 (1, 3) => {
155 let c = page(handles[0]);
156 let turn = angle_of(sub(page(handles[2]), c), &frame).unwrap_or(0.0);
157 let half = turned(-turn, sub(page(to), c));
158 handles[1] = to;
159 handles[2] = world(add(c, turned(turn, [half[0].abs(), 0.0])));
160 }
161 // The turn handle carries the corner round the middle.
162 (2, 3) => {
163 let c = page(handles[0]);
164 let was = angle_of(sub(page(handles[2]), c), &frame);
165 let now = angle_of(sub(page(to), c), &frame);
166 if let (Some(was), Some(now)) = (was, now) {
167 let corner = sub(page(handles[1]), c);
168 handles[1] = world(add(c, turned(now - was, corner)));
169 }
170 handles[2] = to;
171 }
172 _ => handles[moved] = to,
173 }
174 }
175
176 fn plane(&self, ctx: &HandleCtx) -> Option<(Vec3, Vec3)> {
177 ctx.page.map(|frame| (Vec3::from_array(frame.origin), Vec3::Z))
178 }
179
180 fn outline(&self, node: &FsNode, ctx: &HandleCtx) -> Vec<Vec3> {
181 let Some(frame) = ctx.page else { return Vec::new() };
182 let (c, size, turn) = shape_rows(node);
183 let (hw, hh) = (size[0] * 0.5, size[1] * 0.5);
184 let corners: &[[f32; 2]] = if is_line(node) {
185 &[[-hw, 0.0], [hw, 0.0]]
186 } else {
187 &[[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]]
188 };
189 corners
190 .iter()
191 .map(|v| frame.to_world(add(c, turned(turn, *v)), ctx.world_unit_mm))
192 .collect()
193 }
194
195 fn cage(&self) -> bool {
196 false
197 }
198
199 fn extensible(&self) -> bool {
200 false
201 }
202
203 fn hints(&self) -> &'static str {
204 "drag the middle to move, the corner to size, the edge to turn"
205 }
206
207 fn handle_label(&self, i: usize) -> String {
208 ["move", "size", "turn"].get(i).copied().unwrap_or("").to_string()
209 }
210 }
211
212 impl HandleSource for TextHandles {
213 fn name(&self) -> &'static str {
214 "Image Text"
215 }
216
217 fn accepts(&self, node_type: &str) -> bool {
218 node_type.eq_ignore_ascii_case("page_text")
219 }
220
221 /// The anchor — what the text is aligned against — and a handle one
222 /// Size below it, so the type's size is a length that can be seen.
223 fn read(&self, node: &FsNode, ctx: &HandleCtx) -> Vec<Vec3> {
224 let Some(frame) = ctx.page else { return Vec::new() };
225 let at = [node_param_f32(node, "x", 0.0), node_param_f32(node, "y", 0.0)];
226 let size = node_param_f32(node, "size", 0.25).abs();
227 vec![
228 frame.to_world(at, ctx.world_unit_mm),
229 frame.to_world(add(at, [0.0, size]), ctx.world_unit_mm),
230 ]
231 }
232
233 fn write(&self, node: &mut FsNode, handles: &[Vec3], ctx: &HandleCtx) {
234 let Some(frame) = ctx.page else { return };
235 let [at, below] = handles else { return };
236 let at = frame.from_world(*at, ctx.world_unit_mm);
237 let size = length(sub(frame.from_world(*below, ctx.world_unit_mm), at));
238 set(node, "x", frame.row(at[0]));
239 set(node, "y", frame.row(at[1]));
240 set(node, "size", frame.row(size.max(one_pixel(&frame))));
241 }
242
243 fn drag(&self, handles: &mut Vec<Vec3>, moved: usize, to: Vec3, _ctx: &HandleCtx) {
244 if moved == 0 {
245 let by = to - handles[0];
246 for h in handles.iter_mut() {
247 *h += by;
248 }
249 } else {
250 handles[moved] = to;
251 }
252 }
253
254 fn plane(&self, ctx: &HandleCtx) -> Option<(Vec3, Vec3)> {
255 ctx.page.map(|frame| (Vec3::from_array(frame.origin), Vec3::Z))
256 }
257
258 fn extensible(&self) -> bool {
259 false
260 }
261
262 fn hints(&self) -> &'static str {
263 "drag the anchor to move, the handle under it to size"
264 }
265
266 fn handle_label(&self, i: usize) -> String {
267 if i == 0 { "move".into() } else { "size".into() }
268 }
269 }