mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Add layer transforms, interaction in layer (#3906)
⚠️ Removes `Context::translate_layer`, replacing it with a sticky `set_transform_layer` Adds the capability to scale layers. Allows interaction with scaled and transformed widgets inside transformed layers. I've also added a demo of how to have zooming and panning in a window (see the video below). This probably closes #1811. Having a panning and zooming container would just be creating a new `Area` with a new id, and applying zooming and panning with `ctx.transform_layer`. I've run the github workflow scripts in my repository, so hopefully the formatting and `cargo cranky` is satisfied. I'm not sure if all call sites where transforms would be relevant have been handled. This might also be missing are transforming clipping rects, but I'm not sure where / how to accomplish that. In the demo, the clipping rect is transformed to match, which seems to work. https://github.com/emilk/egui/assets/70821802/77e7e743-cdfe-402f-86e3-7744b3ee7b0f --------- Co-authored-by: tweoss <fchua@puffer5.stanford.edu> Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -32,6 +32,7 @@ impl Default for Demos {
|
||||
Box::<super::MiscDemoWindow>::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::plot_demo::PlotDemo>::default(),
|
||||
Box::<super::scrolling::Scrolling>::default(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
136
crates/egui_demo_lib/src/demo/pan_zoom.rs
Normal file
136
crates/egui_demo_lib/src/demo/pan_zoom.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use egui::emath::TSTransform;
|
||||
|
||||
#[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 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(300.0)
|
||||
.default_height(300.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) {
|
||||
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 (id, pos, callback) in [
|
||||
(
|
||||
"a",
|
||||
egui::Pos2::new(0.0, 0.0),
|
||||
Box::new(|ui: &mut egui::Ui, _: &mut Self| ui.button("top left!"))
|
||||
as Box<dyn Fn(&mut egui::Ui, &mut Self) -> egui::Response>,
|
||||
),
|
||||
(
|
||||
"b",
|
||||
egui::Pos2::new(0.0, 120.0),
|
||||
Box::new(|ui: &mut egui::Ui, _| ui.button("bottom left?")),
|
||||
),
|
||||
(
|
||||
"c",
|
||||
egui::Pos2::new(120.0, 120.0),
|
||||
Box::new(|ui: &mut egui::Ui, _| ui.button("right bottom :D")),
|
||||
),
|
||||
(
|
||||
"d",
|
||||
egui::Pos2::new(120.0, 0.0),
|
||||
Box::new(|ui: &mut egui::Ui, _| ui.button("right top ):")),
|
||||
),
|
||||
(
|
||||
"e",
|
||||
egui::Pos2::new(60.0, 60.0),
|
||||
Box::new(|ui, state| {
|
||||
use egui::epaint::*;
|
||||
// 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"))
|
||||
}),
|
||||
),
|
||||
] {
|
||||
let id = egui::Area::new(id)
|
||||
.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| {
|
||||
ui.set_clip_rect(transform.inverse() * 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);
|
||||
callback(ui, self)
|
||||
});
|
||||
})
|
||||
.response
|
||||
.layer_id;
|
||||
ui.ctx().set_transform_layer(id, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user