1use core::f32;
2
3use emath::{GuiRounding as _, Pos2};
4
5use crate::{
6 InnerResponse, LayerId, PointerButton, Rangef, Rect, Response, Sense, Ui, UiBuilder, Vec2,
7 emath::TSTransform,
8};
9
10fn fit_to_rect_in_scene(
16 rect_in_global: Rect,
17 rect_in_scene: Rect,
18 zoom_range: Rangef,
19) -> TSTransform {
20 let scale = rect_in_global.size() / rect_in_scene.size();
22
23 let scale = scale.min_elem();
25
26 let scale = zoom_range.clamp(scale);
28
29 let center_in_global = rect_in_global.center().to_vec2();
31 let center_scene = rect_in_scene.center().to_vec2();
32
33 TSTransform::from_translation(center_in_global - scale * center_scene)
35 * TSTransform::from_scaling(scale)
36}
37
38#[derive(Clone, Debug)]
45#[must_use = "You should call .show()"]
46pub struct Scene {
47 zoom_range: Rangef,
48 sense: Sense,
49 max_inner_size: Vec2,
50 drag_pan_buttons: DragPanButtons,
51}
52
53#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
55pub struct DragPanButtons(u8);
56
57bitflags::bitflags! {
58 impl DragPanButtons: u8 {
59 const PRIMARY = 1 << 0;
61
62 const SECONDARY = 1 << 1;
64
65 const MIDDLE = 1 << 2;
67
68 const EXTRA_1 = 1 << 3;
70
71 const EXTRA_2 = 1 << 4;
73 }
74}
75
76impl Default for Scene {
77 fn default() -> Self {
78 Self {
79 zoom_range: Rangef::new(f32::EPSILON, 1.0),
80 sense: Sense::click_and_drag(),
81 max_inner_size: Vec2::splat(1000.0),
82 drag_pan_buttons: DragPanButtons::all(),
83 }
84 }
85}
86
87impl Scene {
88 #[inline]
89 pub fn new() -> Self {
90 Default::default()
91 }
92
93 #[inline]
99 pub fn sense(mut self, sense: Sense) -> Self {
100 self.sense = sense;
101 self
102 }
103
104 #[inline]
112 pub fn zoom_range(mut self, zoom_range: impl Into<Rangef>) -> Self {
113 self.zoom_range = zoom_range.into();
114 self
115 }
116
117 #[inline]
119 pub fn max_inner_size(mut self, max_inner_size: impl Into<Vec2>) -> Self {
120 self.max_inner_size = max_inner_size.into();
121 self
122 }
123
124 #[inline]
128 pub fn drag_pan_buttons(mut self, flags: DragPanButtons) -> Self {
129 self.drag_pan_buttons = flags;
130 self
131 }
132
133 pub fn show<R>(
141 &self,
142 parent_ui: &mut Ui,
143 scene_rect: &mut Rect,
144 add_contents: impl FnOnce(&mut Ui) -> R,
145 ) -> InnerResponse<R> {
146 let (outer_rect, _outer_response) =
147 parent_ui.allocate_exact_size(parent_ui.available_size_before_wrap(), Sense::hover());
148
149 let mut to_global = fit_to_rect_in_scene(outer_rect, *scene_rect, self.zoom_range);
150
151 let scene_rect_was_good =
152 to_global.is_valid() && scene_rect.is_finite() && scene_rect.size() != Vec2::ZERO;
153
154 let mut inner_rect = *scene_rect;
155
156 let ret = self.show_global_transform(parent_ui, outer_rect, &mut to_global, |ui| {
157 let r = add_contents(ui);
158 inner_rect = ui.min_rect();
159 r
160 });
161
162 if ret.response.changed() {
163 *scene_rect = to_global.inverse() * outer_rect;
166 }
167
168 if !scene_rect_was_good {
169 let to_global = fit_to_rect_in_scene(outer_rect, inner_rect, self.zoom_range);
172 *scene_rect = to_global.inverse() * outer_rect;
173 }
174
175 ret
176 }
177
178 fn show_global_transform<R>(
179 &self,
180 parent_ui: &mut Ui,
181 outer_rect: Rect,
182 to_global: &mut TSTransform,
183 add_contents: impl FnOnce(&mut Ui) -> R,
184 ) -> InnerResponse<R> {
185 let scene_layer_id = LayerId::new(
187 parent_ui.layer_id().order,
188 parent_ui.id().with("scene_area"),
189 );
190
191 parent_ui
193 .ctx()
194 .set_sublayer(parent_ui.layer_id(), scene_layer_id);
195
196 let mut local_ui = parent_ui.new_child(
197 UiBuilder::new()
198 .layer_id(scene_layer_id)
199 .max_rect(Rect::from_min_size(Pos2::ZERO, self.max_inner_size))
200 .sense(self.sense),
201 );
202
203 let mut pan_response = local_ui.response();
204
205 self.register_pan_and_zoom(&local_ui, &mut pan_response, to_global);
207
208 local_ui.set_clip_rect(to_global.inverse() * outer_rect);
210
211 local_ui
213 .ctx()
214 .set_transform_layer(scene_layer_id, *to_global);
215
216 let ret = add_contents(&mut local_ui);
218
219 local_ui.force_set_min_rect((to_global.inverse() * outer_rect).round_ui());
221
222 InnerResponse {
223 response: pan_response,
224 inner: ret,
225 }
226 }
227
228 pub fn register_pan_and_zoom(&self, ui: &Ui, resp: &mut Response, to_global: &mut TSTransform) {
230 let dragged = self.drag_pan_buttons.iter().any(|button| match button {
231 DragPanButtons::PRIMARY => resp.dragged_by(PointerButton::Primary),
232 DragPanButtons::SECONDARY => resp.dragged_by(PointerButton::Secondary),
233 DragPanButtons::MIDDLE => resp.dragged_by(PointerButton::Middle),
234 DragPanButtons::EXTRA_1 => resp.dragged_by(PointerButton::Extra1),
235 DragPanButtons::EXTRA_2 => resp.dragged_by(PointerButton::Extra2),
236 _ => false,
237 });
238 if dragged {
239 to_global.translation += to_global.scaling * resp.drag_delta();
240 resp.mark_changed();
241 }
242
243 if let Some(mouse_pos) = ui.input(|i| i.pointer.latest_pos()) {
244 if resp.contains_pointer() {
245 let pointer_in_scene = to_global.inverse() * mouse_pos;
246 let zoom_delta = ui.ctx().input(|i| i.zoom_delta());
247 let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta);
248
249 if zoom_delta == 1.0 && pan_delta == Vec2::ZERO {
252 return;
253 }
254
255 if zoom_delta != 1.0 {
256 let zoom_delta = zoom_delta.clamp(
258 self.zoom_range.min / to_global.scaling,
259 self.zoom_range.max / to_global.scaling,
260 );
261
262 *to_global = *to_global
263 * TSTransform::from_translation(pointer_in_scene.to_vec2())
264 * TSTransform::from_scaling(zoom_delta)
265 * TSTransform::from_translation(-pointer_in_scene.to_vec2());
266
267 to_global.scaling = self.zoom_range.clamp(to_global.scaling);
269 }
270
271 *to_global = TSTransform::from_translation(pan_delta) * *to_global;
273 resp.mark_changed();
274 }
275 }
276 }
277}