Jump to content

Building Native Desktop Interfaces with Rust GPUI: Part 1

From JOHNWICK
Revision as of 04:27, 14 November 2025 by PC (talk | contribs) (Created page with "Windows and Linux setup While the code should be the same, otherwise it wouldn’t be a real cross-platform framework, initial setup is different and explained, although temporarily untested, here. The Desktop Renaissance Desktop applications are experiencing a quiet revival. After years of web-first thinking, developers are rediscovering what native code can deliver: instant startup, fluid interactions, and interfaces that feel like they belong to the machine running...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Windows and Linux setup While the code should be the same, otherwise it wouldn’t be a real cross-platform framework, initial setup is different and explained, although temporarily untested, here.

The Desktop Renaissance Desktop applications are experiencing a quiet revival. After years of web-first thinking, developers are rediscovering what native code can deliver: instant startup, fluid interactions, and interfaces that feel like they belong to the machine running them. The question is no longer whether desktop matters, but how to build desktop software that respects both the developer’s time and the user’s expectations. For the last decade, frameworks like Electron and Tauri solved the cross-platform problem by embedding a browser engine inside every application window. This approach delivered on its core promise: write once in HTML, CSS, and JavaScript, then ship everywhere. The trade-off seemed reasonable. Accept some startup delay, some memory overhead, some indirection between your code and the screen. In exchange, get familiar web technologies and broad platform reach. That bargain made sense when the alternative meant maintaining separate codebases for Windows, macOS, and Linux. But the cost was real. Every Electron app carried Chromium’s full rendering stack, even for interfaces that never needed a tenth of its capabilities. Memory usage climbed. Cold starts felt sluggish. Scrolling occasionally stuttered. Users noticed, but they tolerated it because the alternative was often no desktop app at all. The calculus is shifting. Hardware improved, but so did expectations. Users now compare desktop apps not to what was possible five years ago, but to what feels immediate today. A dialog that takes two seconds to appear feels broken. A preferences window that idles at 200MB feels wasteful. Smoothness is no longer a luxury feature. It is baseline expectation. GPUI represents a different answer to the cross-platform question. Born inside Zed, a code editor built for performance, GPUI brings GPU-accelerated rendering and Rust’s safety guarantees to desktop development. It skips the browser entirely. Your code describes a view. GPUI renders it directly through the GPU. No DOM, no style cascade, no JavaScript runtime warming up in the background. The window appears because your application is ready, not because a hidden web page finished loading. This matters more than benchmarks suggest. When you remove the layers between intent and pixels, development changes. You stop working around the framework’s assumptions about what a “document” means. You stop translating your interface into web primitives that were designed for flowing text, not application chrome. You describe what you want, and the GPU draws it. The directness shows in the result. Rust’s role here extends beyond performance. A cross-platform UI framework in Rust means one language, one type system, one set of guarantees from your business logic through your interface layer. There is no boundary between “backend Rust” and “frontend JavaScript.” No serialization overhead crossing an IPC bridge. No impedance mismatch between how your data structures live in memory and how your view code accesses them. The application is coherent all the way through. This combination positions GPUI not as a niche tool for performance enthusiasts, but as a credible foundation for mainstream desktop development. Cross-platform reach without runtime bloat. Native feel without platform-specific rewrites. Type safety without abandoning the UI layer to a different language. These are not incremental improvements. They change what developers can reasonably expect from a desktop framework. The Electron model succeeded because it was the best available answer to a hard problem. As Rust matures and GPU-first frameworks prove themselves in production, that answer is no longer the only viable option. Developers who choose native rendering are no longer choosing to make their lives harder. They are choosing to make their applications faster, lighter, and more consistent with user expectations. GPUI is early, but its architecture points toward where desktop development is headed. Not because native is morally superior, but because the costs that once justified browser-based rendering are dropping while the benefits of direct GPU access are becoming impossible to ignore. When a production code editor demonstrates that you can ship a complex, professional tool with instant startup and smooth interactions on every platform, the bar moves for everyone. This is the first post in a series exploring what that combination makes possible. We’re going to build something small but complete: a modal dialog box with a native macOS appearance. The goal is not to showcase every feature but to establish a foundation. By the end, you’ll have working code that demonstrates how GPUI handles layout, styling, event handling, and window management.

What You’ll Build A centered modal dialog with a semi-transparent backdrop, macOS-style traffic light buttons, and two action buttons. The interface will respond to both mouse clicks and keyboard input. The dialog will look native on macOS because we’ll match Apple’s Human Interface Guidelines pixel by pixel. This tutorial focuses on macOS appearance. The code will run on Linux and Windows without modification, but the styling won’t match those platforms’ native conventions yet. That’s not an oversight. It’s a design choice we’ll address in the next post when we explore adaptive theming.

Setting Up Your Environment Before you write any code, make sure your system has the necessary build tools. On macOS, GPUI requires the Command Line Tools for Xcode and the Metal toolchain. Open a terminal and run: sudo softwareupdate --install "Command Line Tools for Xcode 26.0" xcodebuild -downloadComponent MetalToolchain These commands ensure your system can compile GPU-accelerated code and link against macOS graphics frameworks. Create a new Rust project: cargo new gpui-dialog-tutorial cd gpui-dialog-tutorial Add GPUI to your Cargo.toml: [package] name = "gpui-dialog-tutorial" version = "0.1.0" edition = "2021"

[dependencies] gpui = "0.2"

The Component Model GPUI organizes interfaces around components. A component is any struct that implements the Render trait. The trait has one method: render(). That method returns an element tree describing what should appear on screen. We need two components. The first is a backdrop: a full-screen overlay that dims everything behind the dialog. The second is the dialog itself. Here’s the backdrop: use gpui::*;

struct Backdrop;

impl Render for Backdrop {

   fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
       div()
           .size_full()
           .bg(hsla(0.0, 0.0, 0.0, 0.3))
   }

} The div() function creates a container. You configure it by chaining methods in a builder pattern. size_full() makes the container fill its parent. bg() sets the background color using HSLA: hue, saturation, lightness, alpha. A 30% transparent black overlay gives the modal effect we want.

