Skip to main content

flowsurface_data/chart/kline/
drawing.rs

1use super::YUnit;
2use super::composition::PanelId;
3use crate::config::theme::{ThemeAccent, ThemeColorShade};
4use exchange::UnixMs;
5use serde::{Deserialize, Serialize};
6
7pub const MIN_DRAWING_STROKE_WIDTH: f32 = 0.5;
8pub const MAX_DRAWING_STROKE_WIDTH: f32 = 4.0;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct DrawingId(pub u64);
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
14pub enum DrawingTool {
15    #[default]
16    Cursor,
17    Trendline,
18    HorizontalLine,
19    VerticalLine,
20    Box,
21    Measure,
22}
23
24impl DrawingTool {
25    pub fn allows_panning(self) -> bool {
26        matches!(self, Self::Cursor)
27    }
28
29    pub const fn supports_magnet(self) -> bool {
30        matches!(
31            self,
32            Self::Trendline | Self::Box | Self::HorizontalLine | Self::VerticalLine
33        )
34    }
35
36    pub const fn all() -> &'static [Self] {
37        &[
38            Self::Cursor,
39            Self::Trendline,
40            Self::HorizontalLine,
41            Self::VerticalLine,
42            Self::Box,
43            Self::Measure,
44        ]
45    }
46
47    pub const fn label(self) -> &'static str {
48        match self {
49            Self::Cursor => "Crosshair",
50            Self::Trendline => "Trendline",
51            Self::Box => "Box",
52            Self::HorizontalLine => "Horizontal line",
53            Self::VerticalLine => "Vertical line",
54            Self::Measure => "Measure",
55        }
56    }
57
58    pub fn default_style(self) -> DrawingStyle {
59        let mut style = DrawingStyle::default();
60
61        match self {
62            Self::Cursor | Self::Measure => {}
63            Self::Trendline => {
64                style.stroke_color = DrawingColor::accent(ThemeAccent::One, 1.0);
65                style.stroke_width = 1.2;
66            }
67            Self::Box => {
68                style.stroke_color = DrawingColor::accent(ThemeAccent::One, 1.0);
69                style.stroke_width = 1.2;
70                style.fill_color = Some(DrawingColor::accent(ThemeAccent::One, 0.16));
71            }
72            Self::HorizontalLine => {
73                style.stroke_color = DrawingColor::accent(ThemeAccent::One, 1.0);
74                style.stroke_width = 1.0;
75            }
76            Self::VerticalLine => {
77                style.stroke_color = DrawingColor::accent(ThemeAccent::One, 1.0);
78                style.stroke_width = 1.0;
79            }
80        }
81
82        style
83    }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
87pub struct DrawingColor {
88    pub accent: ThemeAccent,
89    #[serde(default)]
90    pub shade: ThemeColorShade,
91    pub alpha: f32,
92}
93
94impl DrawingColor {
95    pub const fn accent(accent: ThemeAccent, alpha: f32) -> Self {
96        Self::themed(accent, ThemeColorShade::Base, alpha)
97    }
98
99    pub const fn themed(accent: ThemeAccent, shade: ThemeColorShade, alpha: f32) -> Self {
100        Self {
101            accent,
102            shade,
103            alpha,
104        }
105    }
106
107    pub fn with_accent(self, accent: ThemeAccent, alpha: f32) -> Self {
108        Self {
109            accent,
110            alpha,
111            ..self
112        }
113    }
114
115    pub fn scale_alpha(self, factor: f32) -> Self {
116        Self {
117            alpha: self.alpha * factor,
118            ..self
119        }
120    }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq)]
124pub enum DrawingX {
125    Time(UnixMs),
126    Tick(f64),
127}
128
129impl Default for DrawingX {
130    fn default() -> Self {
131        Self::Time(UnixMs::new(0))
132    }
133}
134
135impl Serialize for DrawingX {
136    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
137        match self {
138            Self::Time(time) => time.serialize(serializer),
139            Self::Tick(_) => Err(serde::ser::Error::custom("tick drawings are session-only")),
140        }
141    }
142}
143
144impl<'de> Deserialize<'de> for DrawingX {
145    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
146        UnixMs::deserialize(deserializer).map(Self::Time)
147    }
148}
149
150impl PartialOrd for DrawingX {
151    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
152        match (*self, *other) {
153            (Self::Time(a), Self::Time(b)) => a.partial_cmp(&b),
154            (Self::Tick(a), Self::Tick(b)) if a.is_finite() && b.is_finite() => a.partial_cmp(&b),
155            _ => None,
156        }
157    }
158}
159
160impl DrawingX {
161    pub fn is_time(self) -> bool {
162        matches!(self, Self::Time(_))
163    }
164
165    pub fn is_valid(self) -> bool {
166        match self {
167            Self::Time(_) => true,
168            Self::Tick(position) => position.is_finite(),
169        }
170    }
171
172    pub fn compatible(self, other: Self) -> bool {
173        self.partial_cmp(&other).is_some()
174    }
175
176    pub fn min(self, other: Self) -> Self {
177        if self <= other { self } else { other }
178    }
179
180    pub fn max(self, other: Self) -> Self {
181        if self >= other { self } else { other }
182    }
183
184    pub fn tick_bar_position(self, trades_per_bar: u64) -> Option<f64> {
185        match self {
186            Self::Tick(position) if position.is_finite() && trades_per_bar > 0 => {
187                Some(position / trades_per_bar as f64)
188            }
189            _ => None,
190        }
191    }
192
193    fn translated(self, origin: Self, current: Self) -> Option<Self> {
194        match (self, origin, current) {
195            (Self::Time(time), Self::Time(origin), Self::Time(current)) => {
196                Some(Self::Time(if current >= origin {
197                    time.saturating_add(current.saturating_diff(origin))
198                } else {
199                    time.saturating_sub(origin.saturating_diff(current))
200                }))
201            }
202            (Self::Tick(position), Self::Tick(origin), Self::Tick(current))
203                if position.is_finite() && origin.is_finite() && current.is_finite() =>
204            {
205                let translated = position + (current - origin);
206                translated.is_finite().then_some(Self::Tick(translated))
207            }
208            _ => None,
209        }
210    }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
214pub struct DrawingAnchor {
215    pub panel_id: PanelId,
216    #[serde(rename = "time")]
217    pub x: DrawingX,
218    pub y_unit: YUnit,
219}
220
221#[derive(Debug, Clone, PartialEq)]
222pub enum KlineWidgetDrawingEvent {
223    MagnetModifierChanged(bool),
224    Selected(Option<DrawingId>),
225    MarqueeSelected(Vec<DrawingId>),
226    AnchorPressed(DrawingAnchor),
227    AnchorMoved(DrawingAnchor),
228    DragStarted {
229        id: DrawingId,
230        target: DrawingDragTarget,
231        operation: DrawingDragOperation,
232        anchor: DrawingAnchor,
233    },
234    DragMoved {
235        id: DrawingId,
236        target: DrawingDragTarget,
237        anchor: DrawingAnchor,
238    },
239    DragFinished {
240        id: DrawingId,
241    },
242    MeasureClick(DrawingAnchor),
243    MeasureMove(DrawingAnchor),
244    MeasureCanceled,
245    DraftCanceled,
246}
247
248#[derive(Debug, Clone, Copy, PartialEq)]
249pub struct MeasureState {
250    pub panel_id: PanelId,
251    pub start: DrawingAnchor,
252    pub end: DrawingAnchor,
253}
254
255#[derive(Debug, Clone, Copy, PartialEq)]
256pub enum DrawingMeasure {
257    Pending(MeasureState),
258    Committed(MeasureState),
259}
260
261impl DrawingMeasure {
262    pub fn state(self) -> MeasureState {
263        match self {
264            Self::Pending(state) | Self::Committed(state) => state,
265        }
266    }
267}
268
269#[derive(Debug, Clone, Copy)]
270pub struct DrawingSnapshot<'a> {
271    pub active_tool: DrawingTool,
272    pub magnet_enabled: bool,
273    pub entities: &'a [DrawingEntity],
274    pub selected_drawings: &'a [DrawingId],
275    pub drawing_draft: Option<&'a DrawingDraft>,
276    pub measure: Option<DrawingMeasure>,
277}
278
279impl<'a> DrawingSnapshot<'a> {
280    pub const fn new(
281        active_tool: DrawingTool,
282        magnet_enabled: bool,
283        entities: &'a [DrawingEntity],
284        selected_drawings: &'a [DrawingId],
285        drawing_draft: Option<&'a DrawingDraft>,
286        measure: Option<DrawingMeasure>,
287    ) -> Self {
288        Self {
289            active_tool,
290            magnet_enabled,
291            entities,
292            selected_drawings,
293            drawing_draft,
294            measure,
295        }
296    }
297
298    pub fn allows_panning(&self) -> bool {
299        self.active_tool.allows_panning()
300    }
301
302    pub fn has_state(&self) -> bool {
303        !self.entities.is_empty()
304            || !self.selected_drawings.is_empty()
305            || self.drawing_draft.is_some()
306            || self.measure.is_some()
307    }
308
309    pub fn draft_panel_id(&self) -> Option<PanelId> {
310        self.drawing_draft.map(DrawingDraft::panel_id)
311    }
312
313    pub fn selected_visible_drawings(&self) -> impl Iterator<Item = &'a DrawingEntity> {
314        self.entities
315            .iter()
316            .filter(|drawing| drawing.visible && self.selected_drawings.contains(&drawing.id))
317    }
318
319    fn selected_visible_drawing(&self) -> Option<&'a DrawingEntity> {
320        if self.selected_drawings.len() == 1 {
321            self.selected_visible_drawings().next()
322        } else {
323            None
324        }
325    }
326
327    pub fn active_axis_labeled_object(&self) -> Option<DrawingObject> {
328        if let Some(draft) = self.drawing_draft {
329            return Some(draft.preview_object());
330        }
331
332        self.selected_visible_drawing()
333            .map(|drawing| drawing.object.clone())
334    }
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
338pub struct DrawingStyle {
339    pub stroke_color: DrawingColor,
340    pub stroke_width: f32,
341    pub fill_color: Option<DrawingColor>,
342}
343
344impl Default for DrawingStyle {
345    fn default() -> Self {
346        Self {
347            stroke_color: DrawingColor::accent(ThemeAccent::One, 1.0),
348            stroke_width: 1.2,
349            fill_color: None,
350        }
351    }
352}
353
354impl DrawingStyle {
355    pub fn normalized_stroke_width(self) -> f32 {
356        Self::normalize_stroke_width(self.stroke_width)
357    }
358
359    pub fn set_stroke_width(&mut self, width: f32) -> bool {
360        let width = Self::normalize_stroke_width(width);
361
362        if self.stroke_width == width {
363            return false;
364        }
365
366        self.stroke_width = width;
367        true
368    }
369
370    fn normalize_stroke_width(width: f32) -> f32 {
371        if width.is_finite() {
372            width.clamp(MIN_DRAWING_STROKE_WIDTH, MAX_DRAWING_STROKE_WIDTH)
373        } else {
374            Self::default().stroke_width
375        }
376    }
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
380#[serde(default)]
381pub struct DrawingStyleDefaults {
382    pub trendline: DrawingStyle,
383    pub box_drawing: DrawingStyle,
384    pub horizontal_line: DrawingStyle,
385    pub vertical_line: DrawingStyle,
386}
387
388impl Default for DrawingStyleDefaults {
389    fn default() -> Self {
390        Self {
391            trendline: DrawingTool::Trendline.default_style(),
392            box_drawing: DrawingTool::Box.default_style(),
393            horizontal_line: DrawingTool::HorizontalLine.default_style(),
394            vertical_line: DrawingTool::VerticalLine.default_style(),
395        }
396    }
397}
398
399impl DrawingStyleDefaults {
400    pub fn style_for(self, tool: DrawingTool) -> DrawingStyle {
401        match tool {
402            DrawingTool::Trendline => self.trendline,
403            DrawingTool::Box => self.box_drawing,
404            DrawingTool::HorizontalLine => self.horizontal_line,
405            DrawingTool::VerticalLine => self.vertical_line,
406            DrawingTool::Cursor | DrawingTool::Measure => tool.default_style(),
407        }
408    }
409
410    pub fn set_style(&mut self, tool: DrawingTool, style: DrawingStyle) -> bool {
411        let stored_style = match tool {
412            DrawingTool::Trendline => &mut self.trendline,
413            DrawingTool::Box => &mut self.box_drawing,
414            DrawingTool::HorizontalLine => &mut self.horizontal_line,
415            DrawingTool::VerticalLine => &mut self.vertical_line,
416            DrawingTool::Cursor | DrawingTool::Measure => return false,
417        };
418
419        if *stored_style == style {
420            return false;
421        }
422
423        *stored_style = style;
424        true
425    }
426}
427
428#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
429#[serde(default)]
430pub struct DrawingSettings {
431    pub style_defaults: DrawingStyleDefaults,
432    pub horizontal_line_ray: bool,
433    pub box_extend_right: bool,
434}
435
436#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
437pub enum DrawingObject {
438    Trendline {
439        start: DrawingAnchor,
440        end: DrawingAnchor,
441    },
442    Box {
443        start: DrawingAnchor,
444        end: DrawingAnchor,
445        #[serde(default)]
446        extend_right: bool,
447    },
448    HorizontalLine {
449        panel_id: PanelId,
450        y_unit: YUnit,
451        #[serde(default)]
452        #[serde(rename = "time")]
453        x: DrawingX,
454        #[serde(default)]
455        ray: bool,
456    },
457    VerticalLine {
458        #[serde(rename = "time")]
459        x: DrawingX,
460    },
461}
462
463#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
464pub struct DrawingEntity {
465    pub id: DrawingId,
466    pub object: DrawingObject,
467    pub style: DrawingStyle,
468    pub locked: bool,
469    pub visible: bool,
470}
471
472#[derive(Debug, Clone, PartialEq)]
473pub enum DrawingDraft {
474    Trendline {
475        start: DrawingAnchor,
476        current: DrawingAnchor,
477        style: DrawingStyle,
478    },
479    Box {
480        start: DrawingAnchor,
481        current: DrawingAnchor,
482        style: DrawingStyle,
483        extend_right: bool,
484    },
485}
486
487impl DrawingDraft {
488    pub fn update_anchor(&mut self, anchor: DrawingAnchor) -> bool {
489        match self {
490            DrawingDraft::Trendline { start, current, .. }
491            | DrawingDraft::Box { start, current, .. } => {
492                if anchor.panel_id != start.panel_id {
493                    return false;
494                }
495
496                *current = anchor;
497            }
498        }
499
500        true
501    }
502
503    pub fn tool(&self) -> DrawingTool {
504        match self {
505            Self::Trendline { .. } => DrawingTool::Trendline,
506            Self::Box { .. } => DrawingTool::Box,
507        }
508    }
509
510    pub fn style(&self) -> DrawingStyle {
511        match self {
512            Self::Trendline { style, .. } | Self::Box { style, .. } => *style,
513        }
514    }
515
516    pub fn preview_object(&self) -> DrawingObject {
517        match self {
518            Self::Trendline { start, current, .. } => DrawingObject::Trendline {
519                start: *start,
520                end: *current,
521            },
522            Self::Box {
523                start,
524                current,
525                extend_right,
526                ..
527            } => DrawingObject::normalized_box(*start, *current, *extend_right),
528        }
529    }
530
531    pub fn panel_id(&self) -> PanelId {
532        match self {
533            Self::Trendline { start, .. } | Self::Box { start, .. } => start.panel_id,
534        }
535    }
536
537    pub fn belongs_to_panel(&self, panel_id: PanelId) -> bool {
538        match self {
539            Self::Trendline { start, current, .. } | Self::Box { start, current, .. } => {
540                start.panel_id == panel_id || current.panel_id == panel_id
541            }
542        }
543    }
544
545    pub fn try_commit(self, end: DrawingAnchor) -> Option<(DrawingObject, DrawingStyle)> {
546        match self {
547            Self::Trendline { start, style, .. } => {
548                if end.panel_id != start.panel_id || !start.x.compatible(end.x) {
549                    return None;
550                }
551
552                Some((DrawingObject::Trendline { start, end }, style))
553            }
554            Self::Box {
555                start,
556                style,
557                extend_right,
558                ..
559            } => {
560                if end.panel_id != start.panel_id || !start.x.compatible(end.x) {
561                    return None;
562                }
563
564                Some((
565                    DrawingObject::normalized_box(start, end, extend_right),
566                    style,
567                ))
568            }
569        }
570    }
571}
572
573#[derive(Debug, Clone, Copy, PartialEq, Eq)]
574pub enum DrawingHandleKind {
575    TrendlineStart,
576    TrendlineEnd,
577    HorizontalLineStart,
578    BoxTopLeft,
579    BoxTopRight,
580    BoxBottomRight,
581    BoxBottomLeft,
582}
583
584#[derive(Debug, Clone, Copy, PartialEq, Eq)]
585pub enum DrawingDragTarget {
586    Translate,
587    Handle(DrawingHandleKind),
588}
589
590#[derive(Debug, Clone, Copy, PartialEq, Eq)]
591pub enum DrawingDragOperation {
592    Move,
593    Copy,
594}
595
596#[derive(Debug, Clone, Copy, PartialEq)]
597pub enum AxisLabelAnchor {
598    X { x: DrawingX },
599    Y { panel_id: PanelId, y_unit: YUnit },
600}
601
602impl DrawingObject {
603    fn normalized_box(
604        mut start: DrawingAnchor,
605        mut end: DrawingAnchor,
606        extend_right: bool,
607    ) -> Self {
608        if extend_right && end.x < start.x {
609            std::mem::swap(&mut start.x, &mut end.x);
610        }
611
612        Self::Box {
613            start,
614            end,
615            extend_right,
616        }
617    }
618
619    pub fn tool(&self) -> DrawingTool {
620        match self {
621            Self::Trendline { .. } => DrawingTool::Trendline,
622            Self::Box { .. } => DrawingTool::Box,
623            Self::HorizontalLine { .. } => DrawingTool::HorizontalLine,
624            Self::VerticalLine { .. } => DrawingTool::VerticalLine,
625        }
626    }
627
628    pub fn title(&self) -> &'static str {
629        match self {
630            Self::Box { .. } => "Box",
631            Self::Trendline { .. } => "Trendline",
632            Self::HorizontalLine { .. } => "Horizontal Line",
633            Self::VerticalLine { .. } => "Vertical Line",
634        }
635    }
636
637    pub fn panel_id(&self) -> Option<PanelId> {
638        match self {
639            Self::Trendline { start, .. } | Self::Box { start, .. } => Some(start.panel_id),
640            Self::HorizontalLine { panel_id, .. } => Some(*panel_id),
641            Self::VerticalLine { .. } => None,
642        }
643    }
644
645    pub fn handle_panel_id(&self) -> Option<PanelId> {
646        match self {
647            Self::Trendline { start, end } | Self::Box { start, end, .. } => {
648                (start.panel_id == end.panel_id).then_some(start.panel_id)
649            }
650            Self::HorizontalLine { panel_id, .. } => Some(*panel_id),
651            Self::VerticalLine { .. } => None,
652        }
653    }
654
655    pub fn axis_label_anchors(&self) -> Vec<AxisLabelAnchor> {
656        match self {
657            Self::Trendline { start, end } => vec![
658                AxisLabelAnchor::X { x: start.x },
659                AxisLabelAnchor::Y {
660                    panel_id: start.panel_id,
661                    y_unit: start.y_unit,
662                },
663                AxisLabelAnchor::X { x: end.x },
664                AxisLabelAnchor::Y {
665                    panel_id: end.panel_id,
666                    y_unit: end.y_unit,
667                },
668            ],
669            Self::Box {
670                start,
671                end,
672                extend_right,
673            } => {
674                let panel_id = start.panel_id;
675                let left = start.x.min(end.x);
676                let top = YUnit(start.y_unit.0.max(end.y_unit.0));
677                let bottom = YUnit(start.y_unit.0.min(end.y_unit.0));
678
679                let mut anchors = vec![
680                    AxisLabelAnchor::X { x: left },
681                    AxisLabelAnchor::Y {
682                        panel_id,
683                        y_unit: top,
684                    },
685                    AxisLabelAnchor::Y {
686                        panel_id,
687                        y_unit: bottom,
688                    },
689                ];
690
691                if !extend_right {
692                    anchors.push(AxisLabelAnchor::X {
693                        x: start.x.max(end.x),
694                    });
695                }
696
697                anchors
698            }
699            Self::HorizontalLine {
700                panel_id,
701                y_unit,
702                x,
703                ray,
704            } => {
705                let mut anchors = Vec::with_capacity(2);
706                if *ray {
707                    anchors.push(AxisLabelAnchor::X { x: *x });
708                }
709                anchors.push(AxisLabelAnchor::Y {
710                    panel_id: *panel_id,
711                    y_unit: *y_unit,
712                });
713                anchors
714            }
715            Self::VerticalLine { x } => vec![AxisLabelAnchor::X { x: *x }],
716        }
717    }
718
719    pub fn belongs_to_panel(&self, panel_id: PanelId) -> bool {
720        match self {
721            Self::Trendline { start, end } | Self::Box { start, end, .. } => {
722                start.panel_id == panel_id || end.panel_id == panel_id
723            }
724            Self::HorizontalLine {
725                panel_id: drawing_panel,
726                ..
727            } => *drawing_panel == panel_id,
728            Self::VerticalLine { .. } => false,
729        }
730    }
731
732    pub fn coordinates_match(&self, x: DrawingX) -> bool {
733        match self {
734            Self::Trendline { start, end } | Self::Box { start, end, .. } => {
735                start.x.compatible(x) && end.x.compatible(x)
736            }
737            Self::HorizontalLine { x: origin, .. } | Self::VerticalLine { x: origin } => {
738                origin.compatible(x)
739            }
740        }
741    }
742
743    pub fn translated(&self, origin: DrawingAnchor, current: DrawingAnchor) -> Option<Self> {
744        let delta_y = current.y_unit.0.saturating_sub(origin.y_unit.0);
745        let shift = |anchor: DrawingAnchor| -> Option<DrawingAnchor> {
746            Some(DrawingAnchor {
747                x: anchor.x.translated(origin.x, current.x)?,
748                y_unit: Self::shift_y_by_delta(anchor.y_unit, delta_y),
749                ..anchor
750            })
751        };
752        Some(match self {
753            Self::Trendline { start, end } => Self::Trendline {
754                start: shift(*start)?,
755                end: shift(*end)?,
756            },
757            Self::Box {
758                start,
759                end,
760                extend_right,
761            } => Self::Box {
762                start: shift(*start)?,
763                end: shift(*end)?,
764                extend_right: *extend_right,
765            },
766            Self::HorizontalLine {
767                panel_id,
768                y_unit,
769                x,
770                ray,
771            } => Self::HorizontalLine {
772                panel_id: *panel_id,
773                y_unit: Self::shift_y_by_delta(*y_unit, delta_y),
774                x: x.translated(origin.x, current.x)?,
775                ray: *ray,
776            },
777            Self::VerticalLine { x } => Self::VerticalLine {
778                x: x.translated(origin.x, current.x)?,
779            },
780        })
781    }
782
783    pub fn handle_dragged(
784        &self,
785        handle: DrawingHandleKind,
786        origin: DrawingAnchor,
787        mut anchor: DrawingAnchor,
788    ) -> Option<Self> {
789        if !self.coordinates_match(anchor.x) || !origin.x.compatible(anchor.x) {
790            return None;
791        }
792        if matches!(anchor.x, DrawingX::Tick(_)) {
793            let x = match (self, handle) {
794                (Self::Trendline { start, .. }, DrawingHandleKind::TrendlineStart) => start.x,
795                (Self::Trendline { end, .. }, DrawingHandleKind::TrendlineEnd) => end.x,
796                (Self::HorizontalLine { x, .. }, DrawingHandleKind::HorizontalLineStart) => *x,
797                (
798                    Self::Box { start, end, .. },
799                    DrawingHandleKind::BoxTopLeft | DrawingHandleKind::BoxBottomLeft,
800                ) => start.x.min(end.x),
801                (
802                    Self::Box { start, end, .. },
803                    DrawingHandleKind::BoxTopRight | DrawingHandleKind::BoxBottomRight,
804                ) => start.x.max(end.x),
805                _ => return None,
806            };
807            anchor.x = x.translated(origin.x, anchor.x)?;
808        }
809        match (self, handle) {
810            (Self::Trendline { start, end }, DrawingHandleKind::TrendlineStart) => {
811                Some(Self::Trendline {
812                    start: DrawingAnchor {
813                        panel_id: start.panel_id,
814                        ..anchor
815                    },
816                    end: *end,
817                })
818            }
819            (Self::Trendline { start, end }, DrawingHandleKind::TrendlineEnd) => {
820                Some(Self::Trendline {
821                    start: *start,
822                    end: DrawingAnchor {
823                        panel_id: end.panel_id,
824                        ..anchor
825                    },
826                })
827            }
828            (
829                Self::HorizontalLine {
830                    panel_id,
831                    ray: true,
832                    ..
833                },
834                DrawingHandleKind::HorizontalLineStart,
835            ) if anchor.panel_id == *panel_id => Some(Self::HorizontalLine {
836                panel_id: *panel_id,
837                y_unit: anchor.y_unit,
838                x: anchor.x,
839                ray: true,
840            }),
841            (
842                Self::Box {
843                    start,
844                    end,
845                    extend_right,
846                },
847                corner,
848            ) => Self::resized_box(*start, *end, *extend_right, corner, anchor),
849            _ => None,
850        }
851    }
852
853    fn resized_box(
854        start: DrawingAnchor,
855        end: DrawingAnchor,
856        extend_right: bool,
857        corner: DrawingHandleKind,
858        anchor: DrawingAnchor,
859    ) -> Option<Self> {
860        if start.panel_id != end.panel_id {
861            return None;
862        }
863
864        let panel_id = start.panel_id;
865        if anchor.panel_id != panel_id {
866            return None;
867        }
868
869        if extend_right {
870            return Self::resized_extended_box(start, end, corner, anchor);
871        }
872
873        let mut left = if start.x <= end.x { start.x } else { end.x };
874        let mut right = if start.x <= end.x { end.x } else { start.x };
875
876        let mut top = start.y_unit.0.max(end.y_unit.0);
877        let mut bottom = start.y_unit.0.min(end.y_unit.0);
878
879        match corner {
880            DrawingHandleKind::BoxTopLeft => {
881                left = anchor.x;
882                top = anchor.y_unit.0;
883            }
884            DrawingHandleKind::BoxTopRight => {
885                right = anchor.x;
886                top = anchor.y_unit.0;
887            }
888            DrawingHandleKind::BoxBottomRight => {
889                right = anchor.x;
890                bottom = anchor.y_unit.0;
891            }
892            DrawingHandleKind::BoxBottomLeft => {
893                left = anchor.x;
894                bottom = anchor.y_unit.0;
895            }
896            DrawingHandleKind::TrendlineStart
897            | DrawingHandleKind::TrendlineEnd
898            | DrawingHandleKind::HorizontalLineStart => {
899                return None;
900            }
901        }
902
903        Some(Self::Box {
904            start: DrawingAnchor {
905                panel_id,
906                x: left,
907                y_unit: YUnit(top),
908            },
909            end: DrawingAnchor {
910                panel_id,
911                x: right,
912                y_unit: YUnit(bottom),
913            },
914            extend_right,
915        })
916    }
917
918    fn resized_extended_box(
919        start: DrawingAnchor,
920        end: DrawingAnchor,
921        corner: DrawingHandleKind,
922        anchor: DrawingAnchor,
923    ) -> Option<Self> {
924        let top = start.y_unit.0.max(end.y_unit.0);
925        let bottom = start.y_unit.0.min(end.y_unit.0);
926        let (top, bottom) = match corner {
927            DrawingHandleKind::BoxTopLeft => (anchor.y_unit.0, bottom),
928            DrawingHandleKind::BoxBottomLeft => (top, anchor.y_unit.0),
929            DrawingHandleKind::TrendlineStart
930            | DrawingHandleKind::TrendlineEnd
931            | DrawingHandleKind::HorizontalLineStart
932            | DrawingHandleKind::BoxTopRight
933            | DrawingHandleKind::BoxBottomRight => return None,
934        };
935
936        Some(Self::Box {
937            start: DrawingAnchor {
938                panel_id: start.panel_id,
939                x: anchor.x,
940                y_unit: YUnit(top),
941            },
942            end: DrawingAnchor {
943                panel_id: end.panel_id,
944                x: end.x,
945                y_unit: YUnit(bottom),
946            },
947            extend_right: true,
948        })
949    }
950
951    fn shift_y_by_delta(y_unit: YUnit, delta_y: i64) -> YUnit {
952        YUnit(y_unit.0.saturating_add(delta_y))
953    }
954}
955
956impl DrawingEntity {
957    pub fn belongs_to_panel(&self, panel_id: PanelId) -> bool {
958        self.object.belongs_to_panel(panel_id)
959    }
960}