Skip to content

Commit

Permalink
Add Ui::with_visual_transform (#5055)
Browse files Browse the repository at this point in the history
* [X] I have followed the instructions in the PR template

This allows you to transform widgets without having to put them on a new
layer.
Example usage: 


https://github.com/user-attachments/assets/6b547782-f15e-42ce-835f-e8febe8d2d65

```rust
use eframe::egui;
use eframe::egui::{Button, Frame, InnerResponse, Label, Pos2, RichText, UiBuilder, Widget};
use eframe::emath::TSTransform;
use eframe::NativeOptions;
use egui::{CentralPanel, Sense, WidgetInfo};

pub fn main() -> eframe::Result {
    eframe::run_simple_native("focus test", NativeOptions::default(), |ctx, _frame| {
        CentralPanel::default().show(ctx, |ui| {
            let response = ui.ctx().read_response(ui.next_auto_id());

            let pressed = response
                .as_ref()
                .is_some_and(|r| r.is_pointer_button_down_on());

            let hovered = response.as_ref().is_some_and(|r| r.hovered());

            let target_scale = match (pressed, hovered) {
                (true, _) => 0.94,
                (_, true) => 1.06,
                _ => 1.0,
            };

            let scale = ui
                .ctx()
                .animate_value_with_time(ui.id().with("Down"), target_scale, 0.1);

            let mut center = response
                .as_ref()
                .map(|r| r.rect.center())
                .unwrap_or_else(|| Pos2::new(0.0, 0.0));
            if center.any_nan() {
                center = Pos2::new(0.0, 0.0);
            }

            let transform = TSTransform::from_translation(center.to_vec2())
                * TSTransform::from_scaling(scale)
                * TSTransform::from_translation(-center.to_vec2());

            ui.with_visual_transform(transform, |ui| {
                Button::new(RichText::new("Yaaaay").size(20.0))
                    .sense(Sense::click())
                    .ui(ui)
            });
        });
    })
}

```
  • Loading branch information
lucasmerlin authored Sep 10, 2024
1 parent f897405 commit 89b6055
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 1 deletion.
14 changes: 13 additions & 1 deletion crates/egui/src/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,14 @@ impl PaintList {
self.0.is_empty()
}

pub fn next_idx(&self) -> ShapeIdx {
ShapeIdx(self.0.len())
}

/// Returns the index of the new [`Shape`] that can be used with `PaintList::set`.
#[inline(always)]
pub fn add(&mut self, clip_rect: Rect, shape: Shape) -> ShapeIdx {
let idx = ShapeIdx(self.0.len());
let idx = self.next_idx();
self.0.push(ClippedShape { clip_rect, shape });
idx
}
Expand Down Expand Up @@ -171,6 +175,14 @@ impl PaintList {
}
}

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

/// Read-only access to all held shapes.
pub fn all_entries(&self) -> impl ExactSizeIterator<Item = &ClippedShape> {
self.0.iter()
Expand Down
27 changes: 27 additions & 0 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2690,6 +2690,33 @@ impl Ui {

(InnerResponse { inner, response }, payload)
}

/// Create a new Scope and transform its contents via a [`emath::TSTransform`].
/// This only affects visuals, inputs will not be transformed. So this is mostly useful
/// to create visual effects on interactions, e.g. scaling a button on hover / click.
///
/// Check out [`Context::set_transform_layer`] for a persistent transform that also affects
/// inputs.
pub fn with_visual_transform<R>(
&mut self,
transform: emath::TSTransform,
add_contents: impl FnOnce(&mut Self) -> R,
) -> InnerResponse<R> {
let start_idx = self.ctx().graphics(|gx| {
gx.get(self.layer_id())
.map_or(crate::layers::ShapeIdx(0), |l| l.next_idx())
});

let r = self.scope_dyn(UiBuilder::new(), Box::new(add_contents));

self.ctx().graphics_mut(|g| {
let list = g.entry(self.layer_id());
let end_idx = list.next_idx();
list.transform_range(start_idx, end_idx, transform);
});

r
}
}

/// # Menus
Expand Down

0 comments on commit 89b6055

Please sign in to comment.