egui_charts/drawings/manager/
edit.rs1use super::{DrawingManager, DrawingSyncAction, queue_sync, sync_id};
4use crate::drawings::domain::{Drawing, DrawingToolType};
5use crate::drawings::services::{ZOrderService, in_z_order};
6use crate::model::PostContent;
7use egui::Rect;
8use std::collections::HashSet;
9
10impl DrawingManager {
11 pub fn delete_drawing(&mut self, id: usize) {
19 if self.drawings.iter().any(|d| d.id == id && d.locked) {
20 return;
21 }
22 if let Some(index) = self.drawings.iter().position(|d| d.id == id) {
23 let drawing = self.drawings.remove(index);
24 if drawing.saveable {
25 self.queue(DrawingSyncAction::Delete(sync_id(&drawing)));
26 }
27 self.history.push_delete(drawing, index);
28 }
29 self.selection.deselect(id);
30 }
31
32 pub(crate) fn finish_new_text(&mut self, id: usize, text: Option<String>) {
36 let Some(index) = self.drawings.iter().position(|d| d.id == id) else {
37 return;
38 };
39 if !self.history.pop_add(id) {
40 self.finished_text_placement = Some(id);
41 match text {
42 Some(text) => self.edit_drawing(id, |d| d.text = Some(text)),
43 None => self.delete_drawing(id),
44 }
45 return;
46 }
47 match text {
48 Some(text) => {
49 self.drawings[index].text = Some(text);
50 self.history.push_add(self.drawings[index].clone());
51 self.queue_update(id);
52 }
53 None => {
54 let drawing = self.drawings.remove(index);
55 if drawing.saveable {
56 self.queue(DrawingSyncAction::Delete(sync_id(&drawing)));
57 }
58 self.selection.deselect(id);
59 }
60 }
61 }
62
63 pub fn delete_selected(&mut self) {
65 let before: Vec<_> = self
66 .drawings
67 .iter()
68 .enumerate()
69 .filter(|(_, d)| d.selectable && self.selection.is_selected(d.id) && !d.locked)
70 .map(|(index, d)| (index, d.clone()))
71 .collect();
72 if before.is_empty() {
73 return;
74 }
75 for (_, drawing) in &before {
76 if drawing.saveable {
77 self.queue(DrawingSyncAction::Delete(sync_id(drawing)));
78 }
79 }
80 self.drawings
81 .retain(|d| !before.iter().any(|(_, old)| old.id == d.id));
82 self.selection
83 .prune(&self.drawings.iter().map(|d| d.id).collect());
84 self.history.push_snapshot(before);
85 }
86
87 pub fn clear_all(&mut self) {
93 self.history.push_snapshot(
94 self.drawings
95 .iter()
96 .enumerate()
97 .filter(|(_, drawing)| !drawing.locked)
98 .map(|(index, drawing)| (index, drawing.clone()))
99 .collect(),
100 );
101 for drawing in self.drawings.iter().filter(|d| !d.locked) {
102 if drawing.saveable {
103 self.queue(DrawingSyncAction::Delete(sync_id(drawing)));
104 }
105 }
106 self.drawings.retain(|d| d.locked);
107 self.cancel_curr_drawing();
108 self.selection.deselect_all();
109 }
110
111 pub fn bring_to_front(&mut self, id: usize) {
118 if let Some(drawing) = self.drawings.iter().find(|drawing| drawing.id == id) {
119 self.history.push_modify(id, drawing.clone());
120 ZOrderService::bring_to_front(&mut self.drawings, id);
121 self.queue_update(id);
122 }
123 }
124
125 pub fn send_to_back(&mut self, id: usize) {
127 if let Some(drawing) = self.drawings.iter().find(|drawing| drawing.id == id) {
128 self.history.push_modify(id, drawing.clone());
129 ZOrderService::send_to_back(&mut self.drawings, id);
130 self.queue_update(id);
131 }
132 }
133
134 pub fn reorder_drawings(&mut self, id: usize, layers: &[(usize, i32)]) {
136 let mut previous = Vec::new();
137 for &(id, layer) in layers {
138 if let Some(drawing) = self.drawings.iter_mut().find(|d| d.id == id)
139 && drawing.z_order != layer
140 {
141 previous.push((id, drawing.z_order));
142 drawing.z_order = layer;
143 self.queue(DrawingSyncAction::UpdateDrawing(id));
144 }
145 }
146 if !previous.is_empty() {
147 self.history.push_reorder(id, previous);
148 }
149 }
150
151 pub fn duplicate_drawing(&mut self, id: usize) {
154 let Some(mut copy) = self.drawings.iter().find(|d| d.id == id).cloned() else {
155 return;
156 };
157 copy.stored_id = None;
158 copy.created_at = None;
159 copy.screen_anchor = copy.screen_anchor.map(|[x, y]| [x + 0.02, y]);
160 let first = copy.first_moved_anchor();
161 for point in copy.chart_points.iter_mut().skip(first) {
162 point.bar_idx += 3.0;
163 }
164 if self.validate_created(©).is_ok() {
165 self.push_synced(copy);
166 }
167 }
168
169 pub fn paste_drawings(&mut self, drawings: &[Drawing]) {
174 let mut ids = Vec::new();
175 for drawing in in_z_order(drawings) {
176 let mut copy = drawing.clone();
177 copy.stored_id = None;
178 copy.created_at = None;
179 copy.screen_anchor = copy.screen_anchor.map(|[x, y]| [x + 0.02, y]);
180 copy.points.clear();
181 copy.level_ys.clear();
182 let first = copy.first_moved_anchor();
183 for point in copy.chart_points.iter_mut().skip(first) {
184 *point = point.with_shifted_bar_idx(3.0);
185 }
186 if self.validate_created(©).is_err() {
187 continue;
188 }
189 copy.id = self.take_id();
190 copy.z_order = self.top_z_order();
191 ids.push(copy.id);
192 let id = copy.id;
193 self.drawings.push(copy);
194 self.queue(DrawingSyncAction::SaveDrawing(id));
195 }
196 if ids.is_empty() {
197 return;
198 }
199 self.select_drawings(ids.iter().copied());
200 self.history
201 .push_command(crate::drawings::services::DrawingCommand::Snapshot {
202 drawings: Vec::new(),
203 ids,
204 });
205 }
206
207 pub(crate) fn replace_saved_drawings(&mut self, drawings: Vec<Drawing>) {
212 let removed: Vec<_> = self
213 .drawings
214 .iter()
215 .enumerate()
216 .filter(|(_, drawing)| drawing.saveable)
217 .map(|(index, drawing)| (index, drawing.clone()))
218 .collect();
219 if removed.is_empty() && drawings.is_empty() {
220 return;
221 }
222 let mut ids: Vec<_> = removed.iter().map(|(_, drawing)| drawing.id).collect();
223 let mut added = Vec::with_capacity(drawings.len());
225 for mut drawing in drawings {
226 drawing.id = self.take_id();
227 ids.push(drawing.id);
228 added.push(drawing);
229 }
230 for (_, drawing) in &removed {
231 self.queue(DrawingSyncAction::Delete(sync_id(drawing)));
232 }
233 self.drawings.retain(|drawing| !drawing.saveable);
234 for drawing in added {
235 let id = drawing.id;
236 self.drawings.push(drawing);
237 self.queue(DrawingSyncAction::SaveDrawing(id));
238 }
239 self.selection
240 .prune(&self.drawings.iter().map(|d| d.id).collect());
241 self.history
242 .push_command(crate::drawings::services::DrawingCommand::Snapshot {
243 drawings: removed,
244 ids,
245 });
246 }
247
248 pub fn edit_drawing(&mut self, id: usize, edit: impl FnOnce(&mut Drawing)) {
253 if let Some(drawing) = self.drawings.iter_mut().find(|d| d.id == id) {
254 let old = drawing.clone();
255 edit(drawing);
256 if old.locked && drawing.locked {
257 let visible = drawing.visible;
258 let label = drawing.label.clone();
259 let folder = drawing.folder.clone();
260 *drawing = old.clone();
261 if visible == old.visible && label == old.label && folder == old.folder {
262 return;
263 }
264 drawing.visible = visible;
265 drawing.label = label;
266 drawing.folder = folder;
267 }
268 if !drawing.selectable {
269 self.selection.deselect(id);
270 }
271 self.queue_edit(&old);
272 self.history.push_modify(id, old);
273 self.painted
274 .get_mut()
275 .unwrap_or_else(|e| e.into_inner())
276 .retain(|p| p.id != id);
277 }
278 }
279
280 pub(crate) fn resolve_link(&mut self, link: &str, content: Option<&PostContent>) -> bool {
284 let mut updated = Vec::new();
285 for drawing in &mut self.drawings {
286 if let Some(post) = &mut drawing.post
287 && post.link == link
288 && post.content.is_none()
289 {
290 if let Some(content) = content {
291 post.content = Some(content.clone());
292 post.failed = false;
293 updated.push(drawing.id);
294 } else {
295 post.failed = true;
296 }
297 }
298 }
299 for &id in &updated {
300 self.queue_update(id);
301 }
302 !updated.is_empty()
303 }
304
305 pub fn edit_drawings(&mut self, ids: &[usize], mut edit: impl FnMut(&mut Drawing)) {
308 if ids.is_empty() {
309 return;
310 }
311 let ids: HashSet<_> = ids.iter().copied().collect();
312 let mut before = Vec::new();
313 for (index, drawing) in self
314 .drawings
315 .iter_mut()
316 .enumerate()
317 .filter(|(_, d)| ids.contains(&d.id))
318 {
319 let old = drawing.clone();
320 edit(drawing);
321 if old.locked && drawing.locked {
322 let (visible, label, folder) = (
323 drawing.visible,
324 drawing.label.clone(),
325 drawing.folder.clone(),
326 );
327 *drawing = old.clone();
328 if visible == old.visible && label == old.label && folder == old.folder {
329 continue;
330 }
331 drawing.visible = visible;
332 drawing.label = label;
333 drawing.folder = folder;
334 }
335 if !drawing.selectable {
336 self.selection.deselect(old.id);
337 }
338 before.push((index, old));
339 }
340 for (_, old) in &before {
341 self.queue_edit(old);
342 }
343 self.history.push_snapshot(before);
344 self.painted
345 .get_mut()
346 .unwrap_or_else(|e| e.into_inner())
347 .retain(|p| !ids.contains(&p.id));
348 }
349
350 pub fn drag_edit_drawing(&mut self, id: usize, edit: impl FnOnce(&mut Drawing)) {
355 if self.drag_old_state.as_ref().is_some_and(|d| d.id != id) {
356 self.end_drag_handle();
357 }
358 if let Some(drawing) = self.drawings.iter_mut().find(|d| d.id == id && !d.locked) {
359 self.drag_old_state.get_or_insert_with(|| drawing.clone());
360 edit(drawing);
361 self.painted
362 .get_mut()
363 .unwrap_or_else(|e| e.into_inner())
364 .retain(|p| p.id != id);
365 }
366 }
367
368 pub fn update_all_screen_coords<F, G>(&mut self, bar_to_x: F, price_to_y: G)
375 where
376 F: Fn(f32) -> f32 + Copy,
377 G: Fn(f64) -> f32 + Copy,
378 {
379 for drawing in &mut self.drawings {
380 drawing.update_screen_coords(bar_to_x, price_to_y);
381 }
382 if let Some(ref mut drawing) = self.curr_drawing {
383 drawing.update_screen_coords(bar_to_x, price_to_y);
384 }
385 if let Some(drawing) = &mut self.measurement {
386 drawing.update_screen_coords(bar_to_x, price_to_y);
387 }
388 }
389
390 pub(crate) fn set_pane(
392 &mut self,
393 pane: Option<usize>,
394 members: Vec<Option<usize>>,
395 rect: Rect,
396 ) {
397 if pane.is_none() {
398 self.pane_clips.clear();
399 }
400 for member in &members {
401 self.pane_clips.insert(*member, rect);
402 }
403 self.pane = pane;
404 self.pane_members = members;
405 self.pane_rect = rect;
406 }
407
408 pub(crate) fn owns_pane(&self, drawing: &Drawing) -> bool {
409 self.pane_members.contains(&drawing.pane)
410 }
411
412 pub(crate) fn update_pane_screen_coords(
413 &mut self,
414 bar_to_x: impl Fn(f32) -> f32 + Copy,
415 price_to_y: impl Fn(f64) -> f32 + Copy,
416 ) {
417 for drawing in self
418 .drawings
419 .iter_mut()
420 .chain(&mut self.curr_drawing)
421 .chain(&mut self.measurement)
422 {
423 if self.pane_members.contains(&drawing.pane) {
424 drawing.update_screen_coords(bar_to_x, price_to_y);
425 drawing.update_pane_coords(self.pane_rect);
426 }
427 }
428 }
429
430 pub fn update_pane_anchors(&mut self, pane: Rect) {
433 self.pane_rect = pane;
434 for drawing in self
435 .drawings
436 .iter_mut()
437 .chain(&mut self.curr_drawing)
438 .chain(&mut self.measurement)
439 {
440 drawing.update_pane_coords(pane);
441 }
442 }
443
444 pub fn shift_bar_indices(&mut self, from: f32, shift: f32) {
452 if shift.abs() < 0.001 {
453 return;
454 }
455 for drawing in &mut self.drawings {
456 drawing.shift_bar_indices(from, shift);
457 }
458 if let Some(ref mut curr) = self.curr_drawing {
459 curr.shift_bar_indices(from, shift);
460 }
461 if let Some(drawing) = &mut self.measurement {
462 drawing.shift_bar_indices(from, shift);
463 }
464 if let Some(old) = &mut self.drag_old_state {
465 old.shift_bar_indices(from, shift);
466 }
467 for drawing in &mut self.drag_group {
468 drawing.shift_bar_indices(from, shift);
469 }
470 self.history.shift_bar_indices(from, shift);
471 }
472
473 pub fn toggle_visibility_all(&mut self) {
478 let visible = !self.drawings.iter().all(|drawing| drawing.visible);
479 self.set_visibility_all(visible);
480 }
481
482 pub fn set_visibility_all(&mut self, visible: bool) {
484 let mut before = Vec::new();
485 for (index, drawing) in self
486 .drawings
487 .iter_mut()
488 .enumerate()
489 .filter(|(_, drawing)| drawing.visible != visible)
490 {
491 before.push((index, drawing.clone()));
492 drawing.visible = visible;
493 if self.sync_enabled
494 && drawing.saveable
495 && let Ok(mut sync) = self.pending_sync.lock()
496 {
497 queue_sync(
498 &mut sync,
499 DrawingSyncAction::UpdateDrawing(drawing.id),
500 Some(&sync_id(drawing)),
501 );
502 }
503 }
504 self.history.push_snapshot(before);
505 }
506
507 pub fn toggle_lock_all(&mut self) {
510 let all_locked = self.drawings.iter().all(|d| d.locked);
511 let mut before = Vec::new();
512 for (index, drawing) in self
513 .drawings
514 .iter_mut()
515 .enumerate()
516 .filter(|(_, d)| d.locked == all_locked)
517 {
518 before.push((index, drawing.clone()));
519 drawing.locked = !all_locked;
520 if self.sync_enabled
521 && drawing.saveable
522 && let Ok(mut sync) = self.pending_sync.lock()
523 {
524 queue_sync(
525 &mut sync,
526 DrawingSyncAction::UpdateDrawing(drawing.id),
527 Some(&sync_id(drawing)),
528 );
529 }
530 }
531 self.history.push_snapshot(before);
532 }
533
534 pub fn add_drawing(&mut self, mut drawing: Drawing) -> Result<usize, &'static str> {
538 self.validate_drawing(&drawing)?;
539 if drawing.custom_tool.is_none() && !self.registry.availability(drawing.tool_type).enabled {
540 return Err("Drawing tool is disabled");
541 }
542 drawing.id = self.take_id();
543 drawing.stored_id = None;
544 drawing.created_at = None;
545 drawing.completed = true;
546 drawing.points.clear();
547 drawing.level_ys.clear();
548 let id = drawing.id;
549 self.history.push_add(drawing.clone());
550 self.drawings.push(drawing);
551 self.queue(DrawingSyncAction::SaveDrawing(id));
552 Ok(id)
553 }
554
555 pub(crate) fn validate_drawing(&self, drawing: &Drawing) -> Result<(), &'static str> {
556 crate::drawings::validation::validate(drawing)?;
557 self.validate_custom_tool(drawing)
558 }
559
560 fn validate_custom_tool(&self, drawing: &Drawing) -> Result<(), &'static str> {
562 if let Some(id) = &drawing.custom_tool {
563 let tool = self.registry.get(id).ok_or("Unknown drawing tool")?;
564 if drawing.tool_type != DrawingToolType::Polyline
565 || drawing.chart_points.len() != tool.anchors
566 {
567 return Err("Invalid custom drawing anchors");
568 }
569 }
570 Ok(())
571 }
572
573 pub(super) fn validate_created(&self, drawing: &Drawing) -> Result<(), &'static str> {
578 crate::drawings::validation::validate_stored(drawing)?;
579 self.validate_custom_tool(drawing)?;
580 if drawing.custom_tool.is_none() && !self.registry.availability(drawing.tool_type).enabled {
581 return Err("Drawing tool is disabled");
582 }
583 Ok(())
584 }
585
586 pub fn listed_drawings(&self) -> impl Iterator<Item = &Drawing> {
588 self.drawings.iter().filter(|drawing| drawing.listed)
589 }
590
591 pub fn group_drawings<'a>(&'a self, group: &'a str) -> impl Iterator<Item = &'a Drawing> {
593 self.drawings
594 .iter()
595 .filter(move |drawing| drawing.group.as_deref() == Some(group))
596 }
597
598 pub fn set_group(&mut self, ids: &[usize], group: Option<String>) {
601 let mut before = Vec::new();
602 for (index, drawing) in self.drawings.iter_mut().enumerate() {
603 if ids.contains(&drawing.id) && !drawing.locked && drawing.group != group {
604 before.push((index, drawing.clone()));
605 drawing.group = group.clone();
606 }
607 }
608 for (_, old) in &before {
609 self.queue_update(old.id);
610 }
611 self.history.push_snapshot(before);
612 }
613
614 pub fn select_group(&mut self, group: &str) {
617 let ids: Vec<_> = self.group_drawings(group).map(|d| d.id).collect();
618 self.select_drawings(ids);
619 }
620
621 pub fn add_synced_drawing(&mut self, drawing: Drawing) -> Result<usize, &'static str> {
632 crate::drawings::validation::validate_stored(&drawing)?;
633 Ok(self.push_synced(drawing))
634 }
635
636 fn push_synced(&mut self, mut drawing: Drawing) -> usize {
638 if drawing.stored_id.is_some()
639 && self
640 .drawings
641 .iter()
642 .any(|d| d.stored_id == drawing.stored_id)
643 {
644 (drawing.stored_id, drawing.created_at) = (None, None);
645 }
646 if drawing.stored_id.is_none() {
647 drawing.z_order = self.top_z_order();
648 }
649 drawing.id = self.take_id();
651 if drawing.stored_id.is_none() {
652 if drawing.saveable {
653 self.queue(DrawingSyncAction::SaveDrawing(drawing.id));
654 }
655 self.history.push_add(drawing.clone());
656 }
657 let id = drawing.id;
658 self.drawings.push(drawing);
659 id
660 }
661}