Building the Dialog The dialog component is more involved. It needs to handle user input, display content, and present a native-looking interface. Start with the struct and event handlers: struct DialogBox;

impl DialogBox {

   fn on_ok_clicked(&mut self, _: &MouseUpEvent, _: &mut Window, cx: &mut Context<Self>) {
       cx.quit();
   }
    
   fn on_cancel_clicked(&mut self, _: &MouseUpEvent, _: &mut Window, cx: &mut Context<Self>) {
       cx.quit();
   }
    
   fn on_escape(&mut self, _: &KeyDownEvent, _: &mut Window, cx: &mut Context<Self>) {
       cx.quit();
   }

} Event handlers in GPUI are methods that receive three parameters: the component itself, the event that triggered the handler, and a context object that provides access to app-level operations. Here, both buttons and the ESC key close the application by calling cx.quit(). The render implementation builds the visual structure: impl Render for DialogBox {

   fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
       div()
           .flex()
           .size_full()
           .justify_center()
           .items_center()
           .on_key_down(cx.listener(Self::on_escape))
           .child(
               div()
                   .flex()
                   .flex_col()
                   .rounded(px(10.0))
                   .shadow_lg()
                   .overflow_hidden()
                   .w_full()
                   .h_full()
                   .child(build_titlebar(cx))
                   .child(build_content(cx))
           )
   }

} The outer container uses flexbox to center its child. The inner container holds the dialog itself: rounded corners, a shadow for depth, and two children (titlebar and content). Attaching the keyboard handler uses cx.listener(), which converts a method reference into an event listener.

