Skip to content

Commit 07fbcd4

Browse files
Desktop: Add fullscreen window mode (#3625)
1 parent fd0addf commit 07fbcd4

20 files changed

Lines changed: 195 additions & 153 deletions

File tree

desktop/src/app.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,11 @@ impl App {
352352
window.toggle_maximize();
353353
}
354354
}
355+
DesktopFrontendMessage::WindowFullscreen => {
356+
if let Some(window) = &mut self.window {
357+
window.toggle_fullscreen();
358+
}
359+
}
355360
DesktopFrontendMessage::WindowDrag => {
356361
if let Some(window) = &self.window {
357362
window.start_drag();

desktop/src/window.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
use crate::consts::APP_NAME;
2+
use crate::event::AppEventScheduler;
3+
use crate::wrapper::messages::MenuItem;
14
use std::collections::HashMap;
25
use std::sync::Arc;
36
use winit::cursor::{CursorIcon, CustomCursor, CustomCursorSource};
47
use winit::event_loop::ActiveEventLoop;
8+
use winit::monitor::Fullscreen;
59
use winit::window::{Window as WinitWindow, WindowAttributes};
610

7-
use crate::consts::APP_NAME;
8-
use crate::event::AppEventScheduler;
9-
use crate::wrapper::messages::MenuItem;
10-
1111
pub(crate) trait NativeWindow {
1212
fn init() {}
1313
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes;
@@ -111,18 +111,32 @@ impl Window {
111111
}
112112

113113
pub(crate) fn toggle_maximize(&self) {
114+
if self.is_fullscreen() {
115+
return;
116+
}
114117
self.winit_window.set_maximized(!self.winit_window.is_maximized());
115118
}
116119

117120
pub(crate) fn is_maximized(&self) -> bool {
118121
self.winit_window.is_maximized()
119122
}
120123

124+
pub(crate) fn toggle_fullscreen(&mut self) {
125+
if self.is_fullscreen() {
126+
self.winit_window.set_fullscreen(None);
127+
} else {
128+
self.winit_window.set_fullscreen(Some(Fullscreen::Borderless(None)));
129+
}
130+
}
131+
121132
pub(crate) fn is_fullscreen(&self) -> bool {
122133
self.winit_window.fullscreen().is_some()
123134
}
124135

125136
pub(crate) fn start_drag(&self) {
137+
if self.is_fullscreen() {
138+
return;
139+
}
126140
let _ = self.winit_window.drag_window();
127141
}
128142

desktop/src/window/win/native_handle.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,10 @@ unsafe fn ensure_helper_class() {
210210
// Main window message handler, called on the UI thread for every message the main window receives.
211211
unsafe extern "system" fn main_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
212212
if msg == WM_NCCALCSIZE && wparam.0 != 0 {
213-
// When maximized, shrink to visible frame so content doesn't extend beyond it.
214-
if unsafe { IsZoomed(hwnd).as_bool() } {
215-
let params = unsafe { &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS) };
213+
let params = unsafe { &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS) };
216214

215+
// When maximized, shrink to visible frame so content doesn't extend beyond it.
216+
if unsafe { IsZoomed(hwnd).as_bool() } && !is_effectively_fullscreen(params.rgrc[0]) {
217217
let dpi = unsafe { GetDpiForWindow(hwnd) };
218218
let size = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
219219
let pad = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
@@ -366,3 +366,27 @@ unsafe fn calculate_resize_direction(helper: HWND, lparam: LPARAM) -> Option<u32
366366
_ => None,
367367
}
368368
}
369+
370+
// Check if the rect is effectively fullscreen, meaning it would cover the entire monitor.
371+
// We need to use this heuristic because Windows doesn't provide a way to check for fullscreen state.
372+
fn is_effectively_fullscreen(rect: RECT) -> bool {
373+
let hmon = unsafe { MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST) };
374+
if hmon.is_invalid() {
375+
return false;
376+
}
377+
378+
let mut monitor_info = MONITORINFO {
379+
cbSize: std::mem::size_of::<MONITORINFO>() as u32,
380+
..Default::default()
381+
};
382+
if !unsafe { GetMonitorInfoW(hmon, &mut monitor_info) }.as_bool() {
383+
return false;
384+
}
385+
386+
// Allow a tiny tolerance for DPI / rounding issues
387+
const EPS: i32 = 1;
388+
(rect.left - monitor_info.rcMonitor.left).abs() <= EPS
389+
&& (rect.top - monitor_info.rcMonitor.top).abs() <= EPS
390+
&& (rect.right - monitor_info.rcMonitor.right).abs() <= EPS
391+
&& (rect.bottom - monitor_info.rcMonitor.bottom).abs() <= EPS
392+
}

desktop/wrapper/src/intercept_frontend_message.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
148148
FrontendMessage::WindowMaximize => {
149149
dispatcher.respond(DesktopFrontendMessage::WindowMaximize);
150150
}
151+
FrontendMessage::WindowFullscreen => {
152+
dispatcher.respond(DesktopFrontendMessage::WindowFullscreen);
153+
}
151154
FrontendMessage::WindowDrag => {
152155
dispatcher.respond(DesktopFrontendMessage::WindowDrag);
153156
}

desktop/wrapper/src/messages.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub enum DesktopFrontendMessage {
6969
WindowClose,
7070
WindowMinimize,
7171
WindowMaximize,
72+
WindowFullscreen,
7273
WindowDrag,
7374
WindowHide,
7475
WindowHideOthers,

editor/src/messages/app_window/app_window_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub enum AppWindowMessage {
1111
Close,
1212
Minimize,
1313
Maximize,
14+
Fullscreen,
1415
Drag,
1516
Hide,
1617
HideOthers,

editor/src/messages/app_window/app_window_message_handler.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
3030
AppWindowMessage::Maximize => {
3131
responses.add(FrontendMessage::WindowMaximize);
3232
}
33+
AppWindowMessage::Fullscreen => {
34+
responses.add(FrontendMessage::WindowFullscreen);
35+
}
3336
AppWindowMessage::Drag => {
3437
responses.add(FrontendMessage::WindowDrag);
3538
}
@@ -48,6 +51,7 @@ impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
4851
Close,
4952
Minimize,
5053
Maximize,
54+
Fullscreen,
5155
Drag,
5256
Hide,
5357
HideOthers,

editor/src/messages/frontend/frontend_message.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ pub enum FrontendMessage {
6464
#[serde(rename = "nodeTypes")]
6565
node_types: Vec<FrontendNodeType>,
6666
},
67-
SendShortcutF11 {
67+
SendShortcutFullscreen {
6868
shortcut: Option<ActionShortcut>,
69+
#[serde(rename = "shortcutMac")]
70+
shortcut_mac: Option<ActionShortcut>,
6971
},
7072
SendShortcutAltClick {
7173
shortcut: Option<ActionShortcut>,
@@ -371,6 +373,7 @@ pub enum FrontendMessage {
371373
WindowClose,
372374
WindowMinimize,
373375
WindowMaximize,
376+
WindowFullscreen,
374377
WindowDrag,
375378
WindowHide,
376379
WindowHideOthers,

editor/src/messages/input_mapper/input_mapper_message_handler.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{self, Key};
44
use crate::messages::input_mapper::utility_types::misc::MappingEntry;
55
use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
66
use crate::messages::prelude::*;
7-
use std::fmt::Write;
87

98
#[derive(ExtractField)]
109
pub struct InputMapperMessageContext<'a> {
@@ -34,27 +33,6 @@ impl InputMapperMessageHandler {
3433
self.mapping = mapping;
3534
}
3635

37-
pub fn hints(&self, actions: ActionList) -> String {
38-
let mut output = String::new();
39-
let mut actions = actions
40-
.into_iter()
41-
.flatten()
42-
.filter(|a| !matches!(*a, MessageDiscriminant::Tool(ToolMessageDiscriminant::ActivateTool) | MessageDiscriminant::Debug(_)));
43-
self.mapping
44-
.key_down
45-
.iter()
46-
.enumerate()
47-
.filter_map(|(i, m)| {
48-
let ma = m.0.iter().find_map(|m| actions.find_map(|a| (a == m.action.to_discriminant()).then(|| m.action.to_discriminant())));
49-
50-
ma.map(|a| ((i as u8).try_into().unwrap(), a))
51-
})
52-
.for_each(|(k, a): (Key, _)| {
53-
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').next_back().unwrap());
54-
});
55-
output.replace("Key", "")
56-
}
57-
5836
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option<KeysGroup> {
5937
let all_key_mapping_entries = std::iter::empty()
6038
.chain(self.mapping.key_up.iter())

editor/src/messages/input_mapper/input_mappings.rs

Lines changed: 20 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@ use glam::DVec2;
1717
impl From<MappingVariant> for Mapping {
1818
fn from(value: MappingVariant) -> Self {
1919
match value {
20-
MappingVariant::Default => input_mappings(),
21-
MappingVariant::ZoomWithScroll => zoom_with_scroll(),
20+
MappingVariant::Default => input_mappings(false),
21+
MappingVariant::ZoomWithScroll => input_mappings(true),
2222
}
2323
}
2424
}
2525

26-
pub fn input_mappings() -> Mapping {
26+
pub fn input_mappings(zoom_with_scroll: bool) -> Mapping {
2727
use InputMapperMessage::*;
2828
use Key::*;
2929

30+
// TODO: Fix this failing to load the correct data (and throwing a console warning) because it's occurring before the value has been supplied during initialization from the JS `initAfterFrontendReady`
31+
let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
32+
3033
// NOTICE:
3134
// If a new mapping you added here isn't working (and perhaps another lower-precedence one is instead), make sure to advertise
3235
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
@@ -54,6 +57,11 @@ pub fn input_mappings() -> Mapping {
5457
// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
5558
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
5659
//
60+
// AppWindowMessage
61+
entry!(KeyDown(F11); disabled=(keyboard_platform == KeyboardPlatformLayout::Mac), action_dispatch=AppWindowMessage::Fullscreen),
62+
entry!(KeyDown(KeyF); modifiers=[Command, Control], disabled=(keyboard_platform != KeyboardPlatformLayout::Mac), action_dispatch=AppWindowMessage::Fullscreen),
63+
entry!(KeyDown(KeyQ); modifiers=[Command], disabled=cfg!(not(target_os = "macos")), action_dispatch=AppWindowMessage::Close),
64+
//
5765
// ClipboardMessage
5866
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=ClipboardMessage::Cut),
5967
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=ClipboardMessage::Copy),
@@ -416,10 +424,14 @@ pub fn input_mappings() -> Mapping {
416424
entry!(KeyDown(FakeKeyPlus); modifiers=[Accel], canonical, action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
417425
entry!(KeyDown(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
418426
entry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }),
419-
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
420-
entry!(WheelScroll; modifiers=[Command], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
421-
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }),
422-
entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }),
427+
entry!(WheelScroll; modifiers=[Control], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
428+
entry!(WheelScroll; modifiers=[Command], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
429+
entry!(WheelScroll; modifiers=[Shift], disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }),
430+
entry!(WheelScroll; disabled=zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }),
431+
// 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
432+
entry!(WheelScroll; modifiers=[Control], disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: keyboard_platform == KeyboardPlatformLayout::Mac }),
433+
entry!(WheelScroll; modifiers=[Shift], disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: keyboard_platform != KeyboardPlatformLayout::Mac }),
434+
entry!(WheelScroll; disabled=!zoom_with_scroll, action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
423435
entry!(KeyDown(PageUp); modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(1., 0.) }),
424436
entry!(KeyDown(PageDown); modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(-1., 0.) }),
425437
entry!(KeyDown(PageUp); action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(0., 1.) }),
@@ -471,7 +483,7 @@ pub fn input_mappings() -> Mapping {
471483
// Sort `pointer_shake`
472484
sort(&mut pointer_shake);
473485

474-
let mut mapping = Mapping {
486+
Mapping {
475487
key_up,
476488
key_down,
477489
key_up_no_repeat,
@@ -480,54 +492,5 @@ pub fn input_mappings() -> Mapping {
480492
wheel_scroll,
481493
pointer_move,
482494
pointer_shake,
483-
};
484-
485-
if cfg!(target_os = "macos") {
486-
let remove: [&[&[MappingEntry; 0]; 0]; 0] = [];
487-
let add = [entry!(KeyDown(KeyQ); modifiers=[Accel], action_dispatch=AppWindowMessage::Close)];
488-
489-
apply_mapping_patch(&mut mapping, remove, add);
490-
}
491-
492-
mapping
493-
}
494-
495-
/// Default mappings except that scrolling without modifier keys held down is bound to zooming instead of vertical panning
496-
pub fn zoom_with_scroll() -> Mapping {
497-
use InputMapperMessage::*;
498-
499-
// 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
500-
let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
501-
502-
let mut mapping = input_mappings();
503-
504-
let remove = [
505-
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
506-
entry!(WheelScroll; modifiers=[Command], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
507-
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }),
508-
entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }),
509-
];
510-
let add = [
511-
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: keyboard_platform == KeyboardPlatformLayout::Mac }),
512-
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: keyboard_platform != KeyboardPlatformLayout::Mac }),
513-
entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
514-
];
515-
516-
apply_mapping_patch(&mut mapping, remove, add);
517-
518-
mapping
519-
}
520-
521-
fn apply_mapping_patch<'a, const N: usize, const M: usize, const X: usize, const Y: usize>(
522-
mapping: &mut Mapping,
523-
remove: impl IntoIterator<Item = &'a [&'a [MappingEntry; N]; M]>,
524-
add: impl IntoIterator<Item = &'a [&'a [MappingEntry; X]; Y]>,
525-
) {
526-
for entry in remove.into_iter().flat_map(|inner| inner.iter()).flat_map(|inner| inner.iter()) {
527-
mapping.remove(entry);
528-
}
529-
530-
for entry in add.into_iter().flat_map(|inner| inner.iter()).flat_map(|inner| inner.iter()) {
531-
mapping.add(entry.clone());
532495
}
533496
}

0 commit comments

Comments
 (0)