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 <noreply@anthropic.com>
This commit is contained in:
Jason Lee 2026-04-21 17:01:42 +08:00 committed by GitHub
parent 81b16f464c
commit 84dcf38dbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 574 additions and 241 deletions

View file

@ -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;

View file

@ -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),

View file

@ -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())

View file

@ -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,
))
})

View file

@ -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())

View file

@ -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(

View file

@ -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),

View file

@ -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())

View file

@ -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())

View file

@ -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)

View file

@ -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),

View file

@ -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

View file

@ -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)

View file

@ -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()),

View file

@ -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),

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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),
),

View file

@ -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),

View file

@ -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),

View file

@ -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)

View file

@ -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),

View file

@ -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),

View file

@ -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)]

View file

@ -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();
}

View file

@ -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()

View file

@ -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,
);

View file

@ -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::{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::TopLeft), 10);
/// assert_eq!(corners.corner(Anchor::BottomRight), 40);
/// ```
///
/// Center positions (calculated as average of adjacent corners):
///
/// ```
/// # use gpui::{Anchor, Corners};
/// let corners = Corners {
/// top_left: 10,
/// top_right: 20,
/// bottom_left: 30,
/// bottom_right: 40
/// };
/// 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.;

View file

@ -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)

View file

@ -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();

View file

@ -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)

View file

@ -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",

View file

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

View file

@ -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 {

View file

@ -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)

View file

@ -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),

View file

@ -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))
}
}

View file

@ -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),

View file

@ -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()
}

View file

@ -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),

View file

@ -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 {

View file

@ -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),

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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()

View file

@ -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))

View file

@ -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.)),
}
})
}

View file

@ -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
}

View file

@ -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)

View file

@ -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();

View file

@ -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, _, _| {

View file

@ -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(

View file

@ -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, {