The macOS Titlebar macOS dialogs have a distinctive titlebar with three colored buttons: red for close, yellow for minimize, green for maximize. Users expect these visual cues. fn build_titlebar(cx: &mut Context<DialogBox>) -> impl IntoElement {

   div()
       .flex()
       .items_center()
       .h(px(22.0))
       .w_full()
       .bg(rgb(0xE8E8E8))
       .border_b_1()
       .border_color(rgb(0xD0D0D0))
       .px_3()
       .gap_2()
       .child(traffic_light(0xFF5F57, 0xE04943, cx, DialogBox::on_cancel_clicked))
       .child(traffic_light(0xFFBD2E, 0xDEA123, cx, |_, _, _, _| {}))
       .child(traffic_light(0x28C940, 0x1AAB29, cx, |_, _, _, _| {}))

}

fn traffic_light(

   bg_color: u32,
   border_color: u32,
   cx: &mut Context<DialogBox>,
   handler: fn(&mut DialogBox, &MouseUpEvent, &mut Window, &mut Context<DialogBox>),

) -> impl IntoElement {

   div()
       .w(px(12.0))
       .h(px(12.0))
       .rounded_full()
       .bg(rgb(bg_color))
       .border_1()
       .border_color(rgb(border_color))
       .cursor_pointer()
       .on_mouse_up(MouseButton::Left, cx.listener(handler))

} Each button is a 12-pixel circle with the appropriate color and border. Only the red button has a real handler. The others are decorative in this example.

Content and Buttons The content area contains the message text and action buttons: fn build_content(cx: &mut Context<DialogBox>) -> impl IntoElement {

   div()
       .flex()
       .flex_col()
       .bg(rgb(0xEFEFEF))
       .flex_1()
       .px_6()
       .py_5()
       .child(
           div()
               .flex()
               .flex_1()
               .items_center()
               .px_3()
               .py_4()
               .child(
                   div()
                       .text_size(px(13.0))
                       .text_color(rgb(0x000000))
                       .font_weight(FontWeight::NORMAL)
                       .line_height(relative(1.4))
                       .child("Hello World!")
               )
       )
       .child(build_buttons(cx))

}

fn build_buttons(cx: &mut Context<DialogBox>) -> impl IntoElement {

   div()
       .flex()
       .gap_3()
       .justify_end()
       .w_full()
       .mt_3()
       .child(button(
           "Cancel",
           rgb(0xFFFFFF),
           rgb(0x000000),
           rgb(0xB8B8B8),
           rgb(0xF8F8F8),
           cx,
           DialogBox::on_cancel_clicked,
       ))
       .child(button(
           "OK",
           rgb(0x007AFF),
           rgb(0xFFFFFF),
           rgb(0x007AFF),
           rgb(0x0068DB),
           cx,
           DialogBox::on_ok_clicked,
       ))

}

fn button(

   label: &str,
   bg: Hsla,
   text: Hsla,
   border: Hsla,
   hover_bg: Hsla,
   cx: &mut Context<DialogBox>,
   handler: fn(&mut DialogBox, &MouseUpEvent, &mut Window, &mut Context<DialogBox>),

) -> impl IntoElement {

   div()
       .flex()
       .items_center()
       .justify_center()
       .px_6()
       .h(px(32.0))
       .min_w(px(90.0))
       .bg(bg)
       .text_color(text)
       .text_size(px(13.0))
       .font_weight(FontWeight::NORMAL)
       .rounded(px(6.0))
       .border_1()
       .border_color(border)
       .cursor_pointer()
       .shadow_sm()
       .hover(|style| style.bg(hover_bg))
       .on_mouse_up(MouseButton::Left, cx.listener(handler))
       .child(label)

} The Cancel button uses a white background with a gray border. The OK button uses macOS accent blue. Both buttons have hover states that darken the background slightly.

Dialog Component Structure The DialogBox component builds a nested tree of elements. Understanding this hierarchy helps you reason about layout and styling:

