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
5 changes: 5 additions & 0 deletions desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@ impl App {
window.toggle_maximize();
}
}
DesktopFrontendMessage::WindowFullscreen => {
if let Some(window) = &mut self.window {
window.toggle_fullscreen();
}
}
DesktopFrontendMessage::WindowDrag => {
if let Some(window) = &self.window {
window.start_drag();
Expand Down
22 changes: 18 additions & 4 deletions desktop/src/window.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::consts::APP_NAME;
use crate::event::AppEventScheduler;
use crate::wrapper::messages::MenuItem;
use std::collections::HashMap;
use std::sync::Arc;
use winit::cursor::{CursorIcon, CustomCursor, CustomCursorSource};
use winit::event_loop::ActiveEventLoop;
use winit::monitor::Fullscreen;
use winit::window::{Window as WinitWindow, WindowAttributes};

use crate::consts::APP_NAME;
use crate::event::AppEventScheduler;
use crate::wrapper::messages::MenuItem;

pub(crate) trait NativeWindow {
fn init() {}
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes;
Expand Down Expand Up @@ -111,18 +111,32 @@ impl Window {
}

pub(crate) fn toggle_maximize(&self) {
if self.is_fullscreen() {
return;
}
self.winit_window.set_maximized(!self.winit_window.is_maximized());
}

pub(crate) fn is_maximized(&self) -> bool {
self.winit_window.is_maximized()
}

pub(crate) fn toggle_fullscreen(&mut self) {
if self.is_fullscreen() {
self.winit_window.set_fullscreen(None);
} else {
self.winit_window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}

pub(crate) fn is_fullscreen(&self) -> bool {
self.winit_window.fullscreen().is_some()
}

pub(crate) fn start_drag(&self) {
if self.is_fullscreen() {
return;
}
let _ = self.winit_window.drag_window();
}

Expand Down
30 changes: 27 additions & 3 deletions desktop/src/window/win/native_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,10 @@ unsafe fn ensure_helper_class() {
// Main window message handler, called on the UI thread for every message the main window receives.
unsafe extern "system" fn main_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
if msg == WM_NCCALCSIZE && wparam.0 != 0 {
// When maximized, shrink to visible frame so content doesn't extend beyond it.
if unsafe { IsZoomed(hwnd).as_bool() } {
let params = unsafe { &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS) };
let params = unsafe { &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS) };

// When maximized, shrink to visible frame so content doesn't extend beyond it.
if unsafe { IsZoomed(hwnd).as_bool() } && !is_effectively_fullscreen(params.rgrc[0]) {
let dpi = unsafe { GetDpiForWindow(hwnd) };
let size = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
let pad = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
Expand Down Expand Up @@ -366,3 +366,27 @@ unsafe fn calculate_resize_direction(helper: HWND, lparam: LPARAM) -> Option<u32
_ => None,
}
}

// Check if the rect is effectively fullscreen, meaning it would cover the entire monitor.
// We need to use this heuristic because Windows doesn't provide a way to check for fullscreen state.
fn is_effectively_fullscreen(rect: RECT) -> bool {
let hmon = unsafe { MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST) };
if hmon.is_invalid() {
return false;
}

let mut monitor_info = MONITORINFO {
cbSize: std::mem::size_of::<MONITORINFO>() as u32,
..Default::default()
};
if !unsafe { GetMonitorInfoW(hmon, &mut monitor_info) }.as_bool() {
return false;
}

// Allow a tiny tolerance for DPI / rounding issues
const EPS: i32 = 1;
(rect.left - monitor_info.rcMonitor.left).abs() <= EPS
&& (rect.top - monitor_info.rcMonitor.top).abs() <= EPS
&& (rect.right - monitor_info.rcMonitor.right).abs() <= EPS
&& (rect.bottom - monitor_info.rcMonitor.bottom).abs() <= EPS
}
3 changes: 3 additions & 0 deletions desktop/wrapper/src/intercept_frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
FrontendMessage::WindowMaximize => {
dispatcher.respond(DesktopFrontendMessage::WindowMaximize);
}
FrontendMessage::WindowFullscreen => {
dispatcher.respond(DesktopFrontendMessage::WindowFullscreen);
}
FrontendMessage::WindowDrag => {
dispatcher.respond(DesktopFrontendMessage::WindowDrag);
}
Expand Down
1 change: 1 addition & 0 deletions desktop/wrapper/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub enum DesktopFrontendMessage {
WindowClose,
WindowMinimize,
WindowMaximize,
WindowFullscreen,
WindowDrag,
WindowHide,
WindowHideOthers,
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/app_window/app_window_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub enum AppWindowMessage {
Close,
Minimize,
Maximize,
Fullscreen,
Drag,
Hide,
HideOthers,
Expand Down
4 changes: 4 additions & 0 deletions editor/src/messages/app_window/app_window_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
AppWindowMessage::Maximize => {
responses.add(FrontendMessage::WindowMaximize);
}
AppWindowMessage::Fullscreen => {
responses.add(FrontendMessage::WindowFullscreen);
}
AppWindowMessage::Drag => {
responses.add(FrontendMessage::WindowDrag);
}
Expand All @@ -48,6 +51,7 @@ impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
Close,
Minimize,
Maximize,
Fullscreen,
Drag,
Hide,
HideOthers,
Expand Down
5 changes: 4 additions & 1 deletion editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ pub enum FrontendMessage {
#[serde(rename = "nodeTypes")]
node_types: Vec<FrontendNodeType>,
},
SendShortcutF11 {
SendShortcutFullscreen {
shortcut: Option<ActionShortcut>,
#[serde(rename = "shortcutMc")]
shortcut_mac: Option<ActionShortcut>,
},
SendShortcutAltClick {
shortcut: Option<ActionShortcut>,
Expand Down Expand Up @@ -371,6 +373,7 @@ pub enum FrontendMessage {
WindowClose,
WindowMinimize,
WindowMaximize,
WindowFullscreen,
WindowDrag,
WindowHide,
WindowHideOthers,
Expand Down
76 changes: 19 additions & 57 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ use glam::DVec2;
impl From<MappingVariant> for Mapping {
fn from(value: MappingVariant) -> Self {
match value {
MappingVariant::Default => input_mappings(),
MappingVariant::ZoomWithScroll => zoom_with_scroll(),
MappingVariant::Default => input_mappings(false),
MappingVariant::ZoomWithScroll => input_mappings(true),
}
}
}

pub fn input_mappings() -> Mapping {
pub fn input_mappings(zoom_with_scroll: bool) -> Mapping {
use InputMapperMessage::*;
use Key::*;

let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();

// NOTICE:
// If a new mapping you added here isn't working (and perhaps another lower-precedence one is instead), make sure to advertise
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
Expand Down Expand Up @@ -54,6 +56,11 @@ pub fn input_mappings() -> Mapping {
// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
//
// AppWindowMessage
entry!(KeyDown(F11); disabled=cfg!(target_os = "macos"), action_dispatch=AppWindowMessage::Fullscreen),
entry!(KeyDown(KeyF); modifiers=[Command, Control], disabled=cfg!(not(target_os = "macos")), action_dispatch=AppWindowMessage::Fullscreen),
entry!(KeyDown(KeyQ); modifiers=[Command], disabled=cfg!(not(target_os = "macos")), action_dispatch=AppWindowMessage::Close),
//
// ClipboardMessage
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=ClipboardMessage::Cut),
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=ClipboardMessage::Copy),
Expand Down Expand Up @@ -416,10 +423,14 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(FakeKeyPlus); modifiers=[Accel], canonical, action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
entry!(KeyDown(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
entry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }),
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
entry!(WheelScroll; modifiers=[Command], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }),
entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }),
entry!(WheelScroll; modifiers=[Control], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
entry!(WheelScroll; modifiers=[Command], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
entry!(WheelScroll; modifiers=[Shift], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }),
entry!(WheelScroll; disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }),
// On Mac, the OS already converts Shift+scroll into horizontal scrolling so we have to reverse the behavior from normal to produce the same outcome
entry!(WheelScroll; modifiers=[Control], disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: keyboard_platform == KeyboardPlatformLayout::Mac }),
entry!(WheelScroll; modifiers=[Shift], disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: keyboard_platform != KeyboardPlatformLayout::Mac }),
entry!(WheelScroll; disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
entry!(KeyDown(PageUp); modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(1., 0.) }),
entry!(KeyDown(PageDown); modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(-1., 0.) }),
entry!(KeyDown(PageUp); action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(0., 1.) }),
Expand Down Expand Up @@ -471,7 +482,7 @@ pub fn input_mappings() -> Mapping {
// Sort `pointer_shake`
sort(&mut pointer_shake);

let mut mapping = Mapping {
Mapping {
key_up,
key_down,
key_up_no_repeat,
Expand All @@ -480,54 +491,5 @@ pub fn input_mappings() -> Mapping {
wheel_scroll,
pointer_move,
pointer_shake,
};

if cfg!(target_os = "macos") {
let remove: [&[&[MappingEntry; 0]; 0]; 0] = [];
let add = [entry!(KeyDown(KeyQ); modifiers=[Accel], action_dispatch=AppWindowMessage::Close)];

apply_mapping_patch(&mut mapping, remove, add);
}

mapping
}

/// Default mappings except that scrolling without modifier keys held down is bound to zooming instead of vertical panning
pub fn zoom_with_scroll() -> Mapping {
use InputMapperMessage::*;

// On Mac, the OS already converts Shift+scroll into horizontal scrolling so we have to reverse the behavior from normal to produce the same outcome
let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();

let mut mapping = input_mappings();

let remove = [
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
entry!(WheelScroll; modifiers=[Command], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }),
entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }),
];
let add = [
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: keyboard_platform == KeyboardPlatformLayout::Mac }),
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: keyboard_platform != KeyboardPlatformLayout::Mac }),
entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
];

