Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f64 = 0.95;
pub const DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS: f64 = 50.;
pub const DRAG_BEYOND_VIEWPORT_SPEED_FACTOR: f64 = 20.;

// FLICK PANNING
pub const FLICK_VELOCITY_SAMPLES: usize = 5;
pub const FLICK_DECAY_RATE: f64 = 0.92;
pub const FLICK_MIN_VELOCITY: f64 = 0.5;
pub const FLICK_MAX_VELOCITY: f64 = 50.;

// SNAPPING POINT
pub const SNAP_POINT_TOLERANCE: f64 = 5.;
/// These are layers whose bounding boxes are used for alignment.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,25 @@ impl PreferencesDialogMessageHandler {
.widget_instance(),
];

rows.extend_from_slice(&[header, zoom_rate_label, zoom_rate, zoom_with_scroll]);
let checkbox_id = CheckboxId::new();
let flick_panning_description = "Continue gliding after releasing pan controls (similar to Photoshop).";
let flick_panning = vec![
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
CheckboxInput::new(preferences.flick_panning)
.tooltip_label("Flick Panning")
.tooltip_description(flick_panning_description)
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::FlickPanning { enabled: checkbox_input.checked }.into())
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Flick Panning")
.tooltip_label("Flick Panning")
.tooltip_description(flick_panning_description)
.for_checkbox(checkbox_id)
.widget_instance(),
];

rows.extend_from_slice(&[header, zoom_rate_label, zoom_rate, zoom_with_scroll, flick_panning]);
}

// =======
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub enum NavigationMessage {
EndCanvasPTZ { abort_transform: bool },
EndCanvasPTZWithClick { commit_key: Key },
FitViewportToBounds { bounds: [DVec2; 2], prevent_zoom_past_100: bool },
FlickPanUpdate,
FitViewportToSelection,
PointerMove { snap: Key },
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::application::Editor;
use crate::consts::{
VIEWPORT_ROTATE_SNAP_INTERVAL, VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MIN_FRACTION_COVER, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN,
VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR,
FLICK_DECAY_RATE, FLICK_MAX_VELOCITY, FLICK_MIN_VELOCITY, FLICK_VELOCITY_SAMPLES, VIEWPORT_ROTATE_SNAP_INTERVAL, VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MIN_FRACTION_COVER,
VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR,
};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
Expand Down Expand Up @@ -31,6 +31,8 @@ pub struct NavigationMessageHandler {
mouse_position: ViewportPosition,
finish_operation_with_click: bool,
abortable_pan_start: Option<f64>,
flick_position_history: VecDeque<(ViewportPosition, f64)>,
flick_velocity: DVec2,
}

#[message_handler_data]
Expand Down Expand Up @@ -79,6 +81,15 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat

HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]).send_layout(responses);

self.flick_position_history.clear();
if self.flick_velocity != DVec2::ZERO {
self.flick_velocity = DVec2::ZERO;
responses.add(BroadcastMessage::UnsubscribeEvent {
on: EventMessage::AnimationFrame,
send: Box::new(NavigationMessage::FlickPanUpdate.into()),
});
}

self.mouse_position = ipp.mouse.position;
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
Expand Down Expand Up @@ -321,9 +332,36 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
} else {
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}

let was_panning = matches!(self.navigation_operation, NavigationOperation::Pan { .. });

// Reset the navigation operation now that it's done
self.navigation_operation = NavigationOperation::None;

if was_panning && !abort_transform && preferences.flick_panning && self.flick_position_history.len() >= 2 {
let (first_pos, first_time) = self.flick_position_history.front().unwrap();
let (last_pos, last_time) = self.flick_position_history.back().unwrap();

let delta_time = last_time - first_time;
if delta_time >= 0.001 {
let delta_pos = DVec2::new(last_pos.x - first_pos.x, last_pos.y - first_pos.y);
let velocity_per_second = delta_pos / delta_time;
self.flick_velocity = velocity_per_second / 60.0;

let speed = self.flick_velocity.length();
if speed > FLICK_MAX_VELOCITY {
self.flick_velocity = self.flick_velocity.normalize() * FLICK_MAX_VELOCITY;
}

if speed >= FLICK_MIN_VELOCITY {
responses.add(BroadcastMessage::SubscribeEvent {
on: EventMessage::AnimationFrame,
send: Box::new(NavigationMessage::FlickPanUpdate.into()),
});
}
}
}

// Send the final messages to close out the operation
responses.add(EventMessage::CanvasTransformed);
responses.add(ToolMessage::UpdateCursor);
Expand Down Expand Up @@ -408,6 +446,12 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
match self.navigation_operation {
NavigationOperation::None => {}
NavigationOperation::Pan { .. } => {
let now = ipp.time as f64 / 1000.0;
self.flick_position_history.push_back((ipp.mouse.position, now));
while self.flick_position_history.len() > FLICK_VELOCITY_SAMPLES {
self.flick_position_history.pop_front();
}

let delta = ipp.mouse.position - self.mouse_position;
responses.add(NavigationMessage::CanvasPan { delta });
}
Expand Down Expand Up @@ -481,6 +525,28 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat

self.mouse_position = ipp.mouse.position;
}
NavigationMessage::FlickPanUpdate => {
if self.flick_velocity == DVec2::ZERO {
return;
}

let delta_seconds = ipp.frame_time.frame_duration().map(|d| d.as_secs_f64()).unwrap_or(1.0 / 60.0);
let safe_delta = delta_seconds.min(0.1);
self.flick_velocity *= FLICK_DECAY_RATE.powf(safe_delta * 60.0);

if self.flick_velocity.length() < FLICK_MIN_VELOCITY {
self.flick_position_history.clear();
self.flick_velocity = DVec2::ZERO;
responses.add(BroadcastMessage::UnsubscribeEvent {
on: EventMessage::AnimationFrame,
send: Box::new(NavigationMessage::FlickPanUpdate.into()),
});
return;
}

let delta = self.flick_velocity * (safe_delta * 60.0);
responses.add(NavigationMessage::CanvasPan { delta });
}
}
}

Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/preferences/preferences_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub enum PreferencesMessage {
SelectionMode { selection_mode: SelectionMode },
BrushTool { enabled: bool },
ModifyLayout { zoom_with_scroll: bool },
FlickPanning { enabled: bool },
GraphWireStyle { style: GraphWireStyle },
ViewportZoomWheelRate { rate: f64 },
UIScale { scale: f64 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct PreferencesMessageContext<'a> {
pub struct PreferencesMessageHandler {
pub selection_mode: SelectionMode,
pub zoom_with_scroll: bool,
pub flick_panning: bool,
pub use_vello: bool,
pub brush_tool: bool,
pub graph_wire_style: GraphWireStyle,
Expand Down Expand Up @@ -44,6 +45,7 @@ impl Default for PreferencesMessageHandler {
Self {
selection_mode: SelectionMode::Touched,
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
flick_panning: false,
use_vello: EditorPreferences::default().use_vello,
brush_tool: false,
graph_wire_style: GraphWireStyle::default(),
Expand Down Expand Up @@ -100,6 +102,9 @@ impl MessageHandler<PreferencesMessage, PreferencesMessageContext<'_>> for Prefe
let variant = if zoom_with_scroll { MappingVariant::ZoomWithScroll } else { MappingVariant::Default };
responses.add(KeyMappingMessage::ModifyMapping { mapping: variant });
}
PreferencesMessage::FlickPanning { enabled } => {
self.flick_panning = enabled;
}
PreferencesMessage::SelectionMode { selection_mode } => {
self.selection_mode = selection_mode;
}
Expand Down
Loading