leptos_leaflet/components/
circle.rs

1use leaflet::CircleOptions;
2use leptos::prelude::*;
3
4use super::{
5    extend_context_with_overlay, FillRule, LayerEvents, LeafletMapContext, LineCap, LineJoin,
6    MouseEvents, MoveEvents, PopupEvents, Position, StringEmptyOption, TooltipEvents,
7};
8use crate::{
9    core::{JsSignal, JsStoredValue},
10    setup_layer_leaflet_option, setup_layer_leaflet_option_ref, setup_layer_leaflet_string,
11};
12
13/// A circle overlay that represents a circle on the map.
14/// 
15/// The `Circle` component is used to create a circle overlay on the map. It provides options to customize
16/// the appearance of the circle, such as the stroke color, fill color, and radius.
17#[component(transparent)]
18pub fn Circle(
19    #[prop(into)] center: JsSignal<Position>,
20    #[prop(into, optional)] stroke: Signal<Option<bool>>,
21    #[prop(into, optional)] color: Signal<String>,
22    #[prop(into, optional)] weight: Signal<Option<f64>>,
23    #[prop(into, optional)] interactive: Signal<Option<bool>>,
24    #[prop(into, optional)] opacity: Signal<Option<f64>>,
25    #[prop(into, optional)] line_cap: Signal<Option<LineCap>>,
26    #[prop(into, optional)] line_join: Signal<Option<LineJoin>>,
27    #[prop(into, optional)] dash_array: Signal<String>,
28    #[prop(into, optional)] dash_offset: Signal<String>,
29    #[prop(into, optional)] fill: Signal<Option<bool>>,
30    #[prop(into, optional)] fill_color: Signal<String>,
31    #[prop(into, optional)] fill_opacity: Signal<Option<f64>>,
32    #[prop(into, optional)] fill_rule: Signal<Option<FillRule>>,
33    #[prop(into, optional)] bubbling_mouse_events: Signal<Option<bool>>,
34    #[prop(into, optional)] class_name: Signal<String>,
35    #[prop(into, optional)] mouse_events: MouseEvents,
36    #[prop(into, optional)] layer_events: LayerEvents,
37    #[prop(into, optional)] popup_events: PopupEvents,
38    #[prop(into, optional)] tooltip_events: TooltipEvents,
39    #[prop(into, optional)] move_events: MoveEvents,
40
41    #[prop(into)] radius: Signal<f64>,
42    #[prop(optional)] children: Option<Children>,
43) -> impl IntoView {
44    let position_tracking = center;
45    let overlay_context = extend_context_with_overlay();
46    let overlay = JsStoredValue::new_local(None::<leaflet::Circle>);
47
48    let color_clone = color;
49    let fill_color_clone = fill_color;
50    Effect::new(move |_| {
51        if let Some(map) = use_context::<LeafletMapContext>()
52            .expect("map context")
53            .map()
54        {
55            let options = CircleOptions::new();
56            setup_layer_leaflet_option!(stroke, options);
57            setup_layer_leaflet_string!(color, options);
58            setup_layer_leaflet_option!(weight, options);
59            setup_layer_leaflet_option!(opacity, options);
60            setup_layer_leaflet_option!(interactive, options);
61            setup_layer_leaflet_option_ref!(line_cap, options);
62            setup_layer_leaflet_option_ref!(line_join, options);
63            setup_layer_leaflet_string!(dash_array, options);
64            setup_layer_leaflet_string!(dash_offset, options);
65            setup_layer_leaflet_option!(fill, options);
66            setup_layer_leaflet_string!(fill_color, options);
67            setup_layer_leaflet_option!(fill_opacity, options);
68            setup_layer_leaflet_option_ref!(fill_rule, options);
69            setup_layer_leaflet_option!(bubbling_mouse_events, options);
70            setup_layer_leaflet_string!(class_name, options);
71            let circle =
72                leaflet::Circle::new_with_options(&center.get_untracked().into(), &options);
73
74            leaflet::Circle::set_radius(&circle, radius.get_untracked());
75
76            mouse_events.setup(&circle);
77            popup_events.setup(&circle);
78            tooltip_events.setup(&circle);
79            layer_events.setup(&circle);
80            move_events.setup(&circle);
81
82            circle.add_to(&map);
83            overlay_context.set_container(&circle);
84            overlay.set_value(Some(circle));
85        };
86    });
87
88    let radius_stop = Effect::watch(
89        move || radius.get(),
90        move |radius, _, _| {
91            if let Some(polygon) = overlay.get_value().as_ref() {
92                polygon.set_radius(*radius);
93            }
94        },
95        false,
96    );
97
98    let stroke_stop = Effect::watch(
99        move || stroke.get(),
100        move |stroke, _, _| {
101            if let (Some(stroke), Some(overlay)) = (stroke, overlay.get_value().as_ref()) {
102                let options = CircleOptions::new();
103                options.set_stroke(*stroke);
104                overlay.set_style(&options);
105            }
106        },
107        false,
108    );
109
110    let color_stop = Effect::watch(
111        move || color_clone.get(),
112        move |color, _, _| {
113            if let (Some(color), Some(overlay)) = (color.to_option(), overlay.get_value().as_ref())
114            {
115                let options = CircleOptions::new();
116                options.set_color(color.to_string());
117                overlay.set_style(&options);
118            }
119        },
120        false,
121    );
122
123    let fill_color_stop = Effect::watch(
124        move || fill_color_clone.get(),
125        move |color, _, _| {
126            if let (Some(color), Some(overlay)) = (color.to_option(), overlay.get_value().as_ref())
127            {
128                let options = CircleOptions::new();
129                options.set_fill_color(color.to_string());
130                overlay.set_style(&options);
131            }
132        },
133        false,
134    );
135
136    let opacity_stop = Effect::watch(
137        move || opacity.get(),
138        move |opacity, _, _| {
139            if let (Some(opacity), Some(overlay)) = (opacity, overlay.get_value().as_ref()) {
140                let options = CircleOptions::new();
141                options.set_opacity(*opacity);
142                overlay.set_style(&options);
143            }
144        },
145        false,
146    );
147
148    let fill_opacity_stop = Effect::watch(
149        move || fill_opacity.get(),
150        move |opacity, _, _| {
151            if let (Some(opacity), Some(overlay)) = (opacity, overlay.get_value().as_ref()) {
152                let options = CircleOptions::new();
153                options.set_fill_opacity(*opacity);
154                overlay.set_style(&options);
155            }
156        },
157        false,
158    );
159
160    let weight_stop = Effect::watch(
161        move || weight.get(),
162        move |weight, _, _| {
163            if let (Some(weight), Some(overlay)) = (weight, overlay.get_value().as_ref()) {
164                let options = CircleOptions::new();
165                options.set_weight(*weight);
166                overlay.set_style(&options);
167            }
168        },
169        false,
170    );
171
172    let position_stop = Effect::watch(
173        move || position_tracking.get(),
174        move |position_tracking, _, _| {
175            if let Some(circle) = overlay.get_value().as_ref() {
176                circle.set_lat_lng(&position_tracking.as_lat_lng());
177            }
178        },
179        false,
180    );
181
182    on_cleanup(move || {
183        position_stop.stop();
184        radius_stop.stop();
185        stroke_stop.stop();
186        color_stop.stop();
187        fill_color_stop.stop();
188        opacity_stop.stop();
189        fill_opacity_stop.stop();
190        weight_stop.stop();
191        if let Some(overlay) = overlay.try_get_value().flatten().as_ref() {
192            overlay.remove();
193        }
194    });
195
196    children.map(|child| child())
197}