dioxus_maplibre/components/
declarative.rs1use dioxus::prelude::*;
4
5use crate::handle::MapHandle;
6use crate::options::{
7 ControlPosition, GeoJsonSourceOptions, ImageSourceOptions, LayerOptions, MarkerOptions,
8 PopupOptions, RasterDemSourceOptions, RasterSourceOptions, VectorSourceOptions,
9};
10use crate::types::LatLng;
11
12use super::context::try_use_map_handle_signal;
13
14#[derive(Debug, Clone, PartialEq)]
15pub enum MapSourceKind {
16 GeoJson(GeoJsonSourceOptions),
17 Vector(VectorSourceOptions),
18 Raster(RasterSourceOptions),
19 RasterDem(RasterDemSourceOptions),
20 Image(ImageSourceOptions),
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum MapControlKind {
25 Navigation,
26 Geolocate,
27 Scale,
28 Fullscreen,
29 Attribution,
30}
31
32#[derive(Debug, Clone, PartialEq)]
33struct SourceState {
34 id: String,
35 source: MapSourceKind,
36}
37
38#[derive(Debug, Clone, PartialEq)]
39struct LayerState {
40 options: LayerOptions,
41 register_click_events: bool,
42 register_hover_events: bool,
43}
44
45#[derive(Debug, Clone, PartialEq)]
46struct MarkerState {
47 id: String,
48 position: LatLng,
49 options: MarkerOptions,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53struct PopupState {
54 id: String,
55 position: LatLng,
56 html: String,
57 options: PopupOptions,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61struct ControlState {
62 kind: MapControlKind,
63 position: ControlPosition,
64}
65
66fn add_source(map: &MapHandle, source: &SourceState) {
67 match &source.source {
68 MapSourceKind::GeoJson(options) => map.add_geojson_source(&source.id, options.clone()),
69 MapSourceKind::Vector(options) => map.add_vector_source(&source.id, options.clone()),
70 MapSourceKind::Raster(options) => map.add_raster_source(&source.id, options.clone()),
71 MapSourceKind::RasterDem(options) => map.add_raster_dem_source(&source.id, options.clone()),
72 MapSourceKind::Image(options) => map.add_image_source(&source.id, options.clone()),
73 }
74}
75
76fn try_update_geojson_source(map: &MapHandle, previous: &SourceState, next: &SourceState) -> bool {
77 if previous.id != next.id {
78 return false;
79 }
80 let (MapSourceKind::GeoJson(previous_opts), MapSourceKind::GeoJson(next_opts)) =
81 (&previous.source, &next.source)
82 else {
83 return false;
84 };
85
86 if previous_opts == next_opts {
87 return true;
88 }
89
90 let unchanged_non_data_fields = previous_opts.cluster == next_opts.cluster
91 && previous_opts.cluster_radius == next_opts.cluster_radius
92 && previous_opts.cluster_max_zoom == next_opts.cluster_max_zoom
93 && previous_opts.cluster_properties == next_opts.cluster_properties
94 && previous_opts.generate_id == next_opts.generate_id
95 && previous_opts.promote_id == next_opts.promote_id;
96
97 if unchanged_non_data_fields {
98 map.update_geojson_source(&next.id, next_opts.data.clone());
99 return true;
100 }
101
102 false
103}
104
105fn remove_layer_bindings(map: &MapHandle, layer: &LayerState) {
106 if layer.register_click_events {
107 map.off_layer_click(&layer.options.id);
108 }
109 if layer.register_hover_events {
110 map.off_layer_hover(&layer.options.id);
111 }
112 map.remove_layer(&layer.options.id);
113}
114
115fn add_layer_bindings(map: &MapHandle, layer: &LayerState) {
116 map.add_layer(layer.options.clone());
117 if layer.register_click_events {
118 map.on_layer_click(&layer.options.id);
119 }
120 if layer.register_hover_events {
121 map.on_layer_hover(&layer.options.id);
122 }
123}
124
125fn remove_control(map: &MapHandle, control: ControlState) {
126 match control.kind {
127 MapControlKind::Navigation => map.remove_navigation_control(control.position),
128 MapControlKind::Geolocate => map.remove_geolocate_control(control.position),
129 MapControlKind::Scale => map.remove_scale_control(control.position),
130 MapControlKind::Fullscreen => map.remove_fullscreen_control(control.position),
131 MapControlKind::Attribution => map.remove_attribution_control(control.position),
132 }
133}
134
135fn add_control(map: &MapHandle, control: ControlState) {
136 match control.kind {
137 MapControlKind::Navigation => map.add_navigation_control(control.position),
138 MapControlKind::Geolocate => map.add_geolocate_control(control.position),
139 MapControlKind::Scale => map.add_scale_control(control.position),
140 MapControlKind::Fullscreen => map.add_fullscreen_control(control.position),
141 MapControlKind::Attribution => map.add_attribution_control(control.position),
142 }
143}
144
145#[derive(Props, Clone, PartialEq)]
147pub struct MapSourceProps {
148 pub id: String,
149 pub source: MapSourceKind,
150 #[props(default)]
151 pub children: Element,
152}
153
154#[component]
155pub fn MapSource(props: MapSourceProps) -> Element {
156 let handle_signal = try_use_map_handle_signal();
157 let mut applied_source = use_signal(|| None::<SourceState>);
158
159 let desired_source = SourceState {
160 id: props.id.clone(),
161 source: props.source.clone(),
162 };
163
164 use_effect(move || {
165 let Some(handle_signal) = handle_signal else {
166 return;
167 };
168 let Some(map) = handle_signal() else {
169 return;
170 };
171
172 let previous = applied_source.peek().clone();
173 if previous.as_ref() == Some(&desired_source) {
174 return;
175 }
176
177 if let Some(previous) = &previous {
178 if try_update_geojson_source(&map, previous, &desired_source) {
179 applied_source.set(Some(desired_source.clone()));
180 return;
181 }
182 map.remove_source(&previous.id);
183 }
184
185 add_source(&map, &desired_source);
186 applied_source.set(Some(desired_source.clone()));
187 });
188
189 use_drop(move || {
190 if let Some(handle_signal) = handle_signal
191 && let Some(map) = handle_signal.peek().clone()
192 && let Some(source) = applied_source.peek().as_ref()
193 {
194 map.remove_source(&source.id);
195 }
196 });
197
198 rsx! {{props.children}}
199}
200
201#[derive(Props, Clone, PartialEq)]
203pub struct MapLayerProps {
204 pub options: LayerOptions,
205 #[props(default = false)]
206 pub register_click_events: bool,
207 #[props(default = false)]
208 pub register_hover_events: bool,
209}
210
211#[component]
212pub fn MapLayer(props: MapLayerProps) -> Element {
213 let handle_signal = try_use_map_handle_signal();
214 let mut applied_layer = use_signal(|| None::<LayerState>);
215
216 let desired_layer = LayerState {
217 options: props.options.clone(),
218 register_click_events: props.register_click_events,
219 register_hover_events: props.register_hover_events,
220 };
221
222 use_effect(move || {
223 let Some(handle_signal) = handle_signal else {
224 return;
225 };
226 let Some(map) = handle_signal() else {
227 return;
228 };
229
230 let previous = applied_layer.peek().clone();
231 if previous.as_ref() == Some(&desired_layer) {
232 return;
233 }
234
235 if let Some(previous) = &previous {
236 remove_layer_bindings(&map, previous);
237 }
238
239 add_layer_bindings(&map, &desired_layer);
240 applied_layer.set(Some(desired_layer.clone()));
241 });
242
243 use_drop(move || {
244 if let Some(handle_signal) = handle_signal
245 && let Some(map) = handle_signal.peek().clone()
246 && let Some(layer) = applied_layer.peek().as_ref()
247 {
248 remove_layer_bindings(&map, layer);
249 }
250 });
251
252 rsx! {}
253}
254
255#[derive(Props, Clone, PartialEq)]
257pub struct MapMarkerProps {
258 pub id: String,
259 pub position: LatLng,
260 #[props(default)]
261 pub options: MarkerOptions,
262}
263
264#[component]
265pub fn MapMarker(props: MapMarkerProps) -> Element {
266 let handle_signal = try_use_map_handle_signal();
267 let mut applied_marker = use_signal(|| None::<MarkerState>);
268
269 let desired_marker = MarkerState {
270 id: props.id,
271 position: props.position,
272 options: props.options,
273 };
274
275 use_effect(move || {
276 let Some(handle_signal) = handle_signal else {
277 return;
278 };
279 let Some(map) = handle_signal() else {
280 return;
281 };
282
283 let previous = applied_marker.peek().clone();
284 if previous.as_ref() == Some(&desired_marker) {
285 return;
286 }
287
288 if let Some(previous) = &previous {
289 if previous.id == desired_marker.id && previous.options == desired_marker.options {
290 map.update_marker_position(&desired_marker.id, desired_marker.position);
291 applied_marker.set(Some(desired_marker.clone()));
292 return;
293 }
294 map.remove_marker(&previous.id);
295 }
296
297 map.add_marker(
298 &desired_marker.id,
299 desired_marker.position,
300 desired_marker.options.clone(),
301 );
302 applied_marker.set(Some(desired_marker.clone()));
303 });
304
305 use_drop(move || {
306 if let Some(handle_signal) = handle_signal
307 && let Some(map) = handle_signal.peek().clone()
308 && let Some(marker) = applied_marker.peek().as_ref()
309 {
310 map.remove_marker(&marker.id);
311 }
312 });
313
314 rsx! {}
315}
316
317#[derive(Props, Clone, PartialEq)]
319pub struct MapPopupProps {
320 pub id: String,
321 pub position: LatLng,
322 pub html: String,
323 #[props(default)]
324 pub options: PopupOptions,
325}
326
327#[component]
328pub fn MapPopup(props: MapPopupProps) -> Element {
329 let handle_signal = try_use_map_handle_signal();
330 let mut applied_popup = use_signal(|| None::<PopupState>);
331
332 let desired_popup = PopupState {
333 id: props.id,
334 position: props.position,
335 html: props.html,
336 options: props.options,
337 };
338
339 use_effect(move || {
340 let Some(handle_signal) = handle_signal else {
341 return;
342 };
343 let Some(map) = handle_signal() else {
344 return;
345 };
346
347 let previous = applied_popup.peek().clone();
348 if previous.as_ref() == Some(&desired_popup) {
349 return;
350 }
351
352 if let Some(previous) = &previous {
353 map.remove_popup(&previous.id);
354 }
355
356 map.add_popup(
357 &desired_popup.id,
358 desired_popup.position,
359 &desired_popup.html,
360 desired_popup.options.clone(),
361 );
362 applied_popup.set(Some(desired_popup.clone()));
363 });
364
365 use_drop(move || {
366 if let Some(handle_signal) = handle_signal
367 && let Some(map) = handle_signal.peek().clone()
368 && let Some(popup) = applied_popup.peek().as_ref()
369 {
370 map.remove_popup(&popup.id);
371 }
372 });
373
374 rsx! {}
375}
376
377#[derive(Props, Clone, PartialEq, Eq)]
379pub struct MapControlProps {
380 pub kind: MapControlKind,
381 #[props(default)]
382 pub position: ControlPosition,
383}
384
385#[component]
386pub fn MapControl(props: MapControlProps) -> Element {
387 let handle_signal = try_use_map_handle_signal();
388 let mut applied_control = use_signal(|| None::<ControlState>);
389
390 let desired_control = ControlState {
391 kind: props.kind,
392 position: props.position,
393 };
394
395 use_effect(move || {
396 let Some(handle_signal) = handle_signal else {
397 return;
398 };
399 let Some(map) = handle_signal() else {
400 return;
401 };
402
403 let previous = *applied_control.peek();
404 if previous == Some(desired_control) {
405 return;
406 }
407
408 if let Some(previous) = previous {
409 remove_control(&map, previous);
410 }
411
412 add_control(&map, desired_control);
413 applied_control.set(Some(desired_control));
414 });
415
416 use_drop(move || {
417 if let Some(handle_signal) = handle_signal
418 && let Some(map) = handle_signal.peek().clone()
419 && let Some(control) = applied_control.peek().as_ref().copied()
420 {
421 remove_control(&map, control);
422 }
423 });
424
425 rsx! {}
426}