git.lucas.co / cce-designer
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 }