linux: Add title bar for rules library (#33025)

Smit Barmase created

Closes #30513

- Abstract away common wrapper component to `platform_title_bar`.
- Use it in both zed and rules library.
- For rules library, keep traffic like only style for macOS, and add
custom title bar for Linux and Windows.

Release Notes:

- Added way to minimize, maximize, and close the rules library window
for Linux.

Change summary

Cargo.lock                                 |   1 
crates/rules_library/Cargo.toml            |   1 
crates/rules_library/src/rules_library.rs  | 173 ++++++++------
crates/title_bar/src/platform_title_bar.rs | 169 ++++++++++++++
crates/title_bar/src/title_bar.rs          | 275 +++++------------------
5 files changed, 339 insertions(+), 280 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -13607,6 +13607,7 @@ dependencies = [
  "serde",
  "settings",
  "theme",
+ "title_bar",
  "ui",
  "util",
  "workspace",

crates/rules_library/Cargo.toml 🔗

@@ -27,6 +27,7 @@ rope.workspace = true
 serde.workspace = true
 settings.workspace = true
 theme.workspace = true
+title_bar.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace-hack.workspace = true

crates/rules_library/src/rules_library.rs 🔗

@@ -20,12 +20,13 @@ use std::sync::Arc;
 use std::sync::atomic::AtomicBool;
 use std::time::Duration;
 use theme::ThemeSettings;
+use title_bar::platform_title_bar::PlatformTitleBar;
 use ui::{
     Context, IconButtonShape, KeyBinding, ListItem, ListItemSpacing, ParentElement, Render,
     SharedString, Styled, Tooltip, Window, div, prelude::*,
 };
 use util::{ResultExt, TryFutureExt};
-use workspace::Workspace;
+use workspace::{Workspace, client_side_decorations};
 use zed_actions::assistant::InlineAssist;
 
 use prompt_store::*;
@@ -110,15 +111,22 @@ pub fn open_rules_library(
         cx.update(|cx| {
             let app_id = ReleaseChannel::global(cx).app_id();
             let bounds = Bounds::centered(None, size(px(1024.0), px(768.0)), cx);
+            let window_decorations = match std::env::var("ZED_WINDOW_DECORATIONS") {
+                Ok(val) if val == "server" => gpui::WindowDecorations::Server,
+                Ok(val) if val == "client" => gpui::WindowDecorations::Client,
+                _ => gpui::WindowDecorations::Client,
+            };
             cx.open_window(
                 WindowOptions {
                     titlebar: Some(TitlebarOptions {
                         title: Some("Rules Library".into()),
-                        appears_transparent: cfg!(target_os = "macos"),
+                        appears_transparent: true,
                         traffic_light_position: Some(point(px(9.0), px(9.0))),
                     }),
                     app_id: Some(app_id.to_owned()),
                     window_bounds: Some(WindowBounds::Windowed(bounds)),
+                    window_background: cx.theme().window_background_appearance(),
+                    window_decorations: Some(window_decorations),
                     ..Default::default()
                 },
                 |window, cx| {
@@ -140,6 +148,7 @@ pub fn open_rules_library(
 }
 
 pub struct RulesLibrary {
+    title_bar: Option<Entity<PlatformTitleBar>>,
     store: Entity<PromptStore>,
     language_registry: Arc<LanguageRegistry>,
     rule_editors: HashMap<PromptId, RuleEditor>,
@@ -395,6 +404,11 @@ impl RulesLibrary {
             picker
         });
         Self {
+            title_bar: if !cfg!(target_os = "macos") {
+                Some(cx.new(|_| PlatformTitleBar::new("rules-library-title-bar")))
+            } else {
+                None
+            },
             store: store.clone(),
             language_registry,
             rule_editors: HashMap::default(),
@@ -1225,75 +1239,90 @@ impl Render for RulesLibrary {
         let ui_font = theme::setup_ui_font(window, cx);
         let theme = cx.theme().clone();
 
-        h_flex()
-            .id("rules-library")
-            .key_context("PromptLibrary")
-            .on_action(cx.listener(|this, &NewRule, window, cx| this.new_rule(window, cx)))
-            .on_action(
-                cx.listener(|this, &DeleteRule, window, cx| this.delete_active_rule(window, cx)),
-            )
-            .on_action(cx.listener(|this, &DuplicateRule, window, cx| {
-                this.duplicate_active_rule(window, cx)
-            }))
-            .on_action(cx.listener(|this, &ToggleDefaultRule, window, cx| {
-                this.toggle_default_for_active_rule(window, cx)
-            }))
-            .size_full()
-            .overflow_hidden()
-            .font(ui_font)
-            .text_color(theme.colors().text)
-            .child(self.render_rule_list(cx))
-            .map(|el| {
-                if self.store.read(cx).prompt_count() == 0 {
-                    el.child(
-                        v_flex()
-                            .w_2_3()
-                            .h_full()
-                            .items_center()
-                            .justify_center()
-                            .gap_4()
-                            .bg(cx.theme().colors().editor_background)
-                            .child(
-                                h_flex()
-                                    .gap_2()
-                                    .child(
-                                        Icon::new(IconName::Book)
-                                            .size(IconSize::Medium)
-                                            .color(Color::Muted),
-                                    )
-                                    .child(
-                                        Label::new("No rules yet")
-                                            .size(LabelSize::Large)
-                                            .color(Color::Muted),
-                                    ),
-                            )
-                            .child(
-                                h_flex()
-                                    .child(h_flex())
-                                    .child(
-                                        v_flex()
-                                            .gap_1()
-                                            .child(Label::new("Create your first rule:"))
-                                            .child(
-                                                Button::new("create-rule", "New Rule")
-                                                    .full_width()
-                                                    .key_binding(KeyBinding::for_action(
-                                                        &NewRule, window, cx,
-                                                    ))
-                                                    .on_click(|_, window, cx| {
-                                                        window.dispatch_action(
-                                                            NewRule.boxed_clone(),
-                                                            cx,
-                                                        )
-                                                    }),
-                                            ),
-                                    )
-                                    .child(h_flex()),
-                            ),
-                    )
-                } else {
-                    el.child(self.render_active_rule(cx))
-                }
-            })
+        client_side_decorations(
+            v_flex()
+                .id("rules-library")
+                .key_context("PromptLibrary")
+                .on_action(cx.listener(|this, &NewRule, window, cx| this.new_rule(window, cx)))
+                .on_action(
+                    cx.listener(|this, &DeleteRule, window, cx| {
+                        this.delete_active_rule(window, cx)
+                    }),
+                )
+                .on_action(cx.listener(|this, &DuplicateRule, window, cx| {
+                    this.duplicate_active_rule(window, cx)
+                }))
+                .on_action(cx.listener(|this, &ToggleDefaultRule, window, cx| {
+                    this.toggle_default_for_active_rule(window, cx)
+                }))
+                .size_full()
+                .overflow_hidden()
+                .font(ui_font)
+                .text_color(theme.colors().text)
+                .children(self.title_bar.clone())
+                .child(
+                    h_flex()
+                        .flex_1()
+                        .child(self.render_rule_list(cx))
+                        .map(|el| {
+                            if self.store.read(cx).prompt_count() == 0 {
+                                el.child(
+                                    v_flex()
+                                        .w_2_3()
+                                        .h_full()
+                                        .items_center()
+                                        .justify_center()
+                                        .gap_4()
+                                        .bg(cx.theme().colors().editor_background)
+                                        .child(
+                                            h_flex()
+                                                .gap_2()
+                                                .child(
+                                                    Icon::new(IconName::Book)
+                                                        .size(IconSize::Medium)
+                                                        .color(Color::Muted),
+                                                )
+                                                .child(
+                                                    Label::new("No rules yet")
+                                                        .size(LabelSize::Large)
+                                                        .color(Color::Muted),
+                                                ),
+                                        )
+                                        .child(
+                                            h_flex()
+                                                .child(h_flex())
+                                                .child(
+                                                    v_flex()
+                                                        .gap_1()
+                                                        .child(Label::new(
+                                                            "Create your first rule:",
+                                                        ))
+                                                        .child(
+                                                            Button::new("create-rule", "New Rule")
+                                                                .full_width()
+                                                                .key_binding(
+                                                                    KeyBinding::for_action(
+                                                                        &NewRule, window, cx,
+                                                                    ),
+                                                                )
+                                                                .on_click(|_, window, cx| {
+                                                                    window.dispatch_action(
+                                                                        NewRule.boxed_clone(),
+                                                                        cx,
+                                                                    )
+                                                                }),
+                                                        ),
+                                                )
+                                                .child(h_flex()),
+                                        ),
+                                )
+                            } else {
+                                el.child(self.render_active_rule(cx))
+                            }
+                        }),
+                ),
+            window,
+            cx,
+        )
     }
 }

crates/title_bar/src/platform_title_bar.rs 🔗

@@ -0,0 +1,169 @@
+use gpui::{
+    AnyElement, Context, Decorations, InteractiveElement, IntoElement, MouseButton, ParentElement,
+    Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
+};
+use smallvec::SmallVec;
+use std::mem;
+use ui::prelude::*;
+
+use crate::platforms::{platform_linux, platform_mac, platform_windows};
+
+pub struct PlatformTitleBar {
+    id: ElementId,
+    platform_style: PlatformStyle,
+    children: SmallVec<[AnyElement; 2]>,
+    should_move: bool,
+}
+
+impl PlatformTitleBar {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        let platform_style = PlatformStyle::platform();
+        Self {
+            id: id.into(),
+            platform_style,
+            children: SmallVec::new(),
+            should_move: false,
+        }
+    }
+
+    #[cfg(not(target_os = "windows"))]
+    pub fn height(window: &mut Window) -> Pixels {
+        (1.75 * window.rem_size()).max(px(34.))
+    }
+
+    #[cfg(target_os = "windows")]
+    pub fn height(_window: &mut Window) -> Pixels {
+        // todo(windows) instead of hard coded size report the actual size to the Windows platform API
+        px(32.)
+    }
+
+    pub fn set_children<T>(&mut self, children: T)
+    where
+        T: IntoIterator<Item = AnyElement>,
+    {
+        self.children = children.into_iter().collect();
+    }
+}
+
+impl Render for PlatformTitleBar {
+    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        let supported_controls = window.window_controls();
+        let decorations = window.window_decorations();
+        let height = Self::height(window);
+        let titlebar_color = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
+            if window.is_window_active() && !self.should_move {
+                cx.theme().colors().title_bar_background
+            } else {
+                cx.theme().colors().title_bar_inactive_background
+            }
+        } else {
+            cx.theme().colors().title_bar_background
+        };
+        let close_action = Box::new(workspace::CloseWindow);
+        let children = mem::take(&mut self.children);
+
+        h_flex()
+            .window_control_area(WindowControlArea::Drag)
+            .w_full()
+            .h(height)
+            .map(|this| {
+                if window.is_fullscreen() {
+                    this.pl_2()
+                } else if self.platform_style == PlatformStyle::Mac {
+                    this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
+                } else {
+                    this.pl_2()
+                }
+            })
+            .map(|el| match decorations {
+                Decorations::Server => el,
+                Decorations::Client { tiling, .. } => el
+                    .when(!(tiling.top || tiling.right), |el| {
+                        el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
+                    })
+                    .when(!(tiling.top || tiling.left), |el| {
+                        el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
+                    })
+                    // this border is to avoid a transparent gap in the rounded corners
+                    .mt(px(-1.))
+                    .border(px(1.))
+                    .border_color(titlebar_color),
+            })
+            .bg(titlebar_color)
+            .content_stretch()
+            .child(
+                div()
+                    .id(self.id.clone())
+                    .flex()
+                    .flex_row()
+                    .items_center()
+                    .justify_between()
+                    .w_full()
+                    // Note: On Windows the title bar behavior is handled by the platform implementation.
+                    .when(self.platform_style == PlatformStyle::Mac, |this| {
+                        this.on_click(|event, window, _| {
+                            if event.up.click_count == 2 {
+                                window.titlebar_double_click();
+                            }
+                        })
+                    })
+                    .when(self.platform_style == PlatformStyle::Linux, |this| {
+                        this.on_click(|event, window, _| {
+                            if event.up.click_count == 2 {
+                                window.zoom_window();
+                            }
+                        })
+                    })
+                    .children(children),
+            )
+            .when(!window.is_fullscreen(), |title_bar| {
+                match self.platform_style {
+                    PlatformStyle::Mac => title_bar,
+                    PlatformStyle::Linux => {
+                        if matches!(decorations, Decorations::Client { .. }) {
+                            title_bar
+                                .child(platform_linux::LinuxWindowControls::new(close_action))
+                                .when(supported_controls.window_menu, |titlebar| {
+                                    titlebar
+                                        .on_mouse_down(MouseButton::Right, move |ev, window, _| {
+                                            window.show_window_menu(ev.position)
+                                        })
+                                })
+                                .on_mouse_move(cx.listener(move |this, _ev, window, _| {
+                                    if this.should_move {
+                                        this.should_move = false;
+                                        window.start_window_move();
+                                    }
+                                }))
+                                .on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
+                                    this.should_move = false;
+                                }))
+                                .on_mouse_up(
+                                    MouseButton::Left,
+                                    cx.listener(move |this, _ev, _window, _cx| {
+                                        this.should_move = false;
+                                    }),
+                                )
+                                .on_mouse_down(
+                                    MouseButton::Left,
+                                    cx.listener(move |this, _ev, _window, _cx| {
+                                        this.should_move = true;
+                                    }),
+                                )
+                        } else {
+                            title_bar
+                        }
+                    }
+                    PlatformStyle::Windows => {
+                        title_bar.child(platform_windows::WindowsWindowControls::new(height))
+                    }
+                }
+            })
+    }
+}
+
+impl ParentElement for PlatformTitleBar {
+    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
+        self.children.extend(elements)
+    }
+}

