chore: migrate to gpui-base (#51)

Reviewed-on: #51
This commit was merged in pull request #51.
This commit is contained in:
2026-09-17 11:50:14 +00:00
parent 874f103752
commit 0c1c23fe9c
69 changed files with 2458 additions and 15637 deletions
+32 -230
View File
@@ -2,20 +2,13 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder as _;
use gpui::{
Anchor, AnyElement, App, Bounds, Context, Deferred, DismissEvent, Div, ElementId, EventEmitter,
FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding, MouseButton,
ParentElement, Pixels, Point, Render, RenderOnce, Stateful, StyleRefinement, Styled,
Subscription, Window, anchored, deferred, div, px,
Anchor, AnyElement, App, Context, Div, ElementId, FocusHandle, InteractiveElement as _,
IntoElement, MouseButton, ParentElement, RenderOnce, Stateful, StyleRefinement, Styled, Window,
};
use gpui_base::Popover as BasePopover;
pub use gpui_base::PopoverState;
use crate::actions::Cancel;
use crate::{ElementExt, Selectable, StyledExt as _, v_flex};
const CONTEXT: &str = "Popover";
pub(crate) fn init(cx: &mut App) {
cx.bind_keys([KeyBinding::new("escape", Cancel, Some(CONTEXT))])
}
use crate::{Selectable, StyledExt as _, v_flex};
/// A popover element that can be triggered by a button or any other element.
#[derive(IntoElement)]
@@ -173,28 +166,6 @@ impl Popover {
self.tracked_focus_handle = Some(handle.clone());
self
}
pub(crate) fn resolved_corner(anchor: Anchor, trigger_bounds: Bounds<Pixels>) -> Point<Pixels> {
match anchor {
Anchor::TopLeft => trigger_bounds.origin,
Anchor::TopCenter => trigger_bounds.top_center(),
Anchor::TopRight => trigger_bounds.top_right(),
Anchor::BottomLeft => Point {
x: trigger_bounds.origin.x,
y: trigger_bounds.origin.y - trigger_bounds.size.height,
},
Anchor::BottomCenter => Point {
x: trigger_bounds.top_center().x,
y: trigger_bounds.origin.y - trigger_bounds.size.height,
},
Anchor::BottomRight => Point {
x: trigger_bounds.top_right().x,
y: trigger_bounds.origin.y - trigger_bounds.size.height,
},
// Fallback for LeftCenter/RightCenter – adjust as needed.
_ => trigger_bounds.origin,
}
}
}
impl ParentElement for Popover {
@@ -209,119 +180,7 @@ impl Styled for Popover {
}
}
pub struct PopoverState {
focus_handle: FocusHandle,
pub(crate) tracked_focus_handle: Option<FocusHandle>,
trigger_bounds: Bounds<Pixels>,
open: bool,
#[allow(clippy::type_complexity)]
on_open_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
_dismiss_subscription: Option<Subscription>,
}
impl PopoverState {
pub fn new(default_open: bool, cx: &mut App) -> Self {
Self {
focus_handle: cx.focus_handle(),
tracked_focus_handle: None,
trigger_bounds: Bounds::default(),
open: default_open,
on_open_change: None,
_dismiss_subscription: None,
}
}
/// Check if the popover is open.
pub fn is_open(&self) -> bool {
self.open
}
/// Dismiss the popover if it is open.
pub fn dismiss(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.open {
self.toggle_open(window, cx);
}
}
/// Open the popover if it is closed.
pub fn show(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if !self.open {
self.toggle_open(window, cx);
}
}
fn toggle_open(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.open = !self.open;
if self.open {
let state = cx.entity();
let focus_handle = if let Some(tracked_focus_handle) = self.tracked_focus_handle.clone()
{
tracked_focus_handle
} else {
self.focus_handle.clone()
};
focus_handle.focus(window, cx);
self._dismiss_subscription =
Some(
window.subscribe(&cx.entity(), cx, move |_, _: &DismissEvent, window, cx| {
state.update(cx, |state, cx| {
state.dismiss(window, cx);
});
window.refresh();
}),
);
} else {
self._dismiss_subscription = None;
}
if let Some(callback) = self.on_open_change.as_ref() {
callback(&self.open, window, cx);
}
cx.notify();
}
fn on_action_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
self.dismiss(window, cx);
}
}
impl Focusable for PopoverState {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for PopoverState {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
}
}
impl EventEmitter<DismissEvent> for PopoverState {}
impl Popover {
pub(crate) fn render_popover<E>(
anchor: Anchor,
trigger_bounds: Bounds<Pixels>,
content: E,
_: &mut Window,
_: &mut App,
) -> Deferred
where
E: IntoElement + 'static,
{
deferred(
anchored()
.snap_to_window_with_margin(px(8.))
.anchor(anchor)
.position(Self::resolved_corner(anchor, trigger_bounds))
.child(div().relative().child(content)),
)
.with_priority(1)
}
pub(crate) fn render_popover_content(
anchor: Anchor,
appearance: bool,
@@ -342,91 +201,34 @@ impl Popover {
}
impl RenderOnce for Popover {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let force_open = self.open;
let default_open = self.default_open;
let tracked_focus_handle = self.tracked_focus_handle.clone();
let state = window.use_keyed_state(self.id.clone(), cx, |_, cx| {
PopoverState::new(default_open, cx)
});
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let anchor = self.anchor;
let appearance = self.appearance;
let style = self.style;
let children = self.children;
let content = self.content;
state.update(cx, |state, _| {
if let Some(tracked_focus_handle) = tracked_focus_handle {
state.tracked_focus_handle = Some(tracked_focus_handle);
}
state.on_open_change = self.on_open_change.clone();
if let Some(force_open) = force_open {
state.open = force_open;
}
});
let open = state.read(cx).open;
let focus_handle = state.read(cx).focus_handle.clone();
let trigger_bounds = state.read(cx).trigger_bounds;
let Some(trigger) = self.trigger else {
return div().id("empty");
};
let parent_view_id = window.current_view();
let el = div()
.id(self.id)
.child((trigger)(open, window, cx))
.on_mouse_down(self.mouse_button, {
let state = state.clone();
move |_, window, cx| {
cx.stop_propagation();
state.update(cx, |state, cx| {
// We force set open to false to toggle it correctly.
// Because if the mouse down out will toggle open first.
state.open = open;
state.toggle_open(window, cx);
});
cx.notify(parent_view_id);
}
BasePopover::new(self.id)
.anchor(anchor)
.mouse_button(self.mouse_button)
.default_open(self.default_open)
.overlay_closable(self.overlay_closable)
.content(move |state, window, cx| {
Self::render_popover_content(anchor, appearance, window, cx)
.when_some(content, |this, content| {
this.child((content)(state, window, cx))
})
.children(children)
.refine_style(&style)
})
.on_prepaint({
let state = state.clone();
move |bounds, _, cx| {
state.update(cx, |state, _| {
state.trigger_bounds = bounds;
})
}
});
if !open {
return el;
}
let popover_content =
Self::render_popover_content(self.anchor, self.appearance, window, cx)
.track_focus(&focus_handle)
.key_context(CONTEXT)
.on_action(window.listener_for(&state, PopoverState::on_action_cancel))
.when_some(self.content, |this, content| {
this.child(state.update(cx, |state, cx| (content)(state, window, cx)))
})
.children(self.children)
.when(self.overlay_closable, |this| {
this.on_mouse_down_out({
let state = state.clone();
move |_, window, cx| {
state.update(cx, |state, cx| {
state.dismiss(window, cx);
});
cx.notify(parent_view_id);
}
})
})
.refine_style(&self.style);
el.child(Self::render_popover(
self.anchor,
trigger_bounds,
popover_content,
window,
cx,
))
.when_some(self.trigger, |this, trigger| this.trigger_with(trigger))
.when_some(self.open, |this, open| this.open(open))
.when_some(self.tracked_focus_handle, |this, handle| {
this.track_focus(&handle)
})
.when_some(self.on_open_change, |this, callback| {
this.on_open_change(move |open, window, cx| callback(open, window, cx))
})
.into_any_element()
}
}