mirror of https://github.com/wayvr-org/wayvr.git
781 lines
21 KiB
Rust
781 lines
21 KiB
Rust
use anyhow::Context;
|
|
use glam::{FloatExt, Vec2};
|
|
use taffy::{NodeId, TaffyTree};
|
|
|
|
use super::drawing::RenderPrimitive;
|
|
|
|
use crate::{
|
|
any::AnyTrait,
|
|
drawing::{self, PrimitiveExtent},
|
|
event::{
|
|
self, CallbackData, CallbackDataCommon, CallbackMetadata, DeviceBitmask, Event, EventAlterables,
|
|
EventListenerCollection,
|
|
EventListenerKind::{self, InternalStateChange, MouseLeave},
|
|
MouseWheelEvent,
|
|
},
|
|
globals::Globals,
|
|
layout::{Layout, LayoutState, WidgetID},
|
|
stack::{ScissorStack, TransformStack},
|
|
};
|
|
|
|
pub mod custom_draw;
|
|
pub mod div;
|
|
pub mod image;
|
|
pub mod label;
|
|
pub mod rectangle;
|
|
pub mod sprite;
|
|
pub mod util;
|
|
|
|
const SWIPE_START_THRESHOLD_UNITS: f32 = 16.0;
|
|
const KINETIC_VELOCITY_DAMPING: f32 = 0.92;
|
|
const KINETIC_VELOCITY_BRAKE: f32 = 0.85;
|
|
const RUBBERBANDING_DAMP: f32 = 0.8;
|
|
|
|
pub struct WidgetData {
|
|
hovered: DeviceBitmask,
|
|
pressed: DeviceBitmask,
|
|
scrolling_target: Vec2, // normalized, 0.0-1.0. Not used in case if overflow != scroll
|
|
scrolling_cur: Vec2, // normalized, used for smooth scrolling animation
|
|
scrolling_cur_prev: Vec2, // for motion interpolation while rendering between ticks
|
|
scrolling_velocity: Vec2,
|
|
press_down_start_mouse_pos: Option<Vec2>,
|
|
swipe_running: bool,
|
|
swipe_scroll_start: Vec2, // normalized, 0.0-1.0
|
|
pub transform: glam::Mat4,
|
|
pub cached_absolute_boundary: drawing::Boundary, // updated in Layout::push_event_widget
|
|
}
|
|
|
|
impl WidgetData {
|
|
pub const fn set_device_pressed(&mut self, device: DeviceBitmask, pressed: bool) -> bool {
|
|
let bit = 1 << device.0;
|
|
let state_changed;
|
|
if pressed {
|
|
state_changed = self.pressed.0 == 0;
|
|
self.pressed.0 |= bit;
|
|
} else {
|
|
state_changed = self.pressed.0 == bit;
|
|
self.pressed.0 &= !bit;
|
|
}
|
|
state_changed
|
|
}
|
|
|
|
pub const fn set_device_hovered(&mut self, device: DeviceBitmask, hovered: bool) -> bool {
|
|
let bit = 1 << device.0;
|
|
let state_changed;
|
|
if hovered {
|
|
state_changed = self.hovered.0 == 0;
|
|
self.hovered.0 |= bit;
|
|
} else {
|
|
state_changed = self.hovered.0 == bit;
|
|
self.hovered.0 &= !bit;
|
|
}
|
|
state_changed
|
|
}
|
|
|
|
pub const fn get_pressed(&self, device: DeviceBitmask) -> bool {
|
|
self.pressed.0 & (1 << device.0) != 0
|
|
}
|
|
|
|
pub const fn get_hovered(&self, device: DeviceBitmask) -> bool {
|
|
self.hovered.0 & (1 << device.0) != 0
|
|
}
|
|
|
|
pub const fn is_pressed(&self) -> bool {
|
|
self.pressed.0 != 0
|
|
}
|
|
|
|
pub const fn is_hovered(&self) -> bool {
|
|
self.hovered.0 != 0
|
|
}
|
|
}
|
|
|
|
pub struct WidgetStateFlags {
|
|
pub interactable: bool,
|
|
|
|
// consume any incoming mouse event which is hovered at given widget
|
|
pub consume_mouse_events: bool,
|
|
|
|
// force a new render pass before rendering this widget
|
|
pub new_pass: bool,
|
|
}
|
|
|
|
impl Default for WidgetStateFlags {
|
|
fn default() -> Self {
|
|
Self {
|
|
interactable: true,
|
|
consume_mouse_events: false,
|
|
new_pass: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct WidgetState {
|
|
pub data: WidgetData,
|
|
pub obj: Box<dyn WidgetObj>,
|
|
pub event_listeners: EventListenerCollection,
|
|
pub flags: WidgetStateFlags,
|
|
}
|
|
|
|
impl WidgetState {
|
|
pub fn get_data_obj_mut(&mut self) -> (&mut WidgetData, &mut dyn WidgetObj) {
|
|
let data = &mut self.data;
|
|
let obj = self.obj.as_mut();
|
|
(data, obj)
|
|
}
|
|
|
|
fn new(flags: WidgetStateFlags, obj: Box<dyn WidgetObj>) -> Self {
|
|
Self {
|
|
data: WidgetData {
|
|
hovered: DeviceBitmask(0),
|
|
pressed: DeviceBitmask(0),
|
|
scrolling_target: Vec2::default(),
|
|
scrolling_cur: Vec2::default(),
|
|
scrolling_cur_prev: Vec2::default(),
|
|
transform: glam::Mat4::IDENTITY,
|
|
cached_absolute_boundary: drawing::Boundary::default(),
|
|
press_down_start_mouse_pos: None,
|
|
swipe_running: false,
|
|
swipe_scroll_start: Vec2::default(),
|
|
scrolling_velocity: Vec2::default(),
|
|
},
|
|
obj,
|
|
event_listeners: EventListenerCollection::default(),
|
|
flags,
|
|
}
|
|
}
|
|
}
|
|
|
|
// global draw params
|
|
pub struct DrawState<'a> {
|
|
pub globals: &'a Globals,
|
|
pub layout: &'a Layout,
|
|
pub primitives: &'a mut Vec<RenderPrimitive>,
|
|
pub transform_stack: &'a mut TransformStack,
|
|
pub scissor_stack: &'a mut ScissorStack,
|
|
pub alterables: &'a mut EventAlterables,
|
|
}
|
|
|
|
// per-widget draw params
|
|
pub struct DrawParams<'a> {
|
|
pub node_id: taffy::NodeId,
|
|
pub style: &'a taffy::Style,
|
|
pub taffy_layout: &'a taffy::Layout,
|
|
}
|
|
|
|
pub enum WidgetType {
|
|
Div,
|
|
Label,
|
|
Sprite,
|
|
Rectangle,
|
|
CustomDraw,
|
|
}
|
|
|
|
impl WidgetType {
|
|
pub const fn as_str(&self) -> &str {
|
|
match self {
|
|
WidgetType::Div => "div",
|
|
WidgetType::Label => "label",
|
|
WidgetType::Sprite => "sprite",
|
|
WidgetType::Rectangle => "rectangle",
|
|
WidgetType::CustomDraw => "custom_draw",
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait WidgetObj: AnyTrait {
|
|
// every widget stores their of id for convenience reasons
|
|
fn get_id(&self) -> WidgetID;
|
|
fn set_id(&mut self, id: WidgetID); // always set at insertion
|
|
fn get_type(&self) -> WidgetType;
|
|
fn debug_print(&self, _globals: &Globals) -> String;
|
|
|
|
fn draw(&mut self, state: &mut DrawState, params: &DrawParams);
|
|
|
|
fn measure(
|
|
&mut self,
|
|
_globals: &Globals,
|
|
_known_dimensions: taffy::Size<Option<f32>>,
|
|
_available_space: taffy::Size<taffy::AvailableSpace>,
|
|
) -> taffy::Size<f32> {
|
|
taffy::Size::ZERO
|
|
}
|
|
}
|
|
|
|
pub struct EventParams<'a> {
|
|
pub node_id: taffy::NodeId,
|
|
pub style: &'a taffy::Style,
|
|
pub state: &'a LayoutState,
|
|
pub alterables: &'a mut EventAlterables,
|
|
pub taffy_layout: &'a taffy::Layout,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd)]
|
|
pub enum EventResult {
|
|
NoHit, // event was pushed but has not found a listener (yet)
|
|
Pass, // widget acknowledged it and allows the event to propagate further
|
|
Consumed, // widget triggered an action, do not propagate further
|
|
ConsumedExclusive, // same as `Consumed`, but with force-disabled swipe-scroll event (used in sliders for example)
|
|
}
|
|
|
|
impl EventResult {
|
|
#[must_use]
|
|
pub const fn can_propagate(&self) -> bool {
|
|
!matches!(self, EventResult::Consumed | EventResult::ConsumedExclusive)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn merge(self, other: Self) -> Self {
|
|
if self > other { self } else { other }
|
|
}
|
|
}
|
|
|
|
fn get_scroll_enabled(style: &taffy::Style) -> (bool, bool) {
|
|
(
|
|
style.overflow.x == taffy::Overflow::Scroll,
|
|
style.overflow.y == taffy::Overflow::Scroll,
|
|
)
|
|
}
|
|
|
|
const OVERFLOW_START_THRESHOLD_UNITS: f32 = 3.0; // don't show scrollbars for nearly non-scrollable lists
|
|
|
|
fn get_scroll_active_axis(style: &taffy::Style, taffy_layout: &taffy::Layout) -> (bool, bool) {
|
|
let (enabled_horiz, enabled_vert) = get_scroll_enabled(style);
|
|
if !enabled_horiz && !enabled_vert {
|
|
return (false, false);
|
|
}
|
|
|
|
let overflow = Vec2::new(taffy_layout.scroll_width(), taffy_layout.scroll_height());
|
|
|
|
(
|
|
overflow.x >= OVERFLOW_START_THRESHOLD_UNITS,
|
|
overflow.y >= OVERFLOW_START_THRESHOLD_UNITS,
|
|
)
|
|
}
|
|
|
|
pub struct ScrollbarInfo {
|
|
// total contents size of the currently scrolling widget
|
|
content_size: Vec2,
|
|
// 0.0 - 1.0
|
|
// 1.0: scrollbar handle not visible (inactive)
|
|
handle_size: Vec2,
|
|
}
|
|
|
|
pub fn get_scrollbar_info(taffy_layout: &taffy::Layout) -> Option<ScrollbarInfo> {
|
|
let overflow = Vec2::new(taffy_layout.scroll_width(), taffy_layout.scroll_height());
|
|
if overflow.x < OVERFLOW_START_THRESHOLD_UNITS && overflow.y < OVERFLOW_START_THRESHOLD_UNITS {
|
|
return None; // not overflowing
|
|
}
|
|
|
|
let content_size = Vec2::new(taffy_layout.content_size.width, taffy_layout.content_size.height);
|
|
let handle_size = 1.0 - (overflow / content_size);
|
|
|
|
Some(ScrollbarInfo {
|
|
content_size,
|
|
handle_size,
|
|
})
|
|
}
|
|
|
|
impl ScrollbarInfo {
|
|
fn get_potential_scroll_axis_multiplier(&self, taffy_layout: &taffy::Layout) -> Vec2 {
|
|
Vec2::new(
|
|
self.content_size.x - taffy_layout.content_box_width(),
|
|
self.content_size.y - taffy_layout.content_box_height(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl dyn WidgetObj {
|
|
pub fn get_as<T: 'static>(&self) -> Option<&T> {
|
|
let any = self.as_any();
|
|
any.downcast_ref::<T>()
|
|
}
|
|
|
|
pub fn cast<T: 'static>(&self) -> anyhow::Result<&T> {
|
|
self.get_as().context("cast failed")
|
|
}
|
|
|
|
pub fn get_as_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
|
let any = self.as_any_mut();
|
|
any.downcast_mut::<T>()
|
|
}
|
|
|
|
pub fn cast_mut<T: 'static>(&mut self) -> anyhow::Result<&mut T> {
|
|
self.get_as_mut().context("cast failed")
|
|
}
|
|
}
|
|
|
|
struct InvokeData<'a, 'b, U1: 'static, U2: 'static> {
|
|
widget_id: WidgetID,
|
|
node_id: taffy::NodeId,
|
|
event_result: &'a mut EventResult,
|
|
user_data: &'a mut (&'b mut U1, &'b mut U2),
|
|
params: &'a mut EventParams<'a>,
|
|
}
|
|
|
|
#[must_use]
|
|
enum InvokeListenersResult {
|
|
NobodyListened,
|
|
AtLeastOneCalled,
|
|
}
|
|
|
|
impl WidgetState {
|
|
fn invoke_listeners<U1: 'static, U2: 'static>(
|
|
&mut self,
|
|
call_data: &mut InvokeData<'_, '_, U1, U2>,
|
|
kind: event::EventListenerKind,
|
|
metadata: CallbackMetadata,
|
|
) -> anyhow::Result<InvokeListenersResult> {
|
|
let mut data = CallbackData {
|
|
obj: self.obj.as_mut(),
|
|
widget_data: &mut self.data,
|
|
widget_id: call_data.widget_id,
|
|
node_id: call_data.node_id,
|
|
metadata,
|
|
};
|
|
|
|
let mut common = CallbackDataCommon {
|
|
state: call_data.params.state,
|
|
alterables: call_data.params.alterables,
|
|
};
|
|
|
|
let mut res = InvokeListenersResult::NobodyListened;
|
|
|
|
for listener in self.event_listeners.iter_filtered::<U1, U2>(kind) {
|
|
let new_result = listener.call_with(&mut common, &mut data, call_data.user_data)?;
|
|
res = InvokeListenersResult::AtLeastOneCalled;
|
|
// Consider all listeners on this widget, even if we had a Consume.
|
|
// Store the highest value for return.
|
|
*call_data.event_result = call_data.event_result.merge(new_result);
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
pub fn get_scroll_shift_smooth(&self, info: &ScrollbarInfo, l: &taffy::Layout, timestep_alpha: f32) -> (Vec2, bool) {
|
|
let currently_animating = self.data.scrolling_cur != self.data.scrolling_cur_prev;
|
|
|
|
let scrolling = self
|
|
.data
|
|
.scrolling_cur_prev
|
|
.lerp(self.data.scrolling_cur, timestep_alpha);
|
|
|
|
(
|
|
Vec2::new(
|
|
(info.content_size.x - l.content_box_width()) * scrolling.x,
|
|
(info.content_size.y - l.content_box_height()) * scrolling.y,
|
|
),
|
|
currently_animating,
|
|
)
|
|
}
|
|
|
|
pub fn get_scroll_shift_raw(&self, info: &ScrollbarInfo, l: &taffy::Layout) -> Vec2 {
|
|
Vec2::new(
|
|
(info.content_size.x - l.content_box_width()) * self.data.scrolling_target.x,
|
|
(info.content_size.y - l.content_box_height()) * self.data.scrolling_target.y,
|
|
)
|
|
}
|
|
|
|
pub fn draw_all(&mut self, state: &mut DrawState, params: &DrawParams) {
|
|
self.obj.draw(state, params);
|
|
}
|
|
|
|
pub fn tick(&mut self, this_widget_id: WidgetID, alterables: &mut EventAlterables) {
|
|
let scrolling_cur = &mut self.data.scrolling_cur;
|
|
let scrolling_cur_prev = &mut self.data.scrolling_cur_prev;
|
|
let scrolling_target = &mut self.data.scrolling_target;
|
|
|
|
let mut perform_next_tick = false;
|
|
|
|
// check for boundaries
|
|
if !self.data.swipe_running {
|
|
// top bound
|
|
if scrolling_target.y < 0.0 {
|
|
self.data.scrolling_velocity.y *= KINETIC_VELOCITY_BRAKE;
|
|
scrolling_target.y *= RUBBERBANDING_DAMP;
|
|
perform_next_tick = true;
|
|
}
|
|
|
|
// left bound
|
|
if scrolling_target.x < 0.0 {
|
|
self.data.scrolling_velocity.x *= KINETIC_VELOCITY_BRAKE;
|
|
scrolling_target.x *= RUBBERBANDING_DAMP;
|
|
perform_next_tick = true;
|
|
}
|
|
|
|
// right bound
|
|
if scrolling_target.x > 1.0 {
|
|
self.data.scrolling_velocity.x *= KINETIC_VELOCITY_BRAKE;
|
|
scrolling_target.x = 1.0.lerp(scrolling_target.x, RUBBERBANDING_DAMP);
|
|
perform_next_tick = true;
|
|
}
|
|
|
|
// bottom bound
|
|
if scrolling_target.y > 1.0 {
|
|
self.data.scrolling_velocity.y *= KINETIC_VELOCITY_BRAKE;
|
|
scrolling_target.y = 1.0.lerp(scrolling_target.y, RUBBERBANDING_DAMP);
|
|
perform_next_tick = true;
|
|
}
|
|
}
|
|
|
|
// Perform kinetic velocity animation
|
|
if self.data.scrolling_velocity.length_squared() > 1e-9 {
|
|
*scrolling_target += self.data.scrolling_velocity;
|
|
self.data.scrolling_velocity *= KINETIC_VELOCITY_DAMPING;
|
|
perform_next_tick = true;
|
|
}
|
|
|
|
*scrolling_cur_prev = *scrolling_cur;
|
|
|
|
if scrolling_cur != scrolling_target {
|
|
// the magic part
|
|
*scrolling_cur = scrolling_cur.lerp(*scrolling_target, 0.2);
|
|
|
|
// trigger tick request again
|
|
perform_next_tick = true;
|
|
|
|
let epsilon = 0.00001;
|
|
if (scrolling_cur.x - scrolling_target.x).abs() < epsilon
|
|
&& (scrolling_cur.y - scrolling_target.y).abs() < epsilon
|
|
{
|
|
*scrolling_cur = *scrolling_target;
|
|
}
|
|
}
|
|
|
|
if perform_next_tick {
|
|
alterables.mark_tick(this_widget_id);
|
|
alterables.mark_redraw();
|
|
}
|
|
}
|
|
|
|
pub fn draw_scrollbars(&mut self, state: &mut DrawState, params: &DrawParams, info: &ScrollbarInfo) {
|
|
let (enabled_horiz, enabled_vert) = get_scroll_enabled(params.style);
|
|
if !enabled_horiz && !enabled_vert {
|
|
return;
|
|
}
|
|
|
|
let transform_widget = state.transform_stack.get();
|
|
let transform_parent = state.transform_stack.parent();
|
|
|
|
let thickness = 6.0;
|
|
let margin = 6.0;
|
|
|
|
let rect_params = drawing::Rectangle {
|
|
color: drawing::Color::new(1.0, 1.0, 1.0, 0.5),
|
|
round_units: 4,
|
|
..Default::default()
|
|
};
|
|
|
|
#[allow(clippy::useless_let_if_seq)]
|
|
let mut boundary: Option<drawing::Boundary> = None;
|
|
|
|
// Horizontal handle
|
|
if enabled_horiz && info.handle_size.x < 1.0 {
|
|
boundary = Some(drawing::Boundary::from_pos_size(
|
|
Vec2::new(
|
|
// X
|
|
transform_widget.content_rel_pos.x
|
|
+ transform_parent.raw_dim.x * (1.0 - info.handle_size.x) * self.data.scrolling_cur.x.clamp(0.0, 1.0),
|
|
// Y
|
|
transform_widget.content_rel_pos.y + transform_widget.raw_dim.y - thickness - margin,
|
|
),
|
|
Vec2::new(transform_widget.raw_dim.x * info.handle_size.x, thickness),
|
|
));
|
|
}
|
|
|
|
// Vertical handle
|
|
if enabled_vert && info.handle_size.y < 1.0 {
|
|
boundary = Some(drawing::Boundary::from_pos_size(
|
|
Vec2::new(
|
|
// X
|
|
transform_widget.content_rel_pos.x + transform_widget.raw_dim.x - thickness - margin,
|
|
// Y
|
|
transform_widget.content_rel_pos.y
|
|
+ transform_parent.raw_dim.y * (1.0 - info.handle_size.y) * self.data.scrolling_cur.y.clamp(0.0, 1.0),
|
|
),
|
|
Vec2::new(thickness, transform_widget.raw_dim.y * info.handle_size.y),
|
|
));
|
|
}
|
|
|
|
if let Some(boundary) = boundary.take() {
|
|
state.primitives.push(drawing::RenderPrimitive::Rectangle(
|
|
PrimitiveExtent {
|
|
boundary,
|
|
transform: transform_parent.transform,
|
|
},
|
|
rect_params,
|
|
));
|
|
}
|
|
}
|
|
|
|
fn process_wheel(&mut self, params: &mut EventParams, wheel: &MouseWheelEvent) -> bool {
|
|
let (enabled_horiz, enabled_vert) = get_scroll_enabled(params.style);
|
|
if !enabled_horiz && !enabled_vert {
|
|
return false;
|
|
}
|
|
|
|
let l = params.taffy_layout;
|
|
let overflow = Vec2::new(l.scroll_width(), l.scroll_height());
|
|
if overflow.x == 0.0 && overflow.y == 0.0 {
|
|
return false; // not overflowing
|
|
}
|
|
|
|
let Some(info) = get_scrollbar_info(l) else {
|
|
return false;
|
|
};
|
|
|
|
let step_pixels = 64.0;
|
|
|
|
let mut handle_scroll =
|
|
|scrolling_target: &mut f32, wheel_delta: f32, handle_size: f32, content_length: f32, content_box_length: f32| {
|
|
if handle_size >= 1.0 || wheel_delta == 0.0 {
|
|
return;
|
|
}
|
|
|
|
let mult = (1.0 / (content_box_length - content_length)) * step_pixels;
|
|
let new_scroll = *scrolling_target + wheel_delta * mult;
|
|
if *scrolling_target == new_scroll {
|
|
return;
|
|
}
|
|
|
|
*scrolling_target = new_scroll;
|
|
params.alterables.mark_tick(self.obj.get_id());
|
|
};
|
|
|
|
handle_scroll(
|
|
&mut self.data.scrolling_target.x,
|
|
wheel.delta.x,
|
|
info.handle_size.x,
|
|
info.content_size.x,
|
|
l.content_box_width(),
|
|
);
|
|
|
|
handle_scroll(
|
|
&mut self.data.scrolling_target.y,
|
|
wheel.delta.y,
|
|
info.handle_size.y,
|
|
info.content_size.y,
|
|
l.content_box_height(),
|
|
);
|
|
|
|
true
|
|
}
|
|
|
|
// this is called before calling children of this widget
|
|
pub fn process_event_priority(
|
|
&mut self,
|
|
params: &mut EventParams,
|
|
widget_id: WidgetID,
|
|
event: &Event,
|
|
) -> anyhow::Result<EventResult> {
|
|
match &event {
|
|
Event::MouseUp(_e) => {
|
|
if self.data.swipe_running {
|
|
self.data.swipe_running = false;
|
|
let kinetic_force = (self.data.scrolling_cur - self.data.scrolling_cur_prev) * 20.0;
|
|
self.data.scrolling_velocity += kinetic_force;
|
|
params.alterables.mark_tick(widget_id);
|
|
params.alterables.mark_redraw();
|
|
}
|
|
self.data.press_down_start_mouse_pos = None;
|
|
}
|
|
Event::MouseMotion(e) => {
|
|
if let Some(start_mouse_pos) = &self.data.press_down_start_mouse_pos {
|
|
let (active_x, active_y) = get_scroll_active_axis(params.style, params.taffy_layout);
|
|
|
|
if !self.data.swipe_running
|
|
&& ((active_x && (e.pos.x - start_mouse_pos.x).abs() >= SWIPE_START_THRESHOLD_UNITS)
|
|
|| (active_y && (e.pos.y - start_mouse_pos.y).abs() >= SWIPE_START_THRESHOLD_UNITS))
|
|
{
|
|
self.data.swipe_running = true;
|
|
params.alterables.emit_global_event(Event::MouseCancel);
|
|
}
|
|
|
|
if self.data.swipe_running
|
|
&& let Some(scrollbar_info) = get_scrollbar_info(params.taffy_layout)
|
|
{
|
|
let mouse_diff = e.pos - start_mouse_pos;
|
|
|
|
let mult = scrollbar_info.get_potential_scroll_axis_multiplier(params.taffy_layout);
|
|
|
|
let scroll_diff_x = if mult.x == 0.0 { 0.0 } else { -mouse_diff.x / mult.x };
|
|
let scroll_diff_y = if mult.y == 0.0 { 0.0 } else { -mouse_diff.y / mult.y };
|
|
|
|
self.data.scrolling_target = self.data.swipe_scroll_start + Vec2::new(scroll_diff_x, scroll_diff_y);
|
|
params.alterables.mark_tick(self.obj.get_id());
|
|
|
|
return Ok(EventResult::Consumed);
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Ok(EventResult::Pass)
|
|
}
|
|
|
|
fn process_swipe_event(&mut self, event: &Event, params: &EventParams) {
|
|
let Event::MouseDown(evt) = &event else {
|
|
return;
|
|
};
|
|
|
|
// firstly, check if this widget is scrollable at all
|
|
let (active_x, active_y) = get_scroll_active_axis(params.style, params.taffy_layout);
|
|
if !active_x && !active_y {
|
|
return;
|
|
}
|
|
|
|
// check if the mouse hovers over *parent* boundaries
|
|
// we assume there that our parent is non-scrollable
|
|
let transform_parent = params.alterables.transform_stack.parent();
|
|
if !Event::test_transform_pos(transform_parent, evt.pos) {
|
|
return;
|
|
}
|
|
|
|
self.data.press_down_start_mouse_pos = Some(evt.pos);
|
|
self.data.swipe_scroll_start = self.data.scrolling_target;
|
|
}
|
|
|
|
pub fn process_event<'a, 'b, U1: 'static, U2: 'static>(
|
|
&mut self,
|
|
params: &'a mut EventParams<'a>,
|
|
widget_id: WidgetID,
|
|
event: &Event,
|
|
event_result: &'a mut EventResult,
|
|
user_data: &'a mut (&'b mut U1, &'b mut U2),
|
|
) -> anyhow::Result<()> {
|
|
// check if we should swipe-scroll this widget
|
|
if *event_result != EventResult::ConsumedExclusive {
|
|
self.process_swipe_event(event, params);
|
|
}
|
|
|
|
if !event_result.can_propagate() {
|
|
// we don't want to pass any other events
|
|
return Ok(());
|
|
}
|
|
|
|
let hovered = event.test_mouse_within_transform(
|
|
params.alterables.transform_stack.get(),
|
|
¶ms.alterables.scissor_stack.get().0,
|
|
);
|
|
|
|
let mut invoke_data = InvokeData {
|
|
widget_id,
|
|
node_id: params.node_id,
|
|
event_result,
|
|
user_data,
|
|
params,
|
|
};
|
|
|
|
let mut res: Option<InvokeListenersResult> = None;
|
|
|
|
match &event {
|
|
Event::TextInput(e) => {
|
|
res = Some(self.invoke_listeners(
|
|
&mut invoke_data,
|
|
EventListenerKind::TextInput,
|
|
CallbackMetadata::TextInput(e.clone()),
|
|
)?);
|
|
}
|
|
Event::MouseCancel => {
|
|
res = Some(self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseCancel, CallbackMetadata::None)?);
|
|
}
|
|
Event::MouseDown(e) => {
|
|
if hovered && self.data.set_device_pressed(e.device, true) {
|
|
res = Some(self.invoke_listeners(
|
|
&mut invoke_data,
|
|
EventListenerKind::MousePress,
|
|
CallbackMetadata::MouseButton(*e),
|
|
)?);
|
|
}
|
|
}
|
|
Event::MouseUp(e) => {
|
|
if self.data.set_device_pressed(e.device, false) {
|
|
res = Some(self.invoke_listeners(
|
|
&mut invoke_data,
|
|
EventListenerKind::MouseRelease,
|
|
CallbackMetadata::MouseButton(*e),
|
|
)?);
|
|
}
|
|
}
|
|
Event::MouseMotion(e) => {
|
|
let hover_state_changed = self.data.set_device_hovered(e.device, hovered);
|
|
if hover_state_changed {
|
|
if self.data.is_hovered() {
|
|
res =
|
|
Some(self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseEnter, CallbackMetadata::None)?);
|
|
} else {
|
|
res =
|
|
Some(self.invoke_listeners(&mut invoke_data, EventListenerKind::MouseLeave, CallbackMetadata::None)?);
|
|
}
|
|
} else {
|
|
res = Some(self.invoke_listeners(
|
|
&mut invoke_data,
|
|
EventListenerKind::MouseMotion,
|
|
CallbackMetadata::MousePosition(event::MousePosition {
|
|
pos: e.pos,
|
|
device: e.device,
|
|
}),
|
|
)?);
|
|
|
|
if self.flags.interactable {
|
|
*invoke_data.event_result = invoke_data.event_result.merge(EventResult::Pass);
|
|
}
|
|
}
|
|
}
|
|
Event::MouseWheel(e) => {
|
|
if hovered && self.process_wheel(invoke_data.params, e) {
|
|
*invoke_data.event_result = EventResult::Consumed;
|
|
return Ok(());
|
|
}
|
|
}
|
|
Event::MouseLeave(e) => {
|
|
if self.data.set_device_hovered(e.device, false) {
|
|
res = Some(self.invoke_listeners(&mut invoke_data, MouseLeave, CallbackMetadata::None)?);
|
|
}
|
|
}
|
|
Event::InternalStateChange(e) => {
|
|
res = Some(self.invoke_listeners(
|
|
&mut invoke_data,
|
|
InternalStateChange,
|
|
CallbackMetadata::Custom(e.metadata),
|
|
)?);
|
|
}
|
|
}
|
|
|
|
if let Some(res) = res {
|
|
match res {
|
|
InvokeListenersResult::NobodyListened => {
|
|
if hovered && self.flags.consume_mouse_events {
|
|
*invoke_data.event_result = EventResult::Consumed;
|
|
}
|
|
}
|
|
InvokeListenersResult::AtLeastOneCalled => {}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct ConstructEssentials<'a> {
|
|
pub layout: &'a mut Layout,
|
|
pub parent: WidgetID,
|
|
}
|
|
|
|
/// Determines whether a given node is visible within the layout tree.
|
|
///
|
|
/// Traversal is definitely a little bit more expensive than just checking the value, but
|
|
/// Taffy doesn't calculate that for us, so here it is.
|
|
pub fn is_node_visible(tree: &TaffyTree<WidgetID>, node_id: NodeId) -> bool {
|
|
let mut cur = Some(node_id);
|
|
while let Some(node_id) = cur {
|
|
if let Ok(style) = tree.style(node_id)
|
|
&& style.display == taffy::Display::None
|
|
{
|
|
return false;
|
|
}
|
|
cur = tree.parent(node_id);
|
|
}
|
|
true
|
|
}
|