crates/title_bar/src/title_bar.rs 🔗

@@ -1,34 +1,32 @@
 mod application_menu;
 mod collab;
 mod onboarding_banner;
+pub mod platform_title_bar;
 mod platforms;
 mod title_bar_settings;
 
 #[cfg(feature = "stories")]
 mod stories;
 
-use crate::application_menu::ApplicationMenu;
+use crate::{application_menu::ApplicationMenu, platform_title_bar::PlatformTitleBar};
 
 #[cfg(not(target_os = "macos"))]
 use crate::application_menu::{
     ActivateDirection, ActivateMenuLeft, ActivateMenuRight, OpenApplicationMenu,
 };
 
-use crate::platforms::{platform_linux, platform_mac, platform_windows};
 use auto_update::AutoUpdateStatus;
 use call::ActiveCall;
 use client::{Client, UserStore};
 use gpui::{
-    Action, AnyElement, App, Context, Corner, Decorations, Element, Entity, InteractiveElement,
-    Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
-    StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, WindowControlArea,
-    actions, div, px,
+    Action, AnyElement, App, Context, Corner, Element, Entity, InteractiveElement, IntoElement,
+    MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled, Subscription,
+    WeakEntity, Window, actions, div,
 };
 use onboarding_banner::OnboardingBanner;
 use project::Project;
 use rpc::proto;
 use settings::Settings as _;
