mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 06:10:06 -04:00
Add egui::Scene for panning/zooming a Ui (#5505)
This is similar to `ScrollArea`, but: * Supports zooming * Has no scroll bars * Has no limits on the scrolling ## TODO * [x] Automatic sizing of `Scene`s outer bounds * [x] Fix text selection in scenes * [x] Implement `fit_rect` * [x] Document / improve API --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -77,8 +77,8 @@ impl Default for DemoGroups {
|
||||
Box::<super::modals::Modals>::default(),
|
||||
Box::<super::multi_touch::MultiTouch>::default(),
|
||||
Box::<super::painting::Painting>::default(),
|
||||
Box::<super::pan_zoom::PanZoom>::default(),
|
||||
Box::<super::panels::Panels>::default(),
|
||||
Box::<super::scene::SceneDemo>::default(),
|
||||
Box::<super::screenshot::Screenshot>::default(),
|
||||
Box::<super::scrolling::Scrolling>::default(),
|
||||
Box::<super::sliders::Sliders>::default(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<dyn Fn(&mut egui::Ui, &mut Self) -> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
crates/egui_demo_lib/src/demo/scene.rs
Normal file
82
crates/egui_demo_lib/src/demo/scene.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use egui::{Pos2, Rect, Scene, Vec2};
|
||||
|
||||
use super::widget_gallery;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct SceneDemo {
|
||||
widget_gallery: widget_gallery::WidgetGallery,
|
||||
scene_rect: Rect,
|
||||
}
|
||||
|
||||
impl Default for SceneDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
widget_gallery: Default::default(),
|
||||
scene_rect: Rect::ZERO, // `egui::Scene` will initialize this to something valid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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("Scene")
|
||||
.default_width(300.0)
|
||||
.default_height(300.0)
|
||||
.scroll(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(
|
||||
"You can pan by scrolling, and zoom using cmd-scroll. \
|
||||
Double click on the background to reset view.",
|
||||
);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
ui.label(format!("Scene rect: {:#?}", &mut self.scene_rect));
|
||||
|
||||
ui.separator();
|
||||
|
||||
egui::Frame::group(ui.style())
|
||||
.inner_margin(0.0)
|
||||
.show(ui, |ui| {
|
||||
let scene = Scene::new()
|
||||
.max_inner_size([350.0, 1000.0])
|
||||
.zoom_range(0.1..=2.0);
|
||||
|
||||
let mut reset_view = false;
|
||||
let mut inner_rect = Rect::NAN;
|
||||
let response = scene
|
||||
.show(ui, &mut self.scene_rect, |ui| {
|
||||
reset_view = ui.button("Reset view").clicked();
|
||||
|
||||
ui.add_space(16.0);
|
||||
|
||||
self.widget_gallery.ui(ui);
|
||||
|
||||
ui.put(
|
||||
Rect::from_min_size(Pos2::new(0.0, -64.0), Vec2::new(200.0, 16.0)),
|
||||
egui::Label::new("You can put a widget anywhere").selectable(false),
|
||||
);
|
||||
|
||||
inner_rect = ui.min_rect();
|
||||
})
|
||||
.response;
|
||||
|
||||
if reset_view || response.double_clicked() {
|
||||
self.scene_rect = inner_rect;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user