Add option to adjust viewport zoom rate in the preferences dialog (#2420)

* zoom rate preference

* Add generic range mapping functions for improved reusability

* cleanup

* Map zoom slider's default value of 50 to the original zoom rate (0.005)

* use . instead of .0 for whole-number floats

* Refactor zoom rate mapping to use a fixed reference point and adjustable curve steepness

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Sidharth-Singh10 2025-04-08 13:21:46 +05:30 committed by GitHub
parent 6a8386d1e9
commit 3c1ec45188
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 60 additions and 2 deletions

View File

@ -16,6 +16,8 @@ pub const VIEWPORT_ZOOM_LEVELS: [f64; 74] = [
0.04, 0.05, 0.06, 0.08, 0.1, 0.125, 0.15, 0.2, 0.25, 0.33333333, 0.4, 0.5, 0.66666666, 0.8, 1., 1.25, 1.6, 2., 2.5, 3.2, 4., 5., 6.4, 8., 10., 12.5, 16., 20., 25., 32., 40., 50., 64., 80., 100.,
128., 160., 200., 256., 320., 400., 512., 640., 800., 1024., 1280., 1600., 2048., 2560.,
];
/// Higher values create a steeper curve (a faster zoom rate change)
pub const VIEWPORT_ZOOM_WHEEL_RATE_CHANGE: f64 = 3.;
/// Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function.
pub const VIEWPORT_GRID_ROUNDING_BIAS: f64 = 0.002;

View File

@ -1,3 +1,4 @@
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
use crate::messages::preferences::SelectionMode;
@ -39,6 +40,32 @@ impl PreferencesDialogMessageHandler {
let navigation_header = vec![TextLabel::new("Navigation").italic(true).widget_holder()];
let zoom_rate_tooltip = "Adjust how fast zooming occurs when using the scroll wheel or pinch gesture (relative to a default of 50)";
let zoom_rate_label = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("Zoom Rate").tooltip(zoom_rate_tooltip).widget_holder(),
];
let zoom_rate = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(map_zoom_rate_to_display(preferences.viewport_zoom_wheel_rate)))
.tooltip(zoom_rate_tooltip)
.mode_range()
.int()
.min(1.)
.max(100.)
.on_update(|number_input: &NumberInput| {
if let Some(display_value) = number_input.value {
let actual_rate = map_display_to_zoom_rate(display_value);
PreferencesMessage::ViewportZoomWheelRate { rate: actual_rate }.into()
} else {
PreferencesMessage::ViewportZoomWheelRate { rate: VIEWPORT_ZOOM_WHEEL_RATE }.into()
}
})
.widget_holder(),
];
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
let zoom_with_scroll = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
@ -184,6 +211,8 @@ impl PreferencesDialogMessageHandler {
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { widgets: navigation_header },
LayoutGroup::Row { widgets: zoom_rate_label },
LayoutGroup::Row { widgets: zoom_rate },
LayoutGroup::Row { widgets: zoom_with_scroll },
LayoutGroup::Row { widgets: editing_header },
LayoutGroup::Row { widgets: selection_label },
@ -250,3 +279,20 @@ impl PreferencesDialogMessageHandler {
});
}
}
/// Maps display values (1-100) to actual zoom rates.
fn map_display_to_zoom_rate(display: f64) -> f64 {
// Calculate the relative distance from the reference point (50)
let distance_from_reference = display - 50.;
let scaling_factor = (VIEWPORT_ZOOM_WHEEL_RATE_CHANGE * distance_from_reference / 50.).exp();
VIEWPORT_ZOOM_WHEEL_RATE * scaling_factor
}
/// Maps actual zoom rates back to display values (1-100).
fn map_zoom_rate_to_display(rate: f64) -> f64 {
// Calculate the scaling factor from the reference rate
let scaling_factor = rate / VIEWPORT_ZOOM_WHEEL_RATE;
let distance_from_reference = 50. * scaling_factor.ln() / VIEWPORT_ZOOM_WHEEL_RATE_CHANGE;
let display = 50. + distance_from_reference;
display.clamp(1., 100.).round()
}

View File

@ -193,6 +193,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
},
document_ptz: &mut self.document_ptz,
graph_view_overlay_open: self.graph_view_overlay_open,
preferences,
};
self.navigation_handler.process_message(message, responses, data);

View File

@ -1,6 +1,6 @@
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, VIEWPORT_ZOOM_WHEEL_RATE,
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};
@ -20,6 +20,7 @@ pub struct NavigationMessageData<'a> {
pub selection_bounds: Option<[DVec2; 2]>,
pub document_ptz: &'a mut PTZ,
pub graph_view_overlay_open: bool,
pub preferences: &'a PreferencesMessageHandler,
}
#[derive(Debug, Clone, PartialEq, Default)]
@ -39,6 +40,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
selection_bounds,
document_ptz,
graph_view_overlay_open,
preferences,
} = data;
fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> {
@ -228,7 +230,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
}
NavigationMessage::CanvasZoomMouseWheel => {
let scroll = ipp.mouse.scroll_delta.scroll_delta();
let mut zoom_factor = 1. + scroll.abs() * VIEWPORT_ZOOM_WHEEL_RATE;
let mut zoom_factor = 1. + scroll.abs() * preferences.viewport_zoom_wheel_rate;
if ipp.mouse.scroll_delta.y > 0. {
zoom_factor = 1. / zoom_factor
}

View File

@ -15,6 +15,7 @@ pub enum PreferencesMessage {
VectorMeshes { enabled: bool },
ModifyLayout { zoom_with_scroll: bool },
GraphWireStyle { style: GraphWireStyle },
ViewportZoomWheelRate { rate: f64 },
// ImaginateRefreshFrequency { seconds: f64 },
// ImaginateServerHostname { hostname: String },
}

View File

@ -1,3 +1,4 @@
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
use crate::messages::input_mapper::key_mapping::MappingVariant;
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
use crate::messages::preferences::SelectionMode;
@ -13,6 +14,7 @@ pub struct PreferencesMessageHandler {
pub use_vello: bool,
pub vector_meshes: bool,
pub graph_wire_style: GraphWireStyle,
pub viewport_zoom_wheel_rate: f64,
}
impl PreferencesMessageHandler {
@ -47,6 +49,7 @@ impl Default for PreferencesMessageHandler {
use_vello,
vector_meshes: false,
graph_wire_style: GraphWireStyle::default(),
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
}
}
}
@ -99,6 +102,9 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
self.graph_wire_style = style;
responses.add(NodeGraphMessage::SendGraph);
}
PreferencesMessage::ViewportZoomWheelRate { rate } => {
self.viewport_zoom_wheel_rate = rate;
}
}
// TODO: Reenable when Imaginate is restored (and move back up one line since the auto-formatter doesn't like it in that block)
// PreferencesMessage::ImaginateRefreshFrequency { seconds } => {