Add `Ui::with_visual_transform` (#5055)

* [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)
            });
        });
    })
}

```
This commit is contained in:
lucasmerlin 2024-09-10 11:04:13 +02:00 committed by GitHub
parent f897405a82
commit 89b6055f9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 1 deletions

View File

@ -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
}
@ -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()

View File

@ -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