gpui: Fix `show: false` support on Windows to create an invisible window (#18161)

Jason Lee created

Release Notes:

- N/A
- 
The `show` of WindowOptions is valid on macOS but not on Windows, this
changes to fix it to support create an invisible window.

```bash
cargo run -p gpui --example window
```

## Before



https://github.com/user-attachments/assets/4157bdaa-39a7-44df-bbdc-30b00e9c61e9


## After



https://github.com/user-attachments/assets/d48fa524-0caa-4f87-932d-01d7a468c488


https://github.com/user-attachments/assets/dd052f15-c8db-4a2a-a6af-a7c0ffecca84

Change summary

crates/gpui/examples/window.rs             | 168 ++++++++++++++++++++++++
crates/gpui/src/platform/mac/window.rs     |   2 
crates/gpui/src/platform/windows/window.rs |  13 +
3 files changed, 180 insertions(+), 3 deletions(-)

Detailed changes

crates/gpui/examples/window.rs 🔗

@@ -0,0 +1,168 @@
+use gpui::*;
+use prelude::FluentBuilder as _;
+
+struct SubWindow {
+    custom_titlebar: bool,
+}
+
+fn button(text: &str, on_click: impl Fn(&mut WindowContext) + 'static) -> impl IntoElement {
+    div()
+        .id(SharedString::from(text.to_string()))
+        .flex_none()
+        .px_2()
+        .bg(rgb(0xf7f7f7))
+        .active(|this| this.opacity(0.85))
+        .border_1()
+        .border_color(rgb(0xe0e0e0))
+        .rounded_md()
+        .cursor_pointer()
+        .child(text.to_string())
+        .on_click(move |_, cx| on_click(cx))
+}
+
+impl Render for SubWindow {
+    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
+        div()
+            .flex()
+            .flex_col()
+            .bg(rgb(0xffffff))
+            .size_full()
+            .gap_2()
+            .when(self.custom_titlebar, |cx| {
+                cx.child(
+                    div()
+                        .flex()
+                        .h(px(32.))
+                        .px_4()
+                        .bg(gpui::blue())
+                        .text_color(gpui::white())
+                        .w_full()
+                        .child(
+                            div()
+                                .flex()
+                                .items_center()
+                                .justify_center()
+                                .size_full()
+                                .child("Custom Titlebar"),
+                        ),
+                )
+            })
+            .child(
+                div()
+                    .p_8()
+                    .gap_2()
+                    .child("SubWindow")
+                    .child(button("Close", |cx| {
+                        cx.remove_window();
+                    })),
+            )
+    }
+}
+
+struct WindowDemo {}
+
+impl Render for WindowDemo {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let window_bounds =
+            WindowBounds::Windowed(Bounds::centered(None, size(px(300.0), px(300.0)), cx));
+
+        div()
+            .p_4()
+            .flex()
+            .flex_wrap()
+            .bg(rgb(0xffffff))
+            .size_full()
+            .justify_center()
+            .items_center()
+            .gap_2()
+            .child(button("Normal", move |cx| {
+                cx.open_window(
+                    WindowOptions {
+                        window_bounds: Some(window_bounds),
+                        ..Default::default()
+                    },
+                    |cx| {
+                        cx.new_view(|_cx| SubWindow {
+                            custom_titlebar: false,
+                        })
+                    },
+                )
+                .unwrap();
+            }))
+            .child(button("Popup", move |cx| {
+                cx.open_window(
+                    WindowOptions {
+                        window_bounds: Some(window_bounds),
+                        kind: WindowKind::PopUp,
+                        ..Default::default()
+                    },
+                    |cx| {
+                        cx.new_view(|_cx| SubWindow {
+                            custom_titlebar: false,
+                        })
+                    },
+                )
+                .unwrap();
+            }))
+            .child(button("Custom Titlebar", move |cx| {
+                cx.open_window(
+                    WindowOptions {
+                        titlebar: None,
+                        window_bounds: Some(window_bounds),
+                        ..Default::default()
+                    },
+                    |cx| {
+                        cx.new_view(|_cx| SubWindow {
+                            custom_titlebar: true,
+                        })
+                    },
+                )
+                .unwrap();
+            }))
+            .child(button("Invisible", move |cx| {
+                cx.open_window(
+                    WindowOptions {
+                        show: false,
+                        window_bounds: Some(window_bounds),
+                        ..Default::default()
+                    },
+                    |cx| {
+                        cx.new_view(|_cx| SubWindow {
+                            custom_titlebar: false,
+                        })
+                    },
+                )
+                .unwrap();
+            }))
+            .child(button("Unmovable", move |cx| {
+                cx.open_window(
+                    WindowOptions {
+                        is_movable: false,
+                        titlebar: None,
+                        window_bounds: Some(window_bounds),
+                        ..Default::default()
+                    },
+                    |cx| {
+                        cx.new_view(|_cx| SubWindow {
+                            custom_titlebar: false,
+                        })
+                    },
+                )
+                .unwrap();
+            }))
+    }
+}
+
+fn main() {
+    App::new().run(|cx: &mut AppContext| {
+        let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
+        cx.open_window(
+            WindowOptions {
+                window_bounds: Some(WindowBounds::Windowed(bounds)),
+                ..Default::default()
+            },
+            |cx| cx.new_view(|_cx| WindowDemo {}),
+        )
+        .unwrap();
+    });
+}

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

@@ -707,7 +707,7 @@ impl MacWindow {
                 }
             }
 
-            if focus {
+            if focus && show {
                 native_window.makeKeyAndOrderFront_(nil);
             } else if show {
                 native_window.orderFront_(nil);

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

@@ -287,7 +287,7 @@ impl WindowsWindow {
                 .map(|title| title.as_ref())
                 .unwrap_or(""),
         );
-        let (dwexstyle, dwstyle) = if params.kind == WindowKind::PopUp {
+        let (dwexstyle, mut dwstyle) = if params.kind == WindowKind::PopUp {
             (WS_EX_TOOLWINDOW, WINDOW_STYLE(0x0))
         } else {
             (
@@ -295,6 +295,10 @@ impl WindowsWindow {
                 WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
             )
         };
+        if !params.show {
+            dwstyle |= WS_MINIMIZE;
+        }
+
         let hinstance = get_module_handle();
         let display = if let Some(display_id) = params.display_id {
             // if we obtain a display_id, then this ID must be valid.
@@ -357,7 +361,12 @@ impl WindowsWindow {
             drop(lock);
             SetWindowPlacement(raw_hwnd, &placement)?;
         }
-        unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok()? };
+
+        if params.show {
+            unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok()? };
+        } else {
+            unsafe { ShowWindow(raw_hwnd, SW_HIDE).ok()? };
+        }
 
         Ok(Self(state_ptr))
     }