From a176408d81ff11e324eb572649187bdb325ddd76 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 13 Nov 2025 13:37:05 +0800 Subject: [PATCH] Revert "root: Render overlays inside Root element by default. (#1570)" (#1584) Revert #1573, #1570 --- crates/story/examples/dock.rs | 9 ++- crates/story/examples/tiles.rs | 10 ++- crates/story/src/lib.rs | 25 ++++--- crates/ui/src/notification.rs | 4 +- crates/ui/src/root.rs | 98 ++++++++++++---------------- docs/docs/components/dialog.md | 31 +++++++++ docs/docs/components/notification.md | 32 +++++++++ docs/docs/components/sheet.md | 34 +++++++++- docs/docs/root.md | 34 +++++++--- examples/dialog_overlay/src/main.rs | 4 +- examples/window_title/src/main.rs | 2 +- 11 files changed, 203 insertions(+), 80 deletions(-) diff --git a/crates/story/examples/dock.rs b/crates/story/examples/dock.rs index 6e803919..4e36d60c 100644 --- a/crates/story/examples/dock.rs +++ b/crates/story/examples/dock.rs @@ -505,7 +505,11 @@ pub fn open_new( } impl Render for StoryWorkspace { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let sheet_layer = Root::render_sheet_layer(window, cx); + let dialog_layer = Root::render_dialog_layer(window, cx); + let notification_layer = Root::render_notification_layer(window, cx); + div() .id("story-workspace") .on_action(cx.listener(Self::on_action_add_panel)) @@ -517,6 +521,9 @@ impl Render for StoryWorkspace { .flex_col() .child(self.title_bar.clone()) .child(self.dock_area.clone()) + .children(sheet_layer) + .children(dialog_layer) + .children(notification_layer) } } diff --git a/crates/story/examples/tiles.rs b/crates/story/examples/tiles.rs index ed2cf6c7..414522a2 100644 --- a/crates/story/examples/tiles.rs +++ b/crates/story/examples/tiles.rs @@ -409,8 +409,13 @@ pub fn open_new( } impl Render for StoryTiles { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let sheet_layer = Root::render_sheet_layer(window, cx); + let dialog_layer = Root::render_dialog_layer(window, cx); + let notification_layer = Root::render_notification_layer(window, cx); + div() + .font_family(".SystemUIFont") .relative() .size_full() .flex() @@ -419,6 +424,9 @@ impl Render for StoryTiles { .text_color(cx.theme().foreground) .child(TitleBar::new().child(div().flex().items_center().child("Story Tiles"))) .child(self.dock_area.clone()) + .children(sheet_layer) + .children(dialog_layer) + .children(notification_layer) } } diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 79843af1..a519237d 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -224,7 +224,7 @@ pub fn create_new_window_with_size( let view = crate_view_fn(window, cx); let root = cx.new(|cx| StoryRoot::new(title.clone(), view, window, cx)); - cx.new(|cx| Root::new(root.into(), window, cx).text_base()) + cx.new(|cx| Root::new(root.into(), window, cx)) }) .expect("failed to open window"); @@ -261,13 +261,22 @@ impl StoryRoot { } impl Render for StoryRoot { - fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { - div().size_full().child( - v_flex() - .size_full() - .child(self.title_bar.clone()) - .child(div().flex_1().overflow_hidden().child(self.view.clone())), - ) + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let sheet_layer = Root::render_sheet_layer(window, cx); + let dialog_layer = Root::render_dialog_layer(window, cx); + let notification_layer = Root::render_notification_layer(window, cx); + + div() + .size_full() + .child( + v_flex() + .size_full() + .child(self.title_bar.clone()) + .child(div().flex_1().overflow_hidden().child(self.view.clone())), + ) + .children(sheet_layer) + .children(dialog_layer) + .children(notification_layer) } } diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index a548ad55..0921e4b1 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -324,8 +324,8 @@ impl Render for Notification { .child( h_flex() .absolute() - .top_1p5() - .right_1p5() + .top_3p5() + .right_3p5() .invisible() .group_hover("", |this| this.visible()) .child( diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index 9fdd05e3..f17d7470 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -3,12 +3,12 @@ use crate::{ input::InputState, notification::{Notification, NotificationList}, sheet::Sheet, - window_border, ActiveTheme, Placement, StyledExt, TITLE_BAR_HEIGHT, + window_border, ActiveTheme, Placement, }; use gpui::{ actions, canvas, div, prelude::FluentBuilder as _, AnyView, App, AppContext, Context, DefiniteLength, Entity, FocusHandle, InteractiveElement, IntoElement, KeyBinding, - ParentElement as _, Render, StyleRefinement, Styled, Window, + ParentElement as _, Render, Styled, Window, }; use std::{any::TypeId, rc::Rc}; @@ -210,7 +210,6 @@ impl WindowExt for Window { /// /// It is used to manage the Sheet, Dialog, and Notification. pub struct Root { - style: StyleRefinement, /// Used to store the focus handle of the previous view. /// When the Dialog, Sheet closes, we will focus back to the previous view. previous_focus_handle: Option, @@ -238,7 +237,6 @@ pub(crate) struct ActiveDialog { impl Root { pub fn new(view: AnyView, window: &mut Window, cx: &mut Context) -> Self { Self { - style: StyleRefinement::default(), previous_focus_handle: None, active_sheet: None, active_dialogs: Vec::new(), @@ -276,64 +274,64 @@ impl Root { } // Render Notification layer. - fn render_notification_layer( - &mut self, - _: &mut Window, - _: &mut Context, + pub fn render_notification_layer( + window: &mut Window, + cx: &mut App, ) -> Option { - let active_sheet_placement = self.active_sheet.clone().map(|d| d.placement); + let root = window.root::()??; + + let active_sheet_placement = root.read(cx).active_sheet.clone().map(|d| d.placement); let (mt, mr) = match active_sheet_placement { - Some(Placement::Right) => (None, self.sheet_size), - Some(Placement::Top) => (self.sheet_size, None), + Some(Placement::Right) => (None, root.read(cx).sheet_size), + Some(Placement::Top) => (root.read(cx).sheet_size, None), _ => (None, None), }; Some( div() .absolute() - .top(TITLE_BAR_HEIGHT) + .top_0() .right_0() .when_some(mt, |this, offset| this.mt(offset)) .when_some(mr, |this, offset| this.mr(offset)) - .child(self.notification.clone()), + .child(root.read(cx).notification.clone()), ) } /// Render the Sheet layer. - fn render_sheet_layer( - &mut self, - window: &mut Window, - cx: &mut Context, - ) -> Option { - let active_sheet = self.active_sheet.clone()?; - let root = cx.entity(); - let mut sheet = Sheet::new(window, cx); - sheet = (active_sheet.builder)(sheet, window, cx); - sheet.focus_handle = active_sheet.focus_handle.clone(); - sheet.placement = active_sheet.placement; + pub fn render_sheet_layer(window: &mut Window, cx: &mut App) -> Option { + let root = window.root::()??; - let size = sheet.size; + if let Some(active_sheet) = root.read(cx).active_sheet.clone() { + let mut sheet = Sheet::new(window, cx); + sheet = (active_sheet.builder)(sheet, window, cx); + sheet.focus_handle = active_sheet.focus_handle.clone(); + sheet.placement = active_sheet.placement; - Some( - div().relative().child(sheet).child( - canvas( - move |_, _, cx| root.update(cx, |r, _| r.sheet_size = Some(size)), - |_, _, _, _| {}, - ) - .absolute() - .size_full(), - ), - ) + let size = sheet.size; + + return Some( + div().relative().child(sheet).child( + canvas( + move |_, _, cx| root.update(cx, |r, _| r.sheet_size = Some(size)), + |_, _, _, _| {}, + ) + .absolute() + .size_full(), + ), + ); + } + + None } /// Render the Dialog layer. - fn render_dialog_layer( - &mut self, - window: &mut Window, - cx: &mut Context, - ) -> Option { - let active_dialogs = self.active_dialogs.clone(); + pub fn render_dialog_layer(window: &mut Window, cx: &mut App) -> Option { + let root = window.root::()??; + + let active_dialogs = root.read(cx).active_dialogs.clone(); + if active_dialogs.is_empty() { return None; } @@ -387,12 +385,6 @@ impl Root { } } -impl Styled for Root { - fn style(&mut self) -> &mut StyleRefinement { - &mut self.style - } -} - impl Render for Root { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let base_font_size = cx.theme().font_size; @@ -404,16 +396,12 @@ impl Render for Root { .key_context(CONTEXT) .on_action(cx.listener(Self::on_action_tab)) .on_action(cx.listener(Self::on_action_tab_prev)) - .font_family(".SystemUIFont") - .refine_style(&self.style) - .bg(cx.theme().background) - .text_color(cx.theme().foreground) .relative() .size_full() - .child(self.view.clone()) - .children(self.render_sheet_layer(window, cx)) - .children(self.render_dialog_layer(window, cx)) - .children(self.render_notification_layer(window, cx)), + .font_family(".SystemUIFont") + .bg(cx.theme().background) + .text_color(cx.theme().foreground) + .child(self.view.clone()), ) } } diff --git a/docs/docs/components/dialog.md b/docs/docs/components/dialog.md index 66eb1327..25c5ef3f 100644 --- a/docs/docs/components/dialog.md +++ b/docs/docs/components/dialog.md @@ -16,6 +16,37 @@ use gpui_component::WindowExt; ## Usage +### Setup application root view for display of dialogs + +You need to set up your application's root view to render the dialog layer. This is typically done in your main application struct's render method. + +The [Root::render_dialog_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_dialog_layer) function handles rendering any active dialogs on top of your app content. + +```rust +use gpui_component::TitleBar; + +struct MyApp { + view: AnyView, +} + +impl Render for MyApp { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let dialog_layer = Root::render_dialog_layer(window, cx); + + div() + .size_full() + .child( + v_flex() + .size_full() + .child(TitleBar::new()) + .child(div().flex_1().overflow_hidden().child(self.view.clone())), + ) + // Render the dialog layer on top of the app content + .children(dialog_layer) + } +} +``` + ### Basic Dialog ```rust diff --git a/docs/docs/components/notification.md b/docs/docs/components/notification.md index 30caa210..c83664ac 100644 --- a/docs/docs/components/notification.md +++ b/docs/docs/components/notification.md @@ -16,6 +16,38 @@ use gpui_component::WindowExt; ## Usage +### Setup application root view for display of notifications + +You need to set up your application's root view to render the notification layer. This is typically done in your main application struct's render method. + +The [Root::render_notification_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_notification_layer) function handles rendering any active modals on top of your app content. + +```rust +use gpui_component::TitleBar; + +struct MyApp { + view: AnyView, +} + +impl Render for MyApp { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let notification_layer = Root::render_notification_layer(window, cx); + + + div() + .size_full() + .child( + v_flex() + .size_full() + .child(TitleBar::new()) + .child(div().flex_1().overflow_hidden().child(self.view.clone())), + ) + // Render the notification layer on top of the app content + .children(notification_layer) + } +} +``` + ### Basic Notification ```rust diff --git a/docs/docs/components/sheet.md b/docs/docs/components/sheet.md index e423d295..33ed57eb 100644 --- a/docs/docs/components/sheet.md +++ b/docs/docs/components/sheet.md @@ -10,11 +10,43 @@ A Sheet (also known as a sidebar or slide-out panel) is a navigation component t ## Import ```rust -use gpui_component::{WindowExt, Placement}; +use gpui_component::WindowExt; +use gpui_component::Placement; ``` ## Usage +### Setup application root view for display of sheets + +You need to set up your application's root view to render the sheet layer. This is typically done in your main application struct's render method. + +The [Root::render_sheet_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_sheet_layer) function handles rendering any active modals on top of your app content. + +```rust +use gpui_component::TitleBar; + +struct MyApp { + view: AnyView, +} + +impl Render for MyApp { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let sheet_layer = Root::render_sheet_layer(window, cx); + + div() + .size_full() + .child( + v_flex() + .size_full() + .child(TitleBar::new()) + .child(div().flex_1().overflow_hidden().child(self.view.clone())), + ) + // Render the sheet layer on top of the app content + .children(sheet_layer) + } +} +``` + ### Basic Sheet ```rust diff --git a/docs/docs/root.md b/docs/docs/root.md index 3fc16984..87709780 100644 --- a/docs/docs/root.md +++ b/docs/docs/root.md @@ -30,19 +30,33 @@ fn main() { } ``` -## Setup base styles +## Overlays -You can setup window level base styles by using `Styled` fluent method on [Root], then all child views will inherit these styles. +We have dialogs, sheets, notifications, we need placement for them to show, so [Root] provides methods to render these overlays: + +- [Root::render_dialog_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_dialog_layer) - Render the current opened modals. +- [Root::render_sheet_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_sheet_layer) - Render the current opened drawers. +- [Root::render_notification_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_notification_layer) - Render the notification list. + +We can put these layers in the `render` method your first level view (Root > YourFirstView): ```rs -Root::new(view.into(), window, cx) - // This default is `.SystemUIFont` it will use system UI font. - .font_family("Your Special Font") - .text_sm() +struct MyApp; + +impl Render for MyApp { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .size_full() + .child("My App Content") + .children(Root::render_dialog_layer(cx)) + .children(Root::render_sheet_layer(cx)) + .children(Root::render_notification_layer(cx)) + } +} ``` +:::tip +Here the example we used `children` method, it because if there is no opened dialogs, sheets, notifications, these methods will return `None`, so GPUI will not render anything. +::: + [Root]: https://docs.rs/gpui-component/latest/gpui_component/root/struct.Root.html - -``` - -``` diff --git a/examples/dialog_overlay/src/main.rs b/examples/dialog_overlay/src/main.rs index 696b77a2..0c0a6399 100644 --- a/examples/dialog_overlay/src/main.rs +++ b/examples/dialog_overlay/src/main.rs @@ -20,7 +20,7 @@ impl HelloWorld { } impl Render for HelloWorld { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { div() .bg(gpui::white()) .size_full() @@ -72,6 +72,8 @@ impl Render for HelloWorld { }), ), ) + .children(Root::render_dialog_layer(window, cx)) + .children(Root::render_sheet_layer(window, cx)) } } diff --git a/examples/window_title/src/main.rs b/examples/window_title/src/main.rs index 8db573c0..56dd02ae 100644 --- a/examples/window_title/src/main.rs +++ b/examples/window_title/src/main.rs @@ -79,7 +79,7 @@ fn main() { cx.open_window(window_options, |window, cx| { let view = cx.new(|_| Example); - cx.new(|cx| Root::new(view.into(), window, cx).text_sm()) + cx.new(|cx| Root::new(view.into(), window, cx)) })?; Ok::<_, anyhow::Error>(())