From dd992b9781730270db7e5a7bc8938de4a2dd5e40 Mon Sep 17 00:00:00 2001 From: Udo Hoffmann Date: Mon, 27 Apr 2026 20:21:08 +0200 Subject: [PATCH] Refactoring layer --- src/map/mapvas_egui.rs | 144 ++++++++++---------- src/map/mapvas_egui/layer.rs | 84 +++--------- src/map/mapvas_egui/layer/shape_layer.rs | 57 ++++---- src/map/mapvas_egui/layer/timeline_layer.rs | 69 ++++------ src/mapvas_ui.rs | 18 ++- 5 files changed, 161 insertions(+), 211 deletions(-) diff --git a/src/map/mapvas_egui.rs b/src/map/mapvas_egui.rs index efa0a8b..7515be1 100644 --- a/src/map/mapvas_egui.rs +++ b/src/map/mapvas_egui.rs @@ -1,4 +1,5 @@ use std::{ + cell::RefCell, rc::Rc, sync::{Mutex, MutexGuard, mpsc::Receiver}, }; @@ -15,7 +16,7 @@ use egui::{InputState, PointerButton, Rect, Response, Sense, Ui, Widget}; use helpers::{ MAX_ZOOM, MIN_ZOOM, fit_to_screen, point_to_coordinate, set_coordinate_to_pixel, show_box, }; -use layer::Layer; +use layer::{Layer, TimelineLayer}; use log::{debug, warn}; use super::{ @@ -43,6 +44,7 @@ pub struct GeometryInfo { pub struct Map { transform: Transform, layers: Rc>>>, + timeline: Rc>, recv: Rc>, ctx: egui::Context, remote: Remote, @@ -71,7 +73,7 @@ impl Map { let screenshot_layer = layer::ScreenshotLayer::new(ctx.clone()); let screenshot_layer_sender = screenshot_layer.get_sender(); - let timeline_layer = layer::TimelineLayer::new(); + let timeline = Rc::new(RefCell::new(layer::TimelineLayer::new())); let (send, recv) = std::sync::mpsc::channel(); @@ -87,10 +89,9 @@ impl Map { Box::new(shape_layer), Box::new(command), Box::new(screenshot_layer), - Box::new(timeline_layer), ])); - let map_data_holder = Rc::new(MapLayerHolderImpl::new(layers.clone())); + let map_data_holder = Rc::new(MapLayerHolderImpl::new(layers.clone(), timeline.clone())); let remote = Remote { layer: shape_layer_sender.clone(), @@ -109,6 +110,7 @@ impl Map { Self { transform: Transform::invalid(), layers, + timeline, recv: recv.into(), ctx, remote: remote.clone(), @@ -587,6 +589,8 @@ impl Widget for &mut Map { profile_scope!("Layer::draw", layer.name()); layer.draw(ui, &self.transform, rect); } + profile_scope!("Layer::draw", "Timeline"); + self.timeline.borrow_mut().draw(ui, &self.transform, rect); } } self.handle_map_events(rect); @@ -701,11 +705,12 @@ impl Map { is_playing: bool, playback_speed: f32, ) { - if let Ok(mut layers) = self.layers.lock() { - for layer in layers.iter_mut() { - layer.update_timeline(time_range, current_interval, is_playing, playback_speed); - } - } + self.timeline.borrow_mut().update_timeline( + time_range, + current_interval, + is_playing, + playback_speed, + ); } /// Get the current timeline interval from the timeline layer @@ -716,66 +721,29 @@ impl Map { Option>, Option>, ) { - if let Ok(layers) = self.layers.lock() { - for layer in layers.iter() { - let interval = layer.get_timeline_interval(); - if interval.0.is_some() || interval.1.is_some() { - return interval; - } - } - } - (None, None) + self.timeline.borrow().get_interval() } /// Get the timeline playback state from the timeline layer #[must_use] pub fn get_timeline_playback_state(&self) -> (bool, f32) { - if let Ok(layers) = self.layers.lock() { - for layer in layers.iter() { - let (is_playing, speed) = layer.get_timeline_playback_state(); - if is_playing || (speed - 1.0).abs() > f32::EPSILON { - return (is_playing, speed); - } - } - } - (false, 1.0) + self.timeline.borrow().playback_state() } /// Set the timeline layer visibility pub fn set_timeline_visible(&mut self, visible: bool) { - if let Ok(mut layers) = self.layers.lock() { - for layer in layers.iter_mut() { - if layer.name() == "Timeline" { - layer.set_visible(visible); - break; - } - } - } + self.timeline.borrow_mut().set_visible(visible); } /// Check if the timeline layer is visible #[must_use] pub fn is_timeline_visible(&self) -> bool { - if let Ok(layers) = self.layers.lock() { - for layer in layers.iter() { - if layer.name() == "Timeline" { - return layer.is_visible(); - } - } - } - false + self.timeline.borrow().visible() } /// Toggle timeline interval lock state pub fn toggle_timeline_interval_lock(&mut self) { - if let Ok(mut layers) = self.layers.lock() { - for layer in layers.iter_mut() { - if layer.name() == "Timeline" { - layer.toggle_timeline_interval_lock(); - return; - } - } - } + self.timeline.borrow_mut().toggle_interval_lock(); } /// Get current timeline interval lock state @@ -783,21 +751,16 @@ impl Map { pub fn get_timeline_interval_lock( &self, ) -> crate::map::mapvas_egui::timeline_widget::IntervalLock { - if let Ok(layers) = self.layers.lock() { - for layer in layers.iter() { - if layer.name() == "Timeline" { - return layer.get_timeline_interval_lock(); - } - } - } - crate::map::mapvas_egui::timeline_widget::IntervalLock::None + self.timeline.borrow().interval_lock() } /// Search geometries across all layers pub fn search_geometries(&mut self, query: &str) { if let Ok(mut layers) = self.layers.lock() { for layer in layers.iter_mut() { - layer.search_geometries(query); + if let Some(s) = layer.as_searchable_mut() { + s.search_geometries(query); + } } } } @@ -806,7 +769,9 @@ impl Map { pub fn next_search_result(&mut self) -> bool { if let Ok(mut layers) = self.layers.lock() { for layer in layers.iter_mut() { - if layer.next_search_result() { + if let Some(s) = layer.as_searchable_mut() + && s.next_search_result() + { return true; } } @@ -818,7 +783,9 @@ impl Map { pub fn previous_search_result(&mut self) -> bool { if let Ok(mut layers) = self.layers.lock() { for layer in layers.iter_mut() { - if layer.previous_search_result() { + if let Some(s) = layer.as_searchable_mut() + && s.previous_search_result() + { return true; } } @@ -830,7 +797,9 @@ impl Map { pub fn show_search_result_popup(&mut self) { if let Ok(mut layers) = self.layers.lock() { for layer in layers.iter_mut() { - layer.show_search_result_popup(); + if let Some(s) = layer.as_searchable_mut() { + s.show_search_result_popup(); + } } } } @@ -839,7 +808,9 @@ impl Map { pub fn filter_geometries(&mut self, query: &str) { if let Ok(mut layers) = self.layers.lock() { for layer in layers.iter_mut() { - layer.filter_geometries(query); + if let Some(s) = layer.as_searchable_mut() { + s.filter_geometries(query); + } } } } @@ -848,7 +819,9 @@ impl Map { pub fn clear_filter(&mut self) { if let Ok(mut layers) = self.layers.lock() { for layer in layers.iter_mut() { - layer.clear_filter(); + if let Some(s) = layer.as_searchable_mut() { + s.clear_filter(); + } } } } @@ -858,9 +831,11 @@ impl Map { pub fn get_search_results_count(&self) -> usize { if let Ok(layers) = self.layers.lock() { for layer in layers.iter() { - let count = layer.get_search_results_count(); - if count > 0 { - return count; + if let Some(s) = layer.as_searchable() { + let count = s.search_results_count(); + if count > 0 { + return count; + } } } } @@ -930,12 +905,24 @@ pub trait MapLayerReader { pub trait MapLayerHolder { fn get_reader(&self) -> Box; + /// Render the timeline's sidebar UI block. + fn timeline_ui(&self, ui: &mut egui::Ui); + /// Read temporal range from the timeline (used by the temporal-controls scan). + fn timeline_temporal_range( + &self, + ) -> ( + Option>, + Option>, + ); } -struct MapLayerHolderImpl(Rc>>>); +struct MapLayerHolderImpl { + layers: Rc>>>, + timeline: Rc>, +} impl MapLayerHolderImpl { - fn new(layers: Rc>>>) -> Self { - Self(layers) + fn new(layers: Rc>>>, timeline: Rc>) -> Self { + Self { layers, timeline } } } @@ -943,11 +930,24 @@ impl MapLayerHolder for MapLayerHolderImpl { fn get_reader(&self) -> Box { Box::new(MapLayerReaderImpl( self - .0 + .layers .lock() .unwrap_or_else(std::sync::PoisonError::into_inner), )) } + + fn timeline_ui(&self, ui: &mut egui::Ui) { + self.timeline.borrow_mut().ui(ui); + } + + fn timeline_temporal_range( + &self, + ) -> ( + Option>, + Option>, + ) { + self.timeline.borrow().get_temporal_range() + } } struct MapLayerReaderImpl<'a>(MutexGuard<'a, Vec>>); diff --git a/src/map/mapvas_egui/layer.rs b/src/map/mapvas_egui/layer.rs index 6508eb7..c3895e4 100644 --- a/src/map/mapvas_egui/layer.rs +++ b/src/map/mapvas_egui/layer.rs @@ -67,34 +67,15 @@ pub trait Layer { false } - /// Search functionality - fn search_geometries(&mut self, _query: &str) { - // Default implementation does nothing - } - - fn next_search_result(&mut self) -> bool { - false - } - - fn previous_search_result(&mut self) -> bool { - false - } - - fn get_search_results_count(&self) -> usize { - 0 - } - - fn show_search_result_popup(&mut self) { - // Default implementation does nothing + /// Capability accessor: layers that support search/filter override this. + fn as_searchable(&self) -> Option<&dyn Searchable> { + None } - fn filter_geometries(&mut self, _query: &str) { - // Default implementation does nothing + fn as_searchable_mut(&mut self) -> Option<&mut dyn Searchable> { + None } - fn clear_filter(&mut self) { - // Default implementation does nothing - } /// Find the closest geometry to the given position and handle selection if applicable /// Returns Some(distance) if this layer can handle the click, None otherwise /// If Some(distance) is returned, the layer should perform its selection action immediately @@ -113,41 +94,6 @@ pub trait Layer { ) { // Default implementation does nothing - layers can override if they support temporal filtering } - /// Timeline-specific methods for temporal control layers - /// Update timeline with time range and current interval - fn update_timeline( - &mut self, - _time_range: ( - Option>, - Option>, - ), - _current_interval: ( - Option>, - Option>, - ), - _is_playing: bool, - _playback_speed: f32, - ) { - // Default implementation does nothing - only timeline layers override this - } - - /// Get the current timeline interval - fn get_timeline_interval( - &self, - ) -> ( - Option>, - Option>, - ) { - // Default implementation returns None - only timeline layers override this - (None, None) - } - - /// Get timeline playback state - fn get_timeline_playback_state(&self) -> (bool, f32) { - // Default implementation returns (not playing, normal speed) - (false, 1.0) - } - /// Check if this layer is visible fn is_visible(&self) -> bool { self.visible() @@ -168,15 +114,6 @@ pub trait Layer { (None, None) // Default implementation returns no temporal data } - /// Toggle timeline interval lock state - fn toggle_timeline_interval_lock(&mut self) {} - - /// Get current timeline interval lock state - fn get_timeline_interval_lock(&self) -> crate::map::mapvas_egui::timeline_widget::IntervalLock { - // Default implementation returns None - crate::map::mapvas_egui::timeline_widget::IntervalLock::None - } - /// Whether this layer has pending async work (e.g. tiles downloading/rendering). fn has_pending_work(&self) -> bool { false @@ -209,6 +146,17 @@ pub trait Layer { } } +/// Capability for layers that support text search and filtering of their contents. +pub trait Searchable { + fn search_geometries(&mut self, query: &str); + fn next_search_result(&mut self) -> bool; + fn previous_search_result(&mut self) -> bool; + fn search_results_count(&self) -> usize; + fn show_search_result_popup(&mut self); + fn filter_geometries(&mut self, query: &str); + fn clear_filter(&mut self); +} + /// Common properties for all layers. pub struct LayerProperties { pub visible: bool, diff --git a/src/map/mapvas_egui/layer/shape_layer.rs b/src/map/mapvas_egui/layer/shape_layer.rs index f494ab3..4e726e4 100644 --- a/src/map/mapvas_egui/layer/shape_layer.rs +++ b/src/map/mapvas_egui/layer/shape_layer.rs @@ -1,5 +1,5 @@ use super::{ - Layer, LayerProperties, SubLayerInfo, geometry_highlighting::GeometryHighlighter, + Layer, LayerProperties, Searchable, SubLayerInfo, geometry_highlighting::GeometryHighlighter, geometry_rasterizer, geometry_selection, }; use rstar::{AABB, RTree, RTreeObject}; @@ -2471,35 +2471,12 @@ impl Layer for ShapeLayer { self.just_double_clicked.is_some() } - fn search_geometries(&mut self, query: &str) { - // Call the ShapeLayer's specific implementation - ShapeLayer::search_geometries(self, query); - } - - fn next_search_result(&mut self) -> bool { - // Call the ShapeLayer's specific implementation - ShapeLayer::next_search_result(self) + fn as_searchable(&self) -> Option<&dyn Searchable> { + Some(self) } - fn previous_search_result(&mut self) -> bool { - // Call the ShapeLayer's specific implementation - ShapeLayer::previous_search_result(self) - } - - fn get_search_results_count(&self) -> usize { - self.get_search_results().len() - } - - fn show_search_result_popup(&mut self) { - ShapeLayer::show_search_result_popup(self); - } - - fn filter_geometries(&mut self, query: &str) { - ShapeLayer::filter_geometries(self, query); - } - - fn clear_filter(&mut self) { - ShapeLayer::clear_filter(self); + fn as_searchable_mut(&mut self) -> Option<&mut dyn Searchable> { + Some(self) } fn closest_geometry_with_selection(&mut self, pos: Pos2, transform: &Transform) -> Option { @@ -2665,6 +2642,30 @@ impl Layer for ShapeLayer { } } +impl Searchable for ShapeLayer { + fn search_geometries(&mut self, query: &str) { + ShapeLayer::search_geometries(self, query); + } + fn next_search_result(&mut self) -> bool { + ShapeLayer::next_search_result(self) + } + fn previous_search_result(&mut self) -> bool { + ShapeLayer::previous_search_result(self) + } + fn search_results_count(&self) -> usize { + self.get_search_results().len() + } + fn show_search_result_popup(&mut self) { + ShapeLayer::show_search_result_popup(self); + } + fn filter_geometries(&mut self, query: &str) { + ShapeLayer::filter_geometries(self, query); + } + fn clear_filter(&mut self) { + ShapeLayer::clear_filter(self); + } +} + impl ShapeLayer { /// Navigate to a specific geometry within nested collections fn get_geometry_at_path<'a>( diff --git a/src/map/mapvas_egui/layer/timeline_layer.rs b/src/map/mapvas_egui/layer/timeline_layer.rs index 154da33..25683ba 100644 --- a/src/map/mapvas_egui/layer/timeline_layer.rs +++ b/src/map/mapvas_egui/layer/timeline_layer.rs @@ -177,6 +177,35 @@ impl Default for TimelineLayer { } } +impl TimelineLayer { + pub fn update_timeline( + &mut self, + time_range: (Option>, Option>), + current_interval: (Option>, Option>), + is_playing: bool, + playback_speed: f32, + ) { + self.set_time_range(time_range.0, time_range.1); + self.set_interval(current_interval.0, current_interval.1); + self.set_playing(is_playing); + self.set_playback_speed(playback_speed); + } + + #[must_use] + pub fn playback_state(&self) -> (bool, f32) { + (self.widget.is_playing(), self.widget.get_playback_speed()) + } + + pub fn toggle_interval_lock(&mut self) { + self.widget.toggle_interval_lock(); + } + + #[must_use] + pub fn interval_lock(&self) -> crate::map::mapvas_egui::timeline_widget::IntervalLock { + self.widget.get_interval_lock() + } +} + impl Layer for TimelineLayer { fn draw(&mut self, ui: &mut Ui, _transform: &Transform, rect: Rect) { if !self.properties.visible { @@ -218,46 +247,6 @@ impl Layer for TimelineLayer { self.widget.get_time_range() } - fn update_timeline( - &mut self, - time_range: ( - Option>, - Option>, - ), - current_interval: ( - Option>, - Option>, - ), - is_playing: bool, - playback_speed: f32, - ) { - self.set_time_range(time_range.0, time_range.1); - self.set_interval(current_interval.0, current_interval.1); - self.set_playing(is_playing); - self.set_playback_speed(playback_speed); - } - - fn get_timeline_interval( - &self, - ) -> ( - Option>, - Option>, - ) { - self.get_interval() - } - - fn get_timeline_playback_state(&self) -> (bool, f32) { - (self.widget.is_playing(), self.widget.get_playback_speed()) - } - - fn toggle_timeline_interval_lock(&mut self) { - self.widget.toggle_interval_lock(); - } - - fn get_timeline_interval_lock(&self) -> crate::map::mapvas_egui::timeline_widget::IntervalLock { - self.widget.get_interval_lock() - } - fn ui_content(&mut self, ui: &mut Ui) { let (time_start, time_end) = self.widget.get_time_range(); if time_start.is_none() || time_end.is_none() { diff --git a/src/mapvas_ui.rs b/src/mapvas_ui.rs index 840eeaa..9b7b7ba 100644 --- a/src/mapvas_ui.rs +++ b/src/mapvas_ui.rs @@ -589,6 +589,15 @@ impl TemporalControls { latest = Some(latest.map_or(layer_latest, |l| l.max(layer_latest))); } } + drop(layer_reader); + + let (tl_earliest, tl_latest) = map_content.timeline_temporal_range(); + if let Some(tl_earliest) = tl_earliest { + earliest = Some(earliest.map_or(tl_earliest, |e| e.min(tl_earliest))); + } + if let Some(tl_latest) = tl_latest { + latest = Some(latest.map_or(tl_latest, |l| l.max(tl_latest))); + } // Only enable temporal filtering if we found actual temporal data if let (Some(start), Some(end)) = (earliest, latest) { @@ -938,11 +947,14 @@ impl Sidebar { egui::CollapsingHeader::new("Map Layers") .default_open(true) .show(ui, |ui| { - let mut layer_reader = self.map_content.get_reader(); ui.vertical(|ui| { - for layer in layer_reader.get_layers() { - layer.ui(ui); + { + let mut layer_reader = self.map_content.get_reader(); + for layer in layer_reader.get_layers() { + layer.ui(ui); + } } + self.map_content.timeline_ui(ui); }); });