gpui: Retain maximized and fullscreen state for new windows derived from previous windows (#44605)

Release Notes:

- Fixed new windows underflowing the taskbar on windows
- Improved new windows spawned from maximized or fullscreened windows by
copying the maximized and fullscreened states
This commit is contained in:
Lukas Wirth
2025-12-11 09:38:38 +00:00
committed by GitHub
parent 7ed5d42696
commit a6b9524d78
4 changed files with 152 additions and 84 deletions
+7
View File
@@ -289,6 +289,13 @@ pub trait PlatformDisplay: Send + Sync + Debug {
/// Get the bounds for this display
fn bounds(&self) -> Bounds<Pixels>;
/// Get the visible bounds for this display, excluding taskbar/dock areas.
/// This is the usable area where windows can be placed without being obscured.
/// Defaults to the full display bounds if not overridden.
fn visible_bounds(&self) -> Bounds<Pixels> {
self.bounds()
}
/// Get the default bounds for this display to place a window
fn default_bounds(&self) -> Bounds<Pixels> {
let bounds = self.bounds();
+51 -2
View File
@@ -1,9 +1,9 @@
use crate::{Bounds, DisplayId, Pixels, PlatformDisplay, px, size};
use crate::{Bounds, DisplayId, Pixels, PlatformDisplay, point, px, size};
use anyhow::Result;
use cocoa::{
appkit::NSScreen,
base::{id, nil},
foundation::{NSDictionary, NSString},
foundation::{NSArray, NSDictionary, NSString},
};
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
@@ -114,4 +114,53 @@ impl PlatformDisplay for MacDisplay {
}
}
}
fn visible_bounds(&self) -> Bounds<Pixels> {
unsafe {
let dominated_screen = self.get_nsscreen();
if dominated_screen == nil {
return self.bounds();
}
let screen_frame = NSScreen::frame(dominated_screen);
let visible_frame = NSScreen::visibleFrame(dominated_screen);
// Convert from bottom-left origin (AppKit) to top-left origin
let origin_y =
screen_frame.size.height - visible_frame.origin.y - visible_frame.size.height
+ screen_frame.origin.y;
Bounds {
origin: point(
px(visible_frame.origin.x as f32 - screen_frame.origin.x as f32),
px(origin_y as f32),
),
size: size(
px(visible_frame.size.width as f32),
px(visible_frame.size.height as f32),
),
}
}
}
}
impl MacDisplay {
/// Find the NSScreen corresponding to this display
unsafe fn get_nsscreen(&self) -> id {
let screens = unsafe { NSScreen::screens(nil) };
let count = unsafe { NSArray::count(screens) };
let screen_number_key: id = unsafe { NSString::alloc(nil).init_str("NSScreenNumber") };
for i in 0..count {
let screen = unsafe { NSArray::objectAtIndex(screens, i) };
let device_description = unsafe { NSScreen::deviceDescription(screen) };
let screen_number = unsafe { device_description.objectForKey_(screen_number_key) };
let screen_id: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue];
if screen_id == self.0 {
return screen;
}
}
nil
}
}
@@ -23,6 +23,7 @@ pub(crate) struct WindowsDisplay {
pub display_id: DisplayId,
scale_factor: f32,
bounds: Bounds<Pixels>,
visible_bounds: Bounds<Pixels>,
physical_bounds: Bounds<DevicePixels>,
uuid: Uuid,
}
@@ -36,6 +37,7 @@ impl WindowsDisplay {
let screen = available_monitors().into_iter().nth(display_id.0 as _)?;
let info = get_monitor_info(screen).log_err()?;
let monitor_size = info.monitorInfo.rcMonitor;
let work_area = info.monitorInfo.rcWork;
let uuid = generate_uuid(&info.szDevice);
let scale_factor = get_scale_factor_for_monitor(screen).log_err()?;
let physical_size = size(
@@ -55,6 +57,14 @@ impl WindowsDisplay {
),
size: physical_size.to_pixels(scale_factor),
},
visible_bounds: Bounds {
origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
size: size(
(work_area.right - work_area.left) as f32 / scale_factor,
(work_area.bottom - work_area.top) as f32 / scale_factor,
)
.map(crate::px),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
@@ -66,6 +76,7 @@ impl WindowsDisplay {
pub fn new_with_handle(monitor: HMONITOR) -> anyhow::Result<Self> {
let info = get_monitor_info(monitor)?;
let monitor_size = info.monitorInfo.rcMonitor;
let work_area = info.monitorInfo.rcWork;
let uuid = generate_uuid(&info.szDevice);
let display_id = available_monitors()
.iter()
@@ -89,6 +100,14 @@ impl WindowsDisplay {
),
size: physical_size.to_pixels(scale_factor),
},
visible_bounds: Bounds {
origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
size: size(
(work_area.right - work_area.left) as f32 / scale_factor,
(work_area.bottom - work_area.top) as f32 / scale_factor,
)
.map(crate::px),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
@@ -100,6 +119,7 @@ impl WindowsDisplay {
fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> anyhow::Result<Self> {
let info = get_monitor_info(handle)?;
let monitor_size = info.monitorInfo.rcMonitor;
let work_area = info.monitorInfo.rcWork;
let uuid = generate_uuid(&info.szDevice);
let scale_factor = get_scale_factor_for_monitor(handle)?;
let physical_size = size(
@@ -119,6 +139,14 @@ impl WindowsDisplay {
),
size: physical_size.to_pixels(scale_factor),
},
visible_bounds: Bounds {
origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
size: size(
(work_area.right - work_area.left) as f32 / scale_factor,
(work_area.bottom - work_area.top) as f32 / scale_factor,
)
.map(crate::px),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
@@ -193,6 +221,10 @@ impl PlatformDisplay for WindowsDisplay {
fn bounds(&self) -> Bounds<Pixels> {
self.bounds
}
fn visible_bounds(&self) -> Bounds<Pixels> {
self.visible_bounds
}
}
fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
+62 -82
View File
@@ -918,86 +918,69 @@ pub(crate) struct ElementStateBox {
pub(crate) type_name: &'static str,
}
fn default_bounds(display_id: Option<DisplayId>, cx: &mut App) -> Bounds<Pixels> {
#[cfg(target_os = "macos")]
{
const CASCADE_OFFSET: f32 = 25.0;
fn default_bounds(display_id: Option<DisplayId>, cx: &mut App) -> WindowBounds {
// TODO, BUG: if you open a window with the currently active window
// on the stack, this will erroneously fallback to `None`
//
// TODO these should be the initial window bounds not considering maximized/fullscreen
let active_window_bounds = cx
.active_window()
.and_then(|w| w.update(cx, |_, window, _| window.window_bounds()).ok());
let display = display_id
.map(|id| cx.find_display(id))
.unwrap_or_else(|| cx.primary_display());
const CASCADE_OFFSET: f32 = 25.0;
let display_bounds = display
.as_ref()
.map(|d| d.bounds())
.unwrap_or_else(|| Bounds::new(point(px(0.), px(0.)), DEFAULT_WINDOW_SIZE));
let display = display_id
.map(|id| cx.find_display(id))
.unwrap_or_else(|| cx.primary_display());
// TODO, BUG: if you open a window with the currently active window
// on the stack, this will erroneously select the 'unwrap_or_else'
// code path
let (base_origin, base_size) = cx
.active_window()
.and_then(|w| {
w.update(cx, |_, window, _| {
let bounds = window.bounds();
(bounds.origin, bounds.size)
})
.ok()
})
.unwrap_or_else(|| {
let default_bounds = display
.as_ref()
.map(|d| d.default_bounds())
.unwrap_or_else(|| Bounds::new(point(px(0.), px(0.)), DEFAULT_WINDOW_SIZE));
(default_bounds.origin, default_bounds.size)
});
let default_placement = || Bounds::new(point(px(0.), px(0.)), DEFAULT_WINDOW_SIZE);
let cascade_offset = point(px(CASCADE_OFFSET), px(CASCADE_OFFSET));
let proposed_origin = base_origin + cascade_offset;
let proposed_bounds = Bounds::new(proposed_origin, base_size);
// Use visible_bounds to exclude taskbar/dock areas
let display_bounds = display
.as_ref()
.map(|d| d.visible_bounds())
.unwrap_or_else(default_placement);
let display_right = display_bounds.origin.x + display_bounds.size.width;
let display_bottom = display_bounds.origin.y + display_bounds.size.height;
let window_right = proposed_bounds.origin.x + proposed_bounds.size.width;
let window_bottom = proposed_bounds.origin.y + proposed_bounds.size.height;
let (
Bounds {
origin: base_origin,
size: base_size,
},
window_bounds_ctor,
): (_, fn(Bounds<Pixels>) -> WindowBounds) = match active_window_bounds {
Some(bounds) => match bounds {
WindowBounds::Windowed(bounds) => (bounds, WindowBounds::Windowed),
WindowBounds::Maximized(bounds) => (bounds, WindowBounds::Maximized),
WindowBounds::Fullscreen(bounds) => (bounds, WindowBounds::Fullscreen),
},
None => (
display
.as_ref()
.map(|d| d.default_bounds())
.unwrap_or_else(default_placement),
WindowBounds::Windowed,
),
};
let fits_horizontally = window_right <= display_right;
let fits_vertically = window_bottom <= display_bottom;
let cascade_offset = point(px(CASCADE_OFFSET), px(CASCADE_OFFSET));
let proposed_origin = base_origin + cascade_offset;
let proposed_bounds = Bounds::new(proposed_origin, base_size);
let final_origin = match (fits_horizontally, fits_vertically) {
(true, true) => proposed_origin,
(false, true) => point(display_bounds.origin.x, base_origin.y),
(true, false) => point(base_origin.x, display_bounds.origin.y),
(false, false) => display_bounds.origin,
};
let display_right = display_bounds.origin.x + display_bounds.size.width;
let display_bottom = display_bounds.origin.y + display_bounds.size.height;
let window_right = proposed_bounds.origin.x + proposed_bounds.size.width;
let window_bottom = proposed_bounds.origin.y + proposed_bounds.size.height;
Bounds::new(final_origin, base_size)
}
let fits_horizontally = window_right <= display_right;
let fits_vertically = window_bottom <= display_bottom;
#[cfg(not(target_os = "macos"))]
{
const DEFAULT_WINDOW_OFFSET: Point<Pixels> = point(px(0.), px(35.));
// TODO, BUG: if you open a window with the currently active window
// on the stack, this will erroneously select the 'unwrap_or_else'
// code path
cx.active_window()
.and_then(|w| w.update(cx, |_, window, _| window.bounds()).ok())
.map(|mut bounds| {
bounds.origin += DEFAULT_WINDOW_OFFSET;
bounds
})
.unwrap_or_else(|| {
let display = display_id
.map(|id| cx.find_display(id))
.unwrap_or_else(|| cx.primary_display());
display
.as_ref()
.map(|display| display.default_bounds())
.unwrap_or_else(|| Bounds::new(point(px(0.), px(0.)), DEFAULT_WINDOW_SIZE))
})
}
let final_origin = match (fits_horizontally, fits_vertically) {
(true, true) => proposed_origin,
(false, true) => point(display_bounds.origin.x, base_origin.y),
(true, false) => point(base_origin.x, display_bounds.origin.y),
(false, false) => display_bounds.origin,
};
window_bounds_ctor(Bounds::new(final_origin, base_size))
}
impl Window {
@@ -1024,13 +1007,11 @@ impl Window {
tabbing_identifier,
} = options;
let bounds = window_bounds
.map(|bounds| bounds.get_bounds())
.unwrap_or_else(|| default_bounds(display_id, cx));
let window_bounds = window_bounds.unwrap_or_else(|| default_bounds(display_id, cx));
let mut platform_window = cx.platform.open_window(
handle,
WindowParams {
bounds,
bounds: window_bounds.get_bounds(),
titlebar,
kind,
is_movable,
@@ -1071,12 +1052,10 @@ impl Window {
.request_decorations(window_decorations.unwrap_or(WindowDecorations::Server));
platform_window.set_background_appearance(window_background);
if let Some(ref window_open_state) = window_bounds {
match window_open_state {
WindowBounds::Fullscreen(_) => platform_window.toggle_fullscreen(),
WindowBounds::Maximized(_) => platform_window.zoom(),
WindowBounds::Windowed(_) => {}
}
match window_bounds {
WindowBounds::Fullscreen(_) => platform_window.toggle_fullscreen(),
WindowBounds::Maximized(_) => platform_window.zoom(),
WindowBounds::Windowed(_) => {}
}
platform_window.on_close(Box::new({
@@ -1518,7 +1497,8 @@ impl Window {
style
}
/// Check if the platform window is maximized
/// Check if the platform window is maximized.
///
/// On some platforms (namely Windows) this is different than the bounds being the size of the display
pub fn is_maximized(&self) -> bool {
self.platform_window.is_maximized()