gpui: Support disabling window resizing and minimizing (#36859)

Floyd Wang created

Add support to disable both window resizing and minimizing.

| | macOS | Windows |
| - | - | - |
| **Unresizable** | <img width="412" height="440"
alt="SCR-20250822-qpea"
src="https://github.com/user-attachments/assets/d6d45510-dc4b-436f-a9fa-ce9cb0b0c411"
/> | <img width="276" height="298" alt="2025-08-22 110757"
src="https://github.com/user-attachments/assets/9deff498-e903-4173-9c26-072dd9409fc1"
/> |
| **Unminimizable** | <img width="412" height="440"
alt="SCR-20250822-qpfl"
src="https://github.com/user-attachments/assets/e1d5f9eb-6de5-4908-8b52-38ccb2e65689"
/> | <img width="276" height="298" alt="2025-08-22 110814"
src="https://github.com/user-attachments/assets/da94b006-3544-4274-8b02-1cab7ca8dd70"
/> |

Release Notes:

- N/A

Change summary

crates/agent_ui/src/ui/agent_notification.rs |  1 
crates/collab_ui/src/collab_ui.rs            |  1 
crates/gpui/examples/window.rs               | 30 ++++++++++++++++++++++
crates/gpui/examples/window_positioning.rs   |  1 
crates/gpui/src/platform.rs                  | 16 +++++++++++
crates/gpui/src/platform/mac/window.rs       | 16 ++++++++--
crates/gpui/src/platform/windows/window.rs   | 15 ++++++++--
crates/gpui/src/window.rs                    |  4 ++
crates/zed/src/zed.rs                        |  1 
9 files changed, 77 insertions(+), 8 deletions(-)

Detailed changes

crates/collab_ui/src/collab_ui.rs 🔗

@@ -66,5 +66,6 @@ fn notification_window_options(
         app_id: Some(app_id.to_owned()),
         window_min_size: None,
         window_decorations: Some(WindowDecorations::Client),
+        ..Default::default()
     }
 }

crates/gpui/examples/window.rs 🔗

@@ -152,6 +152,36 @@ impl Render for WindowDemo {
                 )
                 .unwrap();
             }))
+            .child(button("Unresizable", move |_, cx| {
+                cx.open_window(
+                    WindowOptions {
+                        is_resizable: false,
+                        window_bounds: Some(window_bounds),
+                        ..Default::default()
+                    },
+                    |_, cx| {
+                        cx.new(|_| SubWindow {
+                            custom_titlebar: false,
+                        })
+                    },
+                )
+                .unwrap();
+            }))
+            .child(button("Unminimizable", move |_, cx| {
+                cx.open_window(
+                    WindowOptions {
+                        is_minimizable: false,
+                        window_bounds: Some(window_bounds),
+                        ..Default::default()
+                    },
+                    |_, cx| {
+                        cx.new(|_| SubWindow {
+                            custom_titlebar: false,
+                        })
+                    },
+                )
+                .unwrap();
+            }))
             .child(button("Hide Application", |window, cx| {
                 cx.hide();
 

crates/gpui/examples/window_positioning.rs 🔗

@@ -62,6 +62,7 @@ fn build_window_options(display_id: DisplayId, bounds: Bounds<Pixels>) -> Window
         app_id: None,
         window_min_size: None,
         window_decorations: None,
+        ..Default::default()
     }
 }
 

crates/gpui/src/platform.rs 🔗

@@ -1091,6 +1091,12 @@ pub struct WindowOptions {
     /// Whether the window should be movable by the user
     pub is_movable: bool,
 
+    /// Whether the window should be resizable by the user
+    pub is_resizable: bool,
+
+    /// Whether the window should be minimized by the user
+    pub is_minimizable: bool,
+
     /// The display to create the window on, if this is None,
     /// the window will be created on the main display
     pub display_id: Option<DisplayId>,
@@ -1133,6 +1139,14 @@ pub(crate) struct WindowParams {
     #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
     pub is_movable: bool,
 
+    /// Whether the window should be resizable by the user
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
+    pub is_resizable: bool,
+
+    /// Whether the window should be minimized by the user
+    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
+    pub is_minimizable: bool,
+
     #[cfg_attr(
         any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
         allow(dead_code)
@@ -1191,6 +1205,8 @@ impl Default for WindowOptions {
             show: true,
             kind: WindowKind::Normal,
             is_movable: true,
+            is_resizable: true,
+            is_minimizable: true,
             display_id: None,
             window_background: WindowBackgroundAppearance::default(),
             app_id: None,

crates/gpui/src/platform/mac/window.rs 🔗

@@ -530,6 +530,8 @@ impl MacWindow {
             titlebar,
             kind,
             is_movable,
+            is_resizable,
+            is_minimizable,
             focus,
             show,
             display_id,
@@ -545,10 +547,16 @@ impl MacWindow {
 
             let mut style_mask;
             if let Some(titlebar) = titlebar.as_ref() {
-                style_mask = NSWindowStyleMask::NSClosableWindowMask
-                    | NSWindowStyleMask::NSMiniaturizableWindowMask
-                    | NSWindowStyleMask::NSResizableWindowMask
-                    | NSWindowStyleMask::NSTitledWindowMask;
+                style_mask =
+                    NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSTitledWindowMask;
+
+                if is_resizable {
+                    style_mask |= NSWindowStyleMask::NSResizableWindowMask;
+                }
+
+                if is_minimizable {
+                    style_mask |= NSWindowStyleMask::NSMiniaturizableWindowMask;
+                }
 
                 if titlebar.appears_transparent {
                     style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;

crates/gpui/src/platform/windows/window.rs 🔗

@@ -382,10 +382,17 @@ impl WindowsWindow {
         let (mut dwexstyle, dwstyle) = if params.kind == WindowKind::PopUp {
             (WS_EX_TOOLWINDOW, WINDOW_STYLE(0x0))
         } else {
-            (
-                WS_EX_APPWINDOW,
-                WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
-            )
+            let mut dwstyle = WS_SYSMENU;
+
+            if params.is_resizable {
+                dwstyle |= WS_THICKFRAME | WS_MAXIMIZEBOX;
+            }
+
+            if params.is_minimizable {
+                dwstyle |= WS_MINIMIZEBOX;
+            }
+
+            (WS_EX_APPWINDOW, dwstyle)
         };
         if !disable_direct_composition {
             dwexstyle |= WS_EX_NOREDIRECTIONBITMAP;

crates/gpui/src/window.rs 🔗

@@ -939,6 +939,8 @@ impl Window {
             show,
             kind,
             is_movable,
+            is_resizable,
+            is_minimizable,
             display_id,
             window_background,
             app_id,
@@ -956,6 +958,8 @@ impl Window {
                 titlebar,
                 kind,
                 is_movable,
+                is_resizable,
+                is_minimizable,
                 focus,
                 show,
                 display_id,

crates/zed/src/zed.rs 🔗

@@ -301,6 +301,7 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut App) -> WindowO
             width: px(360.0),
             height: px(240.0),
         }),
+        ..Default::default()
     }
 }