apply_mapping_patch(&mut mapping, remove, add);

mapping
}

fn apply_mapping_patch<'a, const N: usize, const M: usize, const X: usize, const Y: usize>(
mapping: &mut Mapping,
remove: impl IntoIterator<Item = &'a [&'a [MappingEntry; N]; M]>,
add: impl IntoIterator<Item = &'a [&'a [MappingEntry; X]; Y]>,
) {
for entry in remove.into_iter().flat_map(|inner| inner.iter()).flat_map(|inner| inner.iter()) {
mapping.remove(entry);
}

for entry in add.into_iter().flat_map(|inner| inner.iter()).flat_map(|inner| inner.iter()) {
mapping.add(entry.clone());
}
}
47 changes: 41 additions & 6 deletions editor/src/messages/input_mapper/utility_types/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,51 +25,82 @@ macro_rules! modifiers {
/// When an action is currently available, and the user enters that input, the action's message is dispatched on the message bus.
macro_rules! entry {
// Pattern with canonical parameter
($input:expr_2021; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? canonical, action_dispatch=$action_dispatch:expr_2021$(,)?) => {
entry!($input; $($($modifier),*)?; $($($refresh),*)?; $action_dispatch; true)
(
$input:expr_2021;
$(modifiers=[$($modifier:ident),*],)?
$(refresh_keys=[$($refresh:ident),* $(,)?],)?
canonical,
$(disabled=$disabled:expr,)?
action_dispatch=$action_dispatch:expr_2021$(,)?
) => {
entry!(
$input;
$($($modifier),*)?;
$($($refresh),*)?;
$action_dispatch;
true;
false $( || $disabled )?
)
};

// Pattern without canonical parameter
($input:expr_2021; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? action_dispatch=$action_dispatch:expr_2021$(,)?) => {
entry!($input; $($($modifier),*)?; $($($refresh),*)?; $action_dispatch; false)
(
$input:expr_2021;
$(modifiers=[$($modifier:ident),*],)?
$(refresh_keys=[$($refresh:ident),* $(,)?],)?
$(disabled=$disabled:expr,)?
action_dispatch=$action_dispatch:expr_2021$(,)?
) => {
entry!(
$input;
$($($modifier),*)?;
$($($refresh),*)?;
$action_dispatch;
false;
false $( || $disabled )?
)
};

// Implementation macro to avoid code duplication
($input:expr; $($modifier:ident),*; $($refresh:ident),*; $action_dispatch:expr; $canonical:expr) => {
($input:expr; $($modifier:ident),*; $($refresh:ident),*; $action_dispatch:expr; $canonical:expr; $disabled:expr) => {
&[&[
// Cause the `action_dispatch` message to be sent when the specified input occurs.
MappingEntry {
action: $action_dispatch.into(),
input: $input,
modifiers: modifiers!($($modifier),*),
canonical: $canonical,
disabled: $disabled,
},

// Also cause the `action_dispatch` message to be sent when any of the specified refresh keys change.
$(
MappingEntry {
action: $action_dispatch.into(),
input: InputMapperMessage::KeyDown(Key::$refresh),
modifiers: modifiers!(),
canonical: $canonical,
disabled: $disabled,
},
MappingEntry {
action: $action_dispatch.into(),
input: InputMapperMessage::KeyUp(Key::$refresh),
modifiers: modifiers!(),
canonical: $canonical,
disabled: $disabled,
},
MappingEntry {
action: $action_dispatch.into(),
input: InputMapperMessage::KeyDownNoRepeat(Key::$refresh),
modifiers: modifiers!(),
canonical: $canonical,
disabled: $disabled,
},
MappingEntry {
action: $action_dispatch.into(),
input: InputMapperMessage::KeyUpNoRepeat(Key::$refresh),
modifiers: modifiers!(),
canonical: $canonical,
disabled: $disabled,
},
)*
]]
Expand Down Expand Up @@ -97,6 +128,10 @@ macro_rules! mapping {
for entry_slice in $entry {
// Each entry in the slice (usually just one, except when `refresh_keys` adds additional key entries)
for entry in entry_slice.into_iter() {
if entry.disabled {
continue;
}

let corresponding_list = match entry.input {
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
InputMapperMessage::KeyUp(key) => &mut key_up[key as usize],
Expand Down
2 changes: 2 additions & 0 deletions editor/src/messages/input_mapper/utility_types/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ pub struct MappingEntry {
pub modifiers: KeyStates,
/// True indicates that this takes priority as the labeled hotkey shown in UI menus and tooltips instead of an alternate binding for the same action
pub canonical: bool,
/// Whether this mapping is disabled
pub disabled: bool,
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
Expand Down
Loading