Skip to content

Commit

Permalink
fix drag jitter (#674)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmoulton authored Nov 13, 2024
1 parent 6bb8b4f commit 4658e65
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 29 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ Inspired by [Xilem](https://github.com/linebender/xilem), [Leptos](https://githu
- **Cross-platform**: Floem supports Windows, macOS and Linux with rendering using [wgpu](https://github.com/gfx-rs/wgpu). In case a GPU is unavailable, a CPU renderer powered by [tiny-skia](https://github.com/RazrFalcon/tiny-skia) will be used.
- **Fine-grained reactivity**: The entire library is built around reactive primitives inspired by [leptos_reactive](https://crates.io/crates/leptos_reactive). The reactive "signals" allow you to keep your UI up-to-date with minimal effort, all while maintaining very high performance.
- **Performance**: The view tree is constructed only once, safeguarding you from accidentally creating a bottleneck in a view generation function that slows down your entire application. Floem also provides tools to help you write efficient UI code, such as a [virtual list](https://github.com/lapce/floem/tree/main/examples/virtual_list).
- **Ergonomic API**: Floem aspires to have a highly ergonmic API that is a joy to use.
- **Flexbox layout**: Using [Taffy](https://crates.io/crates/taffy), the library provides the Flexbox and Grid layout systems, which can be applied to any View node.
- **Customizable widgets**: Widgets are highly customizable. You can customize both the appearance and behavior of widgets using the styling API, which supports theming with classes. You can also install third-party themes.
- **Transitions and Animations**: Floem supports both transitions and animations. Transitions, like css transitions, can animate any property that can be interpolated and can be applied alongside other styles, including in classes.
Expand Down
2 changes: 2 additions & 0 deletions src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct AppState {
/// regardless of the status of the animation
pub(crate) cursor: Option<CursorStyle>,
pub(crate) last_cursor: CursorIcon,
pub(crate) last_cursor_location: Point,
pub(crate) keyboard_navigation: bool,
pub(crate) window_menu: HashMap<usize, Box<dyn Fn()>>,
pub(crate) context_menu: HashMap<usize, Box<dyn Fn()>>,
Expand Down Expand Up @@ -76,6 +77,7 @@ impl AppState {
hovered: HashSet::new(),
cursor: None,
last_cursor: CursorIcon::Default,
last_cursor_location: Default::default(),
keyboard_navigation: false,
grid_bps: GridBreakpoints::default(),
window_menu: HashMap::new(),
Expand Down
66 changes: 41 additions & 25 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use web_time::{Duration, Instant};
use taffy::prelude::NodeId;

use crate::animate::{AnimStateKind, RepeatMode};
use crate::easing::{Easing, Linear};
use crate::renderer::Renderer;
use crate::style::DisplayProp;
use crate::view_state::IsHiddenState;
Expand Down Expand Up @@ -50,6 +51,7 @@ pub struct DragState {
pub(crate) id: ViewId,
pub(crate) offset: Vec2,
pub(crate) released_at: Option<Instant>,
pub(crate) release_location: Option<Point>,
}

pub(crate) enum FrameUpdate {
Expand Down Expand Up @@ -253,26 +255,26 @@ impl<'a> EventCx<'a> {
.as_ref()
.filter(|(drag_id, _)| drag_id == &view_id)
{
let vec2 = pointer_event.pos - *drag_start;

let offset = pointer_event.pos - *drag_start;
if let Some(dragging) = self
.app_state
.dragging
.as_mut()
.filter(|d| d.id == view_id && d.released_at.is_none())
{
// update the dragging offset if the view is dragging and not released
dragging.offset = vec2;
// update the mouse position if the view is dragging and not released
dragging.offset = drag_start.to_vec2();
self.app_state.request_paint(view_id);
} else if vec2.x.abs() + vec2.y.abs() > 1.0 {
} else if offset.x.abs() + offset.y.abs() > 1.0 {
// start dragging when moved 1 px
self.app_state.active = None;
self.update_active(view_id);
self.app_state.dragging = Some(DragState {
id: view_id,
offset: vec2,
offset: drag_start.to_vec2(),
released_at: None,
release_location: None,
});
self.update_active(view_id);
self.app_state.request_paint(view_id);
view_id.apply_event(&EventListener::DragStart, &event);
}
Expand Down Expand Up @@ -312,6 +314,7 @@ impl<'a> EventCx<'a> {
{
let dragging_id = dragging.id;
dragging.released_at = Some(Instant::now());
dragging.release_location = Some(pointer_event.pos);
self.app_state.request_paint(view_id);
dragging_id.apply_event(&EventListener::DragEnd, &event);
}
Expand Down Expand Up @@ -1018,41 +1021,50 @@ impl<'a> PaintCx<'a> {
paint_border(self, &layout_props, &view_style_props, size);
paint_outline(self, &view_style_props, size)
}

let mut drag_set_to_none = false;

if let Some(dragging) = self.app_state.dragging.as_ref() {
if dragging.id == id {
let dragging_offset = dragging.offset;
let mut offset_scale = None;
if let Some(released_at) = dragging.released_at {
const LIMIT: f64 = 300.0;
let transform = if let Some((released_at, release_location)) =
dragging.released_at.zip(dragging.release_location)
{
let easing = Linear;
const ANIMATION_DURATION_MS: f64 = 300.0;
let elapsed = released_at.elapsed().as_millis() as f64;
if elapsed < LIMIT {
offset_scale = Some(1.0 - elapsed / LIMIT);
let progress = elapsed / ANIMATION_DURATION_MS;

if !(easing.finished(progress)) {
let offset_scale = 1.0 - easing.eval(progress);
let release_offset = release_location.to_vec2() - dragging.offset;

// Schedule next animation frame
exec_after(Duration::from_millis(8), move |_| {
id.request_paint();
});

Some(self.transform * Affine::translate(release_offset * offset_scale))
} else {
drag_set_to_none = true;
None
}
} else {
offset_scale = Some(1.0);
}
// Handle active dragging
let translation =
self.app_state.last_cursor_location.to_vec2() - dragging.offset;
Some(self.transform.with_translation(translation))
};

if let Some(offset_scale) = offset_scale {
let offset = dragging_offset * offset_scale;
if let Some(transform) = transform {
self.save();

let mut new = self.transform.as_coeffs();
new[4] += offset.x;
new[5] += offset.y;
self.transform = Affine::new(new);
self.transform = transform;
self.paint_state.renderer_mut().transform(self.transform);
self.set_z_index(1000);
self.clear_clip();

// Apply styles
let style = view_state.borrow().combined_style.clone();
let mut view_style_props = view_state.borrow().view_style_props.clone();

if let Some(dragging_style) = view_state.borrow().dragging_style.clone() {
let style = style.apply(dragging_style);
let mut _new_frame = false;
Expand All @@ -1063,27 +1075,31 @@ impl<'a> PaintCx<'a> {
&mut _new_frame,
);
}

// Paint with drag styling
let layout_props = view_state.borrow().layout_props.clone();

// Important: If any method early exit points are added in this
// code block, they MUST call CURRENT_DRAG_PAINTING_ID.take() before
// returning.

CURRENT_DRAG_PAINTING_ID.set(Some(id));
paint_bg(self, &view_style_props, size);

paint_bg(self, &view_style_props, size);
view.borrow_mut().paint(self);
paint_border(self, &layout_props, &view_style_props, size);
paint_outline(self, &view_style_props, size);

self.restore();

CURRENT_DRAG_PAINTING_ID.take();
}
}
}

if drag_set_to_none {
self.app_state.dragging = None;
}

self.restore();
}

Expand Down
4 changes: 2 additions & 2 deletions src/easing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub trait Easing: std::fmt::Debug {
None
}
fn finished(&self, time: f64) -> bool {
time >= 1. || time <= 0.
!(0. ..1.).contains(&time)
}
}

Expand Down Expand Up @@ -213,7 +213,7 @@ impl Spring {
}
}

const THRESHOLD: f64 = 0.005;
pub const THRESHOLD: f64 = 0.005;
pub fn finished(&self, time: f64) -> bool {
let position = self.eval(time);
let velocity = self.velocity(time);
Expand Down
7 changes: 6 additions & 1 deletion src/window_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,12 @@ impl WindowHandle {
app_state: &mut self.app_state,
};

let is_pointer_move = matches!(&event, Event::PointerMove(_));
let is_pointer_move = if let Event::PointerMove(pme) = &event {
cx.app_state.last_cursor_location = pme.pos;
true
} else {
false
};
let (was_hovered, was_dragging_over) = if is_pointer_move {
cx.app_state.cursor = None;
let was_hovered = std::mem::take(&mut cx.app_state.hovered);
Expand Down

0 comments on commit 4658e65

Please sign in to comment.