gpui: Improve Anchored to support center position (#47154)

Jason Lee and Claude Sonnet 4.6 created

Release Notes:

- N/A

Ref https://github.com/longbridge/gpui-component/pull/1956 extract my
fork version of `anchored.rs` to let GPUI to support position Anchored
at center.


https://github.com/user-attachments/assets/8d0230ed-4b75-440b-b8c3-9bde3decd141

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

Change summary

crates/activity_indicator/src/activity_indicator.rs      |   2 
crates/agent_ui/src/agent_configuration.rs               |  10 
crates/agent_ui/src/agent_model_selector.rs              |   2 
crates/agent_ui/src/agent_panel.rs                       |  14 
crates/agent_ui/src/config_options.rs                    |   2 
crates/agent_ui/src/conversation_view/thread_view.rs     |  10 
crates/agent_ui/src/mode_selector.rs                     |   2 
crates/agent_ui/src/model_selector_popover.rs            |   2 
crates/agent_ui/src/profile_selector.rs                  |   2 
crates/collab_ui/src/collab_panel.rs                     |   2 
crates/collab_ui/src/collab_panel/channel_modal.rs       |   2 
crates/debugger_tools/src/dap_log.rs                     |   2 
crates/debugger_ui/src/debugger_panel.rs                 |   6 
crates/debugger_ui/src/dropdown_menus.rs                 |   6 
crates/debugger_ui/src/new_process_modal.rs              |   2 
crates/debugger_ui/src/session/running/console.rs        |   6 
crates/debugger_ui/src/session/running/memory_view.rs    |   2 
crates/debugger_ui/src/session/running/variable_list.rs  |   2 
crates/edit_prediction_ui/src/edit_prediction_button.rs  |  12 
crates/editor/src/element.rs                             |  32 
crates/extensions_ui/src/extensions_ui.rs                |   4 
crates/file_finder/src/file_finder.rs                    |   8 
crates/git_graph/src/git_graph.rs                        |   4 
crates/git_ui/src/commit_modal.rs                        |   4 
crates/git_ui/src/git_panel.rs                           |  12 
crates/git_ui/src/git_ui.rs                              |   4 
crates/gpui/examples/anchor.rs                           | 201 +++++++++
crates/gpui/examples/popover.rs                          |   8 
crates/gpui/src/elements/anchored.rs                     |  33 
crates/gpui/src/geometry.rs                              | 223 +++++++--
crates/keymap_editor/src/keymap_editor.rs                |   4 
crates/language_tools/src/highlights_tree_view.rs        |  10 
crates/language_tools/src/lsp_button.rs                  |   4 
crates/language_tools/src/lsp_log_view.rs                |  10 
crates/outline_panel/src/outline_panel.rs                |   2 
crates/picker/src/popover_menu.rs                        |   6 
crates/project_panel/src/project_panel.rs                |   2 
crates/recent_projects/src/recent_projects.rs            |   2 
crates/repl/src/components/kernel_options.rs             |   2 
crates/settings_ui/src/components/ollama_model_picker.rs |   2 
crates/settings_ui/src/pages/tool_permissions_setup.rs   |   4 
crates/settings_ui/src/settings_ui.rs                    |   8 
crates/sidebar/src/sidebar.rs                            |  12 
crates/terminal_view/src/terminal_panel.rs               |   8 
crates/terminal_view/src/terminal_view.rs                |   2 
crates/title_bar/src/title_bar.rs                        |  14 
crates/ui/src/components/context_menu.rs                 |   4 
crates/ui/src/components/dropdown_menu.rs                |   8 
crates/ui/src/components/popover_menu.rs                 |  38 +
crates/ui/src/components/right_click_menu.rs             |  10 
crates/ui/src/components/scrollbar.rs                    |  10 
crates/workspace/src/dock.rs                             |   6 
crates/workspace/src/pane.rs                             |   8 
crates/workspace/src/status_bar.rs                       |  10 
crates/zed/src/zed/quick_action_bar.rs                   |   8 
55 files changed, 574 insertions(+), 241 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs πŸ”—

@@ -758,7 +758,7 @@ impl Render for ActivityIndicator {
                             }),
                     ),
                 )
-                .anchor(gpui::Corner::BottomLeft)
+                .anchor(gpui::Anchor::BottomLeft)
                 .menu(move |window, cx| {
                     let strong_this = activity_indicator.upgrade()?;
                     let mut has_work = false;

crates/agent_ui/src/agent_configuration.rs πŸ”—

@@ -16,7 +16,7 @@ use extension::ExtensionManifest;
 use extension_host::ExtensionStore;
 use fs::Fs;
 use gpui::{
-    Action, AnyView, App, AsyncWindowContext, Corner, Entity, EventEmitter, FocusHandle, Focusable,
+    Action, Anchor, AnyView, App, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable,
     ScrollHandle, Subscription, Task, WeakEntity,
 };
 use itertools::Itertools;
@@ -463,7 +463,7 @@ impl AgentConfiguration {
                     }))
                 }
             })
-            .anchor(gpui::Corner::TopRight)
+            .anchor(gpui::Anchor::TopRight)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(2.0),
@@ -562,7 +562,7 @@ impl AgentConfiguration {
                     }))
                 }
             })
-            .anchor(gpui::Corner::TopRight)
+            .anchor(gpui::Anchor::TopRight)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(2.0),
@@ -705,7 +705,7 @@ impl AgentConfiguration {
                     .icon_size(IconSize::Small),
                 Tooltip::text("Configure MCP Server"),
             )
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
             .menu({
                 let fs = self.fs.clone();
                 let context_server_id = context_server_id.clone();
@@ -1059,7 +1059,7 @@ impl AgentConfiguration {
                     }))
                 }
             })
-            .anchor(gpui::Corner::TopRight)
+            .anchor(gpui::Anchor::TopRight)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(2.0),

crates/agent_ui/src/agent_panel.rs πŸ”—

@@ -59,7 +59,7 @@ use extension::ExtensionEvents;
 use extension_host::ExtensionStore;
 use fs::Fs;
 use gpui::{
-    Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, ClipboardItem, Corner,
+    Action, Anchor, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, ClipboardItem,
     DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels,
     Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
 };
