diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 5f4cd001b05..de1d96cddf4 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -32,6 +32,7 @@ impl Default for Demos { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), diff --git a/crates/egui_demo_lib/src/demo/mod.rs b/crates/egui_demo_lib/src/demo/mod.rs index f041b00db59..d54498bbcf9 100644 --- a/crates/egui_demo_lib/src/demo/mod.rs +++ b/crates/egui_demo_lib/src/demo/mod.rs @@ -19,6 +19,7 @@ pub mod misc_demo_window; pub mod multi_touch; pub mod paint_bezier; pub mod painting; +pub mod pan_zoom; pub mod panels; pub mod password; pub mod plot_demo; diff --git a/crates/egui_demo_lib/src/demo/pan_zoom.rs b/crates/egui_demo_lib/src/demo/pan_zoom.rs new file mode 100644 index 00000000000..5918f6f1987 --- /dev/null +++ b/crates/egui_demo_lib/src/demo/pan_zoom.rs @@ -0,0 +1,108 @@ +use egui::{Area, Sense}; + +#[derive(Clone, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct PanZoom { + pan: egui::Vec2, + zoom: f32, +} + +impl Eq for PanZoom {} + +impl super::Demo for PanZoom { + fn name(&self) -> &'static str { + "🗖 Pan Zoom" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + use super::View as _; + let window = egui::Window::new("Pan Zoom") + .default_width(200.0) + .default_height(200.0) + .vscroll(false) + .open(open); + window.show(ctx, |ui| self.ui(ui)); + } +} + +impl super::View for PanZoom { + fn ui(&mut self, ui: &mut egui::Ui) { + // On initialization, zoom is 0 + if self.zoom == 0.0 { + self.zoom = 1.0; + } + + let (id, rect) = ui.allocate_space(ui.available_size()); + let response = ui.interact(rect, id, egui::Sense::click_and_drag()); + // Uncomment to allow dragging the background as well. + // self.pan += response.drag_delta() / self.zoom; + + // Plot-like reset + if response.double_clicked() { + self.zoom = 1.0; + self.pan = egui::Vec2::ZERO; + } + + if let Some(pointer) = ui.ctx().input(|i| i.pointer.hover_pos()) { + // Ignore if some other widget is covering this container. + if response.rect.contains(pointer) { + let original_zoom = self.zoom; + self.zoom *= ui.ctx().input(|i| i.zoom_delta()); + let delta = pointer / self.zoom - pointer / original_zoom; + self.pan += delta; + + // Keep mouse centered. + self.pan += ui.ctx().input(|i| i.raw_scroll_delta) / self.zoom; + } + } + + let current_size = ui.min_rect(); + let layer_ids = [ + ( + current_size.left_top() + egui::Vec2::new(10.0, 10.0), + "top left!", + ), + ( + current_size.left_bottom() + egui::Vec2::new(10.0, -10.0), + "bottom left?", + ), + ( + current_size.right_bottom() + egui::Vec2::new(-10.0, -10.0), + "right bottom :D", + ), + ( + current_size.right_top() + egui::Vec2::new(-10.0, 10.0), + "right top ):", + ), + ] + .iter() + .map(|(pos, msg)| { + Area::new(*msg) + .default_pos(*pos) + // Need to cover up the pan_zoom demo window, + // but may also cover over other windows. + .order(egui::Order::Foreground) + .show(ui.ctx(), |ui| { + let rect = egui::Rect::from_min_max( + (rect.min / self.zoom) - self.pan, + (rect.max / self.zoom) - self.pan, + ); + ui.set_clip_rect(rect); + egui::Frame::default() + .rounding(egui::Rounding::same(4.0)) + .inner_margin(egui::Margin::same(8.0)) + .stroke(ui.ctx().style().visuals.window_stroke) + .fill(ui.style().visuals.panel_fill) + .show(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.button(*msg).clicked(); + }); + }) + .response + .layer_id + }) + .for_each(|id| { + ui.ctx().transform_layer(id, self.pan, self.zoom); + }); + } +}