Skip to main content

egui_map/map/
objects.rs

1//! Data types consumed by the [`Map`](super::Map) widget.
2//!
3//! This module contains the geometry primitives ([`RawPoint`], [`RawLine`]),
4//! the map content types ([`MapPoint`], [`MapLine`], [`MapLabel`]) and the
5//! customization points of the widget: [`MapSettings`], [`MapStyle`],
6//! [`VisibilitySetting`], [`ContextMenuManager`] and [`NodeTemplate`].
7
8use egui::{Align2, Color32, FontFamily, FontId, Pos2, Stroke, Ui};
9use std::convert::{From, Into};
10use std::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub};
11use std::time::Instant;
12
13/// A point (or vector) in 2D map coordinates.
14///
15/// `RawPoint` supports component-wise arithmetic: [`Mul`], [`Div`],
16/// [`MulAssign`] and [`DivAssign`] with `f32` and the common integer types, and
17/// [`Add`]/[`Sub`] with other points (by value or by reference). It also
18/// converts from and to `[f32; 2]`, integer arrays and [`egui::Pos2`].
19///
20/// # Examples
21///
22/// ```
23/// use egui_map::map::objects::RawPoint;
24///
25/// let a = RawPoint::new(1.0, 2.0);
26/// let b = RawPoint::new(3.0, -1.0);
27///
28/// assert_eq!((a + b).components, [4.0, 1.0]);
29/// assert_eq!((a * 2.0f32).components, [2.0, 4.0]);
30/// ```
31#[derive(Copy, Clone, Debug)]
32pub struct RawPoint {
33    /// The `x` and `y` components of the point.
34    pub components: [f32; 2],
35}
36
37impl RawPoint {
38    /// Creates a point from its `x` and `y` coordinates.
39    pub fn new(x: f32, y: f32) -> Self {
40        Self { components: [x, y] }
41    }
42}
43
44impl Default for RawPoint {
45    fn default() -> Self {
46        Self::new(0.00, 0.00)
47    }
48}
49
50impl Mul<i64> for RawPoint {
51    type Output = Self;
52
53    fn mul(self, rhs: i64) -> Self::Output {
54        Self {
55            components: [
56                self.components[0] * rhs as f32,
57                self.components[1] * rhs as f32,
58            ],
59        }
60    }
61}
62
63impl Mul<i32> for RawPoint {
64    type Output = Self;
65
66    fn mul(self, rhs: i32) -> Self::Output {
67        Self {
68            components: [
69                self.components[0] * rhs as f32,
70                self.components[1] * rhs as f32,
71            ],
72        }
73    }
74}
75
76impl Mul<u64> for RawPoint {
77    type Output = Self;
78
79    fn mul(self, rhs: u64) -> Self::Output {
80        Self {
81            components: [
82                self.components[0] * rhs as f32,
83                self.components[1] * rhs as f32,
84            ],
85        }
86    }
87}
88
89impl Mul<u32> for RawPoint {
90    type Output = Self;
91
92    fn mul(self, rhs: u32) -> Self::Output {
93        Self {
94            components: [
95                self.components[0] * rhs as f32,
96                self.components[1] * rhs as f32,
97            ],
98        }
99    }
100}
101
102impl Mul<f32> for RawPoint {
103    type Output = Self;
104
105    fn mul(self, rhs: f32) -> Self::Output {
106        Self {
107            components: [self.components[0] * rhs, self.components[1] * rhs],
108        }
109    }
110}
111
112impl MulAssign<i64> for RawPoint {
113    fn mul_assign(&mut self, rhs: i64) {
114        self.components[0] = self.components[0] * rhs as f32;
115        self.components[1] = self.components[1] * rhs as f32;
116    }
117}
118
119impl MulAssign<i32> for RawPoint {
120    fn mul_assign(&mut self, rhs: i32) {
121        self.components[0] = self.components[0] * rhs as f32;
122        self.components[1] = self.components[1] * rhs as f32;
123    }
124}
125
126impl MulAssign<u64> for RawPoint {
127    fn mul_assign(&mut self, rhs: u64) {
128        self.components[0] = self.components[0] * rhs as f32;
129        self.components[1] = self.components[1] * rhs as f32;
130    }
131}
132
133impl MulAssign<u32> for RawPoint {
134    fn mul_assign(&mut self, rhs: u32) {
135        self.components[0] = self.components[0] * rhs as f32;
136        self.components[1] = self.components[1] * rhs as f32;
137    }
138}
139
140impl MulAssign<f32> for RawPoint {
141    fn mul_assign(&mut self, rhs: f32) {
142        self.components[0] = self.components[0] * rhs;
143        self.components[1] = self.components[1] * rhs;
144    }
145}
146
147impl Div<i64> for RawPoint {
148    type Output = Self;
149
150    fn div(self, rhs: i64) -> Self::Output {
151        Self {
152            components: [
153                self.components[0] / rhs as f32,
154                self.components[1] / rhs as f32,
155            ],
156        }
157    }
158}
159
160impl Div<i32> for RawPoint {
161    type Output = Self;
162
163    fn div(self, rhs: i32) -> Self::Output {
164        Self {
165            components: [
166                self.components[0] / rhs as f32,
167                self.components[1] / rhs as f32,
168            ],
169        }
170    }
171}
172
173impl Div<u64> for RawPoint {
174    type Output = Self;
175
176    fn div(self, rhs: u64) -> Self::Output {
177        Self {
178            components: [
179                self.components[0] / rhs as f32,
180                self.components[1] / rhs as f32,
181            ],
182        }
183    }
184}
185
186impl Div<u32> for RawPoint {
187    type Output = Self;
188
189    fn div(self, rhs: u32) -> Self::Output {
190        Self {
191            components: [
192                self.components[0] / rhs as f32,
193                self.components[1] / rhs as f32,
194            ],
195        }
196    }
197}
198
199impl Div<f32> for RawPoint {
200    type Output = Self;
201
202    fn div(self, rhs: f32) -> Self::Output {
203        Self {
204            components: [self.components[0] / rhs, self.components[1] / rhs],
205        }
206    }
207}
208
209impl DivAssign<i64> for RawPoint {
210    fn div_assign(&mut self, rhs: i64) {
211        self.components[0] = self.components[0] / rhs as f32;
212        self.components[1] = self.components[1] / rhs as f32;
213    }
214}
215
216impl DivAssign<i32> for RawPoint {
217    fn div_assign(&mut self, rhs: i32) {
218        self.components[0] = self.components[0] / rhs as f32;
219        self.components[1] = self.components[1] / rhs as f32;
220    }
221}
222
223impl DivAssign<u64> for RawPoint {
224    fn div_assign(&mut self, rhs: u64) {
225        self.components[0] = self.components[0] / rhs as f32;
226        self.components[1] = self.components[1] / rhs as f32;
227    }
228}
229
230impl DivAssign<u32> for RawPoint {
231    fn div_assign(&mut self, rhs: u32) {
232        self.components[0] = self.components[0] / rhs as f32;
233        self.components[1] = self.components[1] / rhs as f32;
234    }
235}
236
237impl DivAssign<f32> for RawPoint {
238    fn div_assign(&mut self, rhs: f32) {
239        self.components[0] = self.components[0] / rhs;
240        self.components[1] = self.components[1] / rhs;
241    }
242}
243
244impl Add<RawPoint> for RawPoint {
245    type Output = RawPoint;
246    fn add(self, rhs: RawPoint) -> Self::Output {
247        Self {
248            components: [
249                self.components[0] + rhs.components[0],
250                self.components[1] + rhs.components[1],
251            ],
252        }
253    }
254}
255
256impl Sub<RawPoint> for RawPoint {
257    type Output = RawPoint;
258    fn sub(self, rhs: RawPoint) -> Self::Output {
259        Self {
260            components: [
261                self.components[0] - rhs.components[0],
262                self.components[1] - rhs.components[1],
263            ],
264        }
265    }
266}
267
268impl Add<&RawPoint> for RawPoint {
269    type Output = RawPoint;
270    fn add(self, rhs: &RawPoint) -> Self::Output {
271        Self {
272            components: [
273                self.components[0] + rhs.components[0],
274                self.components[1] + rhs.components[1],
275            ],
276        }
277    }
278}
279
280impl Sub<&RawPoint> for RawPoint {
281    type Output = RawPoint;
282    fn sub(self, rhs: &RawPoint) -> Self::Output {
283        Self {
284            components: [
285                self.components[0] - rhs.components[0],
286                self.components[1] - rhs.components[1],
287            ],
288        }
289    }
290}
291
292impl From<[f32; 2]> for RawPoint {
293    fn from(value: [f32; 2]) -> Self {
294        Self { components: value }
295    }
296}
297
298impl From<Pos2> for RawPoint {
299    fn from(value: Pos2) -> Self {
300        Self {
301            components: [value.x, value.y],
302        }
303    }
304}
305
306impl From<[i64; 2]> for RawPoint {
307    fn from(value: [i64; 2]) -> Self {
308        Self {
309            components: [value[0] as f32, value[1] as f32],
310        }
311    }
312}
313
314impl From<[i32; 2]> for RawPoint {
315    fn from(value: [i32; 2]) -> Self {
316        Self {
317            components: [value[0] as f32, value[1] as f32],
318        }
319    }
320}
321
322impl From<[i16; 2]> for RawPoint {
323    fn from(value: [i16; 2]) -> Self {
324        Self {
325            components: [value[0] as f32, value[1] as f32],
326        }
327    }
328}
329
330impl From<[i8; 2]> for RawPoint {
331    fn from(value: [i8; 2]) -> Self {
332        Self {
333            components: [value[0] as f32, value[1] as f32],
334        }
335    }
336}
337
338impl From<RawPoint> for [f32; 2] {
339    fn from(val: RawPoint) -> Self {
340        [val.components[0], val.components[1]]
341    }
342}
343
344impl From<RawPoint> for Pos2 {
345    fn from(val: RawPoint) -> Self {
346        Pos2::from(val.components)
347    }
348}
349
350/// A straight line segment between two [`RawPoint`]s.
351#[derive(Copy, Clone, Debug)]
352pub struct RawLine {
353    /// The two end points of the segment.
354    pub points: [RawPoint; 2],
355}
356
357impl RawLine {
358    /// Creates a segment between `a` and `b`.
359    pub fn new(a: RawPoint, b: RawPoint) -> Self {
360        Self { points: [a, b] }
361    }
362
363    /// Returns the Euclidean distance between the two end points.
364    pub fn distance(self) -> f32 {
365        let x = self.points[0].components[0] - self.points[1].components[0];
366        let y = self.points[0].components[1] - self.points[1].components[1];
367        (x.powi(2) + y.powi(2)).sqrt()
368    }
369
370    /// Returns the point halfway between the two end points.
371    pub fn midpoint(self) -> RawPoint {
372        let x = (self.points[0].components[0] + self.points[1].components[0]) / 2.0;
373        let y = (self.points[0].components[1] + self.points[1].components[1]) / 2.0;
374        RawPoint::new(x, y)
375    }
376}
377
378impl From<RawLine> for [Pos2; 2] {
379    fn from(val: RawLine) -> Self {
380        let position1 = val.points[0].into();
381        let position2 = val.points[1].into();
382        [position1, position2]
383    }
384}
385
386impl From<[[i64; 2]; 2]> for RawLine {
387    fn from(value: [[i64; 2]; 2]) -> Self {
388        Self {
389            points: [RawPoint::from(value[0]), RawPoint::from(value[1])],
390        }
391    }
392}
393
394/// Visual style used to paint the map under a given theme.
395///
396/// Multiplying or dividing a `MapStyle` by a number scales the stroke widths
397/// and the font size, leaving colors untouched; the widget uses this to scale
398/// the active style with the current zoom factor. Fields that are `None` are
399/// left untouched by those operators.
400#[derive(Clone, Debug)]
401pub struct MapStyle {
402    /// Stroke used for the widget border.
403    pub border: Option<Stroke>,
404    /// Stroke used for the connection lines between nodes.
405    pub line: Option<Stroke>,
406    /// Color used to fill node shapes.
407    pub fill_color: Color32,
408    /// Color used for text.
409    pub text_color: Color32,
410    /// Font used for map labels.
411    pub font: Option<FontId>,
412    /// Background color of the map canvas.
413    pub background_color: Color32,
414    /// Color used for notification pulse animations.
415    pub alert_color: Color32,
416}
417
418impl MapStyle {
419    /// Creates a fully transparent style with no border, line or font.
420    pub fn new() -> Self {
421        MapStyle {
422            border: None,
423            line: None,
424            fill_color: Color32::TRANSPARENT,
425            text_color: Color32::TRANSPARENT,
426            font: None,
427            background_color: Color32::TRANSPARENT,
428            alert_color: Color32::TRANSPARENT,
429        }
430    }
431}
432
433impl Default for MapStyle {
434    fn default() -> Self {
435        MapStyle::new()
436    }
437}
438
439impl MapStyle {
440    /// Returns a copy with the stroke widths and font size scaled by `factor`.
441    /// Fields that are `None` are left untouched.
442    fn scaled(mut self, factor: f32) -> Self {
443        if let Some(border) = self.border.as_mut() {
444            border.width *= factor;
445        }
446        if let Some(line) = self.line.as_mut() {
447            line.width *= factor;
448        }
449        if let Some(font) = self.font.as_mut() {
450            font.size *= factor;
451        }
452        self
453    }
454}
455
456impl Mul<i64> for MapStyle {
457    type Output = Self;
458
459    fn mul(self, rhs: i64) -> Self::Output {
460        self.scaled(rhs as f32)
461    }
462}
463
464impl Mul<i32> for MapStyle {
465    type Output = Self;
466
467    fn mul(self, rhs: i32) -> Self::Output {
468        self.scaled(rhs as f32)
469    }
470}
471
472impl Mul<f32> for MapStyle {
473    type Output = Self;
474
475    fn mul(self, rhs: f32) -> Self::Output {
476        self.scaled(rhs)
477    }
478}
479
480impl Mul<f64> for MapStyle {
481    type Output = Self;
482
483    fn mul(self, rhs: f64) -> Self::Output {
484        self.scaled(rhs as f32)
485    }
486}
487
488impl Div<i64> for MapStyle {
489    type Output = Self;
490
491    fn div(self, rhs: i64) -> Self::Output {
492        self.scaled(1.0 / rhs as f32)
493    }
494}
495
496impl Div<i32> for MapStyle {
497    type Output = Self;
498
499    fn div(self, rhs: i32) -> Self::Output {
500        self.scaled(1.0 / rhs as f32)
501    }
502}
503
504impl Div<f32> for MapStyle {
505    type Output = Self;
506
507    fn div(self, rhs: f32) -> Self::Output {
508        self.scaled(1.0 / rhs)
509    }
510}
511
512impl Div<f64> for MapStyle {
513    type Output = Self;
514
515    fn div(self, rhs: f64) -> Self::Output {
516        self.scaled(1.0 / rhs as f32)
517    }
518}
519
520/// A free-floating text label drawn on the map.
521///
522/// Labels are installed with [`Map::add_labels`](super::Map::add_labels).
523#[derive(Clone, Debug)]
524pub struct MapLabel {
525    /// The text to display.
526    pub text: String,
527    /// The position of the label's center.
528    pub center: Pos2,
529}
530
531impl Default for MapLabel {
532    fn default() -> Self {
533        MapLabel::new()
534    }
535}
536
537impl MapLabel {
538    /// Creates an empty label centered at the origin.
539    pub fn new() -> Self {
540        MapLabel {
541            text: String::new(),
542            center: Pos2::new(0.00, 0.00),
543        }
544    }
545}
546
547/// A connection line between two points on the map.
548///
549/// Lines are installed with [`Map::add_lines`](super::Map::add_lines), keyed by
550/// an id that nodes reference through [`MapPoint::connections`].
551#[derive(Clone, Debug)]
552pub struct MapLine {
553    /// Optional identifier of the line.
554    pub id: Option<String>,
555    /// The segment geometry, in map coordinates.
556    pub raw_line: RawLine,
557}
558
559impl MapLine {
560    /// Creates a line between `point1` and `point2` with no id.
561    pub fn new(point1: RawPoint, point2: RawPoint) -> Self {
562        MapLine {
563            id: None,
564            raw_line: RawLine::new(point1, point2),
565        }
566    }
567}
568
569/// A node on the map: an id, a position and an optional display name.
570///
571/// Nodes are loaded into the widget through
572/// [`Map::add_hashmap_points`](super::Map::add_hashmap_points), keyed by their
573/// id.
574#[derive(Clone, Debug)]
575pub struct MapPoint {
576    /// Position of the node, in map coordinates.
577    pub raw_point: RawPoint,
578    /// Ids of the lines connecting this node with others.
579    ///
580    /// Each entry must match a key of the map passed to
581    /// [`Map::add_lines`](super::Map::add_lines). The usual pattern is to push
582    /// the same line id into the `connections` of **both** endpoint nodes; a
583    /// line is drawn as soon as any node referencing it becomes visible.
584    pub connections: Vec<String>,
585    /// Node identifier, used for lookups, notifications and markers.
586    id: usize,
587    /// Display name shown next to the node.
588    name: String,
589}
590
591impl MapPoint {
592    /// Creates a node with the given `id` at the given map coordinates.
593    pub fn new(id: usize, coords: RawPoint) -> MapPoint {
594        MapPoint {
595            raw_point: coords,
596            id,
597            connections: Vec::new(),
598            name: String::new(),
599        }
600    }
601
602    /// Returns the node identifier.
603    pub fn get_id(&self) -> usize {
604        self.id
605    }
606
607    /// Returns the node display name (empty if it was never set).
608    pub fn get_name(&self) -> String {
609        self.name.clone()
610    }
611
612    /// Sets the node display name.
613    pub fn set_name(&mut self, value: String) {
614        self.name = value;
615    }
616}
617
618impl From<std::collections::hash_map::OccupiedEntry<'_, usize, MapPoint>> for MapPoint {
619    fn from(value: std::collections::hash_map::OccupiedEntry<'_, usize, MapPoint>) -> Self {
620        let k = value.get();
621        k.clone()
622    }
623}
624
625#[derive(Clone)]
626pub(crate) struct MapBounds {
627    pub min: RawPoint,
628    pub max: RawPoint,
629    pub pos: RawPoint,
630    pub dist: f32,
631}
632
633impl MapBounds {
634    pub fn new() -> Self {
635        MapBounds {
636            min: RawPoint::default(),
637            max: RawPoint::default(),
638            pos: RawPoint::default(),
639            dist: 0.0,
640        }
641    }
642}
643
644impl Default for MapBounds {
645    fn default() -> Self {
646        MapBounds::new()
647    }
648}
649
650pub(crate) struct TextSettings {
651    pub position: RawPoint,
652    pub anchor: Align2,
653    pub text: String,
654    pub size: f32,
655    pub family: FontFamily,
656    pub text_color: Color32,
657}
658
659/// Configuration of a [`Map`](super::Map) widget.
660///
661/// [`MapSettings::default()`] provides sensible zoom limits plus a light and a
662/// dark theme; the widget picks the style to apply based on
663/// [`egui::Visuals::dark_mode`], using `styles[0]` in light mode and
664/// `styles[1]` in dark mode.
665#[derive(Clone, Debug)]
666pub struct MapSettings {
667    /// Maximum zoom factor.
668    pub max_zoom: f32,
669    /// Minimum zoom factor.
670    pub min_zoom: f32,
671    /// Zoom threshold above which connection lines become visible.
672    pub line_visible_zoom: f32,
673    /// Zoom threshold above which node names become visible when
674    /// [`node_text_visibility`](Self::node_text_visibility) is
675    /// [`VisibilitySetting::Always`].
676    pub label_visible_zoom: f32,
677    /// Controls when node names are displayed.
678    pub node_text_visibility: VisibilitySetting,
679    /// Per-theme styles; index `0` is used in light mode, index `1` in dark
680    /// mode.
681    pub styles: Vec<MapStyle>,
682}
683
684impl MapSettings {
685    /// Creates settings with all zoom thresholds set to `0.0` and a single
686    /// transparent style.
687    ///
688    /// Prefer [`MapSettings::default()`] unless you really need to build the
689    /// configuration from scratch.
690    pub fn new() -> Self {
691        MapSettings {
692            max_zoom: 0.0,
693            min_zoom: 0.0,
694            line_visible_zoom: 0.0,
695            label_visible_zoom: 0.0,
696            node_text_visibility: VisibilitySetting::Always,
697            styles: vec![MapStyle::new()],
698        }
699    }
700}
701
702impl Default for MapSettings {
703    /// Returns the default configuration: zoom from `0.1` to `2.0`, connection
704    /// lines visible above `0.2`, node names above `0.58`, and built-in light
705    /// and dark themes.
706    fn default() -> Self {
707        let mut obj = MapSettings {
708            max_zoom: 2.0,
709            min_zoom: 0.1,
710            line_visible_zoom: 0.2,
711            label_visible_zoom: 0.58,
712            node_text_visibility: VisibilitySetting::Always,
713            styles: Vec::new(),
714        };
715
716        // light Theme
717        obj.styles.push(MapStyle {
718            border: Some(egui::Stroke {
719                width: 2.0,
720                color: Color32::from_rgb(216, 142, 58),
721            }),
722            line: Some(egui::Stroke {
723                width: 2.0,
724                color: Color32::DARK_RED,
725            }),
726            fill_color: Color32::from_rgb(216, 142, 58),
727            text_color: Color32::DARK_GREEN,
728            font: Some(FontId::new(12.00, FontFamily::Proportional)),
729            background_color: Color32::WHITE,
730            alert_color: Color32::from_rgb(246, 30, 131),
731        });
732
733        // Dark Theme
734        obj.styles.push(MapStyle {
735            border: Some(egui::Stroke {
736                width: 2.0,
737                color: Color32::GOLD,
738            }),
739            line: Some(egui::Stroke {
740                width: 2.0,
741                color: Color32::LIGHT_RED,
742            }),
743            fill_color: Color32::GOLD,
744            text_color: Color32::LIGHT_GREEN,
745            font: Some(FontId::new(12.00, FontFamily::Proportional)),
746            background_color: Color32::DARK_GRAY,
747            alert_color: Color32::from_rgb(128, 12, 67),
748        });
749        obj
750    }
751}
752
753/// Controls when the name of a node is displayed next to it.
754#[derive(Clone, Debug, PartialEq)]
755pub enum VisibilitySetting {
756    /// Never show node names.
757    Hidden,
758    /// Only show the name of the node closest to the mouse pointer.
759    Hover,
760    /// Always show node names, subject to [`MapSettings::label_visible_zoom`].
761    Always,
762}
763
764/// Provides the contents of the widget's right-click context menu.
765///
766/// Install an implementation with
767/// [`Map::set_context_manager`](super::Map::set_context_manager).
768///
769/// # Examples
770///
771/// ```
772/// use egui_map::map::objects::ContextMenuManager;
773///
774/// struct MyMenu;
775///
776/// impl ContextMenuManager for MyMenu {
777///     fn ui(&self, ui: &mut egui::Ui) {
778///         ui.label("Hello from the map!");
779///     }
780/// }
781/// ```
782pub trait ContextMenuManager {
783    /// Builds the menu contents; called every frame while the menu is open.
784    fn ui(&self, ui: &mut Ui);
785}
786
787/// Customizes how nodes and their visual effects are rendered.
788///
789/// When a template is installed with
790/// [`Map::set_node_template`](super::Map::set_node_template), the widget
791/// delegates all node painting to it instead of using the built-in shapes and
792/// animations — including the node name labels, so draw the name yourself in
793/// [`NodeTemplate::node_ui`] if you need it.
794///
795/// The positions passed to these methods are in screen coordinates: already
796/// scaled by `zoom` and translated to the viewport origin. Multiply every size
797/// by `zoom` so your shapes scale together with the map.
798///
799/// # Animation idioms
800///
801/// egui only repaints on demand, so any method that animates (a blinking
802/// marker, a fading notification, ...) must call
803/// [`ui.ctx().request_repaint()`](egui::Context::request_repaint) to keep the
804/// frames coming. Time-driven effects are usually computed from
805/// [`Instant::now()`] (see `initial_time` in
806/// [`NodeTemplate::notification_ui`]) or from the system clock.
807///
808/// # Examples
809///
810/// A node drawn as a rounded box with its name inside, plus a notification
811/// animation that expands and fades out over two seconds:
812///
813/// ```
814/// use egui_map::map::objects::{MapPoint, NodeTemplate};
815/// use egui::{Align2, Color32, CornerRadius, FontId, Pos2, Rect, Stroke, Ui, Vec2};
816/// use std::time::Instant;
817///
818/// struct BoxedNodes;
819///
820/// impl NodeTemplate for BoxedNodes {
821///     fn node_ui(&self, ui: &mut Ui, position: Pos2, zoom: f32, point: &MapPoint) {
822///         // Multiply every size by `zoom` so the node scales with the map.
823///         let rect = Rect::from_center_size(position, Vec2::new(90.0 * zoom, 35.0 * zoom));
824///         let rounding = CornerRadius::same((10.0 * zoom) as u8);
825///         let painter = ui.painter();
826///         painter.rect_filled(rect, rounding, ui.visuals().extreme_bg_color);
827///         painter.rect_stroke(
828///             rect,
829///             rounding,
830///             Stroke::new(4.0 * zoom, Color32::WHITE),
831///             egui::StrokeKind::Middle,
832///         );
833///         painter.text(
834///             position,
835///             Align2::CENTER_CENTER,
836///             point.get_name(),
837///             FontId::proportional(12.0 * zoom),
838///             Color32::WHITE,
839///         );
840///     }
841///
842///     fn notification_ui(
843///         &self,
844///         ui: &mut Ui,
845///         position: Pos2,
846///         zoom: f32,
847///         initial_time: Instant,
848///         color: Color32,
849///     ) -> bool {
850///         let secs = Instant::now().duration_since(initial_time).as_secs_f32();
851///         // Expand the stroke and fade the color out over 2 seconds.
852///         let alpha = (1.0 - secs / 2.0).clamp(0.0, 1.0);
853///         let fading =
854///             Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), (255.0 * alpha) as u8);
855///         let rect = Rect::from_center_size(position, Vec2::new(90.0 * zoom, 35.0 * zoom));
856///         ui.painter().rect_stroke(
857///             rect,
858///             CornerRadius::same((10.0 * zoom) as u8),
859///             Stroke::new((4.0 + 25.0 * secs) * zoom, fading),
860///             egui::StrokeKind::Middle,
861///         );
862///         // Keep the animation frames coming.
863///         ui.ctx().request_repaint();
864///         // Returning `false` removes the notification.
865///         secs < 2.0
866///     }
867///     # fn selection_ui(&self, ui: &mut Ui, point: Pos2, zoom: f32) {
868///     #     let rect = Rect::from_center_size(point, Vec2::new(94.0 * zoom, 39.0 * zoom));
869///     #     ui.painter().rect_stroke(
870///     #         rect,
871///     #         CornerRadius::same((10.0 * zoom) as u8),
872///     #         Stroke::new(3.0 * zoom, Color32::YELLOW),
873///     #         egui::StrokeKind::Middle,
874///     #     );
875///     # }
876///     # fn marker_ui(&self, ui: &mut Ui, point: Pos2, zoom: f32) {
877///     #     ui.painter().circle_stroke(point, 6.0 * zoom, Stroke::new(2.0 * zoom, Color32::LIGHT_GREEN));
878///     #     ui.ctx().request_repaint();
879///     # }
880/// }
881/// ```
882pub trait NodeTemplate {
883    /// Draws a node, replacing the default filled circle.
884    ///
885    /// Called every frame for each visible node. The widget no longer draws
886    /// the node name once a template is installed, so render it here (e.g.
887    /// with [`Painter::text`](egui::Painter::text)) if you need it.
888    fn node_ui(&self, ui: &mut Ui, _viewport_position: Pos2, _zoom: f32, _point: &MapPoint);
889
890    /// Draws the highlight over the node closest to the mouse pointer.
891    ///
892    /// The nearest node is only computed while the pointer is over the map and
893    /// [`MapSettings::node_text_visibility`] is [`VisibilitySetting::Hover`].
894    fn selection_ui(&self, ui: &mut Ui, _viewport_position: Pos2, _zoom: f32);
895
896    /// Draws the notification effect of a node notified at `initial_time`.
897    ///
898    /// Called every frame for each node passed to
899    /// [`Map::notify`](super::Map::notify). Should return `true` while the
900    /// animation is still playing — remember to call
901    /// [`ui.ctx().request_repaint()`](egui::Context::request_repaint) —; once
902    /// it returns `false` the notification is discarded.
903    fn notification_ui(
904        &self,
905        ui: &mut Ui,
906        _viewport_position: Pos2,
907        _zoom: f32,
908        initial_time: Instant,
909        color: Color32,
910    ) -> bool;
911
912    /// Draws a marker over the given node.
913    ///
914    /// Called every frame for each marker registered with
915    /// [`Map::update_marker`](super::Map::update_marker). For animated markers
916    /// (e.g. a blinking light), drive the effect from the system clock and
917    /// call [`ui.ctx().request_repaint()`](egui::Context::request_repaint).
918    fn marker_ui(&self, ui: &mut Ui, _viewport_position: Pos2, _zoom: f32);
919}
920
921#[cfg(test)]
922mod tests {
923    use super::*;
924    use std::collections::HashMap;
925
926    // ---------- RawPoint ----------
927
928    #[test]
929    fn raw_point_new() {
930        let p = RawPoint::new(3.5, -2.0);
931        assert_eq!(p.components, [3.5, -2.0]);
932    }
933
934    #[test]
935    fn raw_point_default() {
936        let p = RawPoint::default();
937        assert_eq!(p.components, [0.0, 0.0]);
938    }
939
940    #[test]
941    fn raw_point_mul_i64() {
942        let p = RawPoint::new(2.0, -3.0) * 3i64;
943        assert_eq!(p.components, [6.0, -9.0]);
944    }
945
946    #[test]
947    fn raw_point_mul_i32() {
948        let p = RawPoint::new(2.0, -3.0) * 3i32;
949        assert_eq!(p.components, [6.0, -9.0]);
950    }
951
952    #[test]
953    fn raw_point_mul_u64() {
954        let p = RawPoint::new(2.0, -3.0) * 3u64;
955        assert_eq!(p.components, [6.0, -9.0]);
956    }
957
958    #[test]
959    fn raw_point_mul_u32() {
960        let p = RawPoint::new(2.0, -3.0) * 3u32;
961        assert_eq!(p.components, [6.0, -9.0]);
962    }
963
964    #[test]
965    fn raw_point_mul_f32() {
966        let p = RawPoint::new(2.0, -3.0) * 0.5f32;
967        assert_eq!(p.components, [1.0, -1.5]);
968    }
969
970    #[test]
971    fn raw_point_mul_assign_i64() {
972        let mut p = RawPoint::new(2.0, -3.0);
973        p *= 3i64;
974        assert_eq!(p.components, [6.0, -9.0]);
975    }
976
977    #[test]
978    fn raw_point_mul_assign_i32() {
979        let mut p = RawPoint::new(2.0, -3.0);
980        p *= 3i32;
981        assert_eq!(p.components, [6.0, -9.0]);
982    }
983
984    #[test]
985    fn raw_point_mul_assign_u64() {
986        let mut p = RawPoint::new(2.0, -3.0);
987        p *= 3u64;
988        assert_eq!(p.components, [6.0, -9.0]);
989    }
990
991    #[test]
992    fn raw_point_mul_assign_u32() {
993        let mut p = RawPoint::new(2.0, -3.0);
994        p *= 3u32;
995        assert_eq!(p.components, [6.0, -9.0]);
996    }
997
998    #[test]
999    fn raw_point_mul_assign_f32() {
1000        let mut p = RawPoint::new(2.0, -3.0);
1001        p *= 0.5f32;
1002        assert_eq!(p.components, [1.0, -1.5]);
1003    }
1004
1005    #[test]
1006    fn raw_point_div_i64() {
1007        let p = RawPoint::new(6.0, -9.0) / 3i64;
1008        assert_eq!(p.components, [2.0, -3.0]);
1009    }
1010
1011    #[test]
1012    fn raw_point_div_i32() {
1013        let p = RawPoint::new(6.0, -9.0) / 3i32;
1014        assert_eq!(p.components, [2.0, -3.0]);
1015    }
1016
1017    #[test]
1018    fn raw_point_div_u64() {
1019        let p = RawPoint::new(6.0, -9.0) / 3u64;
1020        assert_eq!(p.components, [2.0, -3.0]);
1021    }
1022
1023    #[test]
1024    fn raw_point_div_u32() {
1025        let p = RawPoint::new(6.0, -9.0) / 3u32;
1026        assert_eq!(p.components, [2.0, -3.0]);
1027    }
1028
1029    #[test]
1030    fn raw_point_div_f32() {
1031        let p = RawPoint::new(1.0, -1.5) / 0.5f32;
1032        assert_eq!(p.components, [2.0, -3.0]);
1033    }
1034
1035    #[test]
1036    fn raw_point_div_assign_i64() {
1037        let mut p = RawPoint::new(6.0, -9.0);
1038        p /= 3i64;
1039        assert_eq!(p.components, [2.0, -3.0]);
1040    }
1041
1042    #[test]
1043    fn raw_point_div_assign_i32() {
1044        let mut p = RawPoint::new(6.0, -9.0);
1045        p /= 3i32;
1046        assert_eq!(p.components, [2.0, -3.0]);
1047    }
1048
1049    #[test]
1050    fn raw_point_div_assign_u64() {
1051        let mut p = RawPoint::new(6.0, -9.0);
1052        p /= 3u64;
1053        assert_eq!(p.components, [2.0, -3.0]);
1054    }
1055
1056    #[test]
1057    fn raw_point_div_assign_u32() {
1058        let mut p = RawPoint::new(6.0, -9.0);
1059        p /= 3u32;
1060        assert_eq!(p.components, [2.0, -3.0]);
1061    }
1062
1063    #[test]
1064    fn raw_point_div_assign_f32() {
1065        let mut p = RawPoint::new(1.0, -1.5);
1066        p /= 0.5f32;
1067        assert_eq!(p.components, [2.0, -3.0]);
1068    }
1069
1070    #[test]
1071    fn raw_point_add() {
1072        let a = RawPoint::new(1.0, 2.0);
1073        let b = RawPoint::new(3.0, -4.0);
1074        let c = a + b;
1075        assert_eq!(c.components, [4.0, -2.0]);
1076    }
1077
1078    #[test]
1079    fn raw_point_sub() {
1080        let a = RawPoint::new(1.0, 2.0);
1081        let b = RawPoint::new(3.0, -4.0);
1082        let c = a - b;
1083        assert_eq!(c.components, [-2.0, 6.0]);
1084    }
1085
1086    #[test]
1087    #[allow(clippy::op_ref)] // se prueba a propósito la impl Add<&RawPoint>
1088    fn raw_point_add_ref() {
1089        let a = RawPoint::new(1.0, 2.0);
1090        let b = RawPoint::new(3.0, -4.0);
1091        let c = a + &b;
1092        assert_eq!(c.components, [4.0, -2.0]);
1093        // b sigue siendo usable tras la suma por referencia
1094        assert_eq!(b.components, [3.0, -4.0]);
1095    }
1096
1097    #[test]
1098    #[allow(clippy::op_ref)] // se prueba a propósito la impl Sub<&RawPoint>
1099    fn raw_point_sub_ref() {
1100        let a = RawPoint::new(1.0, 2.0);
1101        let b = RawPoint::new(3.0, -4.0);
1102        let c = a - &b;
1103        assert_eq!(c.components, [-2.0, 6.0]);
1104        assert_eq!(b.components, [3.0, -4.0]);
1105    }
1106
1107    #[test]
1108    fn raw_point_from_f32_array() {
1109        let p = RawPoint::from([1.5f32, -2.5f32]);
1110        assert_eq!(p.components, [1.5, -2.5]);
1111    }
1112
1113    #[test]
1114    fn raw_point_from_i64_array() {
1115        let p = RawPoint::from([3i64, -4i64]);
1116        assert_eq!(p.components, [3.0, -4.0]);
1117    }
1118
1119    #[test]
1120    fn raw_point_from_i32_array() {
1121        let p = RawPoint::from([3i32, -4i32]);
1122        assert_eq!(p.components, [3.0, -4.0]);
1123    }
1124
1125    #[test]
1126    fn raw_point_from_i16_array() {
1127        let p = RawPoint::from([3i16, -4i16]);
1128        assert_eq!(p.components, [3.0, -4.0]);
1129    }
1130
1131    #[test]
1132    fn raw_point_from_i8_array() {
1133        let p = RawPoint::from([3i8, -4i8]);
1134        assert_eq!(p.components, [3.0, -4.0]);
1135    }
1136
1137    #[test]
1138    fn raw_point_from_pos2() {
1139        let p = RawPoint::from(Pos2::new(7.0, 8.0));
1140        assert_eq!(p.components, [7.0, 8.0]);
1141    }
1142
1143    #[test]
1144    fn raw_point_into_f32_array() {
1145        let arr: [f32; 2] = RawPoint::new(7.0, 8.0).into();
1146        assert_eq!(arr, [7.0, 8.0]);
1147    }
1148
1149    #[test]
1150    fn raw_point_into_pos2() {
1151        let pos: Pos2 = RawPoint::new(7.0, 8.0).into();
1152        assert_eq!(pos, Pos2::new(7.0, 8.0));
1153    }
1154
1155    // ---------- RawLine ----------
1156
1157    #[test]
1158    fn raw_line_new() {
1159        let a = RawPoint::new(1.0, 2.0);
1160        let b = RawPoint::new(3.0, 4.0);
1161        let line = RawLine::new(a, b);
1162        assert_eq!(line.points[0].components, [1.0, 2.0]);
1163        assert_eq!(line.points[1].components, [3.0, 4.0]);
1164    }
1165
1166    #[test]
1167    fn raw_line_distance() {
1168        // triángulo 3-4-5
1169        let line = RawLine::new(RawPoint::new(0.0, 0.0), RawPoint::new(3.0, 4.0));
1170        assert_eq!(line.distance(), 5.0);
1171    }
1172
1173    #[test]
1174    fn raw_line_distance_zero() {
1175        let line = RawLine::new(RawPoint::new(2.0, 2.0), RawPoint::new(2.0, 2.0));
1176        assert_eq!(line.distance(), 0.0);
1177    }
1178
1179    #[test]
1180    fn raw_line_midpoint() {
1181        let line = RawLine::new(RawPoint::new(0.0, 0.0), RawPoint::new(4.0, 6.0));
1182        let mid = line.midpoint();
1183        assert_eq!(mid.components, [2.0, 3.0]);
1184    }
1185
1186    #[test]
1187    fn raw_line_into_pos2_array() {
1188        let line = RawLine::new(RawPoint::new(1.0, 2.0), RawPoint::new(3.0, 4.0));
1189        let arr: [Pos2; 2] = line.into();
1190        assert_eq!(arr, [Pos2::new(1.0, 2.0), Pos2::new(3.0, 4.0)]);
1191    }
1192
1193    #[test]
1194    fn raw_line_from_i64_arrays() {
1195        let line = RawLine::from([[1i64, 2i64], [3i64, 4i64]]);
1196        assert_eq!(line.points[0].components, [1.0, 2.0]);
1197        assert_eq!(line.points[1].components, [3.0, 4.0]);
1198    }
1199
1200    // ---------- MapStyle ----------
1201
1202    fn full_style() -> MapStyle {
1203        MapStyle {
1204            border: Some(Stroke::new(2.0, Color32::RED)),
1205            line: Some(Stroke::new(4.0, Color32::BLUE)),
1206            fill_color: Color32::GREEN,
1207            text_color: Color32::WHITE,
1208            font: Some(FontId::new(10.0, FontFamily::Proportional)),
1209            background_color: Color32::BLACK,
1210            alert_color: Color32::YELLOW,
1211        }
1212    }
1213
1214    #[test]
1215    fn map_style_new() {
1216        let s = MapStyle::new();
1217        assert!(s.border.is_none());
1218        assert!(s.line.is_none());
1219        assert!(s.font.is_none());
1220        assert_eq!(s.fill_color, Color32::TRANSPARENT);
1221        assert_eq!(s.text_color, Color32::TRANSPARENT);
1222        assert_eq!(s.background_color, Color32::TRANSPARENT);
1223        assert_eq!(s.alert_color, Color32::TRANSPARENT);
1224    }
1225
1226    #[test]
1227    fn map_style_default_equals_new() {
1228        let s = MapStyle::default();
1229        assert!(s.border.is_none());
1230        assert!(s.line.is_none());
1231        assert!(s.font.is_none());
1232    }
1233
1234    #[test]
1235    fn map_style_mul_i64() {
1236        let s = full_style() * 2i64;
1237        assert_eq!(s.border.unwrap().width, 4.0);
1238        assert_eq!(s.line.unwrap().width, 8.0);
1239        assert_eq!(s.font.unwrap().size, 20.0);
1240    }
1241
1242    #[test]
1243    fn map_style_mul_i32() {
1244        let s = full_style() * 2i32;
1245        assert_eq!(s.border.unwrap().width, 4.0);
1246        assert_eq!(s.line.unwrap().width, 8.0);
1247        assert_eq!(s.font.unwrap().size, 20.0);
1248    }
1249
1250    #[test]
1251    fn map_style_mul_f32() {
1252        let s = full_style() * 0.5f32;
1253        assert_eq!(s.border.unwrap().width, 1.0);
1254        assert_eq!(s.line.unwrap().width, 2.0);
1255        assert_eq!(s.font.unwrap().size, 5.0);
1256    }
1257
1258    #[test]
1259    fn map_style_mul_f64() {
1260        let s = full_style() * 0.5f64;
1261        assert_eq!(s.border.unwrap().width, 1.0);
1262        assert_eq!(s.line.unwrap().width, 2.0);
1263        assert_eq!(s.font.unwrap().size, 5.0);
1264    }
1265
1266    #[test]
1267    fn map_style_div_i64() {
1268        let s = full_style() / 2i64;
1269        assert_eq!(s.border.unwrap().width, 1.0);
1270        assert_eq!(s.line.unwrap().width, 2.0);
1271        assert_eq!(s.font.unwrap().size, 5.0);
1272    }
1273
1274    #[test]
1275    fn map_style_div_i32() {
1276        let s = full_style() / 2i32;
1277        assert_eq!(s.border.unwrap().width, 1.0);
1278        assert_eq!(s.line.unwrap().width, 2.0);
1279        assert_eq!(s.font.unwrap().size, 5.0);
1280    }
1281
1282    #[test]
1283    fn map_style_div_f32() {
1284        let s = full_style() / 0.5f32;
1285        assert_eq!(s.border.unwrap().width, 4.0);
1286        assert_eq!(s.line.unwrap().width, 8.0);
1287        assert_eq!(s.font.unwrap().size, 20.0);
1288    }
1289
1290    #[test]
1291    fn map_style_div_f64() {
1292        let s = full_style() / 0.5f64;
1293        assert_eq!(s.border.unwrap().width, 4.0);
1294        assert_eq!(s.line.unwrap().width, 8.0);
1295        assert_eq!(s.font.unwrap().size, 20.0);
1296    }
1297
1298    // ---------- MapLabel ----------
1299
1300    #[test]
1301    fn map_label_new() {
1302        let l = MapLabel::new();
1303        assert_eq!(l.text, String::new());
1304        assert_eq!(l.center, Pos2::new(0.0, 0.0));
1305    }
1306
1307    #[test]
1308    fn map_label_default_equals_new() {
1309        let l = MapLabel::default();
1310        assert_eq!(l.text, String::new());
1311        assert_eq!(l.center, Pos2::new(0.0, 0.0));
1312    }
1313
1314    // ---------- MapLine ----------
1315
1316    #[test]
1317    fn map_line_new() {
1318        let a = RawPoint::new(1.0, 2.0);
1319        let b = RawPoint::new(3.0, 4.0);
1320        let line = MapLine::new(a, b);
1321        assert!(line.id.is_none());
1322        assert_eq!(line.raw_line.points[0].components, [1.0, 2.0]);
1323        assert_eq!(line.raw_line.points[1].components, [3.0, 4.0]);
1324    }
1325
1326    // ---------- MapPoint ----------
1327
1328    #[test]
1329    fn map_point_new() {
1330        let p = MapPoint::new(42, RawPoint::new(1.0, 2.0));
1331        assert_eq!(p.get_id(), 42);
1332        assert_eq!(p.raw_point.components, [1.0, 2.0]);
1333        assert!(p.connections.is_empty());
1334        assert_eq!(p.get_name(), String::new());
1335    }
1336
1337    #[test]
1338    fn map_point_set_and_get_name() {
1339        let mut p = MapPoint::new(1, RawPoint::default());
1340        p.set_name("Jita".to_string());
1341        assert_eq!(p.get_name(), "Jita");
1342    }
1343
1344    #[test]
1345    fn map_point_from_occupied_entry() {
1346        let mut map: HashMap<usize, MapPoint> = HashMap::new();
1347        let mut original = MapPoint::new(7, RawPoint::new(5.0, 6.0));
1348        original.set_name("Amarr".to_string());
1349        map.insert(7, original);
1350
1351        use std::collections::hash_map::Entry;
1352        if let Entry::Occupied(entry) = map.entry(7) {
1353            let cloned = MapPoint::from(entry);
1354            assert_eq!(cloned.get_id(), 7);
1355            assert_eq!(cloned.get_name(), "Amarr");
1356            assert_eq!(cloned.raw_point.components, [5.0, 6.0]);
1357        } else {
1358            panic!("se esperaba una entrada ocupada");
1359        }
1360    }
1361
1362    // ---------- MapBounds ----------
1363
1364    #[test]
1365    fn map_bounds_new() {
1366        let b = MapBounds::new();
1367        assert_eq!(b.min.components, [0.0, 0.0]);
1368        assert_eq!(b.max.components, [0.0, 0.0]);
1369        assert_eq!(b.pos.components, [0.0, 0.0]);
1370        assert_eq!(b.dist, 0.0);
1371    }
1372
1373    #[test]
1374    fn map_bounds_default_equals_new() {
1375        let b = MapBounds::default();
1376        assert_eq!(b.dist, 0.0);
1377        assert_eq!(b.pos.components, [0.0, 0.0]);
1378    }
1379
1380    // ---------- MapSettings ----------
1381
1382    #[test]
1383    fn map_settings_new() {
1384        let s = MapSettings::new();
1385        assert_eq!(s.max_zoom, 0.0);
1386        assert_eq!(s.min_zoom, 0.0);
1387        assert_eq!(s.line_visible_zoom, 0.0);
1388        assert_eq!(s.label_visible_zoom, 0.0);
1389        assert_eq!(s.node_text_visibility, VisibilitySetting::Always);
1390        assert_eq!(s.styles.len(), 1);
1391    }
1392
1393    #[test]
1394    fn map_settings_default() {
1395        let s = MapSettings::default();
1396        assert_eq!(s.max_zoom, 2.0);
1397        assert_eq!(s.min_zoom, 0.1);
1398        assert_eq!(s.line_visible_zoom, 0.2);
1399        assert_eq!(s.label_visible_zoom, 0.58);
1400        assert_eq!(s.node_text_visibility, VisibilitySetting::Always);
1401        // light + dark themes
1402        assert_eq!(s.styles.len(), 2);
1403        // light theme
1404        assert_eq!(s.styles[0].background_color, Color32::WHITE);
1405        assert!(s.styles[0].border.is_some());
1406        assert!(s.styles[0].line.is_some());
1407        assert!(s.styles[0].font.is_some());
1408        // dark theme
1409        assert_eq!(s.styles[1].background_color, Color32::DARK_GRAY);
1410        assert!(s.styles[1].border.is_some());
1411        assert!(s.styles[1].line.is_some());
1412        assert!(s.styles[1].font.is_some());
1413    }
1414
1415    // ---------- VisibilitySetting ----------
1416
1417    #[test]
1418    fn visibility_setting_equality() {
1419        assert_eq!(VisibilitySetting::Hidden, VisibilitySetting::Hidden);
1420        assert_eq!(VisibilitySetting::Hover, VisibilitySetting::Hover);
1421        assert_eq!(VisibilitySetting::Always, VisibilitySetting::Always);
1422        assert_ne!(VisibilitySetting::Hidden, VisibilitySetting::Hover);
1423        assert_ne!(VisibilitySetting::Hover, VisibilitySetting::Always);
1424        assert_ne!(VisibilitySetting::Hidden, VisibilitySetting::Always);
1425    }
1426}