@@ -3156,7 +3156,7 @@ impl AgentPanel {
                     }
                 },
             )
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
             .with_handle(self.agent_panel_menu_handle.clone())
             .menu({
                 move |window, cx| {
@@ -3213,7 +3213,7 @@ impl AgentPanel {
     fn render_recent_entries_menu(
         &self,
         icon: IconName,
-        corner: Corner,
+        corner: Anchor,
         cx: &mut Context<Self>,
     ) -> impl IntoElement {
         let focus_handle = self.focus_handle(cx);
@@ -3602,7 +3602,7 @@ impl AgentPanel {
                     move |window, cx| builder(window, cx)
                 })
                 .with_handle(self.new_thread_menu_handle.clone())
-                .anchor(Corner::TopLeft)
+                .anchor(Anchor::TopLeft)
                 .offset(gpui::Point {
                     x: px(1.0),
                     y: px(1.0),
@@ -3626,7 +3626,7 @@ impl AgentPanel {
                         .when(show_history_menu && !agent_v2_enabled, |this| {
                             this.child(self.render_recent_entries_menu(
                                 IconName::MenuAltTemp,
-                                Corner::TopRight,
+                                Anchor::TopRight,
                                 cx,
                             ))
                         })
@@ -3650,7 +3650,7 @@ impl AgentPanel {
                         }
                     },
                 )
-                .anchor(Corner::TopRight)
+                .anchor(Anchor::TopRight)
                 .with_handle(self.new_thread_menu_handle.clone())
                 .menu(move |window, cx| new_thread_menu_builder(window, cx));
 
@@ -3678,7 +3678,7 @@ impl AgentPanel {
                         .when(show_history_menu && !agent_v2_enabled, |this| {
                             this.child(self.render_recent_entries_menu(
                                 IconName::MenuAltTemp,
-                                Corner::TopRight,
+                                Anchor::TopRight,
                                 cx,
                             ))
                         })

crates/agent_ui/src/config_options.rs πŸ”—

@@ -381,7 +381,7 @@ impl Render for ConfigOptionSelector {
             self.picker.clone(),
             trigger_button,
             tooltip,
-            gpui::Corner::BottomRight,
+            gpui::Anchor::BottomRight,
             cx,
         )
         .with_handle(self.picker_handle.clone())

crates/agent_ui/src/conversation_view/thread_view.rs πŸ”—

@@ -11,7 +11,7 @@ use feature_flags::AcpBetaFeatureFlag;
 
 use crate::message_editor::SharedSessionCapabilities;
 
-use gpui::{Corner, List};
+use gpui::List;
 use heapless::Vec as ArrayVec;
 use language_model::{LanguageModelEffortLevel, Speed};
 use settings::{SidebarSide, update_settings_file};
@@ -4033,7 +4033,7 @@ impl ThreadView {
                 x: px(0.0),
                 y: px(-2.0),
             })
-            .anchor(Corner::BottomLeft)
+            .anchor(gpui::Anchor::BottomLeft)
     }
 
     fn render_send_button(&self, cx: &mut Context<Self>) -> AnyElement {
@@ -4137,7 +4137,7 @@ impl ThreadView {
                     }
                 },
             )
-            .anchor(Corner::BottomLeft)
+            .anchor(gpui::Anchor::BottomLeft)
             .with_handle(self.add_context_menu_handle.clone())
             .offset(gpui::Point {
                 x: px(0.0),
@@ -7031,8 +7031,8 @@ impl ThreadView {
 
         PopoverMenu::new(("permission-granularity", entry_ix))
             .with_handle(permission_dropdown_handle.clone())
-            .anchor(Corner::TopRight)
-            .attach(Corner::BottomRight)
+            .anchor(gpui::Anchor::TopRight)
+            .attach(gpui::Anchor::BottomRight)
             .trigger(
                 Button::new(("granularity-trigger", entry_ix), current_label)
                     .end_icon(

crates/agent_ui/src/mode_selector.rs πŸ”—

@@ -197,7 +197,7 @@ impl Render for ModeSelector {
                     }
                 }),
             )
-            .anchor(gpui::Corner::BottomRight)
+            .anchor(gpui::Anchor::BottomRight)
             .with_handle(self.menu_handle.clone())
             .offset(gpui::Point {
                 x: px(0.0),

crates/agent_ui/src/model_selector_popover.rs πŸ”—

@@ -92,7 +92,7 @@ impl Render for ModelSelectorPopover {
                 })
                 .end_icon(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
             tooltip,
-            gpui::Corner::BottomRight,
+            gpui::Anchor::BottomRight,
             cx,
         )
         .with_handle(self.menu_handle.clone())

crates/agent_ui/src/profile_selector.rs πŸ”—

@@ -215,7 +215,7 @@ impl Render for ProfileSelector {
             picker,
             trigger_button,
             tooltip,
-            gpui::Corner::BottomRight,
+            gpui::Anchor::BottomRight,
             cx,
         )
         .with_handle(self.picker_handle.clone())

crates/collab_ui/src/collab_panel.rs πŸ”—

@@ -3693,7 +3693,7 @@ impl Render for CollabPanel {
                 deferred(
                     anchored()
                         .position(*position)
-                        .anchor(gpui::Corner::TopLeft)
+                        .anchor(gpui::Anchor::TopLeft)
                         .child(menu.clone()),
                 )
                 .with_priority(1)

crates/debugger_tools/src/dap_log.rs πŸ”—

@@ -518,7 +518,7 @@ impl Render for DapLogToolbarItemView {
             .and_then(|session_id| menu_rows.iter().find(|row| row.session_id == session_id));
 
         let dap_menu: PopoverMenu<_> = PopoverMenu::new("DapLogView")
-            .anchor(gpui::Corner::TopLeft)
+            .anchor(gpui::Anchor::TopLeft)
             .trigger(Button::new(
                 "debug_client_menu_header",
                 current_client

crates/debugger_ui/src/debugger_panel.rs πŸ”—

@@ -17,7 +17,7 @@ use dap::{client::SessionId, debugger_settings::DebuggerSettings};
 use editor::{Editor, MultiBufferOffset, ToPoint};
 use feature_flags::{FeatureFlag, FeatureFlagAppExt as _, PresenceFlag, register_feature_flag};
 use gpui::{
-    Action, App, AsyncWindowContext, ClipboardItem, Context, Corner, DismissEvent, Entity,
+    Action, Anchor, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity,
     EntityId, EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point,
     Subscription, Task, WeakEntity, anchored, deferred,
 };
@@ -1434,7 +1434,7 @@ impl DebugPanel {
                     ))
                 }
             })
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
     }
 }
 
@@ -1792,7 +1792,7 @@ impl Render for DebugPanel {
                     deferred(
                         anchored()
                             .position(*position)
-                            .anchor(gpui::Corner::TopLeft)
+                            .anchor(gpui::Anchor::TopLeft)
                             .child(menu.clone()),
                     )
                     .with_priority(1)

crates/debugger_ui/src/dropdown_menus.rs πŸ”—

@@ -1,7 +1,7 @@
 use std::rc::Rc;
 
 use collections::HashMap;
-use gpui::{Corner, Entity, WeakEntity};
+use gpui::{Anchor, Entity, WeakEntity};
 use project::debugger::session::{ThreadId, ThreadStatus};
 use ui::{CommonAnimationExt, ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
 use util::{maybe, truncate_and_trailoff};
@@ -211,7 +211,7 @@ impl DebugPanel {
                 this
             }),
         )
-        .attach(Corner::BottomLeft)
+        .attach(Anchor::BottomLeft)
         .style(DropdownStyle::Ghost)
         .handle(self.session_picker_menu_handle.clone());
 
@@ -323,7 +323,7 @@ impl DebugPanel {
                         this
                     }),
                 )
-                .attach(Corner::BottomLeft)
+                .attach(Anchor::BottomLeft)
                 .disabled(session_terminated)
                 .style(DropdownStyle::Ghost)
                 .handle(self.thread_picker_menu_handle.clone()),

crates/debugger_ui/src/new_process_modal.rs πŸ”—

@@ -523,7 +523,7 @@ impl NewProcessModal {
         )
         .style(ui::DropdownStyle::Outlined)
         .tab_index(0)
-        .attach(gpui::Corner::BottomLeft)
+        .attach(gpui::Anchor::BottomLeft)
         .offset(gpui::Point {
             x: px(0.0),
             y: px(2.0),

crates/debugger_ui/src/session/running/console.rs πŸ”—

@@ -12,8 +12,8 @@ use editor::{
 };
 use fuzzy::StringMatchCandidate;
 use gpui::{
-    Action as _, AppContext, Context, Corner, Entity, FocusHandle, Focusable, HighlightStyle, Hsla,
-    Render, Subscription, Task, TextStyle, WeakEntity, actions,
+    Action as _, AppContext, Context, Entity, FocusHandle, Focusable, HighlightStyle, Hsla, Render,
+    Subscription, Task, TextStyle, WeakEntity, actions,
 };
 use language::{Anchor, Buffer, CharScopeContext, CodeLabel, TextBufferSnapshot, ToOffset};
 use menu::{Confirm, SelectNext, SelectPrevious};
@@ -386,7 +386,7 @@ impl Console {
                     })
                 },
             )
-            .anchor(Corner::TopRight)
+            .anchor(gpui::Anchor::TopRight)
     }
 
     fn render_console(&self, cx: &Context<Self>) -> impl IntoElement {

crates/edit_prediction_ui/src/edit_prediction_button.rs πŸ”—

@@ -11,7 +11,7 @@ use editor::{
 use feature_flags::FeatureFlagAppExt;
 use fs::Fs;
 use gpui::{
-    Action, Animation, AnimationExt, App, AsyncWindowContext, Corner, Entity, FocusHandle,
+    Action, Anchor, Animation, AnimationExt, App, AsyncWindowContext, Entity, FocusHandle,
     Focusable, IntoElement, ParentElement, Render, Subscription, WeakEntity, actions, div,
     ease_in_out, pulsating_between,
 };
@@ -172,7 +172,7 @@ impl Render for EditPredictionButton {
                             }
                             .ok()
                         })
-                        .anchor(Corner::BottomRight)
+                        .anchor(Anchor::BottomRight)
                         .trigger_with_tooltip(
                             IconButton::new("copilot-icon", icon),
                             |_window, cx| Tooltip::for_action("GitHub Copilot", &ToggleMenu, cx),
@@ -216,7 +216,7 @@ impl Render for EditPredictionButton {
                             })
                             .ok()
                         })
-                        .anchor(Corner::BottomRight)
+                        .anchor(Anchor::BottomRight)
                         .trigger_with_tooltip(
                             IconButton::new("codestral-icon", IconName::AiMistral)
                                 .shape(IconButtonShape::Square)
@@ -260,7 +260,7 @@ impl Render for EditPredictionButton {
                             })
                             .ok()
                         })
-                        .anchor(Corner::BottomRight)
+                        .anchor(Anchor::BottomRight)
                         .trigger(
                             IconButton::new("openai-compatible-api-icon", IconName::AiOpenAiCompat)
                                 .shape(IconButtonShape::Square)
@@ -290,7 +290,7 @@ impl Render for EditPredictionButton {
                             })
                             .ok()
                         })
-                        .anchor(Corner::BottomRight)
+                        .anchor(Anchor::BottomRight)
                         .trigger_with_tooltip(
                             IconButton::new("ollama-icon", IconName::AiOllama)
                                 .shape(IconButtonShape::Square)
@@ -485,7 +485,7 @@ impl Render for EditPredictionButton {
                             .ok()
                         })
                     })
-                    .anchor(Corner::BottomRight)
+                    .anchor(Anchor::BottomRight)
                     .with_handle(self.popover_menu_handle.clone());
 
                 let is_refreshing = self

crates/editor/src/element.rs πŸ”—

