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