Window Management GPUI applications start by creating an Application instance and opening windows. We need two windows: one for the backdrop, one for the dialog. fn main() {

   Application::new().run(|cx: &mut App| {
       let displays = cx.displays();
       let display = displays.first().unwrap();
       let screen_size = display.bounds().size;
    
       // Create backdrop
       cx.open_window(
           WindowOptions {
               window_bounds: Some(WindowBounds::Windowed(Bounds {
                   origin: point(px(0.0), px(0.0)),
                   size: screen_size,
               })),
               titlebar: None,
               focus: false,
               show: true,
               kind: WindowKind::PopUp,
               is_movable: false,
               display_id: Some(display.id()),
               window_background: WindowBackgroundAppearance::Transparent,
               ..Default::default()
           },
           |_, cx| cx.new(|_cx| Backdrop),
       )
       .unwrap();
    
       // Calculate dialog position
       let dialog_width = px(460.0);
       let dialog_height = px(180.0);
       let x = (screen_size.width - dialog_width) / 2.0;
       let y = (screen_size.height - dialog_height) / 2.0;
    
       let dialog_bounds = Bounds {
           origin: point(x, y),
           size: size(dialog_width, dialog_height),
       };
    
       // Create dialog
       cx.open_window(
           WindowOptions {
               window_bounds: Some(WindowBounds::Windowed(dialog_bounds)),
               titlebar: None,
               focus: true,
               show: true,
               kind: WindowKind::PopUp,
               is_movable: false,
               display_id: Some(display.id()),
               window_background: WindowBackgroundAppearance::Transparent,
               ..Default::default()
           },
           |_, cx| cx.new(|_cx| DialogBox),
       )
       .unwrap();
   });

} The backdrop window is full-screen and unfocused. The dialog window is centered and focused. Both use WindowKind::PopUp to float above other windows. Both use transparent backgrounds so our custom rendering (rounded corners, shadows) displays correctly.

Two-Window Architecture GPUI allows multiple windows to coexist in the same application. For modal dialogs, we use a two-window strategy:

Run It Build and run: cargo run A centered dialog appears with a dimmed backdrop behind it. The traffic lights are positioned correctly. The buttons respond to clicks. Pressing ESC closes the application. The interface feels immediate because there’s no browser runtime warming up, no JavaScript bundle parsing, no cascade of style recalculations. You described a view. GPUI rendered it.

What We Haven’t Addressed This dialog looks native on macOS. It will run on Linux and Windows without code changes, but it won’t match those platforms’ design languages. The colors, spacing, and button styles are distinctly Apple. That’s intentional. Before we tackle adaptive theming, we need to understand the primitives: components, rendering, event handling, window configuration. Now we have that foundation. The next post will introduce theme systems and platform detection. We’ll build a dialog that adapts its appearance based on the operating system. Same code, different presentation. The macOS version will keep these traffic lights. The Windows version will get its own chrome. The Linux version will respect the user’s GTK or KDE theme.

Why This Matters You built a real interface without a browser. No Chromium, no bundler, no IPC bridge between UI and logic. The window appeared instantly because there’s no engine to initialize. Memory usage stayed low because there’s no document model holding layout state. The code stayed readable because Rust’s type system guided you through GPUI’s API. This is not a toy example scaled up. This is the model scaled down. The same patterns that power a production code editor now power your 180-pixel-tall dialog. That consistency matters. You can trust that techniques learned here will transfer to larger projects.

What Comes Next The next post adds adaptive theming. We’ll detect the platform, select appropriate styles, and render interfaces that feel native everywhere. After that, we’ll explore state management, complex layouts, and custom rendering. Each post builds on the previous one. For now, you have a dialog. It opens. It responds. It looks right. That’s the starting point.

Complete Code Listing Below is the full implementation with comprehensive comments explaining every section. Cargo.toml First, ensure your Cargo.toml is configured correctly: [package] name = "gpui" version = "0.1.0" edition = "2021"

