Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add layer transforms, interaction in layer #3906

Merged
merged 39 commits into from
Feb 17, 2024
Merged
Changes from 8 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
24143f4
add scaling layers, interaction with scaled items
Jan 27, 2024
0f6f82c
fix style
Tweoss Jan 27, 2024
141433d
add pan_zoom demo
Tweoss Jan 29, 2024
c001c5a
add pan_zoom demo
Tweoss Jan 29, 2024
085c593
fix warnings
Tweoss Jan 29, 2024
af79f81
Merge branch 'master' into transform-layer
Tweoss Jan 29, 2024
9fc5b71
Merge branch 'master' into transform-layer
Tweoss Jan 29, 2024
264726b
clean up
Tweoss Jan 29, 2024
17bb75e
port TSTransform to emath, make transform persist
Tweoss Feb 1, 2024
b2342dc
use smooth scrolling in demo
Tweoss Feb 1, 2024
ac2ae96
update docstring in layers.rs
Tweoss Feb 1, 2024
998a6e6
fix cranky, style
Tweoss Feb 1, 2024
b0d73d0
Update crates/egui/src/context.rs doc comment
Tweoss Feb 1, 2024
18347d3
add TSTransform doctests, switch transform order
Tweoss Feb 1, 2024
e593bbf
minor refactoring
Tweoss Feb 1, 2024
227bd6a
add invert method for TSTransform
Tweoss Feb 1, 2024
5902ffd
add back translate for mesh.
Tweoss Feb 1, 2024
635fc2f
Merge branch 'master' into transform-layer
Tweoss Feb 1, 2024
9090324
Update docstring crates/emath/src/ts_transform.rs
Tweoss Feb 10, 2024
6178cd8
simplify pan_zoom logic, multiply TSTransforms
Tweoss Feb 10, 2024
f1df40a
fix TSTransform doc comments
Tweoss Feb 10, 2024
05ba7c8
optimize arcs in transforming galley
Tweoss Feb 10, 2024
be325f0
scale thickness of strokes when transforming
Tweoss Feb 10, 2024
0b892d7
clarify paintcallback scaling comment
Tweoss Feb 10, 2024
2526e5b
fix cranky
Tweoss Feb 10, 2024
5b33246
Merge branch 'master' into transform-layer
Tweoss Feb 10, 2024
f70293a
Merge branch 'master' into transform-layer
Tweoss Feb 15, 2024
663a1b3
fix drag ratio
Tweoss Feb 16, 2024
09c4a50
apply transform to mouse input positions
Tweoss Feb 16, 2024
edafee5
add drag_value example
Tweoss Feb 16, 2024
7fd7e7c
fix formatting
Tweoss Feb 16, 2024
499492e
fix area: use response (not ctx) pointer delta
Tweoss Feb 16, 2024
3c27b3b
refactor hover_pos transform in response.rs
Tweoss Feb 16, 2024
bd46536
add label and link to pan_zoom demo
Tweoss Feb 16, 2024
b0afe90
add tracking inner contents to outer window
Tweoss Feb 16, 2024
c6ca843
fix text inner bounds
Tweoss Feb 17, 2024
f6851ab
add painter example
Tweoss Feb 17, 2024
f4ed104
fix pointer interaction for transforms in SidePanel
Tweoss Feb 17, 2024
b109093
fix topbottom panel pointer position
Tweoss Feb 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ use crate::{
input_state::*,
layers::GraphicLayers,
load::{Bytes, Loaders, SizedTexture},
memory::Options,
memory::{LayerTransform, Options},
os::OperatingSystem,
output::FullOutput,
util::IdTypeMap,
@@ -2071,9 +2071,14 @@ impl Context {
/// Move all the graphics at the given layer.
///
/// Can be used to implement drag-and-drop (see relevant demo).
pub fn translate_layer(&self, layer_id: LayerId, delta: Vec2) {
pub fn transform_layer(&self, layer_id: LayerId, delta: Vec2, scale: f32) {
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
if delta != Vec2::ZERO {
self.graphics_mut(|g| g.entry(layer_id).translate(delta));
let transform = LayerTransform {
translation: delta,
scale,
};
self.graphics_mut(|g| g.entry(layer_id).transform(&transform));
self.memory_mut(|m| m.layer_transforms_mut().insert(layer_id, transform));
}
}

@@ -2102,6 +2107,10 @@ impl Context {
///
/// See also [`Response::contains_pointer`].
pub fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
let transform = self
.memory(|m| m.layer_transforms().get(&layer_id).cloned())
.unwrap_or_default();
let rect = transform.apply(rect);
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
if !rect.is_positive() {
return false;
}
@@ -2142,6 +2151,12 @@ impl Context {
let mut blocking_widget = None;

self.write(|ctx| {
let transform = ctx
.memory
.layer_transforms()
.get(&layer_id)
.cloned()
.unwrap_or_default();
let viewport = ctx.viewport();

// We add all widgets here, even non-interactive ones,
@@ -2165,7 +2180,7 @@ impl Context {
// which means there are no widgets covering us.
break;
}
if !blocking.rect.contains(pointer_pos) {
if !transform.apply(blocking.rect).contains(pointer_pos) {
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
continue;
}
if sense.interactive() && !blocking.sense.interactive() {
8 changes: 4 additions & 4 deletions crates/egui/src/layers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Handles paint layers, i.e. how things
//! are sometimes painted behind or in front of other things.

use crate::{Id, *};
use crate::{memory::LayerTransform, Id, *};
use epaint::{ClippedShape, Shape};

/// Different layer categories
@@ -159,10 +159,10 @@ impl PaintList {
}

/// Translate each [`Shape`] and clip rectangle by this much, in-place
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
pub fn translate(&mut self, delta: Vec2) {
pub fn transform(&mut self, transform: &LayerTransform) {
for ClippedShape { clip_rect, shape } in &mut self.0 {
*clip_rect = clip_rect.translate(delta);
shape.translate(delta);
*clip_rect = transform.apply(*clip_rect);
shape.transform(transform.translation, transform.scale);
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
}
}

54 changes: 52 additions & 2 deletions crates/egui/src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![warn(missing_docs)] // Let's keep this file well-documented.` to memory.rs

use ahash::HashMap;

use crate::{
area, vec2,
window::{self, WindowInteraction},
@@ -88,6 +90,7 @@ pub struct Memory {
// -------------------------------------------------
// Per-viewport:
areas: ViewportIdMap<Areas>,
layer_transforms: HashMap<LayerId, LayerTransform>,

#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) interactions: ViewportIdMap<Interaction>,
@@ -107,6 +110,7 @@ impl Default for Memory {
viewport_id: Default::default(),
window_interactions: Default::default(),
areas: Default::default(),
layer_transforms: Default::default(),
popup: Default::default(),
everything_is_visible: Default::default(),
};
@@ -151,6 +155,32 @@ impl FocusDirection {
}
}

#[derive(Clone, Debug)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct LayerTransform {
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
pub translation: Vec2,
pub scale: f32,
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
}

impl Default for LayerTransform {
fn default() -> Self {
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
Self {
translation: Vec2::default(),
scale: 1.0,
}
}
}

impl LayerTransform {
pub(crate) fn apply(&self, rect: Rect) -> Rect {
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
Rect::from_center_size(
(rect.center() + self.translation) * self.scale,
rect.size() * self.scale,
)
}
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
}

// ----------------------------------------------------------------------------

/// Some global options that you can read and write.
@@ -605,9 +635,20 @@ impl Memory {
self.areas.entry(self.viewport_id).or_default()
}

/// Access layer transformations.
pub fn layer_transforms(&self) -> &HashMap<LayerId, LayerTransform> {
&self.layer_transforms
}

/// Access layer transformations.
pub fn layer_transforms_mut(&mut self) -> &mut HashMap<LayerId, LayerTransform> {
&mut self.layer_transforms
}

/// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<LayerId> {
self.areas().layer_id_at(pos, resize_interact_radius_side)
self.areas()
.layer_id_at(pos, resize_interact_radius_side, &self.layer_transforms)
}

/// An iterator over all layers. Back-to-front. Top is last.
@@ -883,7 +924,12 @@ impl Areas {
}

/// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<LayerId> {
pub fn layer_id_at(
&self,
pos: Pos2,
resize_interact_radius_side: f32,
layer_transforms: &HashMap<LayerId, LayerTransform>,
) -> Option<LayerId> {
for layer in self.order.iter().rev() {
if self.is_visible(layer) {
if let Some(state) = self.areas.get(&layer.id) {
@@ -894,6 +940,10 @@ impl Areas {
rect = rect.expand(resize_interact_radius_side);
}

if let Some(transform) = layer_transforms.get(layer) {
rect = transform.apply(rect);
}

if rect.contains(pos) {
return Some(*layer);
}
2 changes: 1 addition & 1 deletion crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
@@ -2153,7 +2153,7 @@ impl Ui {

if let Some(pointer_pos) = self.ctx().pointer_interact_pos() {
let delta = pointer_pos - response.rect.center();
self.ctx().translate_layer(layer_id, delta);
self.ctx().transform_layer(layer_id, delta, 1.0);
}

InnerResponse::new(inner, response)
1 change: 1 addition & 0 deletions crates/egui_demo_lib/src/demo/demo_app_windows.rs
Original file line number Diff line number Diff line change
@@ -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(),
1 change: 1 addition & 0 deletions crates/egui_demo_lib/src/demo/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
106 changes: 106 additions & 0 deletions crates/egui_demo_lib/src/demo/pan_zoom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct PanZoom {
pan: egui::Vec2,
zoom: f32,
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
}

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) {
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
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;
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
}
}

let current_size = ui.min_rect();
[
(
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)| {
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
egui::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);
});
}
}
3 changes: 2 additions & 1 deletion crates/epaint/src/mesh.rs
Original file line number Diff line number Diff line change
@@ -269,9 +269,10 @@ impl Mesh {
}

/// Translate location by this much, in-place
pub fn translate(&mut self, delta: Vec2) {
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
pub fn transform(&mut self, delta: Vec2, scale: f32) {
for v in &mut self.vertices {
v.pos += delta;
v.pos = (v.pos.to_vec2() * scale).to_pos2();
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
}
}

Loading