From 84dcf38dbe9cd83fb8dcdaef76e70ef426b06849 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 21 Apr 2026 17:01:42 +0800 Subject: [PATCH] gpui: Improve Anchored to support center position (#47154) 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 --- .../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 +- .../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 +- .../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 +- .../src/session/running/console.rs | 6 +- .../src/session/running/memory_view.rs | 2 +- .../src/session/running/variable_list.rs | 2 +- .../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 +- .../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 +- .../src/components/ollama_model_picker.rs | 2 +- .../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(-) create mode 100644 crates/gpui/examples/anchor.rs diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index d2d8b6505a080cf2816f28b43c6f3c35406cce85..5f4e25b5ccd40cb7b3c97eda505ccc680e219a92 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/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; diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index 13ec53b25c50b5865c0070daee76d7eadde10c7b..da0704889e7fb96a295a7017277dd5cc939c1f4f 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/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), diff --git a/crates/agent_ui/src/agent_model_selector.rs b/crates/agent_ui/src/agent_model_selector.rs index 93984121c261034a5cc6198621e79d87d2de1ff4..cbffced96df3263c3f46a690cada15cd479b1d27 100644 --- a/crates/agent_ui/src/agent_model_selector.rs +++ b/crates/agent_ui/src/agent_model_selector.rs @@ -131,7 +131,7 @@ impl Render for AgentModelSelector { .size(IconSize::XSmall), ), tooltip, - gpui::Corner::TopRight, + gpui::Anchor::TopRight, cx, ) .with_handle(self.menu_handle.clone()) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index b873decd220008a8faa1ad14651319f5d26e3f77..41c72c414ff258c2a8091afcd5d270e32993c0c7 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/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, ) -> 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, )) }) diff --git a/crates/agent_ui/src/config_options.rs b/crates/agent_ui/src/config_options.rs index cf2809b87b94eae8a3eb75844539ddffc652b7df..58f9606d80dcce51cb5722d47c745322187f02c4 100644 --- a/crates/agent_ui/src/config_options.rs +++ b/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()) diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index b6bda7738d5424e842d66c4a625fe64684ce5c14..db8a15c62719322921bdd2d0f307651a5efb8572 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/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) -> 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( diff --git a/crates/agent_ui/src/mode_selector.rs b/crates/agent_ui/src/mode_selector.rs index 2b0754e9dc993c47fd32064219461df5304bad4d..b82d5ff99bfb4045edce98a91b35c3a683a26d30 100644 --- a/crates/agent_ui/src/mode_selector.rs +++ b/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), diff --git a/crates/agent_ui/src/model_selector_popover.rs b/crates/agent_ui/src/model_selector_popover.rs index 75ef5ab8cc907c0ffbce370c846bb1a1b651e938..2396622ef89636d64c5e356163bd90d1d1a1fcb7 100644 --- a/crates/agent_ui/src/model_selector_popover.rs +++ b/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()) diff --git a/crates/agent_ui/src/profile_selector.rs b/crates/agent_ui/src/profile_selector.rs index 2b62b3121f80d0a3e9f463c71f2c0abf5a380e5b..2f32d27983589f78c50893bfe0ef00b1aff1501c 100644 --- a/crates/agent_ui/src/profile_selector.rs +++ b/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()) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index a80d5682eb56526d9060fd1014d29f1deac4d7d2..908d11cd654f9be1b52790a3f61739719e8f6a13 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/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) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 3b3d974f3e50a9a16f32cee0c68fa399f00cd4b1..1781a8e93e0476a324b29523cddffda3b38a4cce 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -433,7 +433,7 @@ impl PickerDelegate for ChannelModalDelegate { Some( deferred( anchored() - .anchor(gpui::Corner::TopRight) + .anchor(gpui::Anchor::TopRight) .child(menu.clone()), ) .with_priority(1), diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index c364cdd244752afdda203911653e8a60e54b7871..fc2cbd9e1c3f6f3edc39322e51cef4e999c6fcca 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/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 diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index d727a112e31950c280683e31dd43018b355e28ac..f92b87a773c82da1a432ba9a75da32dec3866fa0 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/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) diff --git a/crates/debugger_ui/src/dropdown_menus.rs b/crates/debugger_ui/src/dropdown_menus.rs index e0c3628f4fc0a927857adbe93549087f930145d6..0e07cb8841b08c52980062d43a61103051478571 100644 --- a/crates/debugger_ui/src/dropdown_menus.rs +++ b/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()), diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index 1ea974c4fe2ace4be4aeaf0064304a7a4ee2fb08..f0d243995f6991b55b5af1fe0f022d9043b263ac 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/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), diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index d1c53203329d738bfd96b2fc0ac89446d7cdcc54..5177fb259e7f46e71903e98f39a782f3b19ba48d 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/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) -> impl IntoElement { diff --git a/crates/debugger_ui/src/session/running/memory_view.rs b/crates/debugger_ui/src/session/running/memory_view.rs index 3c1498113d603ac01303bce06013a02554ad5d08..a344a92eadd8266d45ecd2726beab326b8c606cb 100644 --- a/crates/debugger_ui/src/session/running/memory_view.rs +++ b/crates/debugger_ui/src/session/running/memory_view.rs @@ -914,7 +914,7 @@ impl Render for MemoryView { deferred( anchored() .position(*position) - .anchor(gpui::Corner::TopLeft) + .anchor(gpui::Anchor::TopLeft) .child(menu.clone()), ) .with_priority(1) diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs index fd8fd736b9e5194d34df3928c0c2983bb40be954..991961f627cb0c55967c1492544a8a0ce033d418 100644 --- a/crates/debugger_ui/src/session/running/variable_list.rs +++ b/crates/debugger_ui/src/session/running/variable_list.rs @@ -1579,7 +1579,7 @@ impl Render for VariableList { deferred( anchored() .position(*position) - .anchor(gpui::Corner::TopLeft) + .anchor(gpui::Anchor::TopLeft) .child(menu.clone()), ) .with_priority(1) diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index 7f9415763de55439e081dff72cabe09770ae5c85..4d048c25a53528e058766469cb927b8db264460e 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/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 diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2875ac50f7aa87381ea25800d37e2090e82ef186..78e70b0ae20d595dbec062994bbcae409b1bdefa 100644 --- a/crates/editor/src/element.rs +++ b/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), ), diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 19bf62d8bbc476049548f65616e6ca1e12f5378a..0e6bfe8498dc5b3d65f06ad0ec36aa962c2601dc 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/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), diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index ddba89c9c744f80cdddd84c35ae5856d7b2464b9..9a9cc983fa74d922cd9cf8ac2b6653ceda32ab46 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/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), diff --git a/crates/git_graph/src/git_graph.rs b/crates/git_graph/src/git_graph.rs index 8f32887b03532573a8c57cd6d629fd8f3af7a76f..34599ddefdac5baf62259bccf2c7ab46feb6c43f 100644 --- a/crates/git_graph/src/git_graph.rs +++ b/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) diff --git a/crates/git_ui/src/commit_modal.rs b/crates/git_ui/src/commit_modal.rs index 2088ad77ec5d7e71bdfb42ebcbfab6d001f64375..ad6d960a307ffd75d080878ea4f3be1cf35eacf3 100644 --- a/crates/git_ui/src/commit_modal.rs +++ b/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) -> 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), diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index c8b249a7dff60266f397506b3c79e87fbfcc1dba..13e2d0970f1639086a1baa67fd1eb10890130bdb 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/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) -> (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), diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index fb81847a7f9df2971f4c0dcc3f28823d574d6b04..0b44b1c51757b27bc67889f122c6a6b30a663df2 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/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)] diff --git a/crates/gpui/examples/anchor.rs b/crates/gpui/examples/anchor.rs new file mode 100644 index 0000000000000000000000000000000000000000..7aa9bb4cf92639ad50698bb47eeef6c09fe30bd5 --- /dev/null +++ b/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, +} + +struct ButtonDemo { + label: SharedString, + corner: Option, +} + +fn resolved_position(corner: Anchor, button_size: Size) -> Point { + 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 { + 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) -> 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(); +} diff --git a/crates/gpui/examples/popover.rs b/crates/gpui/examples/popover.rs index 9d5f84a1f43462e6e49ec5b0984dbd7b1c50230a..e4f0ac0a3ca3711c9f22c4be232a72fbb267677e 100644 --- a/crates/gpui/examples/popover.rs +++ b/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() diff --git a/crates/gpui/src/elements/anchored.rs b/crates/gpui/src/elements/anchored.rs index f92593ef8db992ca40ca46a12efbf14aa259c83c..ad8fa11b71ee8242e1aa4f76f7e846f17ef9c164 100644 --- a/crates/gpui/src/elements/anchored.rs +++ b/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>, 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>, - anchor_corner: Corner, + anchor: Anchor, size: Size, bounds: Bounds, offset: Option>, @@ -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, ); diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index 76157a06a587ac851d19f19fc5a4ed23c634bab5..e5951a129667ce6dcef8068f19b42463e32d877f 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -826,24 +826,45 @@ where }; Bounds { origin, size } } +} +impl Bounds +where + T: Sub + 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, size: Size) -> Bounds { + pub fn from_anchor_and_size(corner: Anchor, origin: Point, size: Size) -> Bounds { 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 Bounds +where + T: Add + Half + Clone + Debug + Default + PartialEq, +{ + /// Returns the top center point of the bounds. + pub fn top_center(&self) -> Point { + 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 { + 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 { + 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 { + Point { + x: self.origin.x.clone() + self.size.width.clone(), + y: self.origin.y.clone() + self.size.height.half(), + } + } +} + impl Bounds where T: PartialOrd + Add + Clone + Debug + Default + PartialEq, @@ -1334,7 +1392,12 @@ where y: self.origin.y.clone() + self.size.height.clone(), } } +} +impl Bounds +where + T: Add + 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 { + pub fn corner(&self, corner: Anchor) -> Point { 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 for Edges { } } -/// 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 { impl Corners where - T: Clone + Debug + Default + PartialEq, + T: Add + 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` 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 + 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) -> Corners { let max = cmp::min(size.width, size.height) / 2.; diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index 70d6f326a5fd0a17c306c8a8af4645d8e234f837..b1f5f7c6af70645cf7812d30fcd2bc681631f093 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/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) diff --git a/crates/language_tools/src/highlights_tree_view.rs b/crates/language_tools/src/highlights_tree_view.rs index 763cdf76dab46a7fc1c233eda84cfb4ab50e6975..96f673fa1dc0aa0d5e979c45c4fb32107d30e292 100644 --- a/crates/language_tools/src/highlights_tree_view.rs +++ b/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(); diff --git a/crates/language_tools/src/lsp_button.rs b/crates/language_tools/src/lsp_button.rs index 43b1736223478fe29f45aac0a712fafad1d2dcbe..85ab4684351195f97df3f3f536373febf448c1a6 100644 --- a/crates/language_tools/src/lsp_button.rs +++ b/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) diff --git a/crates/language_tools/src/lsp_log_view.rs b/crates/language_tools/src/lsp_log_view.rs index 59571040eac6281fa2b2032a655dafabfa345f0a..e3a9c0b80d8f35f1060c93e2ee62e60aee02f908 100644 --- a/crates/language_tools/src/lsp_log_view.rs +++ b/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", diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 0a423f7e54774deff297fa0e44d9ecc7b0c49f34..4a30f2ff8743c1520570cddd7977a099ad620331 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -4725,7 +4725,7 @@ impl OutlinePanel { deferred( anchored() .position(*position) - .anchor(gpui::Corner::TopLeft) + .anchor(gpui::Anchor::TopLeft) .child(menu.clone()), ) .with_priority(1) diff --git a/crates/picker/src/popover_menu.rs b/crates/picker/src/popover_menu.rs index 42eedb2492149aa56de527e38fcf4f2b0e4da608..b534f8f2ba3818528c140c80269effa111f3b71b 100644 --- a/crates/picker/src/popover_menu.rs +++ b/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>>, - anchor: Corner, + anchor: Anchor, offset: Option>, _subscriptions: Vec, } @@ -33,7 +33,7 @@ where picker: Entity>, trigger: T, tooltip: TT, - anchor: Corner, + anchor: Anchor, cx: &mut App, ) -> Self { Self { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 3a5047c0d7a6d40968ca7b5d10f65e317dbc92a8..ed139546669c94b3aca7c5c4febc8a7e54435ef0 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/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) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 52904d0e287522ffcf17300d0df6e0f5ae6edb10..7eb924b7d12910d630e8f90e4ae64c10ea6d1300 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/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), diff --git a/crates/repl/src/components/kernel_options.rs b/crates/repl/src/components/kernel_options.rs index ce68a4d30285fe04427c54aa8d5fbdc3aa059648..32db5785884eaaa356b9c5f3bae510b136f19e19 100644 --- a/crates/repl/src/components/kernel_options.rs +++ b/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)) } } diff --git a/crates/settings_ui/src/components/ollama_model_picker.rs b/crates/settings_ui/src/components/ollama_model_picker.rs index 268bf196bce3d0fc16a20eac5974fe78ec8532fd..bc6ae06cb4349040228cfaa0d6f1c53d900ec961 100644 --- a/crates/settings_ui/src/components/ollama_model_picker.rs +++ b/crates/settings_ui/src/components/ollama_model_picker.rs @@ -203,7 +203,7 @@ pub fn render_ollama_model_picker( .max_height(Some(rems(18.).into())) })) }) - .anchor(gpui::Corner::TopLeft) + .anchor(gpui::Anchor::TopLeft) .offset(gpui::Point { x: px(0.0), y: px(2.0), diff --git a/crates/settings_ui/src/pages/tool_permissions_setup.rs b/crates/settings_ui/src/pages/tool_permissions_setup.rs index bbfcd1849dd561764a031a95cdc28fadfdeab87e..e6b49dd6c8ab27acffc398045ec39bc2e6c77621 100644 --- a/crates/settings_ui/src/pages/tool_permissions_setup.rs +++ b/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() } diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index bd503dcb28061749ca60ebf2af5c3f92eb839305..1567a87445a184dd18d2e1d1a0ed4c31c30f1415 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/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), diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 91d689e6e92dde06c09b84e68160e16764a6151c..fe11e3f4398e5562e607a11d1d9755933764c554 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/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 { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index a813a1adc55fe5de75f5d9547839b15eb391192e..642243ae147539364d5775e3fc81d75603331f80 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/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), diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 5c772c18f19fb12d77c2100d602aab08e635b1d2..34689aa6aa48e34c4c65bfd5d60ab867ea79c4a4 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/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) diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 95d022e5fb95a8cb5bd87df5ca254bf6b38b8b0c..9834be10ef2adc37076acaf013a2a5a461b15b17 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/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) } } diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 006892effc8676756a10988bfdbff9b60673810c..c82b05a98a3493c77123b2f297bd1f9d3e59c436 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/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() diff --git a/crates/ui/src/components/dropdown_menu.rs b/crates/ui/src/components/dropdown_menu.rs index 961608461c04971cda81cfdd64d9eb62577f07ed..c3cb3bcf0d5335ab260b8c9e27fcc42db412b398 100644 --- a/crates/ui/src/components/dropdown_menu.rs +++ b/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>, - attach: Option, + attach: Option, offset: Option>, tab_index: Option, 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)) diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index cd79e50ce01b1f4e697b252801c2ae76765726d2..9522d844d1a82f47d00ff5d3bf00a6b0408ac5c7 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/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 { >, >, menu_builder: Option Option> + 'static>>, - anchor: Corner, - attach: Option, + anchor: Anchor, + attach: Option, offset: Option>, trigger_handle: Option>, on_open: Option>, @@ -152,7 +152,7 @@ impl PopoverMenu { 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 PopoverMenu { /// 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 PopoverMenu { 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 { @@ -256,8 +261,11 @@ impl PopoverMenu { // 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.)), } }) } diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index faf2cb3429b610727209e13188656c174aefb655..a6b9515cacad3011ff2ef094f5da972d8c110632 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/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 { id: ElementId, child_builder: Option AnyElement + 'static>>, menu_builder: Option Entity + 'static>>, - anchor: Option, - attach: Option, + anchor: Option, + attach: Option, } impl RightClickMenu { @@ -34,13 +34,13 @@ impl RightClickMenu { /// 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 } diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index 86f5e3b4ccbe80dd340cbeafb52ed499bb79895a..77ceae9a34684a2ca4c33c69e741d339a93df1d4 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/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 Element for ScrollbarElement { .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) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index ca8584fb1eb6dc2db6a08a528526ac76c37e860e..1983b2921ffcc510f9376c733cb8adb8bbc473b8 100644 --- a/crates/workspace/src/dock.rs +++ b/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(); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 861e86576216073ae2acc2012f39051debabdd18..9c68671eb794acaafb2c80eb4883ae283d24eea6 100644 --- a/crates/workspace/src/pane.rs +++ b/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) -> 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, _, _| { diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index dad5389f2f5574c773af740fd61c6c1501c2fea0..fcd64e6e7ab9358a0896e2c3840f8d1abadc937b 100644 --- a/crates/workspace/src/status_bar.rs +++ b/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( diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index e35bd2aad5d08739e1d8fb51968ddfae746939a7..c6cb2ed8ee6d5df32b4c4b711d544d581ce74d9f 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/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, {