[dependencies] gpui = "0.2" The project uses Rust edition 2021 and depends on GPUI version 0.2. That’s all you need to get started. src/main.rs Copy this into your src/main.rs file: // GPUI Tutorial: Creating a Native macOS Dialog // ================================================ // This tutorial demonstrates how to build a native-looking macOS dialog using GPUI. // We'll cover: components, event handling, styling, and window management. // // ============================================================================= // KEY CONCEPTS COVERED IN THIS TUTORIAL // ============================================================================= // // 1. COMPONENTS: Structs that implement the Render trait // - Backdrop: A simple full-screen overlay // - DialogBox: A complex component with state and event handlers // // 2. RENDER TRAIT: Defines how a component looks // - Returns an element tree using the builder pattern // // 3. ELEMENT BUILDER: Chain methods to configure elements // - Layout: .flex(), .flex_col(), .justify_center() // - Sizing: .w(), .h(), .size_full() // - Styling: .bg(), .text_color(), .rounded() // - Events: .on_mouse_up(), .on_key_down() // // 4. EVENT HANDLING: Respond to user input // - cx.listener() wraps methods as event handlers // - Handlers receive event, window, and context parameters // // 5. WINDOW MANAGEMENT: Create and configure windows // - WindowOptions configures behavior and appearance // - WindowKind::PopUp for floating modal windows // - Transparent backgrounds for custom shapes // // 6. COLORS: Multiple ways to specify colors // - rgb(0xRRGGBB): Opaque RGB color // - hsla(h, s, l, a): Hue, saturation, lightness, alpha // // 7. SIZING: Use px() for pixel-based dimensions // - px(10.0): 10 pixels // - relative(1.4): Relative to parent (for line-height, etc.) // // =============================================================================

// Import all GPUI types and traits use gpui::*;

// ============================================================================= // BACKDROP COMPONENT // ============================================================================= // The Backdrop creates a semi-transparent overlay behind the dialog. // This is a common pattern in modal dialogs to dim the background and // focus user attention on the dialog itself.

struct Backdrop;

// The Render trait is required for all GPUI components that display UI. // It has one method: render(), which returns the component's visual representation. impl Render for Backdrop {

   fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {

// div() creates a container element (similar to HTML's

)
       // GPUI uses a builder pattern where you chain methods to configure the element
       div()
           .size_full() // Fill the entire window
           .bg(hsla(0.0, 0.0, 0.0, 0.3)) // Semi-transparent black (30% opacity)
                                         // hsla(hue, saturation, lightness, alpha)
   }

}

// ============================================================================= // DIALOG BOX COMPONENT // ============================================================================= // The main dialog component that displays content and interactive buttons.

struct DialogBox;

// Implementation block for event handlers // In GPUI, event handlers are methods that receive events and can modify state impl DialogBox {

   // Mouse event handler for the OK button
   // Parameters:
   // - &mut self: mutable reference to this component
   // - MouseUpEvent: the event that triggered this handler
   // - &mut Window: reference to the window (unused here)
   // - cx: Context provides access to app-level operations
   fn on_ok_clicked(&mut self, _: &MouseUpEvent, _: &mut Window, cx: &mut Context<Self>) {
       cx.quit(); // Exit the application
   }
    
   // Mouse event handler for the Cancel button
   fn on_cancel_clicked(&mut self, _: &MouseUpEvent, _: &mut Window, cx: &mut Context<Self>) {
       cx.quit(); // Exit the application
   }
    
   // Keyboard event handler for ESC key
   // This allows users to close the dialog with the keyboard
   fn on_escape(&mut self, _: &KeyDownEvent, _: &mut Window, cx: &mut Context<Self>) {
       cx.quit(); // Exit the application
   }

}