-use smallvec::SmallVec;
 use std::sync::Arc;
 use theme::ActiveTheme;
 use title_bar_settings::TitleBarSettings;
@@ -111,14 +109,11 @@ pub fn init(cx: &mut App) {
 }
 
 pub struct TitleBar {
-    platform_style: PlatformStyle,
-    content: Stateful<Div>,
-    children: SmallVec<[AnyElement; 2]>,
+    platform_titlebar: Entity<PlatformTitleBar>,
     project: Entity<Project>,
     user_store: Entity<UserStore>,
     client: Arc<Client>,
     workspace: WeakEntity<Workspace>,
-    should_move: bool,
     application_menu: Option<Entity<ApplicationMenu>>,
     _subscriptions: Vec<Subscription>,
     banner: Entity<OnboardingBanner>,
@@ -127,173 +122,69 @@ pub struct TitleBar {
 impl Render for TitleBar {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let title_bar_settings = *TitleBarSettings::get_global(cx);
-        let close_action = Box::new(workspace::CloseWindow);
-        let height = Self::height(window);
-        let supported_controls = window.window_controls();
-        let decorations = window.window_decorations();
-        let titlebar_color = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
-            if window.is_window_active() && !self.should_move {
-                cx.theme().colors().title_bar_background
-            } else {
-                cx.theme().colors().title_bar_inactive_background
-            }
-        } else {
-            cx.theme().colors().title_bar_background
-        };
 
-        h_flex()
-            .id("titlebar")
-            .window_control_area(WindowControlArea::Drag)
-            .w_full()
-            .h(height)
-            .map(|this| {
-                if window.is_fullscreen() {
-                    this.pl_2()
-                } else if self.platform_style == PlatformStyle::Mac {
-                    this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
-                } else {
-                    this.pl_2()
-                }
-            })
-            .map(|el| match decorations {
-                Decorations::Server => el,
-                Decorations::Client { tiling, .. } => el
-                    .when(!(tiling.top || tiling.right), |el| {
-                        el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
-                    })
-                    .when(!(tiling.top || tiling.left), |el| {
-                        el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
-                    })
-                    // this border is to avoid a transparent gap in the rounded corners
-                    .mt(px(-1.))
-                    .border(px(1.))
-                    .border_color(titlebar_color),
-            })
-            .bg(titlebar_color)
-            .content_stretch()
-            .child(
-                div()
-                    .id("titlebar-content")
-                    .flex()
-                    .flex_row()
-                    .items_center()
-                    .justify_between()
-                    .w_full()
-                    // Note: On Windows the title bar behavior is handled by the platform implementation.
-                    .when(self.platform_style == PlatformStyle::Mac, |this| {
-                        this.on_click(|event, window, _| {
-                            if event.up.click_count == 2 {
-                                window.titlebar_double_click();
-                            }
+        let mut children = Vec::new();
+
+        children.push(
+            h_flex()
+                .gap_1()
+                .map(|title_bar| {
+                    let mut render_project_items = title_bar_settings.show_branch_name
+                        || title_bar_settings.show_project_items;
+                    title_bar
+                        .when_some(self.application_menu.clone(), |title_bar, menu| {
+                            render_project_items &= !menu.read(cx).all_menus_shown();
+                            title_bar.child(menu)
                         })
-                    })
-                    .when(self.platform_style == PlatformStyle::Linux, |this| {
-                        this.on_click(|event, window, _| {
-                            if event.up.click_count == 2 {
-                                window.zoom_window();
-                            }
-                        })
-                    })
-                    .child(
-                        h_flex()
-                            .gap_1()
-                            .map(|title_bar| {
-                                let mut render_project_items = title_bar_settings.show_branch_name
-                                    || title_bar_settings.show_project_items;
-                                title_bar
-                                    .when_some(self.application_menu.clone(), |title_bar, menu| {
-                                        render_project_items &= !menu.read(cx).all_menus_shown();
-                                        title_bar.child(menu)
-                                    })
-                                    .when(render_project_items, |title_bar| {
-                                        title_bar
-                                            .when(
-                                                title_bar_settings.show_project_items,
-                                                |title_bar| {
-                                                    title_bar
-                                                        .children(self.render_project_host(cx))
-                                                        .child(self.render_project_name(cx))
-                                                },
-                                            )
-                                            .when(
-                                                title_bar_settings.show_branch_name,
-                                                |title_bar| {
-                                                    title_bar
-                                                        .children(self.render_project_branch(cx))
-                                                },
-                                            )
-                                    })
-                            })
-                            .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()),
-                    )
-                    .child(self.render_collaborator_list(window, cx))
-                    .when(title_bar_settings.show_onboarding_banner, |title_bar| {
-                        title_bar.child(self.banner.clone())
-                    })
-                    .child(
-                        h_flex()
-                            .gap_1()
-                            .pr_1()
-                            .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
-                            .children(self.render_call_controls(window, cx))
-                            .map(|el| {
-                                let status = self.client.status();
-                                let status = &*status.borrow();
-                                if matches!(status, client::Status::Connected { .. }) {
-                                    el.child(self.render_user_menu_button(cx))
-                                } else {
-                                    el.children(self.render_connection_status(status, cx))
-                                        .when(TitleBarSettings::get_global(cx).show_sign_in, |el| {
-                                            el.child(self.render_sign_in_button(cx))
-                                        })
-                                        .child(self.render_user_menu_button(cx))
-                                }
-                            }),
-                    ),
-            )
-            .when(!window.is_fullscreen(), |title_bar| {
-                match self.platform_style {
-                    PlatformStyle::Mac => title_bar,
-                    PlatformStyle::Linux => {
-                        if matches!(decorations, Decorations::Client { .. }) {
+                        .when(render_project_items, |title_bar| {
                             title_bar
-                                .child(platform_linux::LinuxWindowControls::new(close_action))
-                                .when(supported_controls.window_menu, |titlebar| {
-                                    titlebar.on_mouse_down(
-                                        gpui::MouseButton::Right,
-                                        move |ev, window, _| window.show_window_menu(ev.position),
-                                    )
+                                .when(title_bar_settings.show_project_items, |title_bar| {
+                                    title_bar
+                                        .children(self.render_project_host(cx))
+                                        .child(self.render_project_name(cx))
                                 })
-                                .on_mouse_move(cx.listener(move |this, _ev, window, _| {
-                                    if this.should_move {
-                                        this.should_move = false;
-                                        window.start_window_move();
-                                    }
-                                }))
-                                .on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
-                                    this.should_move = false;
-                                }))
-                                .on_mouse_up(
-                                    gpui::MouseButton::Left,
-                                    cx.listener(move |this, _ev, _window, _cx| {
-                                        this.should_move = false;
-                                    }),
-                                )
-                                .on_mouse_down(
-                                    gpui::MouseButton::Left,
-                                    cx.listener(move |this, _ev, _window, _cx| {
-                                        this.should_move = true;
-                                    }),
-                                )
-                        } else {
-                            title_bar
-                        }
-                    }
-                    PlatformStyle::Windows => {
-                        title_bar.child(platform_windows::WindowsWindowControls::new(height))
+                                .when(title_bar_settings.show_branch_name, |title_bar| {
+                                    title_bar.children(self.render_project_branch(cx))
+                                })
+                        })
+                })
+                .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
+                .into_any_element(),
+        );
+
+        children.push(self.render_collaborator_list(window, cx).into_any_element());
+
+        if title_bar_settings.show_onboarding_banner {
+            children.push(self.banner.clone().into_any_element())
+        }
+
+        children.push(
+            h_flex()
+                .gap_1()
+                .pr_1()
+                .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
+                .children(self.render_call_controls(window, cx))
+                .map(|el| {
+                    let status = self.client.status();
+                    let status = &*status.borrow();
+                    if matches!(status, client::Status::Connected { .. }) {
+                        el.child(self.render_user_menu_button(cx))
+                    } else {
+                        el.children(self.render_connection_status(status, cx))
+                            .when(TitleBarSettings::get_global(cx).show_sign_in, |el| {
+                                el.child(self.render_sign_in_button(cx))
+                            })
+                            .child(self.render_user_menu_button(cx))
                     }
-                }
-            })
+                })
+                .into_any_element(),
+        );
+
+        self.platform_titlebar.update(cx, |this, _| {
+            this.set_children(children);
+        });
+
+        self.platform_titlebar.clone().into_any_element()
     }
 }
 
@@ -345,13 +236,12 @@ impl TitleBar {
             )
         });
 
