diff --git a/src/bin/mapvas/main.rs b/src/bin/mapvas/main.rs index 4ccb09b..e9a2a75 100644 --- a/src/bin/mapvas/main.rs +++ b/src/bin/mapvas/main.rs @@ -56,7 +56,7 @@ fn main() -> eframe::Result { let config = Config::new(); init_style_config(config.vector_style_file.as_deref()); - let (map, remote, data_holder) = Map::new(cc.egui_ctx.clone()); + let (map, remote, data_holder) = Map::new(cc.egui_ctx.clone(), config.clone()); spawn_remote_runner(runtime, remote.clone()); Ok(Box::new(MapApp::new( map, diff --git a/src/headless.rs b/src/headless.rs index 8549122..98c6790 100644 --- a/src/headless.rs +++ b/src/headless.rs @@ -61,7 +61,7 @@ impl HeadlessRenderer { #[must_use] pub fn render(&self, events: &[MapEvent], width: u32, height: u32) -> image::RgbaImage { let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, self.config.clone()); map.set_headless(); for event in events { diff --git a/src/map/mapvas_egui.rs b/src/map/mapvas_egui.rs index 4b2f7ea..efa0a8b 100644 --- a/src/map/mapvas_egui.rs +++ b/src/map/mapvas_egui.rs @@ -54,9 +54,10 @@ pub struct Map { impl Map { #[must_use] - pub fn new(ctx: egui::Context) -> (Self, Remote, Rc) { - let cfg = crate::config::Config::new(); - + pub fn new( + ctx: egui::Context, + cfg: crate::config::Config, + ) -> (Self, Remote, Rc) { let tile_layer = layer::TileLayer::from_config(ctx.clone(), &cfg); let shape_info = std::sync::Arc::new(std::sync::RwLock::new(std::collections::HashMap::new())); let shape_layer = layer::ShapeLayer::new(cfg.clone(), ctx.clone(), shape_info.clone()); diff --git a/src/map/mapvas_egui/helpers.rs b/src/map/mapvas_egui/helpers.rs index 4a19de6..4fc5a4c 100644 --- a/src/map/mapvas_egui/helpers.rs +++ b/src/map/mapvas_egui/helpers.rs @@ -35,27 +35,36 @@ pub(crate) fn fit_to_screen(transform: &mut Transform, rect: &Rect) { transform.zoom = transform.zoom.clamp(MIN_ZOOM, MAX_ZOOM); let inv = transform.invert(); - let PixelCoordinate { x: _, y } = inv.apply(PixelPosition { x: 0., y: 0. }); - if y < 0. { - transform.translate( - PixelPosition { - x: 0., - y: y.min(0.), - } * transform.zoom, - ); - } + let world_h_screen = CANVAS_SIZE * transform.zoom; + let view_h = rect.height(); + let top_y = inv.apply(PixelPosition { x: 0., y: 0. }).y; - let PixelCoordinate { x: _, y } = inv.apply(PixelPosition { - x: rect.max.x, - y: rect.max.y, - }); - if y > CANVAS_SIZE { - transform.translate( - PixelPosition { + if view_h >= world_h_screen { + // Viewport is taller than the world — center the world vertically. Without this, + // top-anchor and bottom-anchor clamps below would both fire and oscillate. + let desired_top_y = -(view_h - world_h_screen) / (2. * transform.zoom); + let shift = (top_y - desired_top_y) * transform.zoom; + if shift.abs() > 0.01 { + transform.translate(PixelPosition { x: 0., y: shift }); + } + } else if top_y < 0. { + transform.translate(PixelPosition { + x: 0., + y: top_y * transform.zoom, + }); + } else { + let bottom_y = inv + .apply(PixelPosition { + x: rect.max.x, + y: rect.max.y, + }) + .y; + if bottom_y > CANVAS_SIZE { + transform.translate(PixelPosition { x: 0., - y: (y - CANVAS_SIZE).max(0.), - } * transform.zoom, - ); + y: (bottom_y - CANVAS_SIZE) * transform.zoom, + }); + } } // Wrap the x-translation so the viewport stays within one canonical world. diff --git a/tests/grid_snapshot_tests.rs b/tests/grid_snapshot_tests.rs index 60dbffc..0873d92 100644 --- a/tests/grid_snapshot_tests.rs +++ b/tests/grid_snapshot_tests.rs @@ -13,7 +13,7 @@ use std::io::Cursor; fn create_test_app_with_geojson_grid_mode(geojson_content: String) -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); // Parse the GeoJSON content and inject it as a map event @@ -35,7 +35,7 @@ fn create_test_app_with_geojson_grid_mode(geojson_content: String) -> MapApp { fn create_test_app_grid_mode() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); MapApp::new(map, remote, data_holder, config, None) } @@ -221,17 +221,26 @@ async fn grid_mode_ui_workflow() { app, ); - // Step 1: Initial state with loaded data + // Setup: switch to grid-only display via the settings UI so subsequent + // snapshots don't depend on real tile loads. + harness.run(); + harness.get_by_label("Tile Layer").click(); + harness.run(); + harness.get_by_label("Grid Only").click(); harness.run(); + harness.get_by_label("Tile Layer").click(); + harness.run(); + + // Step 1: Initial state with loaded data harness.snapshot("workflow_initial_with_data"); - // Step 2: Open tile layer settings + // Step 2: Open tile layer settings (Grid Only is already selected from setup) let tile_layer = harness.get_by_label("Tile Layer"); tile_layer.click(); harness.run(); harness.snapshot("workflow_settings_opened"); - // Step 3: Switch directly to grid mode (avoiding tile dependencies) + // Step 3: Re-click Grid Only to verify the radio still works let grid_only_button = harness.get_by_label("Grid Only"); grid_only_button.click(); harness.run(); diff --git a/tests/highlight_snapshot_tests.rs b/tests/highlight_snapshot_tests.rs index 9ea7c99..07bf188 100644 --- a/tests/highlight_snapshot_tests.rs +++ b/tests/highlight_snapshot_tests.rs @@ -13,7 +13,7 @@ use std::io::Cursor; fn create_test_app_with_geometries() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); // Use nested folders KML to have both collections and individual geometries @@ -90,6 +90,19 @@ async fn test_individual_geometry_highlighting() { harness.run(); + // Switch to grid mode so snapshots don't depend on real tile loads + let tile_layer = harness.get_by_label("Tile Layer"); + tile_layer.click(); + harness.run(); + + let grid_only_button = harness.get_by_label("Grid Only"); + grid_only_button.click(); + harness.run(); + + let tile_layer = harness.get_by_label("Tile Layer"); + tile_layer.click(); + harness.run(); + // Setup to reach individual geometries let shape_layer = harness.get_by_label("Shape Layer"); shape_layer.click(); diff --git a/tests/popup_double_click_tests.rs b/tests/popup_double_click_tests.rs index 3958abe..a5543f7 100644 --- a/tests/popup_double_click_tests.rs +++ b/tests/popup_double_click_tests.rs @@ -12,7 +12,7 @@ use std::io::Cursor; fn create_test_app_with_nested_kml() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); // Create nested KML content with multiple levels diff --git a/tests/popup_snapshot_tests.rs b/tests/popup_snapshot_tests.rs index b532d66..54744e1 100644 --- a/tests/popup_snapshot_tests.rs +++ b/tests/popup_snapshot_tests.rs @@ -13,7 +13,7 @@ use std::io::Cursor; fn create_test_app_with_popup_kml() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); // Read and parse the test KML file with MultiGeometry diff --git a/tests/snapshots/coordinates_grid_only_mode.png b/tests/snapshots/coordinates_grid_only_mode.png index 7734e3e..eac4d0e 100644 Binary files a/tests/snapshots/coordinates_grid_only_mode.png and b/tests/snapshots/coordinates_grid_only_mode.png differ diff --git a/tests/snapshots/coordinates_off_mode.png b/tests/snapshots/coordinates_off_mode.png index cb002e6..497733e 100644 Binary files a/tests/snapshots/coordinates_off_mode.png and b/tests/snapshots/coordinates_off_mode.png differ diff --git a/tests/snapshots/debug_range_timeline_fixed.png b/tests/snapshots/debug_range_timeline_fixed.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/debug_range_timeline_fixed.png and b/tests/snapshots/debug_range_timeline_fixed.png differ diff --git a/tests/snapshots/empty_grid_mode.png b/tests/snapshots/empty_grid_mode.png index c2cc111..aee0963 100644 Binary files a/tests/snapshots/empty_grid_mode.png and b/tests/snapshots/empty_grid_mode.png differ diff --git a/tests/snapshots/empty_grid_mode_clean.png b/tests/snapshots/empty_grid_mode_clean.png index 8da5262..58694be 100644 Binary files a/tests/snapshots/empty_grid_mode_clean.png and b/tests/snapshots/empty_grid_mode_clean.png differ diff --git a/tests/snapshots/geojson_grid_clean_view.png b/tests/snapshots/geojson_grid_clean_view.png index 32859f5..3bd24d6 100644 Binary files a/tests/snapshots/geojson_grid_clean_view.png and b/tests/snapshots/geojson_grid_clean_view.png differ diff --git a/tests/snapshots/geojson_on_grid_mode.png b/tests/snapshots/geojson_on_grid_mode.png index 96a8037..b8e912a 100644 Binary files a/tests/snapshots/geojson_on_grid_mode.png and b/tests/snapshots/geojson_on_grid_mode.png differ diff --git a/tests/snapshots/geometry_shapes_expanded.png b/tests/snapshots/geometry_shapes_expanded.png index 0a8380a..dd7b310 100644 Binary files a/tests/snapshots/geometry_shapes_expanded.png and b/tests/snapshots/geometry_shapes_expanded.png differ diff --git a/tests/snapshots/geometry_ui_ready_for_interaction.png b/tests/snapshots/geometry_ui_ready_for_interaction.png index 0a8380a..dd7b310 100644 Binary files a/tests/snapshots/geometry_ui_ready_for_interaction.png and b/tests/snapshots/geometry_ui_ready_for_interaction.png differ diff --git a/tests/snapshots/grid_mode_sidebar_collapsed.png b/tests/snapshots/grid_mode_sidebar_collapsed.png index 8da5262..58694be 100644 Binary files a/tests/snapshots/grid_mode_sidebar_collapsed.png and b/tests/snapshots/grid_mode_sidebar_collapsed.png differ diff --git a/tests/snapshots/grid_mode_sidebar_full.png b/tests/snapshots/grid_mode_sidebar_full.png index c2cc111..aee0963 100644 Binary files a/tests/snapshots/grid_mode_sidebar_full.png and b/tests/snapshots/grid_mode_sidebar_full.png differ diff --git a/tests/snapshots/grid_only_mode_basic.png b/tests/snapshots/grid_only_mode_basic.png index c2cc111..aee0963 100644 Binary files a/tests/snapshots/grid_only_mode_basic.png and b/tests/snapshots/grid_only_mode_basic.png differ diff --git a/tests/snapshots/individual_geometries_final_state.png b/tests/snapshots/individual_geometries_final_state.png index 9eb5ec7..ce2d927 100644 Binary files a/tests/snapshots/individual_geometries_final_state.png and b/tests/snapshots/individual_geometries_final_state.png differ diff --git a/tests/snapshots/individual_geometries_shape_layer_expanded.png b/tests/snapshots/individual_geometries_shape_layer_expanded.png index 9eb5ec7..ce2d927 100644 Binary files a/tests/snapshots/individual_geometries_shape_layer_expanded.png and b/tests/snapshots/individual_geometries_shape_layer_expanded.png differ diff --git a/tests/snapshots/large_file_temporal_range.png b/tests/snapshots/large_file_temporal_range.png index 9d670b8..fe73b87 100644 Binary files a/tests/snapshots/large_file_temporal_range.png and b/tests/snapshots/large_file_temporal_range.png differ diff --git a/tests/snapshots/popup_functionality_test.png b/tests/snapshots/popup_functionality_test.png index 9ccb65f..35b721b 100644 Binary files a/tests/snapshots/popup_functionality_test.png and b/tests/snapshots/popup_functionality_test.png differ diff --git a/tests/snapshots/popup_initial_sidebar.png b/tests/snapshots/popup_initial_sidebar.png index 55f658a..aa19563 100644 Binary files a/tests/snapshots/popup_initial_sidebar.png and b/tests/snapshots/popup_initial_sidebar.png differ diff --git a/tests/snapshots/popup_memory_management_stable.png b/tests/snapshots/popup_memory_management_stable.png index 9ccb65f..35b721b 100644 Binary files a/tests/snapshots/popup_memory_management_stable.png and b/tests/snapshots/popup_memory_management_stable.png differ diff --git a/tests/snapshots/popup_nested_expanded.png b/tests/snapshots/popup_nested_expanded.png index 288401f..a5dba58 100644 Binary files a/tests/snapshots/popup_nested_expanded.png and b/tests/snapshots/popup_nested_expanded.png differ diff --git a/tests/snapshots/popup_nested_initial.png b/tests/snapshots/popup_nested_initial.png index 9ccb65f..35b721b 100644 Binary files a/tests/snapshots/popup_nested_initial.png and b/tests/snapshots/popup_nested_initial.png differ diff --git a/tests/snapshots/popup_nested_ready_for_interaction.png b/tests/snapshots/popup_nested_ready_for_interaction.png index 288401f..a5dba58 100644 Binary files a/tests/snapshots/popup_nested_ready_for_interaction.png and b/tests/snapshots/popup_nested_ready_for_interaction.png differ diff --git a/tests/snapshots/popup_shape_layer_expanded.png b/tests/snapshots/popup_shape_layer_expanded.png index c79a12e..7c1a5b9 100644 Binary files a/tests/snapshots/popup_shape_layer_expanded.png and b/tests/snapshots/popup_shape_layer_expanded.png differ diff --git a/tests/snapshots/popup_shapes_visible.png b/tests/snapshots/popup_shapes_visible.png index 288401f..a5dba58 100644 Binary files a/tests/snapshots/popup_shapes_visible.png and b/tests/snapshots/popup_shapes_visible.png differ diff --git a/tests/snapshots/popup_sidebar_with_collections.png b/tests/snapshots/popup_sidebar_with_collections.png index 55f658a..aa19563 100644 Binary files a/tests/snapshots/popup_sidebar_with_collections.png and b/tests/snapshots/popup_sidebar_with_collections.png differ diff --git a/tests/snapshots/temporal_filtering_09_00_only_morning_visible.png b/tests/snapshots/temporal_filtering_09_00_only_morning_visible.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_filtering_09_00_only_morning_visible.png and b/tests/snapshots/temporal_filtering_09_00_only_morning_visible.png differ diff --git a/tests/snapshots/temporal_filtering_after_animation_started.png b/tests/snapshots/temporal_filtering_after_animation_started.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_filtering_after_animation_started.png and b/tests/snapshots/temporal_filtering_after_animation_started.png differ diff --git a/tests/snapshots/temporal_filtering_after_controls_visible.png b/tests/snapshots/temporal_filtering_after_controls_visible.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_filtering_after_controls_visible.png and b/tests/snapshots/temporal_filtering_after_controls_visible.png differ diff --git a/tests/snapshots/temporal_initial_state.png b/tests/snapshots/temporal_initial_state.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_initial_state.png and b/tests/snapshots/temporal_initial_state.png differ diff --git a/tests/snapshots/temporal_morning_state.png b/tests/snapshots/temporal_morning_state.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_morning_state.png and b/tests/snapshots/temporal_morning_state.png differ diff --git a/tests/snapshots/temporal_timeline_expanded.png b/tests/snapshots/temporal_timeline_expanded.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_timeline_expanded.png and b/tests/snapshots/temporal_timeline_expanded.png differ diff --git a/tests/snapshots/temporal_with_geometries.png b/tests/snapshots/temporal_with_geometries.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_with_geometries.png and b/tests/snapshots/temporal_with_geometries.png differ diff --git a/tests/snapshots/temporal_workflow_step1_timeline_available.png b/tests/snapshots/temporal_workflow_step1_timeline_available.png index d1ad784..3790225 100644 Binary files a/tests/snapshots/temporal_workflow_step1_timeline_available.png and b/tests/snapshots/temporal_workflow_step1_timeline_available.png differ diff --git a/tests/snapshots/temporal_workflow_step2_controls_expanded.png b/tests/snapshots/temporal_workflow_step2_controls_expanded.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_workflow_step2_controls_expanded.png and b/tests/snapshots/temporal_workflow_step2_controls_expanded.png differ diff --git a/tests/snapshots/temporal_workflow_step3_temporal_data_loaded.png b/tests/snapshots/temporal_workflow_step3_temporal_data_loaded.png index 453bae6..7597861 100644 Binary files a/tests/snapshots/temporal_workflow_step3_temporal_data_loaded.png and b/tests/snapshots/temporal_workflow_step3_temporal_data_loaded.png differ diff --git a/tests/snapshots/unix_epoch_first_timestamp.png b/tests/snapshots/unix_epoch_first_timestamp.png index 1f4ebed..dbee712 100644 Binary files a/tests/snapshots/unix_epoch_first_timestamp.png and b/tests/snapshots/unix_epoch_first_timestamp.png differ diff --git a/tests/snapshots/unix_epoch_no_timezone_parsed.png b/tests/snapshots/unix_epoch_no_timezone_parsed.png index 1f4ebed..dbee712 100644 Binary files a/tests/snapshots/unix_epoch_no_timezone_parsed.png and b/tests/snapshots/unix_epoch_no_timezone_parsed.png differ diff --git a/tests/snapshots/unix_epoch_timeline_controls_visible.png b/tests/snapshots/unix_epoch_timeline_controls_visible.png index 1f4ebed..dbee712 100644 Binary files a/tests/snapshots/unix_epoch_timeline_controls_visible.png and b/tests/snapshots/unix_epoch_timeline_controls_visible.png differ diff --git a/tests/snapshots/unix_epoch_timeline_detected.png b/tests/snapshots/unix_epoch_timeline_detected.png index 1f4ebed..dbee712 100644 Binary files a/tests/snapshots/unix_epoch_timeline_detected.png and b/tests/snapshots/unix_epoch_timeline_detected.png differ diff --git a/tests/snapshots/workflow_final_grid_view.png b/tests/snapshots/workflow_final_grid_view.png index 33d609e..001512a 100644 Binary files a/tests/snapshots/workflow_final_grid_view.png and b/tests/snapshots/workflow_final_grid_view.png differ diff --git a/tests/snapshots/workflow_grid_mode_active.png b/tests/snapshots/workflow_grid_mode_active.png index d236b8d..dd30cc7 100644 Binary files a/tests/snapshots/workflow_grid_mode_active.png and b/tests/snapshots/workflow_grid_mode_active.png differ diff --git a/tests/snapshots/workflow_initial_with_data.png b/tests/snapshots/workflow_initial_with_data.png index 6d7ba8a..001512a 100644 Binary files a/tests/snapshots/workflow_initial_with_data.png and b/tests/snapshots/workflow_initial_with_data.png differ diff --git a/tests/snapshots/workflow_settings_opened.png b/tests/snapshots/workflow_settings_opened.png index 485bcef..897f0e9 100644 Binary files a/tests/snapshots/workflow_settings_opened.png and b/tests/snapshots/workflow_settings_opened.png differ diff --git a/tests/temporal_snapshot_tests.rs b/tests/temporal_snapshot_tests.rs index 7bdd637..b447469 100644 --- a/tests/temporal_snapshot_tests.rs +++ b/tests/temporal_snapshot_tests.rs @@ -13,7 +13,7 @@ use std::io::Cursor; fn create_test_app_with_temporal_kml() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); // Read and parse the temporal KML file @@ -38,7 +38,7 @@ fn create_test_app_with_temporal_kml() -> MapApp { fn create_test_app_with_unix_epoch_kml() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); // Read and parse the unix epoch KML file @@ -390,7 +390,7 @@ async fn unix_epoch_temporal_filtering() { fn create_test_app_with_debug_range_kml() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); // Read and parse the debug range KML file (2-minute span) @@ -415,7 +415,7 @@ fn create_test_app_with_debug_range_kml() -> MapApp { fn create_test_app_with_unix_epoch_no_tz_kml() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); // Read and parse the unix epoch KML file without timezone @@ -529,7 +529,7 @@ async fn large_file_temporal_parsing() { let config = Config::default(); let ctx = egui::Context::default(); - let (mut map, remote, data_holder) = Map::new(ctx); + let (mut map, remote, data_holder) = Map::new(ctx, config.clone()); map.set_headless(); let mut parser = KmlParser::new(); diff --git a/tests/ui_tests.rs b/tests/ui_tests.rs index c881461..9bf8be2 100644 --- a/tests/ui_tests.rs +++ b/tests/ui_tests.rs @@ -7,7 +7,7 @@ use mapvas::{config::Config, map::mapvas_egui::Map, mapvas_ui::MapApp}; fn create_test_app() -> MapApp { let config = Config::default(); let ctx = egui::Context::default(); - let (map, remote, data_holder) = Map::new(ctx); + let (map, remote, data_holder) = Map::new(ctx, config.clone()); MapApp::new(map, remote, data_holder, config, None) }