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
Show file tree
Hide file tree
Changes from 18 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
40 changes: 32 additions & 8 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
use std::{borrow::Cow, cell::RefCell, sync::Arc, time::Duration};

use ahash::HashMap;
use epaint::{mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *};
use epaint::{
emath::TSTransform, mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *,
};

use crate::{
animation_manager::AnimationManager,
Expand Down Expand Up @@ -1810,7 +1812,9 @@ impl ContextImpl {
}
}

let shapes = viewport.graphics.drain(self.memory.areas().order());
let shapes = viewport
.graphics
.drain(self.memory.areas().order(), &self.memory.layer_transforms);

let mut repaint_needed = false;

Expand Down Expand Up @@ -2117,13 +2121,19 @@ impl Context {
}

impl Context {
/// Move all the graphics at the given layer.
/// Transform the graphics of the given layer.
///
/// Can be used to implement drag-and-drop (see relevant demo).
pub fn translate_layer(&self, layer_id: LayerId, delta: Vec2) {
if delta != Vec2::ZERO {
self.graphics_mut(|g| g.entry(layer_id).translate(delta));
}
/// This is a sticky setting, remembered from one frame to the next.
///
/// Can be used to implement pan and zoom (see relevant demo).
pub fn set_transform_layer(&self, layer_id: LayerId, transform: TSTransform) {
self.memory_mut(|m| {
if transform == TSTransform::IDENTITY {
m.layer_transforms.remove(&layer_id)
} else {
m.layer_transforms.insert(layer_id, transform)
}
});
}

/// Top-most layer at the given position.
Expand Down Expand Up @@ -2153,6 +2163,12 @@ impl Context {
///
/// See also [`Response::contains_pointer`].
pub fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
let rect =
if let Some(transform) = self.memory(|m| m.layer_transforms.get(&layer_id).cloned()) {
transform * rect
} else {
rect
};
if !rect.is_positive() {
return false;
}
Expand Down Expand Up @@ -2199,6 +2215,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,
Expand All @@ -2213,6 +2235,8 @@ impl Context {
if contains_pointer {
let pointer_pos = viewport.input.pointer.interact_pos();
if let Some(pointer_pos) = pointer_pos {
// Apply the inverse transformation of this layer to the pointer pos.
let pointer_pos = transform.inverse() * pointer_pos;
if let Some(rects) = viewport.layer_rects_prev_frame.by_layer.get(&layer_id) {
for blocking in rects.iter().rev() {
if blocking.id == id {
Expand Down
22 changes: 16 additions & 6 deletions crates/egui/src/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! are sometimes painted behind or in front of other things.

use crate::{Id, *};
use epaint::{ClippedShape, Shape};
use epaint::{emath::TSTransform, ClippedShape, Shape};

/// Different layer categories
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
Expand Down Expand Up @@ -158,11 +158,11 @@ impl PaintList {
self.0[idx.0].shape = Shape::Noop;
}

/// Translate each [`Shape`] and clip rectangle by this much, in-place
pub fn translate(&mut self, delta: Vec2) {
/// Transform each [`Shape`] and clip rectangle by this much, in-place
pub fn transform(&mut self, transform: TSTransform) {
for ClippedShape { clip_rect, shape } in &mut self.0 {
*clip_rect = clip_rect.translate(delta);
shape.translate(delta);
*clip_rect = transform.mul_rect(*clip_rect);
shape.transform(transform);
}
}

Expand Down Expand Up @@ -194,7 +194,11 @@ impl GraphicLayers {
self.0[layer_id.order as usize].get_mut(&layer_id.id)
}

pub fn drain(&mut self, area_order: &[LayerId]) -> Vec<ClippedShape> {
pub fn drain(
&mut self,
area_order: &[LayerId],
transforms: &ahash::HashMap<LayerId, TSTransform>,
) -> Vec<ClippedShape> {
crate::profile_function!();

let mut all_shapes: Vec<_> = Default::default();
Expand All @@ -211,6 +215,12 @@ impl GraphicLayers {
for layer_id in area_order {
if layer_id.order == order {
if let Some(list) = order_map.get_mut(&layer_id.id) {
if let Some(transform) = transforms.get(layer_id) {
for clipped_shape in &mut list.0 {
clipped_shape.clip_rect = *transform * clipped_shape.clip_rect;
clipped_shape.shape.transform(*transform);
}
}
all_shapes.append(&mut list.0);
}
}
Expand Down
21 changes: 19 additions & 2 deletions crates/egui/src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![warn(missing_docs)] // Let's keep this file well-documented.` to memory.rs

use ahash::HashMap;
use epaint::emath::TSTransform;

use crate::{
area, vec2,
window::{self, WindowInteraction},
Expand Down Expand Up @@ -85,6 +88,9 @@ pub struct Memory {
#[cfg_attr(feature = "persistence", serde(skip))]
everything_is_visible: bool,

/// Transforms per layer
pub layer_transforms: HashMap<LayerId, TSTransform>,

// -------------------------------------------------
// Per-viewport:
areas: ViewportIdMap<Areas>,
Expand All @@ -107,6 +113,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(),
};
Expand Down Expand Up @@ -663,7 +670,8 @@ impl Memory {

/// 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.
Expand Down Expand Up @@ -939,7 +947,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, TSTransform>,
) -> Option<LayerId> {
for layer in self.order.iter().rev() {
if self.is_visible(layer) {
if let Some(state) = self.areas.get(&layer.id) {
Expand All @@ -950,6 +963,10 @@ impl Areas {
rect = rect.expand(resize_interact_radius_side);
}

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

if rect.contains(pos) {
return Some(*layer);
}
Expand Down
3 changes: 2 additions & 1 deletion crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2154,7 +2154,8 @@ 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()
.set_transform_layer(layer_id, emath::TSTransform::from_translation(delta));
}

InnerResponse::new(inner, response)
Expand Down
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
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions crates/egui_demo_lib/src/demo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
102 changes: 102 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,102 @@
use egui::emath::TSTransform;

#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct PanZoom {
transform: TSTransform,
}

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) {
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.transform.translation += response.drag_delta() / self.transform.scaling;

// Plot-like reset
if response.double_clicked() {
self.transform = TSTransform::default();
}

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 original_zoom = self.transform.scaling;
let new_zoom = original_zoom * ui.ctx().input(|i| i.zoom_delta());

let layer_pos = self.transform.inverse() * pointer;
let new_transform = TSTransform::new(self.transform.translation, new_zoom);
let new_pos = new_transform * layer_pos;

// Keep mouse centered.
let pan_delta = pointer - new_pos;

// Handle scrolling.
let pan_delta =
pan_delta + ui.ctx().input(|i| i.smooth_scroll_delta) / original_zoom;

self.transform.scaling = new_zoom;
self.transform.translation += pan_delta;
Tweoss marked this conversation as resolved.
Show resolved Hide resolved
}
}

let current_size = ui.min_rect();
for (pos, msg) in [
(
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 ):",
),
] {
let id = 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| {
ui.set_clip_rect(self.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);
ui.button(msg).clicked();
});
})
.response
.layer_id;
ui.ctx().set_transform_layer(id, self.transform);
}
}
}
2 changes: 2 additions & 0 deletions crates/emath/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ mod rect;
mod rect_transform;
mod rot2;
pub mod smart_aim;
mod ts_transform;
mod vec2;
mod vec2b;

Expand All @@ -48,6 +49,7 @@ pub use {
rect::*,
rect_transform::*,
rot2::*,
ts_transform::*,
vec2::*,
vec2b::*,
};
Expand Down
Loading
Loading