// Implement the Render trait to define how the dialog looks impl Render for DialogBox {

   fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
       // =============================================================================
       // OUTER CONTAINER
       // =============================================================================
       // This is the full-window container that centers the dialog.
       // We use flexbox layout (similar to CSS flexbox) to center content.
    
       div()
           .flex() // Enable flexbox layout
           .size_full() // Take up the full window size
           .justify_center() // Center horizontally
           .items_center() // Center vertically
           // Attach keyboard event handler for ESC key
           // cx.listener() converts a method into an event listener
           .on_key_down(cx.listener(Self::on_escape))
           .child(
               // =====================================================================
               // DIALOG CONTAINER
               // =====================================================================
               // This is the actual dialog box with all its chrome (titlebar, content, buttons)
               div()
                   .flex()
                   .flex_col() // Stack children vertically
                   .rounded(px(10.0)) // 10px rounded corners (macOS standard)
                   .shadow_lg() // Large shadow for elevation/depth
                   .overflow_hidden() // Clip children to rounded corners
                   .w_full() // Fill parent width
                   .h_full() // Fill parent height
                   .child(
                       // =================================================================
                       // TITLEBAR WITH TRAFFIC LIGHTS
                       // =================================================================
                       // macOS dialogs have a gray titlebar with three colored buttons
                       // (red, yellow, green) known as "traffic lights"
                       div()
                           .flex() // Horizontal layout
                           .items_center() // Vertically center the buttons
                           .h(px(22.0)) // 22px height (macOS standard)
                           .w_full() // Full width
                           .bg(rgb(0xE8E8E8)) // Light gray background
                           .border_b_1() // 1px border on bottom
                           .border_color(rgb(0xD0D0D0)) // Darker gray border
                           .px_3() // Horizontal padding
                           .gap_2() // 8px gap between buttons
                           .child(
                               // RED BUTTON (Close)
                               // Clicking this will close the dialog
                               div()
                                   .w(px(12.0)) // 12px diameter
                                   .h(px(12.0))
                                   .rounded_full() // Make it circular
                                   .bg(rgb(0xFF5F57)) // macOS red
                                   .border_1() // 1px border
                                   .border_color(rgb(0xE04943)) // Darker red border
                                   .cursor_pointer() // Show pointer cursor
                                   // Attach click handler
                                   .on_mouse_up(
                                       MouseButton::Left,
                                       cx.listener(Self::on_cancel_clicked),
                                   ),
                           )
                           .child(
                               // YELLOW BUTTON (Minimize)
                               // In this example, it's decorative (no functionality)
                               div()
                                   .w(px(12.0))
                                   .h(px(12.0))
                                   .rounded_full()
                                   .bg(rgb(0xFFBD2E)) // macOS yellow
                                   .border_1()
                                   .border_color(rgb(0xDEA123)), // Darker yellow border
                           )
                           .child(
                               // GREEN BUTTON (Maximize/Zoom)
                               // In this example, it's decorative (no functionality)
                               div()
                                   .w(px(12.0))
                                   .h(px(12.0))
                                   .rounded_full()
                                   .bg(rgb(0x28C940)) // macOS green
                                   .border_1()
                                   .border_color(rgb(0x1AAB29)), // Darker green border
                           ),
                   )
                   .child(
                       // =================================================================
                       // MAIN CONTENT AREA
                       // =================================================================
                       // This contains the dialog message and action buttons
                       div()
                           .flex()
                           .flex_col() // Stack message and buttons vertically
                           .bg(rgb(0xEFEFEF)) // Light gray background (macOS standard)
                           .flex_1() // Take up remaining space
                           .px_6() // 24px horizontal padding
                           .py_5() // 20px vertical padding
                           .child(
                               // =============================================================
                               // MESSAGE TEXT CONTAINER
                               // =============================================================
                               // This holds the actual dialog message
                               div()
                                   .flex() // Enable flex layout
                                   .flex_1() // Expand to fill available space
                                   .items_center() // Center text vertically
                                   .px_3() // 12px horizontal padding
                                   .py_4() // 16px vertical padding
                                   .child(
                                       // THE ACTUAL TEXT
                                       // In GPUI, text styling is applied via methods
                                       div()
                                           .text_size(px(13.0)) // 13px (macOS system font size)
                                           .text_color(rgb(0x000000)) // Black text
                                           .font_weight(FontWeight::NORMAL) // Regular weight
                                           .line_height(relative(1.4)) // 1.4x line spacing
                                           .child("Hello World!"), // The message content
                                   ),
                           )
                           .child(
                               // =============================================================
                               // ACTION BUTTONS CONTAINER
                               // =============================================================
                               // macOS convention: buttons are right-aligned, with Cancel on
                               // the left and the primary action (OK) on the right
                               div()
                                   .flex() // Horizontal layout
                                   .gap_3() // 12px gap between buttons
                                   .justify_end() // Align to the right
                                   .w_full() // Full width
                                   .mt_3() // 12px top margin
                                   .child(
                                       // CANCEL BUTTON
                                       // Secondary action - uses white/gray styling
                                       div()
                                           .flex()
                                           .items_center() // Center text vertically
                                           .justify_center() // Center text horizontally
                                           .px_6() // 24px horizontal padding
                                           .h(px(32.0)) // 32px height
                                           .min_w(px(90.0)) // Minimum 90px width
                                           .bg(rgb(0xFFFFFF)) // White background
                                           .text_color(rgb(0x000000)) // Black text
                                           .text_size(px(13.0)) // 13px font size
                                           .font_weight(FontWeight::NORMAL) // Regular weight
                                           .rounded(px(6.0)) // 6px rounded corners
                                           .border_1() // 1px border
                                           .border_color(rgb(0xB8B8B8)) // Gray border
                                           .cursor_pointer() // Pointer cursor on hover
                                           .shadow_sm() // Small shadow
                                           // Hover state: slightly darken the background
                                           // The closure receives a mutable style object
                                           .hover(|style| style.bg(rgb(0xF8F8F8)))
                                           // Attach click handler
                                           .on_mouse_up(
                                               MouseButton::Left,
                                               cx.listener(Self::on_cancel_clicked),
                                           )
                                           .child("Cancel"), // Button text
                                   )
                                   .child(
                                       // OK BUTTON
                                       // Primary action - uses blue accent color
                                       div()
                                           .flex()
                                           .items_center()
                                           .justify_center()
                                           .px_6() // 24px horizontal padding
                                           .h(px(32.0)) // 32px height
                                           .min_w(px(90.0)) // Minimum 90px width
                                           .bg(rgb(0x007AFF)) // macOS accent blue
                                           .text_color(rgb(0xFFFFFF)) // White text
                                           .text_size(px(13.0)) // 13px font size
                                           .font_weight(FontWeight::NORMAL) // Regular weight
                                           .rounded(px(6.0)) // 6px rounded corners
                                           .cursor_pointer() // Pointer cursor on hover
                                           .shadow_sm() // Small shadow
                                           // Hover state: darken the blue
                                           .hover(|style| style.bg(rgb(0x0068DB)))
                                           // Attach click handler
                                           .on_mouse_up(
                                               MouseButton::Left,
                                               cx.listener(Self::on_ok_clicked),
                                           )
                                           .child("OK"), // Button text
                                   ),
                           ),
                   ),
           )
   }

}