+        let platform_titlebar = cx.new(|_| PlatformTitleBar::new(id));
+
         Self {
-            platform_style,
-            content: div().id(id.into()),
-            children: SmallVec::new(),
+            platform_titlebar,
             application_menu,
             workspace: workspace.weak_handle(),
-            should_move: false,
             project,
             user_store,
             client,
@@ -360,23 +250,6 @@ impl TitleBar {
         }
     }
 
-    #[cfg(not(target_os = "windows"))]
-    pub fn height(window: &mut Window) -> Pixels {
-        (1.75 * window.rem_size()).max(px(34.))
-    }
-
-    #[cfg(target_os = "windows")]
-    pub fn height(_window: &mut Window) -> Pixels {
-        // todo(windows) instead of hard coded size report the actual size to the Windows platform API
-        px(32.)
-    }
-
-    /// Sets the platform style.
-    pub fn platform_style(mut self, style: PlatformStyle) -> Self {
-        self.platform_style = style;
-        self
-    }
-
     fn render_ssh_project_host(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
         let options = self.project.read(cx).ssh_connection_options(cx)?;
         let host: SharedString = options.connection_string().into();
@@ -796,17 +669,3 @@ impl TitleBar {
         }
     }
 }
-
-impl InteractiveElement for TitleBar {
-    fn interactivity(&mut self) -> &mut Interactivity {
-        self.content.interactivity()
-    }
-}
-
-impl StatefulInteractiveElement for TitleBar {}
-
-impl ParentElement for TitleBar {
-    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
-        self.children.extend(elements)
-    }
-}