@@ -40,15 +40,15 @@ use file_icons::FileIcons;
 use git::{Oid, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
 use gpui::{
     Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle,
-    Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
-    DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, Font, FontId,
-    FontWeight, GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement,
-    IsZero, Length, Modifiers, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent,
-    MouseMoveEvent, MousePressureEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels,
-    PressureStage, ScrollDelta, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size,
-    StatefulInteractiveElement, Style, Styled, StyledText, TextAlign, TextRun, TextStyleRefinement,
-    WeakEntity, Window, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline,
-    pattern_slash, point, px, quad, relative, size, solid_background, transparent_black,
+    Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corners, CursorStyle, DispatchPhase,
+    Edges, Element, ElementInputHandler, Entity, Focusable as _, Font, FontId, FontWeight,
+    GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, Length,
+    Modifiers, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent, MouseMoveEvent,
+    MousePressureEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, PressureStage, ScrollDelta,
+    ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
+    Style, Styled, StyledText, TextAlign, TextRun, TextStyleRefinement, WeakEntity, Window,
+    anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, pattern_slash,
+    point, px, quad, relative, size, solid_background, transparent_black,
 };
 use itertools::Itertools;
 use language::{
@@ -2104,8 +2104,8 @@ impl EditorElement {
             MinimapThumb::Hover => thumb_state.is_some(),
         };
 
-        let minimap_bounds = Bounds::from_corner_and_size(
-            Corner::TopRight,
+        let minimap_bounds = Bounds::from_anchor_and_size(
+            gpui::Anchor::TopRight,
             top_right_anchor,
             size(minimap_width, editor_bounds.size.height),
         );
@@ -5270,7 +5270,7 @@ impl EditorElement {
                         anchored()
                             .position(position)
                             .child(context_menu)
-                            .anchor(Corner::TopLeft)
+                            .anchor(gpui::Anchor::TopLeft)
                             .snap_to_window_with_margin(px(8.)),
                     )
                     .with_priority(1)
@@ -11524,8 +11524,8 @@ impl EditorScrollbars {
         let viewport_size = size(editor_width, editor_bounds.size.height);
 
         let scrollbar_bounds_for = |axis: ScrollbarAxis| match axis {
-            ScrollbarAxis::Horizontal => Bounds::from_corner_and_size(
-                Corner::BottomLeft,
+            ScrollbarAxis::Horizontal => Bounds::from_anchor_and_size(
+                gpui::Anchor::BottomLeft,
                 editor_bounds.bottom_left(),
                 size(
                     // The horizontal viewport size differs from the space available for the
@@ -11534,8 +11534,8 @@ impl EditorScrollbars {
                     scrollbar_width,
                 ),
             ),
-            ScrollbarAxis::Vertical => Bounds::from_corner_and_size(
-                Corner::TopRight,
+            ScrollbarAxis::Vertical => Bounds::from_anchor_and_size(
+                gpui::Anchor::TopRight,
                 editor_bounds.top_right(),
                 size(scrollbar_width, viewport_size.height),
             ),

crates/extensions_ui/src/extensions_ui.rs πŸ”—

@@ -14,7 +14,7 @@ use editor::{Editor, EditorElement, EditorStyle};
 use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
 use fuzzy::{StringMatchCandidate, match_strings};
 use gpui::{
-    Action, App, ClipboardItem, Context, Corner, Entity, EventEmitter, Focusable,
+    Action, Anchor, App, ClipboardItem, Context, Entity, EventEmitter, Focusable,
     InteractiveElement, KeyContext, ParentElement, Point, Render, Styled, Task, TextStyle,
     UniformListScrollHandle, WeakEntity, Window, actions, point, uniform_list,
 };
@@ -923,7 +923,7 @@ impl ExtensionsPage {
                                     )
                                     .icon_size(IconSize::Small),
                                 )
-                                .anchor(Corner::TopRight)
+                                .anchor(Anchor::TopRight)
                                 .offset(Point {
                                     x: px(0.0),
                                     y: px(2.0),

crates/file_finder/src/file_finder.rs πŸ”—

@@ -1770,8 +1770,8 @@ impl PickerDelegate for FileFinderDelegate {
                 .child(
                     PopoverMenu::new("filter-menu-popover")
                         .with_handle(self.filter_popover_menu_handle.clone())
-                        .attach(gpui::Corner::BottomRight)
-                        .anchor(gpui::Corner::BottomLeft)
+                        .attach(gpui::Anchor::BottomRight)
+                        .anchor(gpui::Anchor::BottomLeft)
                         .offset(gpui::Point {
                             x: px(1.0),
                             y: px(1.0),
@@ -1830,8 +1830,8 @@ impl PickerDelegate for FileFinderDelegate {
                         .child(
                             PopoverMenu::new("split-menu-popover")
                                 .with_handle(self.split_popover_menu_handle.clone())
-                                .attach(gpui::Corner::BottomRight)
-                                .anchor(gpui::Corner::BottomLeft)
+                                .attach(gpui::Anchor::BottomRight)
+                                .anchor(gpui::Anchor::BottomLeft)
                                 .offset(gpui::Point {
                                     x: px(1.0),
                                     y: px(1.0),

crates/git_graph/src/git_graph.rs πŸ”—

@@ -11,7 +11,7 @@ use git::{
 };
 use git_ui::{commit_tooltip::CommitAvatar, commit_view::CommitView, git_status_icon};
 use gpui::{
-    AnyElement, App, Bounds, ClickEvent, ClipboardItem, Corner, DefiniteLength, DragMoveEvent,
+    Anchor, AnyElement, App, Bounds, ClickEvent, ClipboardItem, DefiniteLength, DragMoveEvent,
     ElementId, Empty, Entity, EventEmitter, FocusHandle, Focusable, Hsla, PathBuilder, Pixels,
     Point, ScrollStrategy, ScrollWheelEvent, SharedString, Subscription, Task, TextStyleRefinement,
     UniformListScrollHandle, WeakEntity, Window, actions, anchored, deferred, point, prelude::*,
@@ -2863,7 +2863,7 @@ impl Render for GitGraph {
                 deferred(
                     anchored()
                         .position(*position)
-                        .anchor(Corner::TopLeft)
+                        .anchor(Anchor::TopLeft)
                         .child(menu.clone()),
                 )
                 .with_priority(1)

crates/git_ui/src/commit_modal.rs πŸ”—

@@ -324,7 +324,7 @@ impl CommitModal {
                 }
             })
             .with_handle(self.commit_menu_handle.clone())
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
     }
 
     pub fn render_footer(&self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
@@ -392,7 +392,7 @@ impl CommitModal {
                 branch_picker_button,
                 Tooltip::for_action_title("Switch Branch", &zed_actions::git::Branch),
             )
-            .anchor(Corner::BottomLeft)
+            .anchor(Anchor::BottomLeft)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(-2.0),

crates/git_ui/src/git_panel.rs πŸ”—

@@ -37,7 +37,7 @@ use git::{
     StashApply, StashPop, TrashUntrackedFiles, UnstageAll,
 };
 use gpui::{
-    Action, AsyncApp, AsyncWindowContext, Bounds, ClickEvent, Corner, DismissEvent, Empty, Entity,
+    Action, Anchor, AsyncApp, AsyncWindowContext, Bounds, ClickEvent, DismissEvent, Empty, Entity,
     EventEmitter, FocusHandle, Focusable, KeyContext, MouseButton, MouseDownEvent, Point,
     PromptLevel, ScrollStrategy, Subscription, Task, TextStyle, UniformListScrollHandle,
     WeakEntity, actions, anchored, deferred, point, size, uniform_list,
@@ -4024,7 +4024,7 @@ impl GitPanel {
                     cx,
                 ))
             })
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
     }
 
     pub(crate) fn render_generate_commit_message_button(
@@ -4196,7 +4196,7 @@ impl GitPanel {
                     }))
                 }
             })
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
     }
 
     pub fn configure_commit_button(&self, cx: &mut Context<Self>) -> (bool, &'static str) {
@@ -5818,7 +5818,7 @@ impl Render for GitPanel {
                 deferred(
                     anchored()
                         .position(*position)
-                        .anchor(Corner::TopLeft)
+                        .anchor(Anchor::TopLeft)
                         .child(menu.clone()),
                 )
                 .with_priority(1)
@@ -6171,7 +6171,7 @@ impl RenderOnce for PanelRepoFooter {
                     }
                 },
             )
-            .anchor(Corner::BottomLeft)
+            .anchor(Anchor::BottomLeft)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(-2.0),
@@ -6196,7 +6196,7 @@ impl RenderOnce for PanelRepoFooter {
                 branch_selector_button,
                 Tooltip::for_action_title("Switch Branch", &zed_actions::git::Switch),
             )
-            .anchor(Corner::BottomLeft)
+            .anchor(Anchor::BottomLeft)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(-2.0),

crates/git_ui/src/git_ui.rs πŸ”—

@@ -735,7 +735,7 @@ fn render_remote_button(
 }
 
 mod remote_button {
-    use gpui::{Action, AnyView, ClickEvent, Corner, FocusHandle};
+    use gpui::{Action, Anchor, AnyView, ClickEvent, FocusHandle};
     use ui::{
         App, ButtonCommon, Clickable, ContextMenu, ElementId, FluentBuilder, Icon, IconName,
         IconSize, IntoElement, Label, LabelCommon, LabelSize, LineHeightStyle, ParentElement,
@@ -923,7 +923,7 @@ mod remote_button {
                         .action("Force Push", git::ForcePush.boxed_clone())
                 }))
             })
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
     }
 
     #[allow(clippy::too_many_arguments)]

crates/gpui/examples/anchor.rs πŸ”—

@@ -0,0 +1,201 @@
+#![cfg_attr(target_family = "wasm", no_main)]
+
+use gpui::{
+    Anchor, AnchoredPositionMode, App, Axis, Bounds, Context, Half as _, InteractiveElement,
+    ParentElement, Pixels, Point, Render, SharedString, Size, Window, WindowBounds, WindowOptions,
+    anchored, deferred, div, point, prelude::*, px, rgb, size,
+};
+use gpui_platform::application;
+
+struct AnchorDemo {
+    hovered_button: Option<usize>,
+}
+
+struct ButtonDemo {
+    label: SharedString,
+    corner: Option<Anchor>,
+}
+
+fn resolved_position(corner: Anchor, button_size: Size<Pixels>) -> Point<Pixels> {
+    let offset = Point {
+        x: px(0.),
+        y: -button_size.height,
+    };
+
+    offset
+        + match corner.other_side_along(Axis::Vertical) {
+            Anchor::TopLeft => point(px(0.0), px(0.0)),
+            Anchor::TopCenter => point(button_size.width.half(), px(0.0)),
+            Anchor::TopRight => point(button_size.width, px(0.0)),
+            Anchor::LeftCenter => point(button_size.width, button_size.height.half()),
+            Anchor::RightCenter => point(px(0.), button_size.height.half()),
+            Anchor::BottomLeft => point(px(0.0), button_size.height),
+            Anchor::BottomCenter => point(button_size.width / 2.0, button_size.height),
+            Anchor::BottomRight => point(button_size.width, button_size.height),
+        }
+}
+
+impl AnchorDemo {
+    fn buttons() -> Vec<ButtonDemo> {
+        vec![
+            ButtonDemo {
+                label: "TopLeft".into(),
+                corner: Some(Anchor::TopLeft),
+            },
+            ButtonDemo {
+                label: "TopCenter".into(),
+                corner: Some(Anchor::TopCenter),
+            },
+            ButtonDemo {
+                label: "TopRight".into(),
+                corner: Some(Anchor::TopRight),
+            },
+            ButtonDemo {
+                label: "LeftCenter".into(),
+                corner: Some(Anchor::LeftCenter),
+            },
+            ButtonDemo {
+                label: "Center".into(),
+                corner: None,
+            },
+            ButtonDemo {
+                label: "RightCenter".into(),
+                corner: Some(Anchor::RightCenter),
+            },
+            ButtonDemo {
+                label: "BottomLeft".into(),
+                corner: Some(Anchor::BottomLeft),
+            },
+            ButtonDemo {
+                label: "BottomCenter".into(),
+                corner: Some(Anchor::BottomCenter),
+            },
+            ButtonDemo {
+                label: "BottomRight".into(),
+                corner: Some(Anchor::BottomRight),
+            },
+        ]
+    }
+}
+
+impl Render for AnchorDemo {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        let buttons = Self::buttons();
+        let button_size = size(px(120.0), px(65.0));
+
+        div()
+            .flex()
+            .flex_col()
+            .size_full()
+            .items_center()
+            .justify_center()
+            .bg(gpui::white())
+            .gap_4()
+            .p_10()
+            .child("Popover with Anchor")
+            .child(
+                div()
+                    .size_128()
+                    .grid()
+                    .grid_cols(3)
+                    .gap_6()
+                    .relative()
+                    .children(buttons.iter().enumerate().map(|(index, button)| {
+                        let is_hovered = self.hovered_button == Some(index);
+                        let is_hoverable = button.corner.is_some();
+                        div()
+                            .relative()
+                            .child(
+                                div()
+                                    .id(("button", index))
+                                    .w(button_size.width)
+                                    .h(button_size.height)
+                                    .flex()
+                                    .items_center()
+                                    .justify_center()
+                                    .bg(gpui::white())
+                                    .when(is_hoverable, |this| {
+                                        this.border_1()
+                                            .rounded_lg()
+                                            .border_color(gpui::black())
+                                            .hover(|style| {
+                                                style.bg(gpui::black()).text_color(gpui::white())
+                                            })
+                                            .on_hover(cx.listener(
+                                                move |this, hovered, _window, cx| {
+                                                    if *hovered {
+                                                        this.hovered_button = Some(index);
+                                                    } else if this.hovered_button == Some(index) {
+                                                        this.hovered_button = None;
+                                                    }
+                                                    cx.notify();
+                                                },
+                                            ))
+                                            .child(button.label.clone())
+                                    }),
+                            )
+                            .when_some(self.hovered_button.filter(|_| is_hovered), |this, index| {
+                                let button = &buttons[index];
+                                let Some(corner) = button.corner else {
+                                    return this;
+                                };
+
+                                let position = resolved_position(corner, button_size);
+                                this.child(deferred(
+                                    anchored()
+                                        .anchor(corner)
+                                        .position(position)
+                                        .position_mode(AnchoredPositionMode::Local)
+                                        .snap_to_window()
+                                        .child(
+                                            div()
+                                                .py_0p5()
+                                                .px_2()
+                                                .bg(gpui::black().opacity(0.75))
+                                                .text_color(rgb(0xffffff))
+                                                .rounded_sm()
+                                                .shadow_sm()
+                                                .min_w(px(100.0))
+                                                .text_sm()
+                                                .child(button.label.clone()),
+                                        ),
+                                ))
+                            })
+                    })),
+            )
+    }
+}
+
+fn run_example() {
+    application().run(|cx: &mut App| {
+        cx.open_window(
+            WindowOptions {
+                window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
+                    None,
+                    size(px(750.), px(600.)),
+                    cx,
+                ))),
+                ..Default::default()
+            },
+            |_, cx| {
+                cx.new(|_| AnchorDemo {
+                    hovered_button: None,
+                })
+            },
+        )
+        .unwrap();
+        cx.activate(true);
+    });
+}
+
+#[cfg(not(target_family = "wasm"))]
+fn main() {
+    run_example();
+}
+
+#[cfg(target_family = "wasm")]
+#[wasm_bindgen::prelude::wasm_bindgen(start)]
+pub fn start() {
+    gpui_platform::web_init();
+    run_example();
+}

crates/gpui/examples/popover.rs πŸ”—

@@ -1,7 +1,7 @@
 #![cfg_attr(target_family = "wasm", no_main)]
 
 use gpui::{
-    App, Context, Corner, Div, Hsla, Stateful, Window, WindowOptions, anchored, deferred, div,
+    Anchor, App, Context, Div, Hsla, Stateful, Window, WindowOptions, anchored, deferred, div,
     prelude::*, px,
 };
 use gpui_platform::application;
@@ -59,7 +59,7 @@ impl HelloWorld {
                     // Now GPUI supports nested deferred!
                     deferred(
                         anchored()
-                            .anchor(Corner::TopLeft)
+                            .anchor(Anchor::TopLeft)
                             .snap_to_window_with_margin(px(8.))
                             .child(
                                 popover()
@@ -98,7 +98,7 @@ impl Render for HelloWorld {
                         button("popover0").child("Opened Popover").child(
                             deferred(
                                 anchored()
-                                    .anchor(Corner::TopLeft)
+                                    .anchor(Anchor::TopLeft)
                                     .snap_to_window_with_margin(px(8.))
                                     .child(popover().w_96().gap_3().child(
                                         "This is a default opened Popover, \
@@ -120,7 +120,7 @@ impl Render for HelloWorld {
                                 this.child(
                                     deferred(
                                         anchored()
-                                            .anchor(Corner::TopLeft)
+                                            .anchor(Anchor::TopLeft)
                                             .snap_to_window_with_margin(px(8.))
                                             .child(
                                                 popover()

crates/gpui/src/elements/anchored.rs πŸ”—

@@ -1,7 +1,7 @@
 use smallvec::SmallVec;
 
 use crate::{
-    AnyElement, App, Axis, Bounds, Corner, Display, Edges, Element, GlobalElementId,
+    Anchor, AnyElement, App, Axis, Bounds, Display, Edges, Element, GlobalElementId,
     InspectorElementId, IntoElement, LayoutId, ParentElement, Pixels, Point, Position, Size, Style,
     Window, point, px,
 };
@@ -15,7 +15,7 @@ pub struct AnchoredState {
 /// will avoid overflowing the window bounds.
 pub struct Anchored {
     children: SmallVec<[AnyElement; 2]>,
-    anchor_corner: Corner,
+    anchor: Anchor,
     fit_mode: AnchoredFitMode,
     anchor_position: Option<Point<Pixels>>,
     position_mode: AnchoredPositionMode,
@@ -27,7 +27,7 @@ pub struct Anchored {
 pub fn anchored() -> Anchored {
     Anchored {
         children: SmallVec::new(),
-        anchor_corner: Corner::TopLeft,
+        anchor: Anchor::TopLeft,
         fit_mode: AnchoredFitMode::SwitchAnchor,
         anchor_position: None,
         position_mode: AnchoredPositionMode::Window,
@@ -37,8 +37,8 @@ pub fn anchored() -> Anchored {
 
 impl Anchored {
     /// Sets which corner of the anchored element should be anchored to the current position.
-    pub fn anchor(mut self, anchor: Corner) -> Self {
-        self.anchor_corner = anchor;
+    pub fn anchor(mut self, anchor: Anchor) -> Self {
+        self.anchor = anchor;
         self
     }
 
@@ -143,7 +143,7 @@ impl Element for Anchored {
 
         let (origin, mut desired) = self.position_mode.get_position_and_bounds(
             self.anchor_position,
-            self.anchor_corner,
+            self.anchor,
             size,
             bounds,
             self.offset,
@@ -155,23 +155,23 @@ impl Element for Anchored {
         };
 
         if self.fit_mode == AnchoredFitMode::SwitchAnchor {
-            let mut anchor_corner = self.anchor_corner;
+            let mut anchor = self.anchor;
 
             if desired.left() < limits.left() || desired.right() > limits.right() {
-                let switched = Bounds::from_corner_and_size(
-                    anchor_corner.other_side_corner_along(Axis::Horizontal),
+                let switched = Bounds::from_anchor_and_size(
+                    anchor.other_side_along(Axis::Horizontal),
                     origin,
                     size,
                 );
                 if !(switched.left() < limits.left() || switched.right() > limits.right()) {
-                    anchor_corner = anchor_corner.other_side_corner_along(Axis::Horizontal);
+                    anchor = anchor.other_side_along(Axis::Horizontal);
                     desired = switched
                 }
             }
 
             if desired.top() < limits.top() || desired.bottom() > limits.bottom() {
-                let switched = Bounds::from_corner_and_size(
-                    anchor_corner.other_side_corner_along(Axis::Vertical),
+                let switched = Bounds::from_anchor_and_size(
+                    anchor.other_side_along(Axis::Vertical),
                     origin,
                     size,
                 );
@@ -264,7 +264,7 @@ impl AnchoredPositionMode {
     fn get_position_and_bounds(
         &self,
         anchor_position: Option<Point<Pixels>>,
-        anchor_corner: Corner,
+        anchor: Anchor,
         size: Size<Pixels>,
         bounds: Bounds<Pixels>,
         offset: Option<Point<Pixels>>,
@@ -274,14 +274,13 @@ impl AnchoredPositionMode {
         match self {
             AnchoredPositionMode::Window => {
                 let anchor_position = anchor_position.unwrap_or(bounds.origin);
-                let bounds =
-                    Bounds::from_corner_and_size(anchor_corner, anchor_position + offset, size);
+                let bounds = Bounds::from_anchor_and_size(anchor, anchor_position + offset, size);
                 (anchor_position, bounds)
             }
             AnchoredPositionMode::Local => {
                 let anchor_position = anchor_position.unwrap_or_default();
-                let bounds = Bounds::from_corner_and_size(
-                    anchor_corner,
+                let bounds = Bounds::from_anchor_and_size(
+                    anchor,
                     bounds.origin + anchor_position + offset,
                     size,
                 );

crates/gpui/src/geometry.rs πŸ”—

@@ -826,24 +826,45 @@ where
         };
         Bounds { origin, size }
     }
+}
 
+impl<T> Bounds<T>
+where
+    T: Sub<Output = T> + Half + Clone + Debug + Default + PartialEq,
+{
     /// Constructs a `Bounds` from a corner point and size. The specified corner will be placed at
     /// the specified origin.
-    pub fn from_corner_and_size(corner: Corner, origin: Point<T>, size: Size<T>) -> Bounds<T> {
+    pub fn from_anchor_and_size(corner: Anchor, origin: Point<T>, size: Size<T>) -> Bounds<T> {
         let origin = match corner {
-            Corner::TopLeft => origin,
-            Corner::TopRight => Point {
+            Anchor::TopLeft => origin,
+            Anchor::TopRight => Point {
                 x: origin.x - size.width.clone(),
                 y: origin.y,
             },
-            Corner::BottomLeft => Point {
+            Anchor::BottomLeft => Point {
                 x: origin.x,
                 y: origin.y - size.height.clone(),
             },
-            Corner::BottomRight => Point {
+            Anchor::BottomRight => Point {
                 x: origin.x - size.width.clone(),
                 y: origin.y - size.height.clone(),
             },
+            Anchor::TopCenter => Point {
+                x: origin.x - size.width.half(),
+                y: origin.y,
+            },
+            Anchor::BottomCenter => Point {
+                x: origin.x - size.width.half(),
+                y: origin.y - size.height.clone(),
+            },
+            Anchor::LeftCenter => Point {
+                x: origin.x,
+                y: origin.y - size.height.half(),
+            },
+            Anchor::RightCenter => Point {
+                x: origin.x - size.width.clone(),
+                y: origin.y - size.height.half(),
+            },
         };
 
         Bounds { origin, size }
@@ -864,6 +885,43 @@ where
     }
 }
 
+impl<T> Bounds<T>
+where
+    T: Add<T, Output = T> + Half + Clone + Debug + Default + PartialEq,
+{
+    /// Returns the top center point of the bounds.
+    pub fn top_center(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone() + self.size.width.half(),
+            y: self.origin.y.clone(),
+        }
+    }
+
+    /// Returns the bottom center point of the bounds.
+    pub fn bottom_center(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone() + self.size.width.half(),
+            y: self.origin.y.clone() + self.size.height.clone(),
+        }
+    }
+
+    /// Returns the left center point of the bounds.
+    pub fn left_center(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone(),
+            y: self.origin.y.clone() + self.size.height.half(),
+        }
+    }
+
+    /// Returns the right center point of the bounds.
+    pub fn right_center(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone() + self.size.width.clone(),
+            y: self.origin.y.clone() + self.size.height.half(),
+        }
+    }
+}
+
 impl<T> Bounds<T>
 where
     T: PartialOrd + Add<T, Output = T> + Clone + Debug + Default + PartialEq,
@@ -1334,7 +1392,12 @@ where
             y: self.origin.y.clone() + self.size.height.clone(),
         }
     }
+}
 
+impl<T> Bounds<T>
+where
+    T: Add<T, Output = T> + Half + Clone + Debug + Default + PartialEq,
+{
     /// Returns the requested corner point of the bounds.
     ///
     /// # Returns
@@ -1344,20 +1407,24 @@ where
     /// # Examples
     ///
     /// ```
-    /// use gpui::{Bounds, Corner, Point, Size};
+    /// use gpui::{Bounds, Anchor, Point, Size};
     /// let bounds = Bounds {
     ///     origin: Point { x: 0, y: 0 },
     ///     size: Size { width: 10, height: 20 },
     /// };
-    /// let bottom_left = bounds.corner(Corner::BottomLeft);
+    /// let bottom_left = bounds.corner(Anchor::BottomLeft);
     /// assert_eq!(bottom_left, Point { x: 0, y: 20 });
     /// ```
-    pub fn corner(&self, corner: Corner) -> Point<T> {
+    pub fn corner(&self, corner: Anchor) -> Point<T> {
         match corner {
-            Corner::TopLeft => self.origin.clone(),
-            Corner::TopRight => self.top_right(),
-            Corner::BottomLeft => self.bottom_left(),
-            Corner::BottomRight => self.bottom_right(),
+            Anchor::TopLeft => self.origin.clone(),
+            Anchor::TopRight => self.top_right(),
+            Anchor::BottomLeft => self.bottom_left(),
+            Anchor::BottomRight => self.bottom_right(),
+            Anchor::TopCenter => self.top_center(),
+            Anchor::BottomCenter => self.bottom_center(),
+            Anchor::LeftCenter => self.left_center(),
+            Anchor::RightCenter => self.right_center(),
         }
     }
 }
@@ -2093,9 +2160,9 @@ impl From<Pixels> for Edges<Pixels> {
     }
 }
 
-/// Identifies a corner of a 2d box.
+/// Identifies a reference point on a 2D box, used to anchor positioned elements.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum Corner {
+pub enum Anchor {
     /// The top left corner
     TopLeft,
     /// The top right corner
@@ -2104,24 +2171,36 @@ pub enum Corner {
     BottomLeft,
     /// The bottom right corner
     BottomRight,
+    /// The top center position
+    TopCenter,
+    /// The bottom center position
+    BottomCenter,
+    /// The left center position
+    LeftCenter,
+    /// The right center position
+    RightCenter,
 }
 
-impl Corner {
+impl Anchor {
     /// Returns the directly opposite corner.
     ///
     /// # Examples
     ///
     /// ```
-    /// # use gpui::Corner;
-    /// assert_eq!(Corner::TopLeft.opposite_corner(), Corner::BottomRight);
+    /// # use gpui::Anchor;
+    /// assert_eq!(Anchor::TopLeft.opposite(), Anchor::BottomRight);
     /// ```
     #[must_use]
-    pub fn opposite_corner(self) -> Self {
+    pub fn opposite(self) -> Self {
         match self {
-            Corner::TopLeft => Corner::BottomRight,
-            Corner::TopRight => Corner::BottomLeft,
-            Corner::BottomLeft => Corner::TopRight,
-            Corner::BottomRight => Corner::TopLeft,
+            Anchor::TopLeft => Anchor::BottomRight,
+            Anchor::TopRight => Anchor::BottomLeft,
+            Anchor::BottomLeft => Anchor::TopRight,
+            Anchor::BottomRight => Anchor::TopLeft,
+            Anchor::TopCenter => Anchor::BottomCenter,
+            Anchor::BottomCenter => Anchor::TopCenter,
+            Anchor::LeftCenter => Anchor::RightCenter,
+            Anchor::RightCenter => Anchor::LeftCenter,
         }
     }
 
@@ -2130,27 +2209,44 @@ impl Corner {
     /// # Examples
     ///
     /// ```
-    /// # use gpui::{Axis, Corner};
-    /// let result = Corner::TopLeft.other_side_corner_along(Axis::Horizontal);
-    /// assert_eq!(result, Corner::TopRight);
+    /// # use gpui::{Axis, Anchor};
+    /// let result = Anchor::TopLeft.other_side_along(Axis::Horizontal);
+    /// assert_eq!(result, Anchor::TopRight);
     /// ```
     #[must_use]
-    pub fn other_side_corner_along(self, axis: Axis) -> Self {
+    pub fn other_side_along(self, axis: Axis) -> Self {
         match axis {
             Axis::Vertical => match self {
-                Corner::TopLeft => Corner::BottomLeft,
-                Corner::TopRight => Corner::BottomRight,
-                Corner::BottomLeft => Corner::TopLeft,
-                Corner::BottomRight => Corner::TopRight,
+                Anchor::TopLeft => Anchor::BottomLeft,
+                Anchor::TopRight => Anchor::BottomRight,
+                Anchor::BottomLeft => Anchor::TopLeft,
+                Anchor::BottomRight => Anchor::TopRight,
+                Anchor::TopCenter => Anchor::BottomCenter,
+                Anchor::BottomCenter => Anchor::TopCenter,
+                Anchor::LeftCenter => Anchor::LeftCenter,
+                Anchor::RightCenter => Anchor::RightCenter,
             },
             Axis::Horizontal => match self {
-                Corner::TopLeft => Corner::TopRight,
-                Corner::TopRight => Corner::TopLeft,
-                Corner::BottomLeft => Corner::BottomRight,
-                Corner::BottomRight => Corner::BottomLeft,
+                Anchor::TopLeft => Anchor::TopRight,
+                Anchor::TopRight => Anchor::TopLeft,
+                Anchor::BottomLeft => Anchor::BottomRight,
+                Anchor::BottomRight => Anchor::BottomLeft,
+                Anchor::TopCenter => Anchor::TopCenter,
+                Anchor::BottomCenter => Anchor::BottomCenter,
+                Anchor::LeftCenter => Anchor::RightCenter,
+                Anchor::RightCenter => Anchor::LeftCenter,
             },
         }
     }
+
+    /// Returns true if at the center.
+    #[inline]
+    pub fn is_center(&self) -> bool {
+        matches!(
+            self,
+            Self::TopCenter | Self::BottomCenter | Self::LeftCenter | Self::RightCenter
+        )
+    }
 }
 
 /// Represents the corners of a box in a 2D space, such as border radius.
@@ -2172,7 +2268,7 @@ pub struct Corners<T: Clone + Debug + Default + PartialEq> {
 
 impl<T> Corners<T>
 where
-    T: Clone + Debug + Default + PartialEq,
+    T: Add<T, Output = T> + Half + Clone + Debug + Default + PartialEq,
 {
     /// Constructs `Corners` where all sides are set to the same specified value.
     ///
@@ -2207,31 +2303,60 @@ where
         }
     }
 
-    /// Returns the requested corner.
+    /// Returns the requested corner value, supporting all eight corner positions.
+    ///
+    /// For the four basic corners (TopLeft, TopRight, BottomLeft, BottomRight),
+    /// this returns the corresponding field value directly.
+    ///
+    /// For the center positions (TopCenter, BottomCenter, LeftCenter, RightCenter),
+    /// this calculates the average of the two adjacent corners.
     ///
     /// # Returns
     ///
-    /// A `Point<T>` representing the corner requested by the parameter.
+    /// A value of type `T` representing the corner requested by the parameter.
     ///
     /// # Examples
     ///
+    /// Basic corner positions:
+    ///
+    /// ```
+    /// # use gpui::{Anchor, Corners};
+    /// let corners = Corners {
+    ///     top_left: 10,
+    ///     top_right: 20,
+    ///     bottom_left: 30,
+    ///     bottom_right: 40
+    /// };
+    /// assert_eq!(corners.corner(Anchor::TopLeft), 10);
+    /// assert_eq!(corners.corner(Anchor::BottomRight), 40);
+    /// ```
+    ///
+    /// Center positions (calculated as average of adjacent corners):
+    ///
     /// ```
-    /// # use gpui::{Corner, Corners};
+    /// # use gpui::{Anchor, Corners};
     /// let corners = Corners {
-    ///     top_left: 1,
-    ///     top_right: 2,
-    ///     bottom_left: 3,
-    ///     bottom_right: 4
+    ///     top_left: 10,
+    ///     top_right: 20,
+    ///     bottom_left: 30,
+    ///     bottom_right: 40
     /// };
-    /// assert_eq!(corners.corner(Corner::BottomLeft), 3);
+    /// assert_eq!(corners.corner(Anchor::TopCenter), 15);
+    /// assert_eq!(corners.corner(Anchor::BottomCenter), 35);
+    /// assert_eq!(corners.corner(Anchor::LeftCenter), 20);
+    /// assert_eq!(corners.corner(Anchor::RightCenter), 30);
     /// ```
     #[must_use]
-    pub fn corner(&self, corner: Corner) -> T {
+    pub fn corner(&self, corner: Anchor) -> T {
         match corner {
-            Corner::TopLeft => self.top_left.clone(),
-            Corner::TopRight => self.top_right.clone(),
-            Corner::BottomLeft => self.bottom_left.clone(),
-            Corner::BottomRight => self.bottom_right.clone(),
+            Anchor::TopLeft => self.top_left.clone(),
+            Anchor::TopRight => self.top_right.clone(),
+            Anchor::BottomLeft => self.bottom_left.clone(),
+            Anchor::BottomRight => self.bottom_right.clone(),
+            Anchor::TopCenter => (self.top_left.clone() + self.top_right.clone()).half(),
+            Anchor::BottomCenter => (self.bottom_left.clone() + self.bottom_right.clone()).half(),
+            Anchor::LeftCenter => (self.top_left.clone() + self.bottom_left.clone()).half(),
+            Anchor::RightCenter => (self.top_right.clone() + self.bottom_right.clone()).half(),
         }
     }
 }
@@ -2337,7 +2462,7 @@ impl<T: Div<f32, Output = T> + Ord + Clone + Debug + Default + PartialEq> Corner
     ///
     /// # Returns
     ///
-    /// Corner radii values clamped to fit.
+    /// Anchor radii values clamped to fit.
     #[must_use]
     pub fn clamp_radii_for_quad_size(self, size: Size<T>) -> Corners<T> {
         let max = cmp::min(size.width, size.height) / 2.;

crates/keymap_editor/src/keymap_editor.rs πŸ”—

@@ -1665,7 +1665,7 @@ impl KeymapEditor {
                     }
                 }))
             })
-            .anchor(gpui::Corner::TopRight)
+            .anchor(gpui::Anchor::TopRight)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(2.0),
@@ -2357,7 +2357,7 @@ impl Render for KeymapEditor {
                 deferred(
                     anchored()
                         .position(*position)
-                        .anchor(gpui::Corner::TopLeft)
+                        .anchor(gpui::Anchor::TopLeft)
                         .child(menu.clone()),
                 )
                 .with_priority(1)

crates/language_tools/src/highlights_tree_view.rs πŸ”—

@@ -3,10 +3,10 @@ use editor::{
     scroll::Autoscroll,
 };
 use gpui::{
-    Action, App, AppContext as _, Context, Corner, Div, Entity, EntityId, EventEmitter,
-    FocusHandle, Focusable, HighlightStyle, Hsla, InteractiveElement, IntoElement, MouseButton,
-    MouseDownEvent, MouseMoveEvent, ParentElement, Render, ScrollStrategy, SharedString, Styled,
-    Task, UniformListScrollHandle, WeakEntity, Window, actions, div, rems, uniform_list,
+    Action, App, AppContext as _, Context, Div, Entity, EntityId, EventEmitter, FocusHandle,
+    Focusable, HighlightStyle, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent,
+    MouseMoveEvent, ParentElement, Render, ScrollStrategy, SharedString, Styled, Task,
+    UniformListScrollHandle, WeakEntity, Window, actions, div, rems, uniform_list,
 };
 use language::{BufferId, Point, ToOffset};
 use menu::{SelectNext, SelectPrevious};
@@ -971,7 +971,7 @@ impl HighlightsTreeToolbarItemView {
                     .toggle_state(self.toggle_settings_handle.is_deployed()),
                 Tooltip::text("Highlights Settings"),
             )
-            .anchor(Corner::TopRight)
+            .anchor(gpui::Anchor::TopRight)
             .with_handle(self.toggle_settings_handle.clone())
             .menu(move |window, cx| {
                 let tree_view_for_text = tree_view.clone();

crates/language_tools/src/lsp_button.rs πŸ”—

@@ -13,7 +13,7 @@ use language::language_settings::{EditPredictionProvider, all_language_settings}
 use client::proto;
 use collections::HashSet;
 use editor::{Editor, EditorEvent};
-use gpui::{Corner, Entity, Subscription, Task, WeakEntity, actions};
+use gpui::{Anchor, Entity, Subscription, Task, WeakEntity, actions};
 use language::{BinaryStatus, BufferId, ServerHealth};
 use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};
 use project::{
@@ -1321,7 +1321,7 @@ impl Render for LspButton {
                         .ok()
                         .flatten()
                 })
-                .anchor(Corner::BottomLeft)
+                .anchor(Anchor::BottomLeft)
                 .with_handle(self.popover_menu_handle.clone())
                 .trigger_with_tooltip(
                     IconButton::new("zed-lsp-tool-button", IconName::BoltOutlined)

crates/language_tools/src/lsp_log_view.rs πŸ”—

@@ -2,7 +2,7 @@ use collections::VecDeque;
 use edit_prediction::EditPredictionStore;
 use editor::{Editor, EditorEvent, MultiBufferOffset, actions::MoveToEnd, scroll::Autoscroll};
 use gpui::{
-    App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement,
+    Anchor, App, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement,
     Render, Styled, Subscription, Task, WeakEntity, Window, actions, div,
 };
 use itertools::Itertools as _;
@@ -956,7 +956,7 @@ impl Render for LspLogToolbarItemView {
         let log_toolbar_view = cx.weak_entity();
 
         let lsp_menu = PopoverMenu::new("LspLogView")
-            .anchor(Corner::TopLeft)
+            .anchor(Anchor::TopLeft)
             .trigger(
                 Button::new(
                     "language_server_menu_header",
@@ -1031,7 +1031,7 @@ impl Render for LspLogToolbarItemView {
                 LogKind::ServerInfo => SERVER_INFO,
             };
             PopoverMenu::new("LspViewSelector")
-                .anchor(Corner::TopLeft)
+                .anchor(Anchor::TopLeft)
                 .trigger(
                     Button::new("language_server_menu_header", label).end_icon(
                         Icon::new(IconName::ChevronDown)
@@ -1123,7 +1123,7 @@ impl Render for LspLogToolbarItemView {
                                 let log_view = log_view.clone();
                                 div().child(
                                     PopoverMenu::new("lsp-trace-level-menu")
-                                        .anchor(Corner::TopLeft)
+                                        .anchor(Anchor::TopLeft)
                                         .trigger(
                                             Button::new(
                                                 "language_server_trace_level_selector",
@@ -1193,7 +1193,7 @@ impl Render for LspLogToolbarItemView {
                                 let log_view = log_view.clone();
                                 div().child(
                                     PopoverMenu::new("lsp-log-level-menu")
-                                        .anchor(Corner::TopLeft)
+                                        .anchor(Anchor::TopLeft)
                                         .trigger(
                                             Button::new(
                                                 "language_server_log_level_selector",

crates/picker/src/popover_menu.rs πŸ”—

@@ -1,5 +1,5 @@
 use gpui::{
-    AnyView, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Point,
+    Anchor, AnyView, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Point,
     Subscription,
 };
 use ui::{
@@ -18,7 +18,7 @@ where
     trigger: T,
     tooltip: TT,
     handle: Option<PopoverMenuHandle<Picker<P>>>,
-    anchor: Corner,
+    anchor: Anchor,
     offset: Option<Point<Pixels>>,
     _subscriptions: Vec<Subscription>,
 }
@@ -33,7 +33,7 @@ where
         picker: Entity<Picker<P>>,
         trigger: T,
         tooltip: TT,
-        anchor: Corner,
+        anchor: Anchor,
         cx: &mut App,
     ) -> Self {
         Self {

crates/project_panel/src/project_panel.rs πŸ”—

@@ -7096,7 +7096,7 @@ impl Render for ProjectPanel {
                     deferred(
                         anchored()
                             .position(*position)
-                            .anchor(gpui::Corner::TopLeft)
+                            .anchor(gpui::Anchor::TopLeft)
                             .child(menu.clone()),
                     )
                     .with_priority(3)

crates/recent_projects/src/recent_projects.rs πŸ”—

@@ -1783,7 +1783,7 @@ impl PickerDelegate for RecentProjectsDelegate {
                 .child(
                     PopoverMenu::new("actions-menu-popover")
                         .with_handle(self.actions_menu_handle.clone())
-                        .anchor(gpui::Corner::BottomRight)
+                        .anchor(gpui::Anchor::BottomRight)
                         .offset(gpui::Point {
                             x: px(0.0),
                             y: px(-2.0),

crates/repl/src/components/kernel_options.rs πŸ”—

@@ -484,7 +484,7 @@ where
         PopoverMenu::new("kernel-switcher")
             .menu(move |_window, _cx| Some(picker_view.clone()))
             .trigger_with_tooltip(self.trigger, self.tooltip)
-            .attach(gpui::Corner::BottomLeft)
+            .attach(gpui::Anchor::BottomLeft)
             .when_some(self.handle, |menu, handle| menu.with_handle(handle))
     }
 }

crates/settings_ui/src/pages/tool_permissions_setup.rs πŸ”—

@@ -1112,7 +1112,7 @@ fn render_global_default_mode_section(current_mode: ToolPermissionMode) -> AnyEl
                         })
                     }))
                 })
-                .anchor(gpui::Corner::TopRight),
+                .anchor(gpui::Anchor::TopRight),
         )
         .into_any_element()
 }
@@ -1171,7 +1171,7 @@ fn render_default_mode_section(
                         })
                     }))
                 })
-                .anchor(gpui::Corner::TopRight),
+                .anchor(gpui::Anchor::TopRight),
         )
         .into_any_element()
 }

crates/settings_ui/src/settings_ui.rs πŸ”—

@@ -2469,7 +2469,7 @@ impl SettingsWindow {
                                     .style(DropdownStyle::Subtle)
                                     .trigger_tooltip(Tooltip::text("View Other Projects"))
                                     .trigger_icon(IconName::ChevronDown)
-                                    .attach(gpui::Corner::BottomLeft)
+                                    .attach(gpui::Anchor::BottomLeft)
                                     .offset(gpui::Point {
                                         x: px(0.0),
                                         y: px(2.0),
@@ -4260,7 +4260,7 @@ fn render_font_picker(
                 )
             }))
         })
-        .anchor(gpui::Corner::TopLeft)
+        .anchor(gpui::Anchor::TopLeft)
         .offset(gpui::Point {
             x: px(0.0),
             y: px(2.0),
@@ -4313,7 +4313,7 @@ fn render_theme_picker(
                 )
             }))
         })
-        .anchor(gpui::Corner::TopLeft)
+        .anchor(gpui::Anchor::TopLeft)
         .offset(gpui::Point {
             x: px(0.0),
             y: px(2.0),
@@ -4366,7 +4366,7 @@ fn render_icon_theme_picker(
                 )
             }))
         })
-        .anchor(gpui::Corner::TopLeft)
+        .anchor(gpui::Anchor::TopLeft)
         .offset(gpui::Point {
             x: px(0.0),
             y: px(2.0),

crates/sidebar/src/sidebar.rs πŸ”—

@@ -2043,7 +2043,7 @@ impl Sidebar {
 
                 Some(menu)
             })
-            .anchor(gpui::Corner::TopRight)
+            .anchor(gpui::Anchor::TopRight)
             .offset(gpui::Point {
                 x: px(0.),
                 y: px(1.),
@@ -4047,7 +4047,7 @@ impl Sidebar {
                 x: px(-2.0),
                 y: px(-2.0),
             })
-            .anchor(gpui::Corner::BottomRight)
+            .anchor(gpui::Anchor::BottomRight)
     }
 
     fn new_thread_in_group(
@@ -4464,14 +4464,14 @@ impl Sidebar {
 
         sidebar_side_context_menu("sidebar-toggle-menu", _cx)
             .anchor(if on_right {
-                gpui::Corner::BottomRight
+                gpui::Anchor::BottomRight
             } else {
-                gpui::Corner::BottomLeft
+                gpui::Anchor::BottomLeft
             })
             .attach(if on_right {
-                gpui::Corner::TopRight
+                gpui::Anchor::TopRight
             } else {
-                gpui::Corner::TopLeft
+                gpui::Anchor::TopLeft
             })
             .trigger(move |_is_active, _window, _cx| {
                 let icon = if on_right {

crates/terminal_view/src/terminal_panel.rs πŸ”—

@@ -11,7 +11,7 @@ use collections::HashMap;
 use db::kvp::KeyValueStore;
 use futures::{channel::oneshot, future::join_all};
 use gpui::{
-    Action, AnyView, App, AsyncApp, AsyncWindowContext, Context, Corner, Entity, EventEmitter,
+    Action, Anchor, AnyView, App, AsyncApp, AsyncWindowContext, Context, Entity, EventEmitter,
     FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, Styled, Task, WeakEntity,
     Window, actions,
 };
@@ -160,7 +160,7 @@ impl TerminalPanel {
                                 IconButton::new("plus", IconName::Plus).icon_size(IconSize::Small),
                                 Tooltip::text("New…"),
                             )
-                            .anchor(Corner::TopRight)
+                            .anchor(Anchor::TopRight)
                             .with_handle(pane.new_item_context_menu_handle.clone())
                             .menu(move |window, cx| {
                                 let focus_handle = focus_handle.clone();
@@ -190,7 +190,7 @@ impl TerminalPanel {
                                     .icon_size(IconSize::Small),
                                 Tooltip::text("Split Pane"),
                             )
-                            .anchor(Corner::TopRight)
+                            .anchor(Anchor::TopRight)
                             .with_handle(pane.split_item_context_menu_handle.clone())
                             .menu({
                                 move |window, cx| {
@@ -1314,7 +1314,7 @@ impl Render for FailedToSpawnTerminal {
                         )
                 }))
             })
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(2.0),

crates/terminal_view/src/terminal_view.rs πŸ”—

@@ -1290,7 +1290,7 @@ impl Render for TerminalView {
                 deferred(
                     anchored()
                         .position(*position)
-                        .anchor(gpui::Corner::TopLeft)
+                        .anchor(gpui::Anchor::TopLeft)
                         .child(menu.clone()),
                 )
                 .with_priority(1)

crates/title_bar/src/title_bar.rs πŸ”—

@@ -26,7 +26,7 @@ use client::{Client, UserStore, zed_urls};
 use cloud_api_types::Plan;
 
 use gpui::{
-    Action, Animation, AnimationExt, AnyElement, App, Context, Corner, Element, Entity, Focusable,
+    Action, Anchor, Animation, AnimationExt, AnyElement, App, Context, Element, Entity, Focusable,
     InteractiveElement, IntoElement, MouseButton, ParentElement, Render,
     StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, actions, div,
     pulsating_between,
@@ -580,7 +580,7 @@ impl TitleBar {
                         )
                     },
                 )
-                .anchor(gpui::Corner::TopLeft)
+                .anchor(gpui::Anchor::TopLeft)
                 .into_any_element(),
         )
     }
@@ -763,7 +763,7 @@ impl TitleBar {
                     )
                 },
             )
-            .anchor(gpui::Corner::TopLeft)
+            .anchor(gpui::Anchor::TopLeft)
             .into_any_element()
     }
 
@@ -820,7 +820,7 @@ impl TitleBar {
                     )
                 },
             )
-            .anchor(gpui::Corner::TopLeft)
+            .anchor(gpui::Anchor::TopLeft)
     }
 
     fn render_project_branch(
@@ -926,7 +926,7 @@ impl TitleBar {
                         )
                     },
                 )
-                .anchor(gpui::Corner::TopLeft)
+                .anchor(gpui::Anchor::TopLeft)
         };
 
         let branch_tooltip_label = branch_name.clone();
@@ -976,7 +976,7 @@ impl TitleBar {
                 };
                 Tooltip::with_meta("Branch & Stash", Some(&zed_actions::git::Branch), meta, cx)
             })
-            .anchor(gpui::Corner::TopLeft);
+            .anchor(gpui::Anchor::TopLeft);
 
         Some(
             h_flex()
@@ -1298,6 +1298,6 @@ impl TitleBar {
                 })
                 .into()
             })
-            .anchor(Corner::TopRight)
+            .anchor(Anchor::TopRight)
     }
 }

crates/ui/src/components/context_menu.rs πŸ”—

@@ -3,7 +3,7 @@ use crate::{
     ListSubHeader, Tooltip, prelude::*, utils::WithRemSize,
 };
 use gpui::{
-    Action, AnyElement, App, Bounds, Corner, DismissEvent, Entity, EventEmitter, FocusHandle,
+    Action, Anchor, AnyElement, App, Bounds, DismissEvent, Entity, EventEmitter, FocusHandle,
     Focusable, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Size,
     Subscription, anchored, canvas, prelude::*, px,
 };
@@ -1694,7 +1694,7 @@ impl ContextMenu {
             }))
             .child(
                 anchored()
-                    .anchor(Corner::TopLeft)
+                    .anchor(Anchor::TopLeft)
                     .snap_to_window_with_margin(px(8.0))
                     .child(
                         div()

crates/ui/src/components/dropdown_menu.rs πŸ”—

@@ -1,4 +1,4 @@
-use gpui::{AnyView, Corner, Entity, Pixels, Point};
+use gpui::{Anchor, AnyView, Entity, Pixels, Point};
 
 use crate::{ButtonLike, ContextMenu, PopoverMenu, prelude::*};
 
@@ -30,7 +30,7 @@ pub struct DropdownMenu {
     full_width: bool,
     disabled: bool,
     handle: Option<PopoverMenuHandle<ContextMenu>>,
-    attach: Option<Corner>,
+    attach: Option<Anchor>,
     offset: Option<Point<Pixels>>,
     tab_index: Option<isize>,
     chevron: bool,
@@ -117,7 +117,7 @@ impl DropdownMenu {
     }
 
     /// Defines which corner of the handle to attach the menu's anchor to.
-    pub fn attach(mut self, attach: Corner) -> Self {
+    pub fn attach(mut self, attach: Anchor) -> Self {
         self.attach = Some(attach);
         self
     }
@@ -215,7 +215,7 @@ impl RenderOnce for DropdownMenu {
         popover
             .attach(match self.attach {
                 Some(attach) => attach,
-                None => Corner::BottomRight,
+                None => Anchor::BottomRight,
             })
             .when_some(self.offset, |this, offset| this.offset(offset))
             .when_some(self.handle, |this, handle| this.with_handle(handle))

crates/ui/src/components/popover_menu.rs πŸ”—

@@ -1,7 +1,7 @@
 use std::{cell::RefCell, rc::Rc};
 
 use gpui::{
-    AnyElement, AnyView, App, Bounds, Corner, DismissEvent, DispatchPhase, Element, ElementId,
+    Anchor, AnyElement, AnyView, App, Bounds, DismissEvent, DispatchPhase, Element, ElementId,
     Entity, Focusable as _, GlobalElementId, HitboxBehavior, HitboxId, InteractiveElement,
     IntoElement, LayoutId, Length, ManagedView, MouseDownEvent, ParentElement, Pixels, Point,
     Style, Window, anchored, deferred, div, point, prelude::FluentBuilder, px, size,
@@ -137,8 +137,8 @@ pub struct PopoverMenu<M: ManagedView> {
         >,
     >,
     menu_builder: Option<Rc<dyn Fn(&mut Window, &mut App) -> Option<Entity<M>> + 'static>>,
-    anchor: Corner,
-    attach: Option<Corner>,
+    anchor: Anchor,
+    attach: Option<Anchor>,
     offset: Option<Point<Pixels>>,
     trigger_handle: Option<PopoverMenuHandle<M>>,
     on_open: Option<Rc<dyn Fn(&mut Window, &mut App)>>,
@@ -152,7 +152,7 @@ impl<M: ManagedView> PopoverMenu<M> {
             id: id.into(),
             child_builder: None,
             menu_builder: None,
-            anchor: Corner::TopLeft,
+            anchor: Anchor::TopLeft,
             attach: None,
             offset: None,
             trigger_handle: None,
@@ -219,13 +219,13 @@ impl<M: ManagedView> PopoverMenu<M> {
 
     /// Defines which corner of the menu to anchor to the attachment point.
     /// By default, it uses the cursor position. Also see the `attach` method.
-    pub fn anchor(mut self, anchor: Corner) -> Self {
+    pub fn anchor(mut self, anchor: Anchor) -> Self {
         self.anchor = anchor;
         self
     }
 
     /// Defines which corner of the handle to attach the menu's anchor to.
-    pub fn attach(mut self, attach: Corner) -> Self {
+    pub fn attach(mut self, attach: Anchor) -> Self {
         self.attach = Some(attach);
         self
     }
@@ -242,13 +242,18 @@ impl<M: ManagedView> PopoverMenu<M> {
         self
     }
 
-    fn resolved_attach(&self) -> Corner {
-        self.attach.unwrap_or(match self.anchor {
-            Corner::TopLeft => Corner::BottomLeft,
-            Corner::TopRight => Corner::BottomRight,
-            Corner::BottomLeft => Corner::TopLeft,
-            Corner::BottomRight => Corner::TopRight,
-        })
+    fn resolved_attach(&self) -> Anchor {
+        self.attach
+            .unwrap_or(self.attach.unwrap_or(match self.anchor {
+                Anchor::TopLeft => Anchor::BottomLeft,
+                Anchor::TopCenter => Anchor::BottomCenter,
+                Anchor::TopRight => Anchor::BottomRight,
+                Anchor::BottomLeft => Anchor::TopLeft,
+                Anchor::BottomCenter => Anchor::TopCenter,
+                Anchor::BottomRight => Anchor::TopRight,
+                Anchor::LeftCenter => Anchor::LeftCenter,
+                Anchor::RightCenter => Anchor::RightCenter,
+            }))
     }
 
     fn resolved_offset(&self, window: &mut Window) -> Point<Pixels> {
@@ -256,8 +261,11 @@ impl<M: ManagedView> PopoverMenu<M> {
             // Default offset = 4px padding + 1px border
             let offset = rems_from_px(5.) * window.rem_size();
             match self.anchor {
-                Corner::TopRight | Corner::BottomRight => point(offset, px(0.)),
-                Corner::TopLeft | Corner::BottomLeft => point(-offset, px(0.)),
+                Anchor::TopRight | Anchor::BottomRight | Anchor::RightCenter => {
+                    point(offset, px(0.))
+                }
+                Anchor::TopLeft | Anchor::BottomLeft | Anchor::LeftCenter => point(-offset, px(0.)),
+                Anchor::TopCenter | Anchor::BottomCenter => point(px(0.), px(0.)),
             }
         })
     }

crates/ui/src/components/right_click_menu.rs πŸ”—

@@ -1,7 +1,7 @@
 use std::{cell::RefCell, rc::Rc};
 
 use gpui::{
-    AnyElement, App, Bounds, Corner, DismissEvent, DispatchPhase, Element, ElementId, Entity,
+    Anchor, AnyElement, App, Bounds, DismissEvent, DispatchPhase, Element, ElementId, Entity,
     Focusable as _, GlobalElementId, Hitbox, HitboxBehavior, InteractiveElement, IntoElement,
     LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Window,
     anchored, deferred, div, px,
@@ -11,8 +11,8 @@ pub struct RightClickMenu<M: ManagedView> {
     id: ElementId,
     child_builder: Option<Box<dyn FnOnce(bool, &mut Window, &mut App) -> AnyElement + 'static>>,
     menu_builder: Option<Rc<dyn Fn(&mut Window, &mut App) -> Entity<M> + 'static>>,
-    anchor: Option<Corner>,
-    attach: Option<Corner>,
+    anchor: Option<Anchor>,
+    attach: Option<Anchor>,
 }
 
 impl<M: ManagedView> RightClickMenu<M> {
@@ -34,13 +34,13 @@ impl<M: ManagedView> RightClickMenu<M> {
 
     /// anchor defines which corner of the menu to anchor to the attachment point
     /// (by default the cursor position, but see attach)
-    pub fn anchor(mut self, anchor: Corner) -> Self {
+    pub fn anchor(mut self, anchor: Anchor) -> Self {
         self.anchor = Some(anchor);
         self
     }
 
     /// attach defines which corner of the handle to attach the menu's anchor to
-    pub fn attach(mut self, attach: Corner) -> Self {
+    pub fn attach(mut self, attach: Anchor) -> Self {
         self.attach = Some(attach);
         self
     }

crates/ui/src/components/scrollbar.rs πŸ”—

@@ -6,8 +6,8 @@ use std::{
 };
 
 use gpui::{
-    Along, App, AppContext as _, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Context,
-    Corner, Corners, CursorStyle, DispatchPhase, Div, Edges, Element, ElementId, Entity, EntityId,
+    Along, Anchor, App, AppContext as _, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask,
+    Context, Corners, CursorStyle, DispatchPhase, Div, Edges, Element, ElementId, Entity, EntityId,
     GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
     LayoutId, ListState, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement,
     Pixels, Point, Position, Render, ScrollHandle, ScrollWheelEvent, Size, Stateful,
@@ -1122,10 +1122,10 @@ impl<T: ScrollableHandle> Element for ScrollbarElement<T> {
                         .into_iter()
                         .map(|(axis, thumb_range, reserved_space)| {
                             let track_anchor = match axis {
-                                ScrollbarAxis::Horizontal => Corner::BottomLeft,
-                                ScrollbarAxis::Vertical => Corner::TopRight,
+                                ScrollbarAxis::Horizontal => Anchor::BottomLeft,
+                                ScrollbarAxis::Vertical => Anchor::TopRight,
                             };
-                            let Bounds { origin, size } = Bounds::from_corner_and_size(
+                            let Bounds { origin, size } = Bounds::from_anchor_and_size(
                                 track_anchor,
                                 bounds
                                     .corner(track_anchor)

crates/workspace/src/dock.rs πŸ”—

@@ -7,7 +7,7 @@ use client::proto;
 use db::kvp::KeyValueStore;
 
 use gpui::{
-    Action, AnyView, App, Axis, Context, Corner, Entity, EntityId, EventEmitter, FocusHandle,
+    Action, Anchor, AnyView, App, Axis, Context, Entity, EntityId, EventEmitter, FocusHandle,
     Focusable, IntoElement, KeyContext, MouseButton, MouseDownEvent, MouseUpEvent, ParentElement,
     Render, SharedString, StyleRefinement, Styled, Subscription, WeakEntity, Window, deferred, div,
     px,
@@ -1189,8 +1189,8 @@ impl Render for PanelButtons {
         let dock_position = dock.position;
 
         let (menu_anchor, menu_attach) = match dock.position {
-            DockPosition::Left => (Corner::BottomLeft, Corner::TopLeft),
-            DockPosition::Bottom | DockPosition::Right => (Corner::BottomRight, Corner::TopRight),
+            DockPosition::Left => (Anchor::BottomLeft, Anchor::TopLeft),
+            DockPosition::Bottom | DockPosition::Right => (Anchor::BottomRight, Anchor::TopRight),
         };
 
         let dock_entity = self.dock.clone();

crates/workspace/src/pane.rs πŸ”—

@@ -18,7 +18,7 @@ use anyhow::Result;
 use collections::{BTreeSet, HashMap, HashSet, VecDeque};
 use futures::{StreamExt, stream::FuturesUnordered};
 use gpui::{
-    Action, AnyElement, App, AsyncWindowContext, ClickEvent, ClipboardItem, Context, Corner, Div,
+    Action, Anchor, AnyElement, App, AsyncWindowContext, ClickEvent, ClipboardItem, Context, Div,
     DragMoveEvent, Entity, EntityId, EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent,
     Focusable, KeyContext, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render,
     ScrollHandle, Subscription, Task, WeakEntity, WeakFocusHandle, Window, actions, anchored,
@@ -3689,7 +3689,7 @@ impl Pane {
 
     pub fn render_menu_overlay(menu: &Entity<ContextMenu>) -> Div {
         div().absolute().bottom_0().right_0().size_0().child(
-            deferred(anchored().anchor(Corner::TopRight).child(menu.clone())).with_priority(1),
+            deferred(anchored().anchor(Anchor::TopRight).child(menu.clone())).with_priority(1),
         )
     }
 
@@ -4188,7 +4188,7 @@ fn default_render_tab_bar_buttons(
                     IconButton::new("plus", IconName::Plus).icon_size(IconSize::Small),
                     Tooltip::text("New..."),
                 )
-                .anchor(Corner::TopRight)
+                .anchor(Anchor::TopRight)
                 .with_handle(pane.new_item_context_menu_handle.clone())
                 .menu(move |window, cx| {
                     Some(ContextMenu::build(window, cx, |menu, _, _| {
@@ -4214,7 +4214,7 @@ fn default_render_tab_bar_buttons(
                         .disabled(!can_clone && !can_split_move),
                     Tooltip::text("Split Pane"),
                 )
-                .anchor(Corner::TopRight)
+                .anchor(Anchor::TopRight)
                 .with_handle(pane.split_item_context_menu_handle.clone())
                 .menu(move |window, cx| {
                     ContextMenu::build(window, cx, |menu, _, _| {

crates/workspace/src/status_bar.rs πŸ”—

@@ -3,7 +3,7 @@ use crate::{
     sidebar_side_context_menu,
 };
 use gpui::{
-    AnyView, App, Context, Corner, Decorations, Entity, IntoElement, ParentElement, Render, Styled,
+    Anchor, AnyView, App, Context, Decorations, Entity, IntoElement, ParentElement, Render, Styled,
     Subscription, WeakEntity, Window,
 };
 use std::any::TypeId;
@@ -144,14 +144,14 @@ impl StatusBar {
 
         let toggle = sidebar_side_context_menu("sidebar-status-toggle-menu", cx)
             .anchor(if on_right {
-                Corner::BottomRight
+                Anchor::BottomRight
             } else {
-                Corner::BottomLeft
+                Anchor::BottomLeft
             })
             .attach(if on_right {
-                Corner::TopRight
+                Anchor::TopRight
             } else {
-                Corner::TopLeft
+                Anchor::TopLeft
             })
             .trigger(move |_is_active, _window, _cx| {
                 IconButton::new(

crates/zed/src/zed/quick_action_bar.rs πŸ”—

@@ -11,7 +11,7 @@ use editor::actions::{
 use editor::code_context_menus::{CodeContextMenu, ContextMenuOrigin};
 use editor::{Editor, EditorSettings};
 use gpui::{
-    Action, AnchoredPositionMode, ClickEvent, Context, Corner, ElementId, Entity, EventEmitter,
+    Action, Anchor, AnchoredPositionMode, ClickEvent, Context, ElementId, Entity, EventEmitter,
     FocusHandle, Focusable, InteractiveElement, ParentElement, Render, Styled, Subscription,
     WeakEntity, Window, anchored, deferred, point,
 };
@@ -227,7 +227,7 @@ impl Render for QuickActionBar {
                         anchored()
                             .position_mode(AnchoredPositionMode::Local)
                             .position(point(px(20.), px(20.)))
-                            .anchor(Corner::TopRight)
+                            .anchor(Anchor::TopRight)
                             .child(menu),
                     )
                 }))
@@ -257,7 +257,7 @@ impl Render for QuickActionBar {
                     Tooltip::text("Selection Controls"),
                 )
                 .with_handle(self.toggle_selections_handle.clone())
-                .anchor(Corner::TopRight)
+                .anchor(Anchor::TopRight)
                 .menu(move |window, cx| {
                     let focus = focus.clone();
                     let menu = ContextMenu::build(window, cx, move |menu, _, _| {
@@ -329,7 +329,7 @@ impl Render for QuickActionBar {
                         .toggle_state(self.toggle_settings_handle.is_deployed()),
                     Tooltip::text("Editor Controls"),
                 )
-                .anchor(Corner::TopRight)
+                .anchor(Anchor::TopRight)
                 .with_handle(self.toggle_settings_handle.clone())
                 .menu(move |window, cx| {
                     let menu = ContextMenu::build(window, cx, {