mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 06:28:11 +08:00
Desktop: UI scale preference (#3475)
* ui scale preference * cleanup * add update ui scale message to SIDE_EFFECT_FREE_MESSAGES * fix mac title bar height * hide UI preference section on web * set % as unit of ui scale
This commit is contained in:
+7
-1
@@ -27,6 +27,7 @@ pub(crate) struct App {
|
|||||||
window_size: PhysicalSize<u32>,
|
window_size: PhysicalSize<u32>,
|
||||||
window_maximized: bool,
|
window_maximized: bool,
|
||||||
window_fullscreen: bool,
|
window_fullscreen: bool,
|
||||||
|
ui_scale: f64,
|
||||||
app_event_receiver: Receiver<AppEvent>,
|
app_event_receiver: Receiver<AppEvent>,
|
||||||
app_event_scheduler: AppEventScheduler,
|
app_event_scheduler: AppEventScheduler,
|
||||||
desktop_wrapper: DesktopWrapper,
|
desktop_wrapper: DesktopWrapper,
|
||||||
@@ -83,6 +84,7 @@ impl App {
|
|||||||
window_size: PhysicalSize { width: 0, height: 0 },
|
window_size: PhysicalSize { width: 0, height: 0 },
|
||||||
window_maximized: false,
|
window_maximized: false,
|
||||||
window_fullscreen: false,
|
window_fullscreen: false,
|
||||||
|
ui_scale: 1.,
|
||||||
app_event_receiver,
|
app_event_receiver,
|
||||||
app_event_scheduler,
|
app_event_scheduler,
|
||||||
desktop_wrapper: DesktopWrapper::new(),
|
desktop_wrapper: DesktopWrapper::new(),
|
||||||
@@ -119,7 +121,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let size = window.surface_size();
|
let size = window.surface_size();
|
||||||
let scale = window.scale_factor();
|
let scale = window.scale_factor() * self.ui_scale;
|
||||||
let is_new_size = size != self.window_size;
|
let is_new_size = size != self.window_size;
|
||||||
let is_new_scale = scale != self.window_scale;
|
let is_new_scale = scale != self.window_scale;
|
||||||
|
|
||||||
@@ -228,6 +230,10 @@ impl App {
|
|||||||
render_state.set_viewport_scale([viewport_scale_x as f32, viewport_scale_y as f32]);
|
render_state.set_viewport_scale([viewport_scale_x as f32, viewport_scale_y as f32]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DesktopFrontendMessage::UpdateUIScale { scale } => {
|
||||||
|
self.ui_scale = scale;
|
||||||
|
self.resize();
|
||||||
|
}
|
||||||
DesktopFrontendMessage::UpdateOverlays(scene) => {
|
DesktopFrontendMessage::UpdateOverlays(scene) => {
|
||||||
if let Some(render_state) = &mut self.render_state {
|
if let Some(render_state) = &mut self.render_state {
|
||||||
render_state.set_overlays_scene(scene);
|
render_state.set_overlays_scene(scene);
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
|||||||
FrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height } => {
|
FrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height } => {
|
||||||
dispatcher.respond(DesktopFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height });
|
dispatcher.respond(DesktopFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height });
|
||||||
}
|
}
|
||||||
|
FrontendMessage::UpdateUIScale { scale } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::UpdateUIScale { scale });
|
||||||
|
return Some(FrontendMessage::UpdateUIScale { scale });
|
||||||
|
}
|
||||||
FrontendMessage::TriggerPersistenceWriteDocument { document_id, document, details } => {
|
FrontendMessage::TriggerPersistenceWriteDocument { document_id, document, details } => {
|
||||||
dispatcher.respond(DesktopFrontendMessage::PersistenceWriteDocument {
|
dispatcher.respond(DesktopFrontendMessage::PersistenceWriteDocument {
|
||||||
id: document_id,
|
id: document_id,
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ pub enum DesktopFrontendMessage {
|
|||||||
width: f64,
|
width: f64,
|
||||||
height: f64,
|
height: f64,
|
||||||
},
|
},
|
||||||
|
UpdateUIScale {
|
||||||
|
scale: f64,
|
||||||
|
},
|
||||||
UpdateOverlays(vello::Scene),
|
UpdateOverlays(vello::Scene),
|
||||||
PersistenceWriteDocument {
|
PersistenceWriteDocument {
|
||||||
id: DocumentId,
|
id: DocumentId,
|
||||||
|
|||||||
@@ -158,3 +158,8 @@ pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;
|
|||||||
|
|
||||||
// INPUT
|
// INPUT
|
||||||
pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
|
pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
|
||||||
|
|
||||||
|
// UI
|
||||||
|
pub const UI_SCALE_DEFAULT: f64 = 1.;
|
||||||
|
pub const UI_SCALE_MIN: f64 = 0.5;
|
||||||
|
pub const UI_SCALE_MAX: f64 = 3.;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
|||||||
))),
|
))),
|
||||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitActiveGraphRender),
|
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitActiveGraphRender),
|
||||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
||||||
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateUIScale),
|
||||||
];
|
];
|
||||||
/// Since we don't need to update the frontend multiple times per frame,
|
/// Since we don't need to update the frontend multiple times per frame,
|
||||||
/// we have a set of messages which we will buffer until the next frame is requested.
|
/// we have a set of messages which we will buffer until the next frame is requested.
|
||||||
|
|||||||
+247
-214
@@ -1,4 +1,4 @@
|
|||||||
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
|
use crate::consts::{UI_SCALE_DEFAULT, UI_SCALE_MAX, UI_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
@@ -36,250 +36,273 @@ impl PreferencesDialogMessageHandler {
|
|||||||
const TITLE: &'static str = "Editor Preferences";
|
const TITLE: &'static str = "Editor Preferences";
|
||||||
|
|
||||||
fn layout(&self, preferences: &PreferencesMessageHandler) -> Layout {
|
fn layout(&self, preferences: &PreferencesMessageHandler) -> Layout {
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
|
||||||
// ==========
|
// ==========
|
||||||
// NAVIGATION
|
// NAVIGATION
|
||||||
// ==========
|
// ==========
|
||||||
|
{
|
||||||
|
let header = vec![TextLabel::new("Navigation").italic(true).widget_instance()];
|
||||||
|
|
||||||
let navigation_header = vec![TextLabel::new("Navigation").italic(true).widget_instance()];
|
let zoom_rate_description = "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_instance(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
TextLabel::new("Zoom Rate").tooltip_label("Zoom Rate").tooltip_description(zoom_rate_description).widget_instance(),
|
||||||
|
];
|
||||||
|
let zoom_rate = vec![
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
NumberInput::new(Some(map_zoom_rate_to_display(preferences.viewport_zoom_wheel_rate)))
|
||||||
|
.tooltip_label("Zoom Rate")
|
||||||
|
.tooltip_description(zoom_rate_description)
|
||||||
|
.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_instance(),
|
||||||
|
];
|
||||||
|
|
||||||
let zoom_rate_description = "Adjust how fast zooming occurs when using the scroll wheel or pinch gesture (relative to a default of 50).";
|
let checkbox_id = CheckboxId::new();
|
||||||
let zoom_rate_label = vec![
|
let zoom_with_scroll_description = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads).";
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
let zoom_with_scroll = vec![
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
TextLabel::new("Zoom Rate").tooltip_label("Zoom Rate").tooltip_description(zoom_rate_description).widget_instance(),
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
];
|
CheckboxInput::new(preferences.zoom_with_scroll)
|
||||||
let zoom_rate = vec![
|
.tooltip_label("Zoom with Scroll")
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
.tooltip_description(zoom_with_scroll_description)
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
.on_update(|checkbox_input: &CheckboxInput| {
|
||||||
NumberInput::new(Some(map_zoom_rate_to_display(preferences.viewport_zoom_wheel_rate)))
|
PreferencesMessage::ModifyLayout {
|
||||||
.tooltip_label("Zoom Rate")
|
zoom_with_scroll: checkbox_input.checked,
|
||||||
.tooltip_description(zoom_rate_description)
|
}
|
||||||
.mode_range()
|
.into()
|
||||||
.int()
|
})
|
||||||
.min(1.)
|
.for_label(checkbox_id)
|
||||||
.max(100.)
|
.widget_instance(),
|
||||||
.on_update(|number_input: &NumberInput| {
|
TextLabel::new("Zoom with Scroll")
|
||||||
if let Some(display_value) = number_input.value {
|
.tooltip_label("Zoom with Scroll")
|
||||||
let actual_rate = map_display_to_zoom_rate(display_value);
|
.tooltip_description(zoom_with_scroll_description)
|
||||||
PreferencesMessage::ViewportZoomWheelRate { rate: actual_rate }.into()
|
.for_checkbox(checkbox_id)
|
||||||
} else {
|
.widget_instance(),
|
||||||
PreferencesMessage::ViewportZoomWheelRate { rate: VIEWPORT_ZOOM_WHEEL_RATE }.into()
|
];
|
||||||
}
|
|
||||||
})
|
|
||||||
.widget_instance(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let checkbox_id = CheckboxId::new();
|
rows.extend_from_slice(&[header, zoom_rate_label, zoom_rate, zoom_with_scroll]);
|
||||||
let zoom_with_scroll_description = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads).";
|
}
|
||||||
let zoom_with_scroll = vec![
|
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
|
||||||
CheckboxInput::new(preferences.zoom_with_scroll)
|
|
||||||
.tooltip_label("Zoom with Scroll")
|
|
||||||
.tooltip_description(zoom_with_scroll_description)
|
|
||||||
.on_update(|checkbox_input: &CheckboxInput| {
|
|
||||||
PreferencesMessage::ModifyLayout {
|
|
||||||
zoom_with_scroll: checkbox_input.checked,
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.for_label(checkbox_id)
|
|
||||||
.widget_instance(),
|
|
||||||
TextLabel::new("Zoom with Scroll")
|
|
||||||
.tooltip_label("Zoom with Scroll")
|
|
||||||
.tooltip_description(zoom_with_scroll_description)
|
|
||||||
.for_checkbox(checkbox_id)
|
|
||||||
.widget_instance(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// =======
|
// =======
|
||||||
// EDITING
|
// EDITING
|
||||||
// =======
|
// =======
|
||||||
|
{
|
||||||
|
let header = vec![TextLabel::new("Editing").italic(true).widget_instance()];
|
||||||
|
|
||||||
let editing_header = vec![TextLabel::new("Editing").italic(true).widget_instance()];
|
let selection_label = vec![
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
TextLabel::new("Selection")
|
||||||
|
.tooltip_label("Selection")
|
||||||
|
.tooltip_description("Choose how targets are selected within dragged rectangular and lasso areas.")
|
||||||
|
.widget_instance(),
|
||||||
|
];
|
||||||
|
|
||||||
let selection_label = vec![
|
let selection_mode = RadioInput::new(vec![
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
RadioEntryData::new(SelectionMode::Touched.to_string())
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
.label(SelectionMode::Touched.to_string())
|
||||||
TextLabel::new("Selection")
|
.tooltip_label(SelectionMode::Touched.to_string())
|
||||||
.tooltip_label("Selection")
|
.tooltip_description(SelectionMode::Touched.tooltip_description())
|
||||||
.tooltip_description("Choose how targets are selected within dragged rectangular and lasso areas.")
|
.on_update(move |_| {
|
||||||
.widget_instance(),
|
PreferencesMessage::SelectionMode {
|
||||||
];
|
selection_mode: SelectionMode::Touched,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
RadioEntryData::new(SelectionMode::Enclosed.to_string())
|
||||||
|
.label(SelectionMode::Enclosed.to_string())
|
||||||
|
.tooltip_label(SelectionMode::Enclosed.to_string())
|
||||||
|
.tooltip_description(SelectionMode::Enclosed.tooltip_description())
|
||||||
|
.on_update(move |_| {
|
||||||
|
PreferencesMessage::SelectionMode {
|
||||||
|
selection_mode: SelectionMode::Enclosed,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
RadioEntryData::new(SelectionMode::Directional.to_string())
|
||||||
|
.label(SelectionMode::Directional.to_string())
|
||||||
|
.tooltip_label(SelectionMode::Directional.to_string())
|
||||||
|
.tooltip_description(SelectionMode::Directional.tooltip_description())
|
||||||
|
.on_update(move |_| {
|
||||||
|
PreferencesMessage::SelectionMode {
|
||||||
|
selection_mode: SelectionMode::Directional,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.selected_index(Some(preferences.selection_mode as u32))
|
||||||
|
.widget_instance();
|
||||||
|
let selection_mode = vec![
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
selection_mode,
|
||||||
|
];
|
||||||
|
|
||||||
let selection_mode = RadioInput::new(vec![
|
rows.extend_from_slice(&[header, selection_label, selection_mode]);
|
||||||
RadioEntryData::new(SelectionMode::Touched.to_string())
|
}
|
||||||
.label(SelectionMode::Touched.to_string())
|
|
||||||
.tooltip_label(SelectionMode::Touched.to_string())
|
// ==========
|
||||||
.tooltip_description(SelectionMode::Touched.tooltip_description())
|
// UI
|
||||||
.on_update(move |_| {
|
// ==========
|
||||||
PreferencesMessage::SelectionMode {
|
#[cfg(not(target_family = "wasm"))]
|
||||||
selection_mode: SelectionMode::Touched,
|
{
|
||||||
}
|
let header = vec![TextLabel::new("UI").italic(true).widget_instance()];
|
||||||
.into()
|
|
||||||
}),
|
let scale_description = "Adjust the scale of the user interface (100 is default).";
|
||||||
RadioEntryData::new(SelectionMode::Enclosed.to_string())
|
let scale_label = vec![
|
||||||
.label(SelectionMode::Enclosed.to_string())
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
.tooltip_label(SelectionMode::Enclosed.to_string())
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
.tooltip_description(SelectionMode::Enclosed.tooltip_description())
|
TextLabel::new("Scale").tooltip_label("Scale").tooltip_description(scale_description).widget_instance(),
|
||||||
.on_update(move |_| {
|
];
|
||||||
PreferencesMessage::SelectionMode {
|
let scale = vec![
|
||||||
selection_mode: SelectionMode::Enclosed,
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
}
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
.into()
|
NumberInput::new(Some(ui_scale_to_display(preferences.ui_scale)))
|
||||||
}),
|
.tooltip_label("Scale")
|
||||||
RadioEntryData::new(SelectionMode::Directional.to_string())
|
.tooltip_description(scale_description)
|
||||||
.label(SelectionMode::Directional.to_string())
|
.mode_range()
|
||||||
.tooltip_label(SelectionMode::Directional.to_string())
|
.int()
|
||||||
.tooltip_description(SelectionMode::Directional.tooltip_description())
|
.min(ui_scale_to_display(UI_SCALE_MIN))
|
||||||
.on_update(move |_| {
|
.max(ui_scale_to_display(UI_SCALE_MAX))
|
||||||
PreferencesMessage::SelectionMode {
|
.unit("%")
|
||||||
selection_mode: SelectionMode::Directional,
|
.on_update(|number_input: &NumberInput| {
|
||||||
}
|
if let Some(display_value) = number_input.value {
|
||||||
.into()
|
let scale = map_display_to_ui_scale(display_value);
|
||||||
}),
|
PreferencesMessage::UIScale { scale }.into()
|
||||||
])
|
} else {
|
||||||
.selected_index(Some(preferences.selection_mode as u32))
|
PreferencesMessage::UIScale { scale: UI_SCALE_DEFAULT }.into()
|
||||||
.widget_instance();
|
}
|
||||||
let selection_mode = vec![
|
})
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
.widget_instance(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
];
|
||||||
selection_mode,
|
|
||||||
];
|
rows.extend_from_slice(&[header, scale_label, scale]);
|
||||||
|
}
|
||||||
|
|
||||||
// ============
|
// ============
|
||||||
// EXPERIMENTAL
|
// EXPERIMENTAL
|
||||||
// ============
|
// ============
|
||||||
|
{
|
||||||
|
let header = vec![TextLabel::new("Experimental").italic(true).widget_instance()];
|
||||||
|
|
||||||
let experimental_header = vec![TextLabel::new("Experimental").italic(true).widget_instance()];
|
let node_graph_section_description = "Configure the appearance of the wires running between node connections in the graph.";
|
||||||
|
let node_graph_wires_label = vec![
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
TextLabel::new("Node Graph Wires")
|
||||||
|
.tooltip_label("Node Graph Wires")
|
||||||
|
.tooltip_description(node_graph_section_description)
|
||||||
|
.widget_instance(),
|
||||||
|
];
|
||||||
|
let graph_wire_style = RadioInput::new(vec![
|
||||||
|
RadioEntryData::new(GraphWireStyle::Direct.to_string())
|
||||||
|
.label(GraphWireStyle::Direct.to_string())
|
||||||
|
.tooltip_label(GraphWireStyle::Direct.to_string())
|
||||||
|
.tooltip_description(GraphWireStyle::Direct.tooltip_description())
|
||||||
|
.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::Direct }.into()),
|
||||||
|
RadioEntryData::new(GraphWireStyle::GridAligned.to_string())
|
||||||
|
.label(GraphWireStyle::GridAligned.to_string())
|
||||||
|
.tooltip_label(GraphWireStyle::GridAligned.to_string())
|
||||||
|
.tooltip_description(GraphWireStyle::GridAligned.tooltip_description())
|
||||||
|
.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::GridAligned }.into()),
|
||||||
|
])
|
||||||
|
.selected_index(Some(preferences.graph_wire_style as u32))
|
||||||
|
.widget_instance();
|
||||||
|
let graph_wire_style = vec![
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
|
graph_wire_style,
|
||||||
|
];
|
||||||
|
|
||||||
let node_graph_section_description = "Configure the appearance of the wires running between node connections in the graph.";
|
let checkbox_id = CheckboxId::new();
|
||||||
let node_graph_wires_label = vec![
|
let vello_description = "Use the experimental Vello renderer instead of SVG-based rendering.".to_string();
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
#[cfg(target_family = "wasm")]
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
let mut vello_description = vello_description;
|
||||||
TextLabel::new("Node Graph Wires")
|
#[cfg(target_family = "wasm")]
|
||||||
.tooltip_label("Node Graph Wires")
|
vello_description.push_str("\n\n(Your browser must support WebGPU.)");
|
||||||
.tooltip_description(node_graph_section_description)
|
|
||||||
.widget_instance(),
|
|
||||||
];
|
|
||||||
let graph_wire_style = RadioInput::new(vec![
|
|
||||||
RadioEntryData::new(GraphWireStyle::Direct.to_string())
|
|
||||||
.label(GraphWireStyle::Direct.to_string())
|
|
||||||
.tooltip_label(GraphWireStyle::Direct.to_string())
|
|
||||||
.tooltip_description(GraphWireStyle::Direct.tooltip_description())
|
|
||||||
.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::Direct }.into()),
|
|
||||||
RadioEntryData::new(GraphWireStyle::GridAligned.to_string())
|
|
||||||
.label(GraphWireStyle::GridAligned.to_string())
|
|
||||||
.tooltip_label(GraphWireStyle::GridAligned.to_string())
|
|
||||||
.tooltip_description(GraphWireStyle::GridAligned.tooltip_description())
|
|
||||||
.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::GridAligned }.into()),
|
|
||||||
])
|
|
||||||
.selected_index(Some(preferences.graph_wire_style as u32))
|
|
||||||
.widget_instance();
|
|
||||||
let graph_wire_style = vec![
|
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
|
||||||
graph_wire_style,
|
|
||||||
];
|
|
||||||
|
|
||||||
let checkbox_id = CheckboxId::new();
|
let use_vello = vec![
|
||||||
let vello_description = "Use the experimental Vello renderer instead of SVG-based rendering.".to_string();
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
#[cfg(target_family = "wasm")]
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
let mut vello_description = vello_description;
|
CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu())
|
||||||
#[cfg(target_family = "wasm")]
|
.tooltip_label("Vello Renderer")
|
||||||
vello_description.push_str("\n\n(Your browser must support WebGPU.)");
|
.tooltip_description(vello_description.clone())
|
||||||
|
.disabled(!preferences.supports_wgpu())
|
||||||
|
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
|
||||||
|
.for_label(checkbox_id)
|
||||||
|
.widget_instance(),
|
||||||
|
TextLabel::new("Vello Renderer")
|
||||||
|
.tooltip_label("Vello Renderer")
|
||||||
|
.tooltip_description(vello_description)
|
||||||
|
.disabled(!preferences.supports_wgpu())
|
||||||
|
.for_checkbox(checkbox_id)
|
||||||
|
.widget_instance(),
|
||||||
|
];
|
||||||
|
|
||||||
let use_vello = vec![
|
let checkbox_id = CheckboxId::new();
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
let vector_mesh_description = "
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
|
||||||
CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu())
|
|
||||||
.tooltip_label("Vello Renderer")
|
|
||||||
.tooltip_description(vello_description.clone())
|
|
||||||
.disabled(!preferences.supports_wgpu())
|
|
||||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
|
|
||||||
.for_label(checkbox_id)
|
|
||||||
.widget_instance(),
|
|
||||||
TextLabel::new("Vello Renderer")
|
|
||||||
.tooltip_label("Vello Renderer")
|
|
||||||
.tooltip_description(vello_description)
|
|
||||||
.disabled(!preferences.supports_wgpu())
|
|
||||||
.for_checkbox(checkbox_id)
|
|
||||||
.widget_instance(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let checkbox_id = CheckboxId::new();
|
|
||||||
let vector_mesh_description = "
|
|
||||||
Allow the Pen tool to produce branching geometry, where more than two segments may be connected to one anchor point.\n\
|
Allow the Pen tool to produce branching geometry, where more than two segments may be connected to one anchor point.\n\
|
||||||
\n\
|
\n\
|
||||||
Currently, vector meshes do not properly render strokes (branching joins) and fills (multiple regions).
|
Currently, vector meshes do not properly render strokes (branching joins) and fills (multiple regions).
|
||||||
"
|
"
|
||||||
.trim();
|
.trim();
|
||||||
let vector_meshes = vec![
|
let vector_meshes = vec![
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
CheckboxInput::new(preferences.vector_meshes)
|
CheckboxInput::new(preferences.vector_meshes)
|
||||||
.tooltip_label("Vector Meshes")
|
.tooltip_label("Vector Meshes")
|
||||||
.tooltip_description(vector_mesh_description)
|
.tooltip_description(vector_mesh_description)
|
||||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
|
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
|
||||||
.for_label(checkbox_id)
|
.for_label(checkbox_id)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
TextLabel::new("Vector Meshes")
|
TextLabel::new("Vector Meshes")
|
||||||
.tooltip_label("Vector Meshes")
|
.tooltip_label("Vector Meshes")
|
||||||
.tooltip_description(vector_mesh_description)
|
.tooltip_description(vector_mesh_description)
|
||||||
.for_checkbox(checkbox_id)
|
.for_checkbox(checkbox_id)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let checkbox_id = CheckboxId::new();
|
let checkbox_id = CheckboxId::new();
|
||||||
let brush_tool_description = "
|
let brush_tool_description = "
|
||||||
Enable the Brush tool to support basic raster-based layer painting.\n\
|
Enable the Brush tool to support basic raster-based layer painting.\n\
|
||||||
\n\
|
\n\
|
||||||
This legacy tool has performance and quality limitations and is slated for replacement in future versions of Graphite that will focus on raster graphics editing.
|
This legacy tool has performance and quality limitations and is slated for replacement in future versions of Graphite that will focus on raster graphics editing.
|
||||||
"
|
"
|
||||||
.trim();
|
.trim();
|
||||||
let brush_tool = vec![
|
let brush_tool = vec![
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||||
CheckboxInput::new(preferences.brush_tool)
|
CheckboxInput::new(preferences.brush_tool)
|
||||||
.tooltip_label("Brush Tool")
|
.tooltip_label("Brush Tool")
|
||||||
.tooltip_description(brush_tool_description)
|
.tooltip_description(brush_tool_description)
|
||||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::BrushTool { enabled: checkbox_input.checked }.into())
|
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::BrushTool { enabled: checkbox_input.checked }.into())
|
||||||
.for_label(checkbox_id)
|
.for_label(checkbox_id)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
TextLabel::new("Brush Tool")
|
TextLabel::new("Brush Tool")
|
||||||
.tooltip_label("Brush Tool")
|
.tooltip_label("Brush Tool")
|
||||||
.tooltip_description(brush_tool_description)
|
.tooltip_description(brush_tool_description)
|
||||||
.for_checkbox(checkbox_id)
|
.for_checkbox(checkbox_id)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![
|
rows.extend_from_slice(&[header, node_graph_wires_label, graph_wire_style, use_vello, vector_meshes, brush_tool]);
|
||||||
// NAVIGATION
|
}
|
||||||
LayoutGroup::Row { widgets: navigation_header },
|
|
||||||
// Navigation: Zoom Rate
|
Layout(rows.into_iter().map(|r| LayoutGroup::Row { widgets: r }).collect())
|
||||||
LayoutGroup::Row { widgets: zoom_rate_label },
|
|
||||||
LayoutGroup::Row { widgets: zoom_rate },
|
|
||||||
// Navigation: Zoom with Scroll
|
|
||||||
LayoutGroup::Row { widgets: zoom_with_scroll },
|
|
||||||
//
|
|
||||||
// EDITING
|
|
||||||
LayoutGroup::Row { widgets: editing_header },
|
|
||||||
// Editing: Selection
|
|
||||||
LayoutGroup::Row { widgets: selection_label },
|
|
||||||
LayoutGroup::Row { widgets: selection_mode },
|
|
||||||
//
|
|
||||||
// EXPERIMENTAL
|
|
||||||
LayoutGroup::Row { widgets: experimental_header },
|
|
||||||
// Experimental: Node Graph Wires
|
|
||||||
LayoutGroup::Row { widgets: node_graph_wires_label },
|
|
||||||
LayoutGroup::Row { widgets: graph_wire_style },
|
|
||||||
// Experimental: Vello Renderer
|
|
||||||
LayoutGroup::Row { widgets: use_vello },
|
|
||||||
// Experimental: Vector Meshes
|
|
||||||
LayoutGroup::Row { widgets: vector_meshes },
|
|
||||||
// Experimental: Brush Tool
|
|
||||||
LayoutGroup::Row { widgets: brush_tool },
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) {
|
pub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) {
|
||||||
@@ -351,3 +374,13 @@ fn map_zoom_rate_to_display(rate: f64) -> f64 {
|
|||||||
let display = 50. + distance_from_reference;
|
let display = 50. + distance_from_reference;
|
||||||
display.clamp(1., 100.).round()
|
display.clamp(1., 100.).round()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maps display values in percent to actual ui scale.
|
||||||
|
fn map_display_to_ui_scale(display: f64) -> f64 {
|
||||||
|
display / 100.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps actual ui scale back to display values in percent.
|
||||||
|
fn ui_scale_to_display(scale: f64) -> f64 {
|
||||||
|
scale * 100.
|
||||||
|
}
|
||||||
|
|||||||
@@ -338,6 +338,9 @@ pub enum FrontendMessage {
|
|||||||
width: f64,
|
width: f64,
|
||||||
height: f64,
|
height: f64,
|
||||||
},
|
},
|
||||||
|
UpdateUIScale {
|
||||||
|
scale: f64,
|
||||||
|
},
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
RenderOverlays {
|
RenderOverlays {
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ pub enum PreferencesMessage {
|
|||||||
ModifyLayout { zoom_with_scroll: bool },
|
ModifyLayout { zoom_with_scroll: bool },
|
||||||
GraphWireStyle { style: GraphWireStyle },
|
GraphWireStyle { style: GraphWireStyle },
|
||||||
ViewportZoomWheelRate { rate: f64 },
|
ViewportZoomWheelRate { rate: f64 },
|
||||||
|
UIScale { scale: f64 },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
|
use crate::consts::{UI_SCALE_DEFAULT, VIEWPORT_ZOOM_WHEEL_RATE};
|
||||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
@@ -14,6 +14,7 @@ pub struct PreferencesMessageHandler {
|
|||||||
pub brush_tool: bool,
|
pub brush_tool: bool,
|
||||||
pub graph_wire_style: GraphWireStyle,
|
pub graph_wire_style: GraphWireStyle,
|
||||||
pub viewport_zoom_wheel_rate: f64,
|
pub viewport_zoom_wheel_rate: f64,
|
||||||
|
pub ui_scale: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreferencesMessageHandler {
|
impl PreferencesMessageHandler {
|
||||||
@@ -42,6 +43,7 @@ impl Default for PreferencesMessageHandler {
|
|||||||
brush_tool: false,
|
brush_tool: false,
|
||||||
graph_wire_style: GraphWireStyle::default(),
|
graph_wire_style: GraphWireStyle::default(),
|
||||||
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
|
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
|
||||||
|
ui_scale: UI_SCALE_DEFAULT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +63,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||||||
responses.add(PreferencesMessage::ModifyLayout {
|
responses.add(PreferencesMessage::ModifyLayout {
|
||||||
zoom_with_scroll: self.zoom_with_scroll,
|
zoom_with_scroll: self.zoom_with_scroll,
|
||||||
});
|
});
|
||||||
|
responses.add(FrontendMessage::UpdateUIScale { scale: self.ui_scale });
|
||||||
}
|
}
|
||||||
PreferencesMessage::ResetToDefaults => {
|
PreferencesMessage::ResetToDefaults => {
|
||||||
refresh_dialog(responses);
|
refresh_dialog(responses);
|
||||||
@@ -99,6 +102,10 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||||||
PreferencesMessage::ViewportZoomWheelRate { rate } => {
|
PreferencesMessage::ViewportZoomWheelRate { rate } => {
|
||||||
self.viewport_zoom_wheel_rate = rate;
|
self.viewport_zoom_wheel_rate = rate;
|
||||||
}
|
}
|
||||||
|
PreferencesMessage::UIScale { scale } => {
|
||||||
|
self.ui_scale = scale;
|
||||||
|
responses.add(FrontendMessage::UpdateUIScale { scale: self.ui_scale });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });
|
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
let menuBarLayout: Layout = [];
|
let menuBarLayout: Layout = [];
|
||||||
|
|
||||||
|
// On mac menu bar needs to be scaled with inverse of UI scale to match native menu buttons.
|
||||||
|
$: height = $appWindow.platform === "Mac" ? 28 * (1 / $appWindow.uiScale) : 28;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
|
editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
|
||||||
patchLayout(menuBarLayout, updateMenuBarLayout);
|
patchLayout(menuBarLayout, updateMenuBarLayout);
|
||||||
@@ -25,7 +28,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="title-bar">
|
<LayoutRow class="title-bar" styles={{ height: height + "px" }}>
|
||||||
<!-- Menu bar -->
|
<!-- Menu bar -->
|
||||||
<LayoutRow>
|
<LayoutRow>
|
||||||
{#if $appWindow.platform !== "Mac"}
|
{#if $appWindow.platform !== "Mac"}
|
||||||
@@ -48,7 +51,6 @@
|
|||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
.title-bar {
|
.title-bar {
|
||||||
height: 28px;
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
> .layout-row {
|
> .layout-row {
|
||||||
|
|||||||
@@ -319,6 +319,10 @@ export class UpdateViewportPhysicalBounds extends JsMessage {
|
|||||||
readonly height!: number;
|
readonly height!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateUIScale extends JsMessage {
|
||||||
|
readonly scale!: number;
|
||||||
|
}
|
||||||
|
|
||||||
// Rust enum `Key`
|
// Rust enum `Key`
|
||||||
export type KeyRaw = string;
|
export type KeyRaw = string;
|
||||||
// Serde converts a Rust `Key` enum variant into this format with both the `Key` variant name (called `RawKey` in TS) and the localized `label` for the key
|
// Serde converts a Rust `Key` enum variant into this format with both the `Key` variant name (called `RawKey` in TS) and the localized `label` for the key
|
||||||
@@ -1766,6 +1770,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
UpdateToolShelfLayout,
|
UpdateToolShelfLayout,
|
||||||
UpdateViewportHolePunch,
|
UpdateViewportHolePunch,
|
||||||
UpdateViewportPhysicalBounds,
|
UpdateViewportPhysicalBounds,
|
||||||
|
UpdateUIScale,
|
||||||
UpdateVisibleNodes,
|
UpdateVisibleNodes,
|
||||||
UpdateWelcomeScreenButtonsLayout,
|
UpdateWelcomeScreenButtonsLayout,
|
||||||
UpdateWirePathInProgress,
|
UpdateWirePathInProgress,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
import { type Editor } from "@graphite/editor";
|
import { type Editor } from "@graphite/editor";
|
||||||
import { type AppWindowPlatform, UpdatePlatform, UpdateViewportHolePunch, UpdateMaximized, UpdateFullscreen } from "@graphite/messages";
|
import { type AppWindowPlatform, UpdatePlatform, UpdateViewportHolePunch, UpdateMaximized, UpdateFullscreen, UpdateUIScale } from "@graphite/messages";
|
||||||
|
|
||||||
export function createAppWindowState(editor: Editor) {
|
export function createAppWindowState(editor: Editor) {
|
||||||
const { subscribe, update } = writable({
|
const { subscribe, update } = writable({
|
||||||
@@ -9,6 +9,7 @@ export function createAppWindowState(editor: Editor) {
|
|||||||
maximized: false,
|
maximized: false,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
viewportHolePunch: false,
|
viewportHolePunch: false,
|
||||||
|
uiScale: 1.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up message subscriptions on creation
|
// Set up message subscriptions on creation
|
||||||
@@ -36,6 +37,12 @@ export function createAppWindowState(editor: Editor) {
|
|||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateUIScale, (uiScale) => {
|
||||||
|
update((state) => {
|
||||||
|
state.uiScale = uiScale.scale;
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
|
|||||||
Reference in New Issue
Block a user