egui_charts/config/drawing_tool/
behavior.rs1use super::{DrawingInteractionMode, DrawingToolType};
7
8impl DrawingToolType {
9 #[inline]
16 pub fn is_cursor(&self) -> bool {
17 matches!(
18 self,
19 Self::CrossCursor | Self::DotCursor | Self::ArrowCursor | Self::Eraser
20 )
21 }
22
23 pub fn supports_fill(&self) -> bool {
25 !matches!(
26 self,
27 Self::CrossCursor
28 | Self::DotCursor
29 | Self::ArrowCursor
30 | Self::Eraser
31 | Self::TrendLine
32 | Self::TrendAngle
33 | Self::InfoLine
34 | Self::ABCDPattern
35 | Self::ThreeDrivesPattern
36 | Self::ElliottImpulse
37 | Self::ElliottCorrection
38 | Self::ElliottTriangle
39 | Self::ElliottDoubleCombo
40 | Self::ElliottTripleCombo
41 | Self::GannFan
42 | Self::GannBox
43 | Self::GannSquare
44 | Self::GannFixed
45 | Self::Ray
46 | Self::ExtendedLine
47 | Self::HorizontalLine
48 | Self::HorizontalRay
49 | Self::VerticalLine
50 | Self::CrossLine
51 | Self::FibonacciRetracement
52 | Self::FibonacciExtension
53 | Self::FibonacciTimeZones
54 | Self::TrendBasedFibTime
55 | Self::FibonacciCircles
56 | Self::FibonacciSpiral
57 | Self::FibonacciSpeedResistanceArcs
58 | Self::FibonacciWedge
59 | Self::Pitchfork
61 | Self::SchiffPitchfork
62 | Self::ModifiedSchiffPitchfork
63 | Self::InsidePitchfork
64 | Self::Pitchfan
65 | Self::GhostFeed
66 | Self::BarsPattern
67 | Self::LongPos
68 | Self::ShortPos
69 | Self::AnchoredVWAP
70 | Self::FixedRangeVolumeProfile
71 | Self::AnchoredVolumeProfile
72 | Self::Brush
73 | Self::Highlighter
74 | Self::ArrowMarker
75 | Self::Arrow
76 | Self::Path
77 | Self::CyclicLines
78 | Self::SineLine
79 | Self::TextLabel
81 | Self::AnchoredText
82 | Self::AnchoredNote
83 | Self::FontIcon
84 | Self::ArrowMarkUp
85 | Self::ArrowMarkDown
86 | Self::ArrowMarkLeft
87 | Self::ArrowMarkRight
88 | Self::Sticker
89 )
90 }
91
92 pub fn supports_extend(&self) -> bool {
94 matches!(
95 self,
96 Self::TrendLine
97 | Self::Ray
98 | Self::ExtendedLine
99 | Self::Rect
100 | Self::InfoLine
101 | Self::TrendAngle
102 | Self::Arrow
103 | Self::ParallelChannel
104 | Self::FlatTopBottom
105 | Self::DisjointChannel
106 | Self::PriceRange
107 | Self::DateAndPriceRange
108 | Self::Pitchfork
109 | Self::SchiffPitchfork
110 | Self::ModifiedSchiffPitchfork
111 | Self::InsidePitchfork
112 | Self::RegressionTrend
113 )
114 }
115
116 pub(crate) fn is_text_annotation(&self) -> bool {
119 matches!(
120 self,
121 Self::TextLabel
122 | Self::AnchoredText
123 | Self::Note
124 | Self::AnchoredNote
125 | Self::Callout
126 | Self::FlagNote
127 | Self::Table
128 | Self::Tweet
129 | Self::Idea
130 )
131 }
132
133 pub fn supports_text(&self) -> bool {
135 self.is_text_annotation()
136 || matches!(
137 self,
138 Self::PriceNote
139 | Self::Comment
140 | Self::Signpost
141 | Self::TrendLine
142 | Self::Ray
143 | Self::InfoLine
144 | Self::ExtendedLine
145 | Self::HorizontalLine
146 | Self::HorizontalRay
147 | Self::VerticalLine
148 | Self::CrossLine
149 | Self::ParallelChannel
150 | Self::RegressionTrend
151 | Self::FlatTopBottom
152 | Self::DisjointChannel
153 | Self::Rect
154 | Self::Circle
155 | Self::Ellipse
156 | Self::Arrow
157 | Self::ArrowMarker
158 | Self::ArrowMarkUp
159 | Self::ArrowMarkDown
160 | Self::ArrowMarkLeft
161 | Self::ArrowMarkRight
162 )
163 }
164
165 pub fn supports_line_ends(&self) -> bool {
167 matches!(
168 self,
169 Self::TrendLine
170 | Self::Ray
171 | Self::ExtendedLine
172 | Self::InfoLine
173 | Self::TrendAngle
174 | Self::HorizontalLine
175 | Self::HorizontalRay
176 | Self::VerticalLine
177 | Self::Arrow
178 | Self::Path
179 | Self::Polyline
180 )
181 }
182
183 pub fn supports_middle_line(&self) -> bool {
185 matches!(self, Self::Rect | Self::ParallelChannel)
186 }
187
188 pub fn edits_text_inline(&self) -> bool {
190 self.is_text_annotation()
191 || matches!(
192 self,
193 Self::Image | Self::PriceNote | Self::Comment | Self::Signpost
194 )
195 }
196
197 pub fn interaction_mode(&self) -> DrawingInteractionMode {
202 match self {
203 Self::CrossCursor | Self::DotCursor | Self::ArrowCursor | Self::Eraser => {
205 DrawingInteractionMode::NotDrawing
206 }
207
208 Self::LongPos
210 | Self::ShortPos
211 | Self::HorizontalLine
212 | Self::VerticalLine
213 | Self::CrossLine
214 | Self::HorizontalRay
215 | Self::TextLabel
216 | Self::AnchoredText
217 | Self::AnchoredNote
218 | Self::PriceLabel
219 | Self::FlagNote
220 | Self::ArrowMarkUp
221 | Self::ArrowMarkDown
222 | Self::ArrowMarkLeft
223 | Self::ArrowMarkRight
224 | Self::Sticker
225 | Self::AnchoredVWAP
226 | Self::AnchoredVolumeProfile
227 | Self::Table
228 | Self::Comment
229 | Self::FontIcon
230 | Self::Image
231 | Self::Tweet
232 | Self::Idea => DrawingInteractionMode::SingleClick,
233
234 Self::Rect
236 | Self::Circle
237 | Self::Ellipse
238 | Self::Measure
239 | Self::PriceRange
240 | Self::DateRange
241 | Self::DateAndPriceRange
242 | Self::GannBox
243 | Self::GannSquare
244 | Self::GannFixed
245 | Self::Forecast
246 | Self::FixedRangeVolumeProfile
247 | Self::FibonacciRetracement
248 | Self::FibonacciSpeedResistanceArcs
249 | Self::FibonacciTimeZones
250 | Self::FibonacciCircles
251 | Self::FibonacciSpeedFan
252 | Self::FibonacciSpiral
253 | Self::GannFan
254 | Self::RegressionTrend
255 | Self::CyclicLines
256 | Self::TimeCycles
257 | Self::SineLine
258 | Self::Callout
259 | Self::Arrow => DrawingInteractionMode::DragToDraw,
260
261 Self::Brush | Self::Highlighter => DrawingInteractionMode::ContinuousDraw,
263
264 Self::RotatedRect
266 | Self::Arc
267 | Self::Sector
268 | Self::FibonacciExtension
269 | Self::FibonacciWedge
270 | Self::TrendBasedFibTime
271 | Self::Pitchfan
272 | Self::Curve
273 | Self::DoubleCurve
274 | Self::ProjectionTool
275 | Self::Polyline
276 | Self::Path
277 | Self::Triangle
278 | Self::ParallelChannel
279 | Self::FlatTopBottom
280 | Self::DisjointChannel
281 | Self::FibonacciChannel
282 | Self::Pitchfork
283 | Self::SchiffPitchfork
284 | Self::ModifiedSchiffPitchfork
285 | Self::InsidePitchfork
286 | Self::XABCDPattern
287 | Self::CypherPattern
288 | Self::HeadAndShoulders
289 | Self::ABCDPattern
290 | Self::TrianglePattern
291 | Self::ThreeDrivesPattern
292 | Self::ElliottImpulse
293 | Self::ElliottCorrection
294 | Self::ElliottTriangle
295 | Self::ElliottDoubleCombo
296 | Self::ElliottTripleCombo
297 | Self::GhostFeed => DrawingInteractionMode::MultiPoint,
298
299 Self::BarsPattern
301 | Self::ArrowMarker
302 | Self::Note
303 | Self::Signpost
304 | Self::PriceNote
305 | Self::TrendLine
306 | Self::Ray
307 | Self::InfoLine
308 | Self::ExtendedLine
309 | Self::TrendAngle => DrawingInteractionMode::ClickClick,
310 }
311 }
312
313 #[cfg(feature = "drawings")]
318 pub(crate) fn anchors_needed(&self) -> usize {
319 match self {
320 Self::ParallelChannel
321 | Self::RotatedRect
322 | Self::FibonacciWedge
323 | Self::Pitchfork
324 | Self::SchiffPitchfork
325 | Self::ModifiedSchiffPitchfork
326 | Self::InsidePitchfork
327 | Self::Pitchfan
328 | Self::Sector => 3,
329 Self::DoubleCurve => 4,
330 Self::Triangle
331 | Self::FlatTopBottom
332 | Self::DisjointChannel
333 | Self::Polyline
334 | Self::Path
335 | Self::GhostFeed
336 | Self::XABCDPattern
337 | Self::CypherPattern
338 | Self::HeadAndShoulders
339 | Self::ABCDPattern
340 | Self::TrianglePattern
341 | Self::ThreeDrivesPattern
342 | Self::ElliottImpulse
343 | Self::ElliottCorrection
344 | Self::ElliottDoubleCombo
345 | Self::ElliottTripleCombo => 1,
346 Self::ElliottTriangle => 6,
347 Self::Note | Self::Signpost => 1,
349 _ if self.interaction_mode() == DrawingInteractionMode::SingleClick => 1,
350 _ => 2,
351 }
352 }
353
354 pub fn required_points(&self) -> Option<usize> {
368 match self.interaction_mode() {
369 DrawingInteractionMode::SingleClick => Some(1),
370 DrawingInteractionMode::ClickClick | DrawingInteractionMode::DragToDraw => Some(2),
371 DrawingInteractionMode::MultiPoint => {
372 match self {
374 Self::Curve | Self::DoubleCurve => Some(2),
375 Self::FibonacciExtension
376 | Self::FibonacciWedge
377 | Self::TrendBasedFibTime
378 | Self::Pitchfan
379 | Self::Arc
380 | Self::Sector
381 | Self::RotatedRect
382 | Self::ProjectionTool
383 | Self::FlatTopBottom
384 | Self::DisjointChannel
385 | Self::FibonacciChannel
386 | Self::Triangle
387 | Self::ParallelChannel
388 | Self::Pitchfork
389 | Self::SchiffPitchfork
390 | Self::ModifiedSchiffPitchfork
391 | Self::InsidePitchfork => Some(3),
392 Self::ABCDPattern
393 | Self::ElliottCorrection
394 | Self::ElliottDoubleCombo
395 | Self::TrianglePattern => Some(4),
396 Self::XABCDPattern | Self::CypherPattern => Some(5),
397 Self::ElliottImpulse | Self::ElliottTriangle | Self::ElliottTripleCombo => {
398 Some(6)
399 }
400 Self::HeadAndShoulders | Self::ThreeDrivesPattern => Some(7),
401 _ => None,
403 }
404 }
405 DrawingInteractionMode::ContinuousDraw => None,
406 DrawingInteractionMode::NotDrawing => None,
407 }
408 }
409}
410
411#[cfg(all(test, feature = "drawings"))]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn placement_collects_every_construction_anchor() {
417 use DrawingToolType::*;
418 for (tool, mode, count) in [
419 (FibonacciExtension, DrawingInteractionMode::MultiPoint, 3),
420 (TrendBasedFibTime, DrawingInteractionMode::MultiPoint, 3),
421 (FibonacciWedge, DrawingInteractionMode::MultiPoint, 3),
422 (Pitchfan, DrawingInteractionMode::MultiPoint, 3),
423 (Arc, DrawingInteractionMode::MultiPoint, 3),
424 (Sector, DrawingInteractionMode::MultiPoint, 3),
425 (ArrowMarkLeft, DrawingInteractionMode::SingleClick, 1),
426 (ArrowMarkRight, DrawingInteractionMode::SingleClick, 1),
427 (Sticker, DrawingInteractionMode::SingleClick, 1),
428 (RotatedRect, DrawingInteractionMode::MultiPoint, 3),
429 (ProjectionTool, DrawingInteractionMode::MultiPoint, 3),
430 (FlatTopBottom, DrawingInteractionMode::MultiPoint, 3),
431 (DisjointChannel, DrawingInteractionMode::MultiPoint, 3),
432 (FibonacciChannel, DrawingInteractionMode::MultiPoint, 3),
433 (Curve, DrawingInteractionMode::MultiPoint, 2),
434 (DoubleCurve, DrawingInteractionMode::MultiPoint, 2),
435 (TrianglePattern, DrawingInteractionMode::MultiPoint, 4),
436 (ThreeDrivesPattern, DrawingInteractionMode::MultiPoint, 7),
437 (ElliottDoubleCombo, DrawingInteractionMode::MultiPoint, 4),
438 (ElliottTriangle, DrawingInteractionMode::MultiPoint, 6),
439 (ElliottTripleCombo, DrawingInteractionMode::MultiPoint, 6),
440 (Note, DrawingInteractionMode::ClickClick, 2),
441 (Signpost, DrawingInteractionMode::ClickClick, 2),
442 (PriceNote, DrawingInteractionMode::ClickClick, 2),
443 (ArrowMarker, DrawingInteractionMode::ClickClick, 2),
444 (LongPos, DrawingInteractionMode::SingleClick, 1),
445 (ShortPos, DrawingInteractionMode::SingleClick, 1),
446 (GhostFeed, DrawingInteractionMode::MultiPoint, 5),
447 ] {
448 assert_eq!(tool.interaction_mode(), mode, "{tool:?}");
449 let required = if tool == GhostFeed { None } else { Some(count) };
450 assert_eq!(tool.required_points(), required, "{tool:?}");
451 let mut manager = crate::drawings::DrawingManager::new();
452 manager.start_drawing_with_coords(
453 tool,
454 egui::pos2(10.0, 500.0),
455 |x| x,
456 |y| 1000.0 - f64::from(y),
457 );
458 for i in 1..count {
459 assert!(manager.drawings.is_empty(), "{tool:?} completed too soon");
460 manager.add_point_with_coords(
461 egui::pos2(10.0 + i as f32 * 100.0, 400.0),
462 |x| x,
463 |y| 1000.0 - f64::from(y),
464 );
465 }
466 if required.is_none() {
467 assert!(manager.drawings.is_empty(), "{tool:?} completed too soon");
468 manager.complete_curr_drawing();
469 }
470 assert_eq!(manager.drawings.len(), 1, "{tool:?}");
471 let stored: &[(f32, f64)] = match tool {
475 Curve => &[(10.0, 500.0), (90.0, 520.0), (110.0, 600.0)],
476 DoubleCurve => &[
477 (10.0, 500.0),
478 (280.0 / 3.0, 1450.0 / 3.0),
479 (80.0 / 3.0, 1850.0 / 3.0),
480 (110.0, 600.0),
481 ],
482 _ => &[],
483 };
484 if !stored.is_empty() {
485 let points = &manager.drawings[0].chart_points;
486 assert_eq!(points.len(), stored.len(), "{tool:?}");
487 for (point, &(bar, price)) in points.iter().zip(stored) {
488 assert!(
489 (point.bar_idx - bar).abs() < 1e-3 && (point.price - price).abs() < 1e-3,
490 "{tool:?}: {point:?}"
491 );
492 }
493 }
494
495 if required.is_none() {
496 assert_eq!(manager.drawings[0].chart_points.len(), count, "{tool:?}");
497 }
498 if matches!(
501 tool,
502 LongPos | ShortPos | ParallelChannel | FlatTopBottom | DisjointChannel
503 ) {
504 continue;
505 }
506 let drawing = &manager.drawings[0];
507 let handles = crate::drawings::services::DrawingInteraction::new().get_handles(drawing);
508 for point in &drawing.points {
509 assert!(
510 handles.iter().any(|(_, p)| p == point),
511 "{tool:?}: no handle for {point:?}"
512 );
513 }
514 }
515 }
516}