// ============================================================================= // MAIN FUNCTION - APPLICATION ENTRY POINT // ============================================================================= // This is where we initialize the GPUI application and create our windows. // A GPUI app can have multiple windows, and we're creating two: // 1. A backdrop window (full-screen, transparent overlay) // 2. The dialog window (centered, with our DialogBox component)

fn main() {

   // Create a new GPUI application and run it
   // The closure receives an App context (cx) which provides access to
   // app-level operations like creating windows, accessing displays, etc.
   Application::new().run(|cx: &mut App| {
       // =====================================================================
       // GET DISPLAY INFORMATION
       // =====================================================================
       // We need to know the screen size to position our windows correctly
    
       let displays = cx.displays(); // Get all connected displays
       let display = displays.first().unwrap(); // Use the primary display
       let screen_size = display.bounds().size; // Get its dimensions
    
       // =====================================================================
       // CREATE BACKDROP WINDOW
       // =====================================================================
       // This creates a full-screen, semi-transparent overlay behind the dialog.
       // It dims the background and gives the dialog a modal appearance.
    
       cx.open_window(
           // WindowOptions configures how the window behaves and appears
           WindowOptions {
               // Position and size: full screen (covers entire display)
               window_bounds: Some(WindowBounds::Windowed(Bounds {
                   origin: point(px(0.0), px(0.0)), // Top-left corner
                   size: screen_size,               // Full screen size
               })),
    
               titlebar: None, // No titlebar (we want a borderless window)
               focus: false,   // Don't steal focus (the dialog should be focused)
               show: true,     // Make window visible immediately
    
               // WindowKind::PopUp creates a floating window without OS chrome
               kind: WindowKind::PopUp,
    
               is_movable: false,              // User can't drag this window
               display_id: Some(display.id()), // Show on this specific display
    
               // Transparent background lets the backdrop's semi-transparent
               // content show through properly
               window_background: WindowBackgroundAppearance::Transparent,
    
               ..Default::default() // Use defaults for other options
           },
           // Window content factory: creates the Backdrop component
           // |_, cx| receives (WindowHandle, Context)
           // cx.new() creates a new component instance
           |_, cx| cx.new(|_cx| Backdrop),
       )
       .unwrap(); // Panic if window creation fails (for this example)
    
       // =====================================================================
       // CALCULATE DIALOG POSITION
       // =====================================================================
       // We want the dialog centered on the screen
    
       let dialog_width = px(460.0); // Dialog width in pixels
       let dialog_height = px(180.0); // Dialog height in pixels
    
       // Calculate centered position
       let x = (screen_size.width - dialog_width) / 2.0; // Horizontal center
       let y = (screen_size.height - dialog_height) / 2.0; // Vertical center
    
       let dialog_bounds = Bounds {
           origin: point(x, y),
           size: size(dialog_width, dialog_height),
       };
    
       // =====================================================================
       // CREATE DIALOG WINDOW
       // =====================================================================
       // This is the actual modal dialog that contains our DialogBox component
    
       cx.open_window(
           WindowOptions {
               // Position and size: centered on screen
               window_bounds: Some(WindowBounds::Windowed(dialog_bounds)),
    
               titlebar: None, // No OS titlebar (we drew our own)
               focus: true,    // This window should have keyboard focus
               show: true,     // Make visible immediately
    
               // PopUp windows float above other windows
               kind: WindowKind::PopUp,
    
               is_movable: false, // Dialog is fixed (can't be dragged)
               display_id: Some(display.id()), // Show on primary display
    
               // Transparent background allows our rounded corners and
               // shadow to render properly
               window_background: WindowBackgroundAppearance::Transparent,
    
               ..Default::default()
           },
           // Create the DialogBox component
           |_, cx| cx.new(|_cx| DialogBox),
       )
       .unwrap();
   });

}

The code includes detailed comments throughout explaining:

  • The component model and Render trait
  • Builder pattern for constructing UI elements
  • Event handling with cx.listener()
  • Flexbox layout for positioning and alignment
  • Window management with WindowOptions
  • Color specifications using rgb() and hsla()
  • macOS native styling conventions

Modify the colors, change the text, add a third button. Break things deliberately. That’s how you learn what the framework actually does versus what you assumed it would do. Welcome to GPUI. This is where desktop development stops apologizing for itself.