diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index 66d279386bc..f7a142c9770 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -9,8 +9,8 @@ use crate::{emath::TSTransform, LayerId, Rect, Response, Sense, Ui, UiBuilder, V /// Creates a transformation that fits a given scene rectangle into the available screen size. /// -/// The resulting visual scene bounds can be larger, ue to letterboxing. -fn fit_to_rect_in_scene(rect_in_ui: Rect, rect_in_scene: Rect) -> TSTransform { +/// The resulting visual scene bounds can be larger, due to letterboxing. +pub fn fit_to_rect_in_scene(rect_in_ui: Rect, rect_in_scene: Rect) -> TSTransform { let available_size_in_ui = rect_in_ui.size(); // Compute the scale factor to fit the bounding rectangle into the available screen size. @@ -55,7 +55,7 @@ impl Scene { /// Provides a zoom-pan area for a given view. /// /// Will fill the entire `max_rect` of the `parent_ui`. - fn show_scene( + pub fn show_scene( &self, parent_ui: &mut Ui, to_global: &mut TSTransform, @@ -111,7 +111,7 @@ impl Scene { } /// Helper function to handle pan and zoom interactions on a response. - fn register_pan_and_zoom(&self, ui: &Ui, resp: &Response, ui_from_scene: &mut TSTransform) { + pub fn register_pan_and_zoom(&self, ui: &Ui, resp: &Response, ui_from_scene: &mut TSTransform) { if resp.dragged() { ui_from_scene.translation += ui_from_scene.scaling * resp.drag_delta(); } 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 b74eb7db3f4..dd2f8f0ecfd 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -77,7 +77,7 @@ impl Default for DemoGroups { Box::::default(), 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 c00725fbd59..cb68a46fb0e 100644 --- a/crates/egui_demo_lib/src/demo/mod.rs +++ b/crates/egui_demo_lib/src/demo/mod.rs @@ -21,9 +21,9 @@ pub mod modals; pub mod multi_touch; pub mod paint_bezier; pub mod painting; -pub mod pan_zoom; pub mod panels; pub mod password; +pub mod scene; pub mod screenshot; pub mod scrolling; pub mod sliders; diff --git a/crates/egui_demo_lib/src/demo/pan_zoom.rs b/crates/egui_demo_lib/src/demo/pan_zoom.rs deleted file mode 100644 index e51b5b9d788..00000000000 --- a/crates/egui_demo_lib/src/demo/pan_zoom.rs +++ /dev/null @@ -1,145 +0,0 @@ -use egui::emath::TSTransform; -use egui::TextWrapMode; - -#[derive(Clone, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct PanZoom { - transform: TSTransform, - drag_value: f32, -} - -impl Eq for PanZoom {} - -impl crate::Demo for PanZoom { - fn name(&self) -> &'static str { - "🔍 Pan Zoom" - } - - fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - use crate::View as _; - let window = egui::Window::new("Pan Zoom") - .default_width(300.0) - .default_height(300.0) - .vscroll(false) - .open(open); - window.show(ctx, |ui| self.ui(ui)); - } -} - -impl crate::View for PanZoom { - fn ui(&mut self, ui: &mut egui::Ui) { - ui.label( - "Pan, zoom in, and zoom out with scrolling (see the plot demo for more instructions). \ - Double click on the background to reset.", - ); - ui.vertical_centered(|ui| { - ui.add(crate::egui_github_link_file!()); - }); - ui.separator(); - - let (id, rect) = ui.allocate_space(ui.available_size()); - let response = ui.interact(rect, id, egui::Sense::click_and_drag()); - // Allow dragging the background as well. - if response.dragged() { - self.transform.translation += response.drag_delta(); - } - - // Plot-like reset - if response.double_clicked() { - self.transform = TSTransform::default(); - } - - let transform = - TSTransform::from_translation(ui.min_rect().left_top().to_vec2()) * self.transform; - - if let Some(pointer) = ui.ctx().input(|i| i.pointer.hover_pos()) { - // Note: doesn't catch zooming / panning if a button in this PanZoom container is hovered. - if response.hovered() { - let pointer_in_layer = transform.inverse() * pointer; - let zoom_delta = ui.ctx().input(|i| i.zoom_delta()); - let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta); - - // Zoom in on pointer: - self.transform = self.transform - * TSTransform::from_translation(pointer_in_layer.to_vec2()) - * TSTransform::from_scaling(zoom_delta) - * TSTransform::from_translation(-pointer_in_layer.to_vec2()); - - // Pan: - self.transform = TSTransform::from_translation(pan_delta) * self.transform; - } - } - - for (i, (pos, callback)) in [ - ( - egui::Pos2::new(0.0, 0.0), - Box::new(|ui: &mut egui::Ui, _: &mut Self| { - ui.button("top left").on_hover_text("Normal tooltip") - }) as Box egui::Response>, - ), - ( - egui::Pos2::new(0.0, 120.0), - Box::new(|ui: &mut egui::Ui, _| { - ui.button("bottom left").on_hover_text("Normal tooltip") - }), - ), - ( - egui::Pos2::new(120.0, 120.0), - Box::new(|ui: &mut egui::Ui, _| { - ui.button("right bottom") - .on_hover_text_at_pointer("Tooltip at pointer") - }), - ), - ( - egui::Pos2::new(120.0, 0.0), - Box::new(|ui: &mut egui::Ui, _| { - ui.button("right top") - .on_hover_text_at_pointer("Tooltip at pointer") - }), - ), - ( - egui::Pos2::new(60.0, 60.0), - Box::new(|ui, state| { - use egui::epaint::{pos2, CircleShape, Color32, QuadraticBezierShape, Stroke}; - // Smiley face. - let painter = ui.painter(); - painter.add(CircleShape::filled(pos2(0.0, -10.0), 1.0, Color32::YELLOW)); - painter.add(CircleShape::filled(pos2(10.0, -10.0), 1.0, Color32::YELLOW)); - painter.add(QuadraticBezierShape::from_points_stroke( - [pos2(0.0, 0.0), pos2(5.0, 3.0), pos2(10.0, 0.0)], - false, - Color32::TRANSPARENT, - Stroke::new(1.0, Color32::YELLOW), - )); - - ui.add(egui::Slider::new(&mut state.drag_value, 0.0..=100.0).text("My value")) - }), - ), - ] - .into_iter() - .enumerate() - { - let window_layer = ui.layer_id(); - let id = egui::Area::new(id.with(("subarea", i))) - .default_pos(pos) - .order(egui::Order::Middle) - .constrain(false) - .show(ui.ctx(), |ui| { - ui.set_clip_rect(transform.inverse() * rect); - egui::Frame::default() - .rounding(egui::Rounding::same(4)) - .inner_margin(egui::Margin::same(8)) - .stroke(ui.ctx().style().visuals.window_stroke) - .fill(ui.style().visuals.panel_fill) - .show(ui, |ui| { - ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); - callback(ui, self) - }); - }) - .response - .layer_id; - ui.ctx().set_transform_layer(id, transform); - ui.ctx().set_sublayer(window_layer, id); - } - } -} diff --git a/crates/egui_demo_lib/src/demo/scene.rs b/crates/egui_demo_lib/src/demo/scene.rs new file mode 100644 index 00000000000..dd79135cccb --- /dev/null +++ b/crates/egui_demo_lib/src/demo/scene.rs @@ -0,0 +1,127 @@ +use egui::emath::TSTransform; +use egui::scene::{fit_to_rect_in_scene, Scene}; +use egui::{Pos2, Rect, Sense, TextWrapMode, UiBuilder, Vec2}; + +#[derive(Clone, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct SceneDemo { + transform: Option, + drag_value: f32, + scene_rect: Option, +} + +impl Eq for SceneDemo {} + +impl crate::Demo for SceneDemo { + fn name(&self) -> &'static str { + "🔍 Scene" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + use crate::View as _; + let window = egui::Window::new("Pan Zoom") + .default_width(300.0) + .default_height(300.0) + .vscroll(false) + .open(open); + window.show(ctx, |ui| self.ui(ui)); + } +} + +impl crate::View for SceneDemo { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.label( + "Pan, zoom in, and zoom out with scrolling (see the plot demo for more instructions). \ + Double click on the background to reset.", + ); + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file!()); + }); + ui.separator(); + + let (_, rect) = ui.allocate_space(ui.available_size()); + + // TODO: Actually write that back + let mut state = self.clone(); + + let to_global = self.transform.get_or_insert_with(|| { + fit_to_rect_in_scene( + rect, + Rect::from_min_max(Pos2::new(0., 0.), Pos2::new(120., 120.)), + ) + }); + + let scene = Scene::new(); + scene.show_scene(ui, to_global, |ui| { + for (i, (pos, callback)) in [ + ( + egui::Pos2::new(0.0, 0.0), + Box::new(|ui: &mut egui::Ui, _: &mut Self| { + ui.button("top left").on_hover_text("Normal tooltip") + }) + as Box egui::Response>, + ), + ( + egui::Pos2::new(0.0, 120.0), + Box::new(|ui: &mut egui::Ui, _| { + ui.button("bottom left").on_hover_text("Normal tooltip") + }), + ), + ( + egui::Pos2::new(120.0, 120.0), + Box::new(|ui: &mut egui::Ui, _| { + ui.button("right bottom") + .on_hover_text_at_pointer("Tooltip at pointer") + }), + ), + ( + egui::Pos2::new(120.0, 0.0), + Box::new(|ui: &mut egui::Ui, _| { + ui.button("right top") + .on_hover_text_at_pointer("Tooltip at pointer") + }), + ), + ( + egui::Pos2::new(60.0, 60.0), + Box::new(|ui, state| { + use egui::epaint::{ + pos2, CircleShape, Color32, QuadraticBezierShape, Stroke, + }; + // Smiley face. + let painter = ui.painter(); + painter.add(CircleShape::filled(pos2(0.0, -10.0), 1.0, Color32::YELLOW)); + painter.add(CircleShape::filled(pos2(10.0, -10.0), 1.0, Color32::YELLOW)); + painter.add(QuadraticBezierShape::from_points_stroke( + [pos2(0.0, 0.0), pos2(5.0, 3.0), pos2(10.0, 0.0)], + false, + Color32::TRANSPARENT, + Stroke::new(1.0, Color32::YELLOW), + )); + + ui.add( + egui::Slider::new(&mut state.drag_value, 0.0..=100.0).text("My value"), + ) + }), + ), + ] + .into_iter() + .enumerate() + { + let builder = UiBuilder::new() + .max_rect(Rect::from_center_size(pos, Vec2::new(200., 200.))) + .sense(Sense::click()); + + let mut content_ui = ui.new_child(builder); + let content_resp = callback(&mut content_ui, &mut state); + state.scene_rect = Some( + state + .scene_rect + .get_or_insert(Rect::NOTHING) + .union(content_resp.rect), + ); + } + }); + + // scene.register_pan_and_zoom(ui, &resp, &mut self.transform); + } +}