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 if tool != Tool::Select {
86 self.column = None;
87 }
88 self.ink.tool = tool;
89 self.leave_ink();
90 Response::redraw()
91 }
92
93 /// The pen's pressure at the pointer, from 0 to 1, for what the pointer does next: none
94 /// for a mouse or finger, whose strokes keep their pen's width.
95 pub fn set_pressure(&mut self, pressure: Option<f32>) {
96 self.ink.pressure = pressure;
97 }
98
99 /// Drawings the lasso or a click picked.
100 pub fn ink_selection(&self) -> &[ExGuid] {
101 &self.ink.selection
102 }
103
104 /// Ends a gesture as though it never began, as a second finger landing does on a touch
105 /// screen: a sweep of the eraser gives back what it erased. The sweep's ops must not have
106 /// been taken since it began.
107 pub fn cancel_ink(&mut self) -> Result<Response> {
108 let gesture = self.ink.gesture.take();
109 self.editor.drag_ink(None);
110 if let Some(Gesture::Erase {
111 erased: true, mark, ..
112 }) = gesture
113 {
114 self.editor.retract_erasing(&mut self.engine, mark)?;
115 }
116 self.changed()
117 }
118
119 /// Ends a gesture unfinished, as a lost focus does.
120 pub(super) fn end_ink(&mut self) {
121 self.ink.gesture = None;
122 self.editor.drag_ink(None);
123 }
124
125 /// Another page shows: the gesture ends and nothing stays picked; the tool stays.
126 pub(super) fn leave_ink(&mut self) {
127 self.end_ink();
128 self.ink.selection.clear();
129 }
130
131 /// Forgets picked drawings the page no longer holds, after a change made elsewhere. A
132 /// stroke, shape or lasso under way goes on; a drag of drawings ends, and the eraser's
133 /// next touch starts an undo step of its own.
134 pub(super) fn refresh_ink(&mut self) {
135 match &mut self.ink.gesture {
136 Some(Gesture::Move { .. }) => self.ink.gesture = None,
137 Some(Gesture::Erase { erased, .. }) => *erased = false,
138 _ => {}
139 }
140 let editor = &self.editor;
141 self.ink
142 .selection
143 .retain(|id| editor.ink_extent(std::slice::from_ref(id)).is_some());
144 }
145
146 fn reach(&self) -> f32 {
147 ERASER_REACH * self.pixel()
148 }
149
150 /// A press at page point `point`, when a drawing tool takes it or it picks ink.
151 pub(super) fn ink_pressed(&mut self, point: [f32; 2]) -> Result<Option<Response>> {
152 if self.column.is_some() {
153 return Ok(None);
154 }
155 let selected = !self.ink.selection.is_empty()
156 && self
157 .editor
158 .ink_extent(&self.ink.selection)
159 .is_some_and(|[x0, y0, x1, y1]| {
160 (x0..=x1).contains(&point[0]) && (y0..=y1).contains(&point[1])
161 });
162 let gesture = match self.ink.tool {
163 Tool::Select | Tool::Lasso if selected => Gesture::Move { press: point },
164 Tool::Select => {
165 self.ink.selection.clear();
166 if self.hit_test(point).is_some() {
167 return Ok(None);
168 }
169 let Some(id) = self.editor.ink_at(point, self.reach()) else {
170 return Ok(None);
171 };
172 self.set_object_focus(None);
173 self.ink.selection = vec![id];
174 Gesture::Move { press: point }
175 }
176 Tool::Pen(_) => Gesture::Stroke(vec![point], self.ink.pressure.into_iter().collect()),
177 Tool::Shape(..) => Gesture::Shape(self.grid(point)),
178 Tool::Eraser => {
179 let mark = self.editor.pending_ops();
180 let erased = self.editor.erase(point, point, self.reach(), false)?;
181 Gesture::Erase {
182 last: point,
183 erased,
184 mark,
185 }
186 }
187 Tool::Lasso => {
188 self.ink.selection.clear();
189 Gesture::Lasso(vec![point])
190 }
191 };
192 self.drag = None;
193 self.ink.gesture = Some(gesture);
194 Ok(Some(self.changed()?))
195 }
196
197 /// The pointer moved to page point `point` during a gesture.
198 pub(super) fn ink_moved(&mut self, point: [f32; 2]) -> Result<Response> {
199 let pressure = self.ink.pressure;
200 match &mut self.ink.gesture {
201 Some(Gesture::Stroke(points, _) | Gesture::Lasso(points)) => {
202 let last = points[points.len() - 1];
203 // Samples closer than a HIMETRIC unit store as one point.
204 if (point[0] - last[0]).hypot(point[1] - last[1]) >= 72.0 / 2540.0 {
205 points.push(point);
206 if let Some(Gesture::Stroke(_, levels)) = &mut self.ink.gesture
207 && let Some(&last) = levels.last()
208 {
209 levels.push(pressure.unwrap_or(last));
210 }
211 }
212 Ok(Response::redraw())
213 }
214 Some(Gesture::Erase { last, erased, .. }) => {
215 let from = std::mem::replace(last, point);
216 let join = *erased;
217 if self.editor.erase(from, point, self.reach(), join)? {
218 if let Some(Gesture::Erase { erased, .. }) = &mut self.ink.gesture {
219 *erased = true;
220 }
221 return self.changed();
222 }
223 Ok(Response::default())
224 }
225 Some(Gesture::Move { press }) => {
226 let delta = [point[0] - press[0], point[1] - press[1]];
227 self.editor
228 .drag_ink(Some((self.ink.selection.clone(), delta)));
229 Ok(Response::redraw())
230 }
231 Some(Gesture::Shape(_)) => Ok(Response::redraw()),
232 None => Ok(Response::default()),
233 }
234 }
235
236 /// The press ended at page point `point`, finishing the gesture.
237 pub(super) fn ink_released(&mut self, point: [f32; 2]) -> Result<Response> {
238 let Some(gesture) = self.ink.gesture.take() else {
239 return Ok(Response::default());
240 };
241 match gesture {
242 Gesture::Stroke(points, pressure) => {
243 let Tool::Pen(pen) = self.ink.tool else {
244 return Ok(Response::default());
245 };
246 self.editor.draw(Ink {
247 id: new_id()?,
248 layout: Default::default(),
249 strokes: vec![pen.stroke(&points, &pressure)?],
250 groups: Vec::new(),
251 shape: None,
252 })?;
253 }
254 Gesture::Shape(from) => {
255 let Tool::Shape(kind, pen) = self.ink.tool else {
256 return Ok(Response::default());
257 };
258 let to = self.grid(point);
259 if from == to {
260 return Ok(Response::redraw());
261 }
262 self.editor
263 .draw(Ink::drawn(kind, from, to, &pen.stroke(&[], &[])?)?)?;
264 }
265 Gesture::Erase { .. } => return Ok(Response::redraw()),
266 Gesture::Lasso(mut points) => {
267 points.push(point);
268 self.ink.selection = self.editor.ink_within(&points);
269 return Ok(Response::redraw());
270 }
271 Gesture::Move { press } => {
272 let delta = [point[0] - press[0], point[1] - press[1]];
273 // As OneNote does, a drawing dropped outside the view stays where it was.
274 let inside = (0..2).all(|axis| {
275 (0.0..self.viewport.size[axis] as f32).contains(&self.pointer[axis])
276 });
277 let selection = self.ink.selection.clone();
278 self.editor
279 .move_ink(&selection, if inside { delta } else { [0.0; 2] })?;
280 }
281 }
282 self.changed()
283 }
284
285 /// Delete and Backspace delete picked drawings; Escape lets them go, then leaves a
286 /// drawing tool for Select & Type, as OneNote's Escape does.
287 pub(super) fn ink_key(&mut self, key: &Key) -> Result<Option<Response>> {
288 if self.column.is_some() {
289 return Ok(None);
290 }
291 match key {
292 Key::Named(NamedKey::Backspace | NamedKey::Delete)
293 if !self.ink.selection.is_empty() =>
294 {
295 let selection = std::mem::take(&mut self.ink.selection);
296 self.editor.delete_ink(&selection)?;
297 Ok(Some(self.changed()?))
298 }
299 Key::Named(NamedKey::Escape) if !self.ink.selection.is_empty() => {
300 self.ink.selection.clear();
301 Ok(Some(Response::redraw()))
302 }
303 Key::Named(NamedKey::Escape) if self.ink.tool != Tool::Select => {
304 Ok(Some(self.set_tool(Tool::Select)))
305 }
306 _ => {
307 self.ink.selection.clear();
308 Ok(None)
309 }
310 }
311 }
312
313 /// Whether a press on the page began a gesture of the drawing tools.
314 pub fn inking(&self) -> bool {
315 self.ink.gesture.is_some()
316 }
317
318 /// Whether a drawing tool takes presses, which then show a crosshair.
319 pub(super) fn drawing(&self) -> bool {
320 self.ink.tool != Tool::Select
321 }
322
323 /// The gesture under way and the picked drawings' frame, over the page.
324 pub(super) fn append_ink_feedback(
325 &self,
326 automatic: [f32; 4],
327 primitives: &mut Vec<Primitive<'_>>,
328 ) {
329 let pixel = self.pixel();
330 let point = self.viewport.document_point(self.pointer);
331 let preview = |ink: Ink, primitives: &mut Vec<Primitive<'_>>| {
332 crate::gpu::page::append_ink(&ink, [0.0; 2], automatic, primitives)
333 };
334 match (&self.ink.gesture, self.ink.tool) {
335 (Some(Gesture::Stroke(points, pressure)), Tool::Pen(pen)) => {
336 if let Ok(stroke) = pen.stroke(points, pressure) {
337 preview(ink(vec![stroke]), primitives);
338 }
339 }
340 (Some(Gesture::Shape(from)), Tool::Shape(kind, pen)) => {
341 let to = self.grid(point);
342 if let Ok(pen) = pen.stroke(&[], &[])
343 && let Ok(shape) = Ink::drawn(kind, *from, to, &pen)
344 {
345 preview(shape, primitives);
346 }
347 }
348 (Some(Gesture::Lasso(points)), _) => {
349 let lasso = colorref(0x00808080);
350 for pair in points.windows(2) {
351 primitives.push(Primitive::Segment {
352 from: pair[0],
353 to: pair[1],
354 width: pixel,
355 round: true,
356 color: lasso,
357 });
358 }
359 }
360 _ => {}
361 }
362 if let Some([x0, y0, x1, y1]) = self.editor.ink_extent(&self.ink.selection) {
363 let pad = 3.0 * pixel;
364 primitives.push(Primitive::RoundedRect {
365 rect: [x0 - pad, y0 - pad, x1 + pad, y1 + pad],
366 radius: [0.0; 2],
367 stroke: Some(Stroke::Dashed(pixel)),
368 color: colorref(0x00e0a060),
369 });
370 }
371 }
372}
373
374fn ink(strokes: Vec<InkStroke>) -> Ink {
375 Ink {
376 id: ExGuid::default(),
377 layout: Default::default(),
378 strokes,
379 groups: Vec::new(),
380 shape: None,
381 }
382}