Started implementing zoom around point.
This commit is contained in:
parent
2d0d465790
commit
98f7235f4f
|
@ -32,8 +32,8 @@
|
|||
* Implement touch scroll inertia.
|
||||
* Implement `ScrollMode::ZOOM`.
|
||||
- Touch gesture.
|
||||
- Scroll wheel zoom.
|
||||
- Center on cursor?
|
||||
- Scroll wheel zoom center point.
|
||||
- Scroll commands center point parameter.
|
||||
- Scroll-to-fill/fit.
|
||||
|
||||
# Touch Events
|
||||
|
|
|
@ -181,6 +181,14 @@ command! {
|
|||
pub static SCROLL_TO_CMD;
|
||||
|
||||
/// Represents the **zoom in** action.
|
||||
///
|
||||
/// # Parameter
|
||||
///
|
||||
/// This commands accepts an optional [`Point`] parameter that defines the origin of the
|
||||
/// scale transform, relative values are resolved in the viewport space. The default value
|
||||
/// is *top-start*.
|
||||
///
|
||||
/// [`Point`]: crate::core::units::Point
|
||||
pub static ZOOM_IN_CMD = {
|
||||
name: "Zoom In",
|
||||
shortcut: shortcut!(CTRL+'+'),
|
||||
|
@ -188,6 +196,14 @@ command! {
|
|||
};
|
||||
|
||||
/// Represents the **zoom out** action.
|
||||
///
|
||||
/// # Parameter
|
||||
///
|
||||
/// This commands accepts an optional [`Point`] parameter that defines the origin of the
|
||||
/// scale transform, relative values are resolved in the viewport space. The default value
|
||||
/// is *top-start*.
|
||||
///
|
||||
/// [`Point`]: crate::core::units::Point
|
||||
pub static ZOOM_OUT_CMD = {
|
||||
name: "Zoom Out",
|
||||
shortcut: shortcut!(CTRL+'-'),
|
||||
|
|
|
@ -516,6 +516,9 @@ pub fn zoom_commands_node(child: impl UiNode) -> impl UiNode {
|
|||
let mut zoom_out = CommandHandle::dummy();
|
||||
let mut zoom_reset = CommandHandle::dummy();
|
||||
|
||||
let mut scale_delta = 0.fct();
|
||||
let mut origin = Point::default();
|
||||
|
||||
match_node(child, move |child, op| match op {
|
||||
UiNodeOp::Init => {
|
||||
let scope = WIDGET.id();
|
||||
|
@ -531,32 +534,57 @@ pub fn zoom_commands_node(child: impl UiNode) -> impl UiNode {
|
|||
zoom_out = CommandHandle::dummy();
|
||||
zoom_reset = CommandHandle::dummy();
|
||||
}
|
||||
UiNodeOp::Layout { .. } => {
|
||||
zoom_in.set_enabled(SCROLL.can_zoom_in());
|
||||
zoom_out.set_enabled(SCROLL.can_zoom_out());
|
||||
zoom_reset.set_enabled(SCROLL.zoom_scale().get() != 1.fct());
|
||||
}
|
||||
UiNodeOp::Event { update } => {
|
||||
child.event(update);
|
||||
|
||||
let scope = WIDGET.id();
|
||||
|
||||
if let Some(args) = ZOOM_IN_CMD.scoped(scope).on(update) {
|
||||
args.handle_enabled(&zoom_in, |_| {
|
||||
let delta = ZOOM_WHEEL_UNIT_VAR.get();
|
||||
SCROLL.chase_zoom(|f| f + delta);
|
||||
args.handle_enabled(&zoom_in, |args| {
|
||||
origin = args.param::<Point>().cloned().unwrap_or_default();
|
||||
scale_delta += ZOOM_WHEEL_UNIT_VAR.get();
|
||||
|
||||
WIDGET.layout();
|
||||
});
|
||||
} else if let Some(args) = ZOOM_OUT_CMD.scoped(scope).on(update) {
|
||||
args.handle_enabled(&zoom_out, |_| {
|
||||
let delta = ZOOM_WHEEL_UNIT_VAR.get();
|
||||
SCROLL.chase_zoom(|f| f - delta);
|
||||
origin = args.param::<Point>().cloned().unwrap_or_default();
|
||||
scale_delta -= ZOOM_WHEEL_UNIT_VAR.get();
|
||||
|
||||
WIDGET.layout();
|
||||
});
|
||||
} else if let Some(args) = ZOOM_RESET_CMD.scoped(scope).on(update) {
|
||||
args.handle_enabled(&zoom_reset, |_| {
|
||||
SCROLL.chase_zoom(|_| 1.fct());
|
||||
scale_delta = 0.fct();
|
||||
});
|
||||
}
|
||||
}
|
||||
UiNodeOp::Layout { wl, final_size } => {
|
||||
*final_size = child.layout(wl);
|
||||
|
||||
zoom_in.set_enabled(SCROLL.can_zoom_in());
|
||||
zoom_out.set_enabled(SCROLL.can_zoom_out());
|
||||
zoom_reset.set_enabled(SCROLL.zoom_scale().get() != 1.fct());
|
||||
|
||||
if scale_delta != 0.fct() {
|
||||
let scroll_info = WIDGET.info().scroll_info().unwrap();
|
||||
let viewport_size = scroll_info.viewport_size();
|
||||
|
||||
let default = PxPoint::new(
|
||||
Px(0),
|
||||
match LAYOUT.direction() {
|
||||
LayoutDirection::LTR => Px(0),
|
||||
LayoutDirection::RTL => viewport_size.width,
|
||||
},
|
||||
);
|
||||
let center_in_viewport =
|
||||
LAYOUT.with_constraints(PxConstraints2d::new_fill_size(viewport_size), || origin.layout_dft(default));
|
||||
|
||||
SCROLL.zoom(|f| f + scale_delta, center_in_viewport);
|
||||
scale_delta = 0.fct();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
|
@ -656,7 +684,7 @@ pub fn scroll_to_node(child: impl UiNode) -> impl UiNode {
|
|||
let target_bounds_in_content = target_bounds;
|
||||
|
||||
// remove offset
|
||||
let rendered_offset = SCROLL.rendered_offset_px();
|
||||
let rendered_offset = SCROLL.rendered_content().origin.to_vector();
|
||||
target_bounds.origin += rendered_offset;
|
||||
|
||||
// replace scale
|
||||
|
@ -940,13 +968,12 @@ pub fn scroll_wheel_node(child: impl UiNode) -> impl UiNode {
|
|||
.and_then(|t| t.transform_point(default))
|
||||
.unwrap_or(default);
|
||||
|
||||
let center = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(scroll_info.viewport_size()), || {
|
||||
let viewport_size = scroll_info.viewport_size();
|
||||
let center_in_viewport = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(viewport_size), || {
|
||||
ZOOM_WHEEL_ORIGIN_VAR.layout_dft(default)
|
||||
});
|
||||
|
||||
println!("!!: TODO, {center:?}");
|
||||
|
||||
SCROLL.chase_zoom(|f| f + scale_delta);
|
||||
SCROLL.zoom(|f| f + scale_delta, center_in_viewport);
|
||||
scale_delta = 0.fct();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,12 +245,15 @@ impl SCROLL {
|
|||
SCROLL_CONFIG.get().rendered.load(Ordering::Relaxed).z
|
||||
}
|
||||
|
||||
/// Latest rendered offset in pixels.
|
||||
pub fn rendered_offset_px(&self) -> PxVector {
|
||||
/// Latest rendered content offset and size.
|
||||
///
|
||||
/// This is the content bounds, scaled and in the viewport space.
|
||||
pub fn rendered_content(&self) -> PxRect {
|
||||
let viewport = SCROLL_VIEWPORT_SIZE_VAR.get();
|
||||
let content = SCROLL_CONTENT_SIZE_VAR.get();
|
||||
let max_scroll = content - viewport;
|
||||
max_scroll.to_vector() * self.rendered_offset()
|
||||
let offset = max_scroll.to_vector() * self.rendered_offset();
|
||||
PxRect::new(offset.to_point(), content)
|
||||
}
|
||||
|
||||
/// Extra vertical offset, requested by touch gesture, that could not be fulfilled because [`vertical_offset`]
|
||||
|
@ -616,6 +619,43 @@ impl SCROLL {
|
|||
}
|
||||
}
|
||||
|
||||
/// Zoom in or out keeping the `origin` point in the viewport aligned with the same point
|
||||
/// in the content.
|
||||
pub fn zoom(&self, modify_scale: impl FnOnce(Factor) -> Factor, origin: PxPoint) {
|
||||
#[cfg(dyn_closure)]
|
||||
let modify_scale: Box<dyn FnOnce(Factor) -> Factor> = Box::new(modify_scale);
|
||||
self.zoom_impl(modify_scale, origin);
|
||||
}
|
||||
fn zoom_impl(&self, modify_scale: impl FnOnce(Factor) -> Factor, center_in_viewport: PxPoint) {
|
||||
if !SCROLL_MODE_VAR.get().contains(ScrollMode::ZOOM) {
|
||||
return;
|
||||
}
|
||||
|
||||
let content = SCROLL.rendered_content();
|
||||
let mut center_in_content = center_in_viewport + content.origin.to_vector();
|
||||
|
||||
SCROLL.chase_zoom(|f| {
|
||||
center_in_content /= f;
|
||||
let s = modify_scale(f);
|
||||
center_in_content *= s;
|
||||
s
|
||||
});
|
||||
|
||||
let viewport_size = SCROLL_VIEWPORT_SIZE_VAR.get();
|
||||
|
||||
// scroll so that new center_in_content is at the same center_in_viewport
|
||||
let max_scroll = content.size - viewport_size;
|
||||
let offset = center_in_content - center_in_viewport;
|
||||
if offset.y != Px(0) && max_scroll.height > Px(0) {
|
||||
let offset_y = offset.y.0 as f32 / max_scroll.height.0 as f32;
|
||||
SCROLL.chase_vertical(|_| offset_y.fct());
|
||||
}
|
||||
if offset.x != Px(0) && max_scroll.width > Px(0) {
|
||||
let offset_x = offset.x.0 as f32 / max_scroll.width.0 as f32;
|
||||
SCROLL.chase_horizontal(|_| offset_x.fct());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the content height is greater then the viewport height.
|
||||
pub fn can_scroll_vertical(&self) -> bool {
|
||||
let viewport = SCROLL_VIEWPORT_SIZE_VAR.get().height;
|
||||
|
|
Loading…
Reference in New Issue