1//! The Draw tab's tools on the page: pens and highlighters drawing a stroke per press, shapes
2//! dragged out on the grid, the stroke eraser, and the lasso whose drawings move and delete,
3//! as OneNote 2010 has them (`corpus/ink-tools`).
4
5use super::{PageView, Response, Result};
6use crate::editor::{FAVORITES, Pen};
7use crate::gpu::colorref;
8use draw::{
9 Primitive, Stroke,
10 edit::{Key, NamedKey},
11};
12use onestore::{
13 ExGuid,
14 page::{Ink, InkStroke, ink::ShapeKind, text::new_id},
15};
16
17/// The pen gallery under a section of tab colour `section` (linear RGBA): a pen in the
18/// section's accent, then OneNote's favourites. The accent takes the light theme's shade in
19/// either theme, so a stroke stores one colour and shows it in both.
20pub fn pens(section: [f32; 4]) -> [Pen; 15] {
21 let [red, green, blue] = draw::srgb_bytes({
22 let [saturation, lightness] = draw::LIGHT_ACCENT;
23 draw::hsl(
24 draw::hue(section),
25 saturation * draw::vividness(section),
26 lightness,
27 )
28 });
29 let accent = Pen::new(35.0, Some(u32::from_le_bytes([red, green, blue, 0])));
30 std::array::from_fn(|place| place.checked_sub(1).map_or(accent, |at| FAVORITES[at]))
31}
32
33/// What a press on the page does.
34#[derive(Clone, Copy, Debug, Default, PartialEq)]
35pub enum Tool {
36 /// Select & Type: text, and drawings picked by a click.
37 #[default]
38 Select,
39 /// A stroke per press.
40 Pen(Pen),
41 /// A shape dragged out, its corners on the grid unless Option is held.
42 Shape(ShapeKind, Pen),
43 /// The stroke eraser.
44 Eraser,
45 Lasso,
46}
47
48#[derive(Default)]
49pub(super) struct State {
50 tool: Tool,
51 gesture: Option<Gesture>,
52 /// Drawings picked by the lasso or a click.
53 selection: Vec<ExGuid>,
54 /// The pen's pressure where the pointer is, from 0 to 1; none for a mouse or finger.
55 pressure: Option<f32>,
56}
57
58enum Gesture {
59 /// The points drawn through, and the pressure at each where the pen reports it.
60 Stroke(Vec<[f32; 2]>, Vec<f32>),
61 Shape([f32; 2]),
62 /// `mark` counts the ops waiting when the sweep began.
63 Erase {
64 last: [f32; 2],
65 erased: bool,
66 mark: usize,
67 },
68 Lasso(Vec<[f32; 2]>),
69 Move {
70 press: [f32; 2],
71 },
72}
73
74/// The eraser's reach either side of its path, in logical pixels.
75const ERASER_REACH: f32 = 4.0;
76
77impl PageView {
78 pub fn tool(&self) -> Tool {
79 self.ink.tool
80 }
81
82 /// Picks what presses on the page do, as the Draw tab's tools do; the lasso's selection
83 /// goes.
84 pub fn set_tool(&mut self, tool: Tool) -> Response {
85 self.ink.tool = tool;
86 self.leave_ink();
87 Response::redraw()
88 }
89
90 /// The pen's pressure at the pointer, from 0 to 1, for what the pointer does next: none
91 /// for a mouse or finger, whose strokes keep their pen's width.
92 pub fn set_pressure(&mut self, pressure: Option<f32>) {
93 self.ink.pressure = pressure;
94 }
95
96 /// Drawings the lasso or a click picked.
97 pub fn ink_selection(&self) -> &[ExGuid] {
98 &self.ink.selection
99 }
100
101 /// Ends a gesture as though it never began, as a second finger landing does on a touch
102 /// screen: a sweep of the eraser gives back what it erased. The sweep's ops must not have
103 /// been taken since it began.
104 pub fn cancel_ink(&mut self) -> Result<Response> {
105 let gesture = self.ink.gesture.take();
106 self.editor.drag_ink(None);
107 if let Some(Gesture::Erase {
108 erased: true, mark, ..
109 }) = gesture
110 {
111 self.editor.retract_erasing(&mut self.engine, mark)?;
112 }
113 self.changed()
114 }
115
116 /// Ends a gesture unfinished, as a lost focus does.
117 pub(super) fn end_ink(&mut self) {
118 self.ink.gesture = None;
119 self.editor.drag_ink(None);
120 }
121
122 /// Another page shows: the gesture ends and nothing stays picked; the tool stays.
123 pub(super) fn leave_ink(&mut self) {
124 self.end_ink();
125 self.ink.selection.clear();
126 }
127
128 /// Forgets picked drawings the page no longer holds, after a change made elsewhere. A
129 /// stroke, shape or lasso under way goes on; a drag of drawings ends, and the eraser's
130 /// next touch starts an undo step of its own.
131 pub(super) fn refresh_ink(&mut self) {
132 match &mut self.ink.gesture {
133 Some(Gesture::Move { .. }) => self.ink.gesture = None,
134 Some(Gesture::Erase { erased, .. }) => *erased = false,
135 _ => {}
136 }
137 let editor = &self.editor;
138 self.ink
139 .selection
140 .retain(|id| editor.ink_extent(std::slice::from_ref(id)).is_some());
141 }
142
143 fn reach(&self) -> f32 {
144 ERASER_REACH * self.pixel()
145 }
146
147 /// A press at page point `point`, when a drawing tool takes it or it picks ink.
148 pub(super) fn ink_pressed(&mut self, point: [f32; 2]) -> Result<Option<Response>> {
149 let selected = !self.ink.selection.is_empty()
150 && self
151 .editor
152 .ink_extent(&self.ink.selection)
153 .is_some_and(|[x0, y0, x1, y1]| {
154 (x0..=x1).contains(&point[0]) && (y0..=y1).contains(&point[1])
155 });
156 let gesture = match self.ink.tool {
157 Tool::Select | Tool::Lasso if selected => Gesture::Move { press: point },
158 Tool::Select => {
159 self.ink.selection.clear();
160 if self.hit_test(point).is_some() {
161 return Ok(None);
162 }
163 let Some(id) = self.editor.ink_at(point, self.reach()) else {
164 return Ok(None);
165 };
166 self.set_object_focus(None);
167 self.ink.selection = vec![id];
168 Gesture::Move { press: point }
169 }
170 Tool::Pen(_) => Gesture::Stroke(vec![point], self.ink.pressure.into_iter().collect()),
171 Tool::Shape(..) => Gesture::Shape(self.grid(point)),
172 Tool::Eraser => {
173 let mark = self.editor.pending_ops();
174 let erased = self.editor.erase(point, point, self.reach(), false)?;
175 Gesture::Erase {
176 last: point,
177 erased,
178 mark,
179 }
180 }
181 Tool::Lasso => {
182 self.ink.selection.clear();
183 Gesture::Lasso(vec![point])
184 }
185 };
186 self.drag = None;
187 self.ink.gesture = Some(gesture);
188 Ok(Some(self.changed()?))
189 }
190
191 /// The pointer moved to page point `point` during a gesture.
192 pub(super) fn ink_moved(&mut self, point: [f32; 2]) -> Result<Response> {
193 let pressure = self.ink.pressure;
194 match &mut self.ink.gesture {
195 Some(Gesture::Stroke(points, _) | Gesture::Lasso(points)) => {
196 let last = points[points.len() - 1];
197 // Samples closer than a HIMETRIC unit store as one point.
198 if (point[0] - last[0]).hypot(point[1] - last[1]) >= 72.0 / 2540.0 {
199 points.push(point);
200 if let Some(Gesture::Stroke(_, levels)) = &mut self.ink.gesture
201 && let Some(&last) = levels.last()
202 {
203 levels.push(pressure.unwrap_or(last));
204 }
205 }
206 Ok(Response::redraw())
207 }
208 Some(Gesture::Erase { last, erased, .. }) => {
209 let from = std::mem::replace(last, point);
210 let join = *erased;
211 if self.editor.erase(from, point, self.reach(), join)? {
212 if let Some(Gesture::Erase { erased, .. }) = &mut self.ink.gesture {
213 *erased = true;
214 }
215 return self.changed();
216 }
217 Ok(Response::default())
218 }
219 Some(Gesture::Move { press }) => {
220 let delta = [point[0] - press[0], point[1] - press[1]];
221 self.editor
222 .drag_ink(Some((self.ink.selection.clone(), delta)));
223 Ok(Response::redraw())
224 }
225 Some(Gesture::Shape(_)) => Ok(Response::redraw()),
226 None => Ok(Response::default()),
227 }
228 }
229
230 /// The press ended at page point `point`, finishing the gesture.
231 pub(super) fn ink_released(&mut self, point: [f32; 2]) -> Result<Response> {
232 let Some(gesture) = self.ink.gesture.take() else {
233 return Ok(Response::default());
234 };
235 match gesture {
236 Gesture::Stroke(points, pressure) => {
237 let Tool::Pen(pen) = self.ink.tool else {
238 return Ok(Response::default());
239 };
240 self.editor.draw(Ink {
241 id: new_id()?,
242 layout: Default::default(),
243 strokes: vec![pen.stroke(&points, &pressure)?],
244 groups: Vec::new(),
245 shape: None,
246 })?;
247 }
248 Gesture::Shape(from) => {
249 let Tool::Shape(kind, pen) = self.ink.tool else {
250 return Ok(Response::default());
251 };
252 let to = self.grid(point);
253 if from == to {
254 return Ok(Response::redraw());
255 }
256 self.editor
257 .draw(Ink::drawn(kind, from, to, &pen.stroke(&[], &[])?)?)?;
258 }
259 Gesture::Erase { .. } => return Ok(Response::redraw()),
260 Gesture::Lasso(mut points) => {
261 points.push(point);
262 self.ink.selection = self.editor.ink_within(&points);
263 return Ok(Response::redraw());
264 }
265 Gesture::Move { press } => {
266 let delta = [point[0] - press[0], point[1] - press[1]];
267 // As OneNote does, a drawing dropped outside the view stays where it was.
268 let inside = (0..2).all(|axis| {
269 (0.0..self.viewport.size[axis] as f32).contains(&self.pointer[axis])
270 });
271 let selection = self.ink.selection.clone();
272 self.editor
273 .move_ink(&selection, if inside { delta } else { [0.0; 2] })?;
274 }
275 }
276 self.changed()
277 }
278
279 /// Delete and Backspace delete picked drawings; Escape lets them go, then leaves a
280 /// drawing tool for Select & Type, as OneNote's Escape does.
281 pub(super) fn ink_key(&mut self, key: &Key) -> Result<Option<Response>> {
282 match key {
283 Key::Named(NamedKey::Backspace | NamedKey::Delete)
284 if !self.ink.selection.is_empty() =>
285 {
286 let selection = std::mem::take(&mut self.ink.selection);
287 self.editor.delete_ink(&selection)?;
288 Ok(Some(self.changed()?))
289 }
290 Key::Named(NamedKey::Escape) if !self.ink.selection.is_empty() => {
291 self.ink.selection.clear();
292 Ok(Some(Response::redraw()))
293 }
294 Key::Named(NamedKey::Escape) if self.ink.tool != Tool::Select => {
295 Ok(Some(self.set_tool(Tool::Select)))
296 }
297 _ => {
298 self.ink.selection.clear();
299 Ok(None)
300 }
301 }
302 }
303
304 /// Whether a press on the page began a gesture of the drawing tools.
305 pub fn inking(&self) -> bool {
306 self.ink.gesture.is_some()
307 }
308
309 /// Whether a drawing tool takes presses, which then show a crosshair.
310 pub(super) fn drawing(&self) -> bool {
311 self.ink.tool != Tool::Select
312 }
313
314 /// The gesture under way and the picked drawings' frame, over the page.
315 pub(super) fn append_ink_feedback(
316 &self,
317 automatic: [f32; 4],
318 primitives: &mut Vec<Primitive<'_>>,
319 ) {
320 let pixel = self.pixel();
321 let point = self.viewport.document_point(self.pointer);
322 let preview = |ink: Ink, primitives: &mut Vec<Primitive<'_>>| {
323 crate::gpu::page::append_ink(&ink, [0.0; 2], automatic, primitives)
324 };
325 match (&self.ink.gesture, self.ink.tool) {
326 (Some(Gesture::Stroke(points, pressure)), Tool::Pen(pen)) => {
327 if let Ok(stroke) = pen.stroke(points, pressure) {
328 preview(ink(vec![stroke]), primitives);
329 }
330 }
331 (Some(Gesture::Shape(from)), Tool::Shape(kind, pen)) => {
332 let to = self.grid(point);
333 if let Ok(pen) = pen.stroke(&[], &[])
334 && let Ok(shape) = Ink::drawn(kind, *from, to, &pen)
335 {
336 preview(shape, primitives);
337 }
338 }
339 (Some(Gesture::Lasso(points)), _) => {
340 let lasso = colorref(0x00808080);
341 for pair in points.windows(2) {
342 primitives.push(Primitive::Segment {
343 from: pair[0],
344 to: pair[1],
345 width: pixel,
346 round: true,
347 color: lasso,
348 });
349 }
350 }
351 _ => {}
352 }
353 if let Some([x0, y0, x1, y1]) = self.editor.ink_extent(&self.ink.selection) {
354 let pad = 3.0 * pixel;
355 primitives.push(Primitive::RoundedRect {
356 rect: [x0 - pad, y0 - pad, x1 + pad, y1 + pad],
357 radius: [0.0; 2],
358 stroke: Some(Stroke::Dashed(pixel)),
359 color: colorref(0x00e0a060),
360 });
361 }
362 }
363}
364
365fn ink(strokes: Vec<InkStroke>) -> Ink {
366 Ink {
367 id: ExGuid::default(),
368 layout: Default::default(),
369 strokes,
370 groups: Vec::new(),
371 shape: None,
372 }
373}