clean up components

This commit is contained in:
2026-10-09 07:17:09 +07:00
parent 193df36dae
commit fea6932c37
32 changed files with 271 additions and 1148 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ theme = { path = "../theme" }
gpui.workspace = true
gpui-base.workspace = true
instant.workspace = true
serde.workspace = true
smallvec.workspace = true
log.workspace = true
uuid = "1.10"
+1 -5
View File
@@ -1,8 +1,4 @@
/// A cubic bezier function like CSS `cubic-bezier`.
///
/// Builder:
///
/// https://cubic-bezier.com
/// A cubic bezier easing function like CSS `cubic-bezier` (see https://cubic-bezier.com).
pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 {
move |t: f32| {
let one_t = 1.0 - t;
+5 -23
View File
@@ -1,4 +1,3 @@
use gpui::prelude::FluentBuilder;
use gpui::{
AbsoluteLength, AnyElement, App, Bounds, Div, Hsla, ImageSource, InteractiveElement,
Interactivity, IntoElement, ObjectFit, ParentElement, PathBuilder, Pixels, Point, RenderOnce,
@@ -24,7 +23,7 @@ const FALLBACK_SEED: &str = "coop";
const CIRCLE_SEGMENTS: usize = 32;
/// Returns the size of the avatar based on the given [`Size`].
pub(super) fn avatar_size(size: Size) -> AbsoluteLength {
fn avatar_size(size: Size) -> AbsoluteLength {
match size {
Size::Large => px(64.).into(),
Size::Medium => px(32.).into(),
@@ -36,7 +35,7 @@ pub(super) fn avatar_size(size: Size) -> AbsoluteLength {
/// A deterministic, offline pixel-art avatar derived from a seed.
#[derive(IntoElement)]
pub(crate) struct PixelAvatar {
struct PixelAvatar {
seed: u64,
size: Size,
style: StyleRefinement,
@@ -44,7 +43,7 @@ pub(crate) struct PixelAvatar {
impl PixelAvatar {
/// Creates a pixel avatar from `seed`.
pub fn new(seed: impl AsRef<str>) -> Self {
fn new(seed: impl AsRef<str>) -> Self {
Self {
seed: fnv1a(seed.as_ref().as_bytes()),
size: Size::Medium,
@@ -188,8 +187,7 @@ fn paint_polygons<'a>(
}
}
/// Approximates the circle of `radius` around `center` as a convex polygon,
/// wound so that its interior is on the left of every directed edge.
/// Approximates the circle of `radius` around `center` as a convex polygon wound left-interior.
fn circle_polygon(center: Point<Pixels>, radius: f32) -> Vec<Point<Pixels>> {
let center_x = center.x.as_f32();
let center_y = center.y.as_f32();
@@ -354,11 +352,9 @@ fn generated_avatar(seed: Option<&str>, size: Pixels) -> AnyElement {
pub struct Avatar {
base: Div,
picture: Option<ImageSource>,
grayscale: bool,
seed: Option<String>,
style: StyleRefinement,
size: Size,
border_color: Option<Hsla>,
selected: bool,
}
@@ -377,11 +373,9 @@ impl Avatar {
Avatar {
base: div(),
picture,
grayscale: false,
seed: None,
style: StyleRefinement::default(),
size: Size::Medium,
border_color: None,
selected: false,
}
}
@@ -425,25 +419,16 @@ impl InteractiveElement for Avatar {
impl RenderOnce for Avatar {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let border_width = if self.border_color.is_some() {
px(2.)
} else {
px(0.)
};
let image_size = avatar_size(self.size).to_pixels(window.rem_size());
let container_size = image_size + border_width * 2.;
let content = match self.picture {
Some(picture) => {
let seed = self.seed;
let grayscale = self.grayscale;
img(picture)
.size(image_size)
.rounded_full()
.object_fit(ObjectFit::Cover)
.grayscale(grayscale)
.bg(cx.theme().ghost_element_background)
.with_fallback(move || generated_avatar(seed.as_deref(), image_size))
.into_any_element()
@@ -453,12 +438,9 @@ impl RenderOnce for Avatar {
div()
.flex_shrink_0()
.size(container_size)
.size(image_size)
.rounded_full()
.overflow_hidden()
.when_some(self.border_color, |this, color| {
this.border(border_width).border_color(color)
})
.child(content)
}
}
+8 -57
View File
@@ -70,42 +70,34 @@ pub enum ButtonVariant {
pub trait ButtonVariants: Sized {
fn with_variant(self, variant: ButtonVariant) -> Self;
/// With the primary style for the Button.
fn primary(self) -> Self {
self.with_variant(ButtonVariant::Primary)
}
/// With the secondary style for the Button.
fn secondary(self) -> Self {
self.with_variant(ButtonVariant::Secondary)
}
/// With the danger style for the Button.
fn danger(self) -> Self {
self.with_variant(ButtonVariant::Danger)
}
/// With the warning style for the Button.
fn warning(self) -> Self {
self.with_variant(ButtonVariant::Warning)
}
/// With the ghost style for the Button.
fn ghost(self) -> Self {
self.with_variant(ButtonVariant::Ghost { alt: false })
}
/// With the ghost style for the Button.
fn ghost_alt(self) -> Self {
self.with_variant(ButtonVariant::Ghost { alt: true })
}
/// With the transparent style for the Button.
fn transparent(self) -> Self {
self.with_variant(ButtonVariant::Transparent)
}
/// With the custom style for the Button.
fn custom(self, style: ButtonCustomVariant) -> Self {
self.with_variant(ButtonVariant::Custom(style))
}
@@ -127,13 +119,9 @@ pub struct Button {
rounded: bool,
compact: bool,
caret: bool,
indicator: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
tab_index: isize,
tab_stop: bool,
pub(crate) selected: bool,
selected: bool,
}
impl From<Button> for AnyElement {
@@ -151,22 +139,18 @@ impl Button {
variant: ButtonVariant::default(),
disabled: false,
selected: false,
indicator: false,
compact: false,
caret: false,
rounded: false,
size: Size::Medium,
tooltip: None,
on_click: None,
on_hover: None,
loading: false,
children: Vec::new(),
tab_index: 0,
tab_stop: true,
}
}
/// Make the button rounded.
pub fn rounded(mut self) -> Self {
self.rounded = true;
self
@@ -178,19 +162,17 @@ impl Button {
self
}
/// Set the icon of the button, if the Button have no label, the button well in Icon Button mode.
/// Set the icon of the button, if the Button has no label, the button will be in Icon Button mode.
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
self.icon = Some(icon.into());
self
}
/// Set the tooltip of the button.
pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
/// Set true to show the loading indicator.
pub fn loading(mut self, loading: bool) -> Self {
self.loading = loading;
self
@@ -202,13 +184,11 @@ impl Button {
self
}
/// Set true to show the caret indicator.
pub fn caret(mut self) -> Self {
self.caret = true;
self
}
/// Add click handler.
pub fn on_click(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
@@ -217,9 +197,7 @@ impl Button {
self
}
/// Set the tab stop of the button, if true, the button will be focusable by tab key.
///
/// Default is true.
/// Set whether the button can be focused with the tab key (default true).
pub fn tab_stop(mut self, tab_stop: bool) -> Self {
self.tab_stop = tab_stop;
self
@@ -229,11 +207,6 @@ impl Button {
fn clickable(&self) -> bool {
!(self.disabled || self.loading) && self.on_click.is_some()
}
#[inline]
fn hoverable(&self) -> bool {
!(self.disabled || self.loading) && self.on_hover.is_some()
}
}
impl Disableable for Button {
@@ -288,9 +261,8 @@ impl InteractiveElement for Button {
impl RenderOnce for Button {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let style: ButtonVariant = self.variant;
let style = self.variant;
let clickable = self.clickable();
let hoverable = self.hoverable();
let normal_style = style.normal(cx);
let icon_size = match self.size {
Size::Size(v) => Size::Size(v * 0.75),
@@ -299,13 +271,12 @@ impl RenderOnce for Button {
};
self.base
.tab_index(self.tab_index)
.tab_index(0)
.tab_stop(self.tab_stop)
.disabled(self.disabled)
.when_some(self.on_click.clone(), |this, on_click| {
this.on_click(move |event, window, cx| {
// Stop handle any click event when disabled.
// To avoid handle dropdown menu open when button is disabled.
// Stop clicks when disabled, so a parent (e.g. dropdown menu) doesn't handle them.
if !clickable {
cx.stop_propagation();
return;
@@ -371,8 +342,7 @@ impl RenderOnce for Button {
}
})
.on_mouse_down(MouseButton::Left, move |_, window, cx| {
// Stop handle any click event when disabled.
// To avoid handle dropdown menu open when button is disabled.
// Stop clicks when disabled, so a parent (e.g. dropdown menu) doesn't handle them.
if self.disabled {
cx.stop_propagation();
return;
@@ -380,11 +350,6 @@ impl RenderOnce for Button {
// Avoid focus on mouse down.
window.prevent_default();
})
.when_some(self.on_hover.filter(|_| hoverable), |this, on_hover| {
this.on_hover(move |hovered, window, cx| {
(on_hover)(hovered, window, cx);
})
})
.child({
h_flex()
.id("label")
@@ -413,17 +378,6 @@ impl RenderOnce for Button {
})
})
.text_color(normal_style.fg)
.when(self.indicator && !self.disabled, |this| {
this.child(
div()
.absolute()
.bottom_px()
.right_px()
.size_1()
.rounded_full()
.bg(gpui::green()),
)
})
.when(!self.disabled && !self.selected, |this| {
this.bg(normal_style.bg)
.hover(|this| {
@@ -574,10 +528,7 @@ impl ButtonVariant {
_ => cx.theme().element_disabled,
};
let fg = match self {
ButtonVariant::Primary => cx.theme().text_muted,
_ => cx.theme().text_muted,
};
let fg = cx.theme().text_muted;
ButtonVariantStyle { bg, fg }
}
+12 -51
View File
@@ -15,7 +15,6 @@ use crate::button::{Button, ButtonCustomVariant, ButtonVariant, ButtonVariants a
use crate::scroll::ScrollableElement;
use crate::{IconName, Root, StyledExt, WindowExtension, h_flex, v_flex};
type OnClose = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
type OnOk = Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static>>;
type OnCancel = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static>;
type RenderButtonFn = Box<dyn FnOnce(&mut Window, &mut App) -> AnyElement>;
@@ -70,7 +69,6 @@ pub struct Dialog {
width: Pixels,
max_width: Option<Pixels>,
on_close: OnClose,
on_ok: OnOk,
on_cancel: OnCancel,
@@ -80,10 +78,10 @@ pub struct Dialog {
show_close: bool,
button_props: DialogButtonProps,
/// This will be change when open the dialog, the focus handle is create when open the dialog.
pub focus_handle: FocusHandle,
pub layer_ix: usize,
pub overlay_visible: bool,
/// The focus handle created when the dialog is opened, owned by the `Root`.
pub(crate) focus_handle: FocusHandle,
pub(crate) layer_ix: usize,
pub(crate) overlay_visible: bool,
}
impl Dialog {
@@ -100,7 +98,6 @@ impl Dialog {
keyboard: true,
layer_ix: 0,
overlay_visible: false,
on_close: Rc::new(|_, _, _| {}),
on_ok: None,
on_cancel: Rc::new(|_, _, _| true),
button_props: DialogButtonProps::default(),
@@ -115,15 +112,8 @@ impl Dialog {
self
}
/// Set the footer of the dialog.
///
/// The `footer` is a function that takes two `RenderButtonFn` and a `WindowContext` and returns a list of `AnyElement`.
///
/// - First `RenderButtonFn` is the render function for the OK button.
/// - Second `RenderButtonFn` is the render function for the CANCEL button.
///
/// When you set the footer, the footer will be placed default footer buttons.
pub fn footer<E, F>(mut self, footer: F) -> Self
/// Sets a custom footer, replacing the default footer buttons.
fn footer<E, F>(mut self, footer: F) -> Self
where
E: IntoElement,
F: Fn(RenderButtonFn, RenderButtonFn, &mut Window, &mut App) -> Vec<E> + 'static,
@@ -150,20 +140,7 @@ impl Dialog {
self
}
/// Sets the callback for when the dialog is closed.
///
/// Called after [`Self::on_ok`] or [`Self::on_cancel`] callback.
pub fn on_close(
mut self,
on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_close = Rc::new(on_close);
self
}
/// Sets the callback for when the dialog is has been confirmed.
///
/// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.
/// Sets the confirm callback, returning `false` keeps the dialog open.
pub fn on_ok(
mut self,
on_ok: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,
@@ -172,9 +149,7 @@ impl Dialog {
self
}
/// Sets the callback for when the dialog is has been canceled.
///
/// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.
/// Sets the cancel callback, returning `false` keeps the dialog open.
pub fn on_cancel(
mut self,
on_cancel: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,
@@ -189,15 +164,13 @@ impl Dialog {
self
}
/// Sets the width of the dialog, defaults to 480px.
/// Sets the width of the dialog, defaults to 380px.
pub fn width(mut self, width: Pixels) -> Self {
self.width = width;
self
}
/// Set the overlay closable of the dialog, defaults to `true`.
///
/// When the overlay is clicked, the dialog will be closed.
/// Sets whether clicking the overlay closes the dialog, defaults to `true`.
pub fn overlay_closable(mut self, overlay_closable: bool) -> Self {
self.overlay_closable = overlay_closable;
self
@@ -209,7 +182,7 @@ impl Dialog {
self
}
pub fn has_overlay(&self) -> bool {
pub(crate) fn has_overlay(&self) -> bool {
self.overlay
}
}
@@ -232,13 +205,11 @@ impl RenderOnce for Dialog {
let is_topmost = layer_ix + 1 == Root::read(window, cx).active_dialogs.len();
let has_footer = self.footer.is_some();
let on_close = self.on_close.clone();
let on_ok = self.on_ok.clone();
let on_cancel = self.on_cancel.clone();
let render_ok: RenderButtonFn = Box::new({
let on_ok = on_ok.clone();
let on_close = on_close.clone();
let ok_variant = self.button_props.ok_variant;
let ok_text = self.button_props.ok_text.unwrap_or_else(|| "OK".into());
@@ -249,7 +220,6 @@ impl RenderOnce for Dialog {
.font_semibold()
.on_click({
let on_ok = on_ok.clone();
let on_close = on_close.clone();
move |_, window, cx| {
if let Some(on_ok) = &on_ok
@@ -258,7 +228,6 @@ impl RenderOnce for Dialog {
return;
}
on_close(&ClickEvent::default(), window, cx);
window.close_dialog(cx);
}
})
@@ -268,7 +237,6 @@ impl RenderOnce for Dialog {
let render_cancel: RenderButtonFn = Box::new({
let on_cancel = on_cancel.clone();
let on_close = on_close.clone();
let cancel_variant = self.button_props.cancel_variant;
let cancel_text = self
.button_props
@@ -281,13 +249,11 @@ impl RenderOnce for Dialog {
.with_variant(cancel_variant)
.on_click({
let on_cancel = on_cancel.clone();
let on_close = on_close.clone();
move |_, window, cx| {
if !on_cancel(&ClickEvent::default(), window, cx) {
return;
}
on_close(&ClickEvent::default(), window, cx);
window.close_dialog(cx);
}
})
@@ -349,7 +315,6 @@ impl RenderOnce for Dialog {
})
.when(self.show_close, |this| {
let on_cancel = on_cancel.clone();
let on_close = on_close.clone();
this.child(
div()
@@ -371,7 +336,6 @@ impl RenderOnce for Dialog {
)
.on_click(move |_, window, cx| {
on_cancel(&ClickEvent::default(), window, cx);
on_close(&ClickEvent::default(), window, cx);
window.close_dialog(cx);
}),
),
@@ -442,9 +406,6 @@ impl RenderOnce for Dialog {
None => has_footer,
})
.on_cancel(move |event, window, cx| on_cancel(event, window, cx))
.on_close(move |event, window, cx| {
on_close(event, window, cx);
window.close_dialog(cx);
})
.on_close(move |_, window, cx| window.close_dialog(cx))
}
}
+5 -4
View File
@@ -83,9 +83,11 @@ pub fn add_panel_to(
window: &mut Window,
cx: &mut App,
) {
let _ = dock.update(cx, |area, cx| {
if let Err(error) = dock.update(cx, |area, cx| {
add_panel(area, panel, placement, window, cx);
});
}) {
log::warn!("Failed to add panel to dock: {error}");
}
}
/// The panel in any region of `area` whose logical id is `key`.
@@ -670,8 +672,7 @@ impl TabGroupRenderer for TabGroupSkin {
window: &mut Window,
cx: &mut App,
) -> AnyElement {
// The left dock's only panel draws bare, so its content can own the
// window's top-left corner instead of a tab bar doing so.
// The plain left group draws bare so its content can own the window's top-left corner.
if self.is_plain_left_group(group, cx) {
return Empty.into_any_element();
}
+7 -56
View File
@@ -8,14 +8,9 @@ use gpui::{
use gpui_base::dock::{PanelId, PanelState};
use crate::button::Button;
use crate::menu::PopupMenu;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PanelEvent {
ZoomIn,
ZoomOut,
LayoutChanged,
}
pub enum PanelEvent {}
pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
/// The name of the panel used to serialize, deserialize and identify the panel.
@@ -31,44 +26,29 @@ pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
true
}
/// Return true if the panel is zoomable, default is `false`.
/// Whether the panel is zoomable, default is `true`.
fn zoomable(&self, _cx: &App) -> bool {
true
}
/// Return false to hide panel, true to show panel, default is `true`.
///
/// This method called in Panel render, we should make sure it is fast.
fn visible(&self, _cx: &App) -> bool {
true
}
/// Set active state of the panel.
///
/// This method will be called when the panel is active or inactive.
///
/// The last_active_panel and current_active_panel will be touched when the panel is active.
fn set_active(&self, _active: bool, _window: &mut Window, _cx: &mut App) {}
/// Set zoomed state of the panel.
///
/// This method will be called when the panel is zoomed or unzoomed.
///
/// Only current Panel will touch this method.
fn set_zoomed(&self, _zoomed: bool, _cx: &mut App) {}
/// The addition popup menu of the panel, default is `None`.
fn popup_menu(&self, this: PopupMenu, _cx: &App) -> PopupMenu {
this
}
/// The addition toolbar buttons of the panel used to show in the right of the title bar, default is `None`.
/// Toolbar buttons shown at the right of the title bar.
fn toolbar_buttons(&self, _window: &Window, _cx: &App) -> Vec<Button> {
vec![]
}
}
pub trait PanelView: 'static + Send + Sync {
pub(crate) trait PanelView: 'static + Send + Sync {
fn panel_id(&self, cx: &App) -> SharedString;
fn title(&self, cx: &App) -> AnyElement;
fn closable(&self, cx: &App) -> bool;
@@ -76,8 +56,6 @@ pub trait PanelView: 'static + Send + Sync {
fn visible(&self, cx: &App) -> bool;
fn set_active(&self, active: bool, window: &mut Window, cx: &mut App);
fn set_zoomed(&self, zoomed: bool, cx: &mut App);
fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu;
fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec<Button>;
fn view(&self) -> AnyView;
fn focus_handle(&self, cx: &App) -> FocusHandle;
}
@@ -115,14 +93,6 @@ impl<T: Panel> PanelView for Entity<T> {
})
}
fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu {
self.read(cx).popup_menu(menu, cx)
}
fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec<Button> {
self.read(cx).toolbar_buttons(window, cx)
}
fn view(&self) -> AnyView {
self.clone().into()
}
@@ -132,24 +102,6 @@ impl<T: Panel> PanelView for Entity<T> {
}
}
impl From<&dyn PanelView> for AnyView {
fn from(handle: &dyn PanelView) -> Self {
handle.view()
}
}
impl<T: Panel> From<&dyn PanelView> for Entity<T> {
fn from(value: &dyn PanelView) -> Self {
value.view().downcast::<T>().unwrap()
}
}
impl PartialEq for dyn PanelView {
fn eq(&self, other: &Self) -> bool {
self.view() == other.view()
}
}
#[derive(Clone)]
pub struct PanelHandle {
id: PanelId,
@@ -164,13 +116,12 @@ impl PanelHandle {
}
}
/// Recover the coop handle behind one of base's.
pub fn of(panel: &Arc<dyn gpui_base::dock::PanelView>) -> Option<&Self> {
/// Downcast a base handle back to the coop handle.
pub(crate) fn of(panel: &Arc<dyn gpui_base::dock::PanelView>) -> Option<&Self> {
panel.as_any().downcast_ref::<Self>()
}
/// The coop panel behind this handle.
pub fn panel(&self) -> &Arc<dyn PanelView> {
pub(crate) fn panel(&self) -> &Arc<dyn PanelView> {
&self.panel
}
}
+1 -14
View File
@@ -22,12 +22,6 @@ pub trait GroupBoxVariants: Sized {
#[must_use]
fn with_variant(self, variant: GroupBoxVariant) -> Self;
/// Set to use [`GroupBoxVariant::Normal`] to GroupBox.
#[must_use]
fn normal(self) -> Self {
self.with_variant(GroupBoxVariant::Normal)
}
/// Set to use [`GroupBoxVariant::Fill`] to GroupBox.
#[must_use]
fn fill(self) -> Self {
@@ -35,16 +29,13 @@ pub trait GroupBoxVariants: Sized {
}
}
/// GroupBox is a styled container element that with
/// an optional title to groups related content together.
/// A styled container with an optional title that groups related content together.
#[derive(IntoElement)]
pub struct GroupBox {
id: Option<ElementId>,
variant: GroupBoxVariant,
style: StyleRefinement,
title_style: StyleRefinement,
title: Option<AnyElement>,
content_style: StyleRefinement,
children: SmallVec<[AnyElement; 1]>,
}
@@ -55,8 +46,6 @@ impl GroupBox {
id: None,
variant: GroupBoxVariant::default(),
style: StyleRefinement::default(),
title_style: StyleRefinement::default(),
content_style: StyleRefinement::default(),
title: None,
children: SmallVec::new(),
}
@@ -120,7 +109,6 @@ impl RenderOnce for GroupBox {
div()
.text_color(cx.theme().text_muted)
.line_height(relative(1.))
.refine_style(&self.title_style)
.text_sm()
.font_semibold()
.child(title),
@@ -133,7 +121,6 @@ impl RenderOnce for GroupBox {
.when(has_paddings, |this| this.p_2())
.gap_4()
.rounded(cx.theme().radius_lg)
.refine_style(&self.content_style)
.children(self.children),
)
}
+3 -45
View File
@@ -1,7 +1,7 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
AnyElement, App, Context, Hsla, IntoElement, Radians, Render, RenderOnce, SharedString,
StyleRefinement, Styled, Svg, Transformation, Window, svg,
AnyElement, App, Context, Hsla, IntoElement, Render, RenderOnce, SharedString, StyleRefinement,
Styled, Svg, Window, svg,
};
use theme::ActiveTheme;
@@ -18,13 +18,11 @@ pub enum IconName {
ArrowLeft,
ArrowRight,
ArrowDownCircle,
Boom,
Book,
Ban,
ChevronDown,
CaretDown,
CaretRight,
CaretUp,
Check,
CheckCircle,
Close,
@@ -32,17 +30,14 @@ pub enum IconName {
CloseCircleFill,
Compass,
Copy,
Device,
Door,
Ellipsis,
Emoji,
Eye,
Folder,
Input,
Info,
Invite,
Inbox,
InboxFill,
Link,
Loader,
Lock,
@@ -54,23 +49,14 @@ pub enum IconName {
PlusChat,
PlusCircle,
Profile,
Reset,
Relay,
Reply,
Refresh,
Scan,
Search,
Settings,
Settings2,
Sun,
Ship,
Shield,
Group,
Hashtag,
History,
UserKey,
Upload,
Usb,
Edit,
PanelLeft,
PanelLeftOpen,
@@ -84,9 +70,6 @@ pub enum IconName {
WindowMaximize,
WindowMinimize,
WindowRestore,
Fistbump,
FistbumpFill,
Zoom,
}
impl IconName {
@@ -95,13 +78,11 @@ impl IconName {
Self::ArrowLeft => "icons/arrow-left.svg",
Self::ArrowRight => "icons/arrow-right.svg",
Self::ArrowDownCircle => "icons/arrow-down-circle.svg",
Self::Boom => "icons/boom.svg",
Self::Book => "icons/book.svg",
Self::Ban => "icons/ban.svg",
Self::ChevronDown => "icons/chevron-down.svg",
Self::CaretDown => "icons/caret-down.svg",
Self::CaretRight => "icons/caret-right.svg",
Self::CaretUp => "icons/caret-up.svg",
Self::Check => "icons/check.svg",
Self::CheckCircle => "icons/check-circle.svg",
Self::Close => "icons/close.svg",
@@ -109,17 +90,14 @@ impl IconName {
Self::CloseCircleFill => "icons/close-circle-fill.svg",
Self::Compass => "icons/compass.svg",
Self::Copy => "icons/copy.svg",
Self::Device => "icons/device.svg",
Self::Door => "icons/door.svg",
Self::Ellipsis => "icons/ellipsis.svg",
Self::Emoji => "icons/emoji.svg",
Self::Eye => "icons/eye.svg",
Self::Folder => "icons/folder.svg",
Self::Input => "icons/input.svg",
Self::Info => "icons/info.svg",
Self::Invite => "icons/invite.svg",
Self::Inbox => "icons/inbox.svg",
Self::InboxFill => "icons/inbox-fill.svg",
Self::Link => "icons/link.svg",
Self::Loader => "icons/loader.svg",
Self::Lock => "icons/lock.svg",
@@ -131,23 +109,14 @@ impl IconName {
Self::PlusChat => "icons/plus-chat.svg",
Self::PlusCircle => "icons/plus-circle.svg",
Self::Profile => "icons/profile.svg",
Self::Reset => "icons/reset.svg",
Self::Relay => "icons/relay.svg",
Self::Reply => "icons/reply.svg",
Self::Refresh => "icons/refresh.svg",
Self::Scan => "icons/scan.svg",
Self::Search => "icons/search.svg",
Self::Settings => "icons/settings.svg",
Self::Settings2 => "icons/settings2.svg",
Self::Sun => "icons/sun.svg",
Self::Ship => "icons/ship.svg",
Self::Shield => "icons/shield.svg",
Self::UserKey => "icons/user-key.svg",
Self::Upload => "icons/upload.svg",
Self::Usb => "icons/usb.svg",
Self::Group => "icons/group.svg",
Self::Hashtag => "icons/hashtag.svg",
Self::History => "icons/history.svg",
Self::PanelLeft => "icons/panel-left.svg",
Self::PanelLeftOpen => "icons/panel-left-open.svg",
Self::PanelRight => "icons/panel-right.svg",
@@ -161,9 +130,6 @@ impl IconName {
Self::WindowMaximize => "icons/window-maximize.svg",
Self::WindowMinimize => "icons/window-minimize.svg",
Self::WindowRestore => "icons/window-restore.svg",
Self::Fistbump => "icons/fistbump.svg",
Self::FistbumpFill => "icons/fistbump-fill.svg",
Self::Zoom => "icons/zoom.svg",
}
.into()
}
@@ -188,7 +154,6 @@ pub struct Icon {
path: SharedString,
text_color: Option<Hsla>,
size: Option<Size>,
rotation: Option<Radians>,
}
impl Default for Icon {
@@ -199,7 +164,6 @@ impl Default for Icon {
path: "".into(),
text_color: None,
size: None,
rotation: None,
}
}
}
@@ -208,7 +172,6 @@ impl Clone for Icon {
fn clone(&self) -> Self {
let mut this = Self::default().path(self.path.clone());
this.style = self.style.clone();
this.rotation = self.rotation;
this.size = self.size;
this.text_color = self.text_color;
this
@@ -224,9 +187,7 @@ impl Icon {
Self::default().path(name.path())
}
/// Set the icon path of the Assets bundle
///
/// For example: `icons/foo.svg`
/// Sets the icon path, relative to the assets bundle (for example `icons/foo.svg`).
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = path.into();
self
@@ -309,8 +270,5 @@ impl Render for Icon {
Size::Large => this.size_6(),
})
.path(self.path.clone())
.when_some(self.rotation, |this, rotation| {
this.with_transformation(Transformation::rotate(rotation))
})
}
}
+6 -6
View File
@@ -15,6 +15,12 @@ pub struct Indicator {
color: Option<Hsla>,
}
impl Default for Indicator {
fn default() -> Self {
Self::new()
}
}
impl Indicator {
pub fn new() -> Self {
Self {
@@ -31,12 +37,6 @@ impl Indicator {
}
}
impl Default for Indicator {
fn default() -> Self {
Self::new()
}
}
impl Sizable for Indicator {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
+8 -75
View File
@@ -1,17 +1,16 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
AnyElement, App, DefiniteLength, Edges, Entity, Hsla, InteractiveElement as _, IntoElement,
MouseButton, ParentElement as _, Pixels, Rems, RenderOnce, StyleRefinement, Styled, TextAlign,
Window, div, px,
App, DefiniteLength, Edges, Entity, Hsla, InteractiveElement as _, IntoElement, MouseButton,
ParentElement as _, Pixels, Rems, RenderOnce, StyleRefinement, Styled, TextAlign, Window, div,
px,
};
use gpui_base::InputBase;
use gpui_base::input::{InputBaseState, InputEditorStyle, InputMode, InputModeKind, TextareaMode};
use theme::ActiveTheme;
use crate::button::{Button, ButtonVariants as _};
use crate::indicator::Indicator;
use crate::input::clear_button;
use crate::{IconName, Selectable, Sizable, Size, StyleSized, StyledExt, h_flex, v_flex};
use crate::{Sizable, Size, StyleSized, StyledExt, h_flex, v_flex};
/// The background of an input frame, which reads muted while the input is disabled.
fn input_background(disabled: bool, cx: &App) -> Hsla {
@@ -23,10 +22,6 @@ fn input_background(disabled: bool, cx: &App) -> Hsla {
}
/// The colors base paints input text with, read from the coop theme.
///
/// Base fills in any color left transparent from its own palette, and that
/// palette is only a projection of this one, so every color coop paints with is
/// named here rather than left to resolve.
fn input_editor_style(cx: &App) -> InputEditorStyle {
let theme = cx.theme();
InputEditorStyle {
@@ -41,10 +36,6 @@ fn input_editor_style(cx: &App) -> InputEditorStyle {
}
/// The input's own padding, resolved to pixels.
///
/// Base applies the multi-line padding itself so that the text, the gutter, and
/// the scrollbar share one inset, and the single-line frame carries its own.
/// Both come from the same size table, resolved through the window's rem size.
fn input_paddings(size: Size, style: &StyleRefinement, window: &Window) -> Edges<Pixels> {
let mut probe = div().input_px(size).input_py(size).refine_style(style);
let padding = probe.style().padding.clone();
@@ -65,23 +56,14 @@ fn input_paddings(size: Size, style: &StyleRefinement, window: &Window) -> Edges
}
/// A text input element bound to an [`InputState`] or a [`TextareaState`].
///
/// The editing kind lives on the state, so `Input::new` accepts either and
/// infers which one is rendered.
#[derive(IntoElement)]
pub struct Input<M: InputModeKind = InputMode> {
state: Entity<InputBaseState<M>>,
style: StyleRefinement,
size: Size,
prefix: Option<AnyElement>,
suffix: Option<AnyElement>,
height: Option<DefiniteLength>,
appearance: bool,
cleanable: bool,
mask_toggle: bool,
disabled: bool,
tab_index: isize,
selected: bool,
}
/// A styled multi-line text input.
@@ -94,17 +76,6 @@ impl<M: InputModeKind> Sizable for Input<M> {
}
}
impl<M: InputModeKind> Selectable for Input<M> {
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl<M: InputModeKind> Input<M> {
/// Create a new [`Input`] element bind to the given state.
pub fn new(state: &Entity<InputBaseState<M>>) -> Self {
@@ -112,28 +83,12 @@ impl<M: InputModeKind> Input<M> {
state: state.clone(),
size: Size::default(),
style: StyleRefinement::default(),
prefix: None,
suffix: None,
height: None,
appearance: true,
cleanable: false,
mask_toggle: false,
disabled: false,
tab_index: 0,
selected: false,
}
}
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
self.prefix = Some(prefix.into_any_element());
self
}
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
self.suffix = Some(suffix.into_any_element());
self
}
/// Set the appearance of the input field, if false the input field will no border, background.
pub fn appearance(mut self, appearance: bool) -> Self {
self.appearance = appearance;
@@ -151,18 +106,6 @@ impl<M: InputModeKind> Input<M> {
self.disabled = disabled;
self
}
fn render_toggle_mask_button(state: &Entity<InputBaseState<M>>) -> impl IntoElement {
Button::new("toggle-mask")
.icon(IconName::Eye)
.xsmall()
.ghost()
.tab_stop(false)
.on_click({
let state = state.clone();
move |_, window, cx| state.update(cx, |state, cx| state.toggle_masked(window, cx))
})
}
}
impl<M: InputModeKind> Styled for Input<M> {
@@ -206,10 +149,8 @@ impl<M: InputModeKind> RenderOnce for Input<M> {
let background = input_background(disabled, cx);
let show_clear_button =
self.cleanable && state.is_editable() && !loading && !text_is_empty && !multi_line;
let has_suffix = self.suffix.is_some() || loading || self.mask_toggle || show_clear_button;
let has_suffix = loading || show_clear_button;
let prefix = self.prefix;
let suffix = self.suffix;
let state_entity = self.state.clone();
InputBase::new(("input", self.state.entity_id()))
@@ -226,19 +167,15 @@ impl<M: InputModeKind> RenderOnce for Input<M> {
move |_, window, cx| state_entity.update(cx, |state, cx| state.focus(window, cx))
})
.items_center()
.when(multi_line, |this| {
this.h_auto()
.when_some(self.height, |this, height| this.h(height))
})
.when(multi_line, |this| this.h_auto())
.when(self.appearance, |this| {
this.bg(background)
.when(self.disabled, |this| this.opacity(0.5))
.rounded(cx.theme().radius)
})
.tab_index(self.tab_index)
.tab_index(0)
.gap(gap_x)
.refine_style(&self.style)
.children(prefix)
.when(!multi_line, |this| this.child(state_entity.clone()))
.when(multi_line, |this| {
this.child(
@@ -254,9 +191,6 @@ impl<M: InputModeKind> RenderOnce for Input<M> {
.gap(gap_x)
.items_center()
.when(loading, |this| this.child(Indicator::new()))
.when(self.mask_toggle, |this| {
this.child(Self::render_toggle_mask_button(&state_entity))
})
.when(show_clear_button, |this| {
this.child(clear_button(cx).on_click({
let state = state_entity.clone();
@@ -267,8 +201,7 @@ impl<M: InputModeKind> RenderOnce for Input<M> {
})
}
}))
})
.children(suffix),
}),
)
})
}
+7 -33
View File
@@ -1,6 +1,6 @@
use gpui::{
Action, AsKeystroke, FocusHandle, IntoElement, KeyContext, Keystroke, ParentElement as _,
RenderOnce, StyleRefinement, Styled, Window, div, relative,
Action, AsKeystroke, IntoElement, KeyContext, Keystroke, ParentElement as _, RenderOnce,
StyleRefinement, Styled, Window, div, relative,
};
use theme::ActiveTheme;
@@ -8,22 +8,13 @@ use crate::StyledExt;
/// A key binding tag
#[derive(IntoElement, Clone, Debug)]
pub struct Kbd {
pub(crate) struct Kbd {
style: StyleRefinement,
stroke: Keystroke,
}
impl From<Keystroke> for Kbd {
fn from(stroke: Keystroke) -> Self {
Self {
style: StyleRefinement::default(),
stroke,
}
}
}
impl Kbd {
pub fn new(stroke: Keystroke) -> Self {
fn new(stroke: Keystroke) -> Self {
Self {
style: StyleRefinement::default(),
stroke,
@@ -31,7 +22,7 @@ impl Kbd {
}
/// Return the first keybinding for the given action and context.
pub fn binding_for_action(
pub(crate) fn binding_for_action(
action: &dyn Action,
context: Option<&str>,
window: &Window,
@@ -50,25 +41,8 @@ impl Kbd {
.map(|key| Self::new(key.as_keystroke().clone()))
}
/// Return the first keybinding for the given action and focus handle.
pub fn binding_for_action_in(
action: &dyn Action,
focus_handle: &FocusHandle,
window: &Window,
) -> Option<Self> {
let binding = window.highest_precedence_binding_for_action_in(action, focus_handle)?;
binding
.keystrokes()
.first()
.map(|key| Self::new(key.as_keystroke().clone()))
}
/// Return the Platform specific keybinding string by KeyStroke
///
/// - macOS: https://support.apple.com/en-us/HT201236
/// - Windows: https://support.microsoft.com/en-us/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec
pub fn format(key: &Keystroke) -> String {
/// Return the platform specific keybinding string for a keystroke.
fn format(key: &Keystroke) -> String {
#[cfg(target_os = "macos")]
const DIVIDER: &str = "";
#[cfg(not(target_os = "macos"))]
+4 -30
View File
@@ -75,12 +75,6 @@ where
}
}
/// Set the anchor corner for the dropdown menu popover.
pub fn anchor(mut self, anchor: impl Into<Anchor>) -> Self {
self.anchor = anchor.into();
self
}
/// Set the style refinement for the dropdown menu trigger.
fn trigger_style(mut self, style: StyleRefinement) -> Self {
self.style = style;
@@ -88,13 +82,10 @@ where
}
}
/// Opens a [`PopupMenu`] when its child is clicked with a mouse button
/// (right by default), keeping the child's own click handler intact.
/// Opens a [`PopupMenu`] when its child is right-clicked, keeping the child's own click handler.
#[derive(IntoElement)]
pub struct ContextMenu {
id: ElementId,
anchor: Anchor,
mouse_button: MouseButton,
child: AnyElement,
builder: Rc<MenuBuilder>,
}
@@ -107,24 +98,10 @@ impl ContextMenu {
) -> Self {
Self {
id: id.into(),
anchor: Anchor::TopLeft,
mouse_button: MouseButton::Right,
child: child.into_any_element(),
builder: Rc::new(builder),
}
}
/// Set the anchor corner of the menu, default is `Anchor::TopLeft`.
pub fn anchor(mut self, anchor: impl Into<Anchor>) -> Self {
self.anchor = anchor.into();
self
}
/// Set the mouse button that opens the menu, default is `MouseButton::Right`.
pub fn mouse_button(mut self, mouse_button: MouseButton) -> Self {
self.mouse_button = mouse_button;
self
}
}
#[derive(Default)]
@@ -132,10 +109,7 @@ struct MenuState {
menu: Option<Entity<PopupMenu>>,
}
/// Builds the menu once and reuses it until it is dismissed.
///
/// The popover content closure runs on every render, so rebuilding the menu
/// entity each time would drop its focus and selection state.
/// Builds the menu once and reuses it until dismissed, preserving focus and selection state.
fn cached_menu(
menu_state: &Entity<MenuState>,
builder: Rc<MenuBuilder>,
@@ -196,8 +170,8 @@ impl RenderOnce for ContextMenu {
Popover::new(SharedString::from(format!("context-menu:{}", self.id)))
.appearance(false)
.overlay_closable(false)
.anchor(self.anchor)
.mouse_button(self.mouse_button)
.anchor(Anchor::TopLeft)
.mouse_button(MouseButton::Right)
.trigger_with(move |_open, _window, _cx| self.child)
.content(move |_, window, cx| cached_menu(&menu_state, builder.clone(), window, cx))
}
+5 -8
View File
@@ -39,18 +39,12 @@ impl MenuItemElement {
}
}
/// Set ListItem as the selected item style.
/// Set the selected style of the item.
pub(crate) fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
/// Set the disabled state of the MenuItem.
pub(crate) fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
/// Set a handler for when the MenuItem is clicked.
pub(crate) fn on_click(
mut self,
@@ -61,7 +55,10 @@ impl MenuItemElement {
}
/// Set a handler for when the mouse enters the MenuItem.
pub fn on_hover(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
pub(crate) fn on_hover(
mut self,
handler: impl Fn(&bool, &mut Window, &mut App) + 'static,
) -> Self {
self.on_hover = Some(Box::new(handler));
self
}
+67 -314
View File
@@ -2,22 +2,22 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder;
use gpui::{
Action, Anchor, AnyElement, App, AppContext, Axis, Bounds, ClickEvent, Context, DismissEvent,
Edges, Entity, EventEmitter, FocusHandle, Focusable, Half, InteractiveElement, IntoElement,
KeyBinding, MouseDownEvent, ParentElement, Pixels, Point, Render, ScrollHandle, SharedString,
StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, anchored, div, px, rems,
Action, Anchor, App, AppContext, Axis, Bounds, ClickEvent, Context, DismissEvent, Edges,
Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,
MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Styled, WeakEntity, Window,
anchored, div, px, rems,
};
use gpui_base::actions::{Cancel, Confirm, SelectDown, SelectLeft, SelectRight, SelectUp};
use theme::{ActiveTheme, Side};
use crate::kbd::Kbd;
use crate::menu::menu_item::MenuItemElement;
use crate::scroll::ScrollableElement;
use crate::{ElementExt, Icon, IconName, Sizable as _, Size, StyledExt, h_flex, v_flex};
use crate::{Disableable, ElementExt, Icon, IconName, Sizable as _, StyledExt, h_flex, v_flex};
const CONTEXT: &str = "PopupMenu";
const MAX_WIDTH: Pixels = px(500.);
pub fn init(cx: &mut App) {
pub(crate) fn init(cx: &mut App) {
cx.bind_keys([
KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)),
KeyBinding::new("escape", Cancel, Some(CONTEXT)),
@@ -28,7 +28,7 @@ pub fn init(cx: &mut App) {
]);
}
/// An menu item in a popup menu.
/// A menu item in a popup menu.
pub enum PopupMenuItem {
/// A menu separator item.
Separator,
@@ -40,26 +40,11 @@ pub enum PopupMenuItem {
label: SharedString,
disabled: bool,
checked: bool,
is_link: bool,
action: Option<Box<dyn Action>>,
// For link item
#[allow(clippy::type_complexity)]
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
},
/// A menu item with custom element render.
ElementItem {
icon: Option<Icon>,
disabled: bool,
checked: bool,
action: Option<Box<dyn Action>>,
#[allow(clippy::type_complexity)]
render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
#[allow(clippy::type_complexity)]
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
},
/// A submenu item that opens another popup menu.
///
/// NOTE: This is only supported when the parent menu is not `scrollable`.
Submenu {
icon: Option<Icon>,
label: SharedString,
@@ -80,14 +65,13 @@ impl PopupMenuItem {
disabled: false,
checked: false,
action: None,
is_link: false,
handler: None,
}
}
/// Create a new submenu item that opens another popup menu.
#[inline]
pub fn submenu(label: impl Into<SharedString>, menu: Entity<PopupMenu>) -> Self {
fn submenu(label: impl Into<SharedString>, menu: Entity<PopupMenu>) -> Self {
PopupMenuItem::Submenu {
icon: None,
label: label.into(),
@@ -98,27 +82,22 @@ impl PopupMenuItem {
/// Create a separator menu item.
#[inline]
pub fn separator() -> Self {
fn separator() -> Self {
PopupMenuItem::Separator
}
/// Creates a label menu item.
/// Create a label menu item.
#[inline]
pub fn label(label: impl Into<SharedString>) -> Self {
fn label(label: impl Into<SharedString>) -> Self {
PopupMenuItem::Label(label.into())
}
/// Set the icon for the menu item.
///
/// Only works for [`PopupMenuItem::Item`], [`PopupMenuItem::ElementItem`] and [`PopupMenuItem::Submenu`].
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
fn icon(mut self, icon: impl Into<Icon>) -> Self {
match &mut self {
PopupMenuItem::Item { icon: i, .. } => {
*i = Some(icon.into());
}
PopupMenuItem::ElementItem { icon: i, .. } => {
*i = Some(icon.into());
}
PopupMenuItem::Submenu { icon: i, .. } => {
*i = Some(icon.into());
}
@@ -128,32 +107,19 @@ impl PopupMenuItem {
}
/// Set the action for the menu item.
///
/// Only works for [`PopupMenuItem::Item`] and [`PopupMenuItem::ElementItem`].
pub fn action(mut self, action: Box<dyn Action>) -> Self {
match &mut self {
PopupMenuItem::Item { action: a, .. } => {
*a = Some(action);
}
PopupMenuItem::ElementItem { action: a, .. } => {
*a = Some(action);
}
_ => {}
fn action(mut self, action: Box<dyn Action>) -> Self {
if let PopupMenuItem::Item { action: a, .. } = &mut self {
*a = Some(action);
}
self
}
/// Set the disabled state for the menu item.
///
/// Only works for [`PopupMenuItem::Item`], [`PopupMenuItem::ElementItem`] and [`PopupMenuItem::Submenu`].
pub fn disabled(mut self, disabled: bool) -> Self {
fn disabled(mut self, disabled: bool) -> Self {
match &mut self {
PopupMenuItem::Item { disabled: d, .. } => {
*d = disabled;
}
PopupMenuItem::ElementItem { disabled: d, .. } => {
*d = disabled;
}
PopupMenuItem::Submenu { disabled: d, .. } => {
*d = disabled;
}
@@ -162,57 +128,37 @@ impl PopupMenuItem {
self
}
/// Set checked state for the menu item.
///
/// NOTE: If `check_side` is [`Side::Left`], the icon will replace with a check icon.
pub fn checked(mut self, checked: bool) -> Self {
match &mut self {
PopupMenuItem::Item { checked: c, .. } => {
*c = checked;
}
PopupMenuItem::ElementItem { checked: c, .. } => {
*c = checked;
}
_ => {}
/// Set the checked state for the menu item.
fn checked(mut self, checked: bool) -> Self {
if let PopupMenuItem::Item { checked: c, .. } = &mut self {
*c = checked;
}
self
}
/// Add a click handler for the menu item.
///
/// Only works for [`PopupMenuItem::Item`] and [`PopupMenuItem::ElementItem`].
pub fn on_click<F>(mut self, handler: F) -> Self
where
F: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
{
match &mut self {
PopupMenuItem::Item { handler: h, .. } => {
*h = Some(Rc::new(handler));
}
PopupMenuItem::ElementItem { handler: h, .. } => {
*h = Some(Rc::new(handler));
}
_ => {}
if let PopupMenuItem::Item { handler: h, .. } = &mut self {
*h = Some(Rc::new(handler));
}
self
}
#[inline]
fn is_clickable(&self) -> bool {
!matches!(self, PopupMenuItem::Separator)
&& matches!(
self,
PopupMenuItem::Item {
disabled: false,
..
} | PopupMenuItem::ElementItem {
disabled: false,
..
} | PopupMenuItem::Submenu {
disabled: false,
..
}
)
matches!(
self,
PopupMenuItem::Item {
disabled: false,
..
} | PopupMenuItem::Submenu {
disabled: false,
..
}
)
}
#[inline]
@@ -220,14 +166,9 @@ impl PopupMenuItem {
matches!(self, PopupMenuItem::Separator)
}
fn has_left_icon(&self, check_side: Side) -> bool {
fn has_left_icon(&self) -> bool {
match self {
PopupMenuItem::Item { icon, checked, .. } => {
icon.is_some() || (check_side.is_left() && *checked)
}
PopupMenuItem::ElementItem { icon, checked, .. } => {
icon.is_some() || (check_side.is_left() && *checked)
}
PopupMenuItem::Item { icon, checked, .. } => icon.is_some() || *checked,
PopupMenuItem::Submenu { icon, .. } => icon.is_some(),
_ => false,
}
@@ -235,66 +176,38 @@ impl PopupMenuItem {
#[inline]
fn is_checked(&self) -> bool {
match self {
PopupMenuItem::Item { checked, .. } => *checked,
PopupMenuItem::ElementItem { checked, .. } => *checked,
_ => false,
}
matches!(self, PopupMenuItem::Item { checked: true, .. })
}
}
pub struct PopupMenu {
pub(crate) focus_handle: FocusHandle,
pub(crate) menu_items: Vec<PopupMenuItem>,
/// The focus handle of Entity to handle actions.
pub(crate) action_context: Option<FocusHandle>,
focus_handle: FocusHandle,
menu_items: Vec<PopupMenuItem>,
axis: Axis,
selected_index: Option<usize>,
min_width: Option<Pixels>,
max_width: Option<Pixels>,
max_height: Option<Pixels>,
bounds: Bounds<Pixels>,
size: Size,
check_side: Side,
/// The parent menu of this menu, if this is a submenu
parent_menu: Option<WeakEntity<Self>>,
scrollable: bool,
external_link_icon: bool,
scroll_handle: ScrollHandle,
/// This will update on render
submenu_anchor: (Anchor, Pixels),
_subscriptions: Vec<Subscription>,
}
impl PopupMenu {
pub(crate) fn new(cx: &mut App) -> Self {
Self {
focus_handle: cx.focus_handle(),
action_context: None,
parent_menu: None,
menu_items: Vec::new(),
selected_index: None,
axis: Axis::Vertical,
min_width: None,
max_width: None,
max_height: None,
check_side: Side::Left,
bounds: Bounds::default(),
scrollable: false,
scroll_handle: ScrollHandle::default(),
external_link_icon: true,
size: Size::default(),
submenu_anchor: (Anchor::TopLeft, Pixels::ZERO),
_subscriptions: vec![],
}
}
pub fn build(
pub(crate) fn build(
window: &mut Window,
cx: &mut App,
f: impl FnOnce(Self, &mut Window, &mut Context<PopupMenu>) -> Self,
@@ -302,45 +215,15 @@ impl PopupMenu {
cx.new(|cx| f(Self::new(cx), window, cx))
}
/// Set min width of the popup menu, default is 120px
pub fn min_w(mut self, width: impl Into<Pixels>) -> Self {
self.min_width = Some(width.into());
self
}
/// Set max height of the popup menu, default is half of the window height
pub fn max_h(mut self, height: impl Into<Pixels>) -> Self {
self.max_height = Some(height.into());
self
}
/// Set the axis of children to horizontal.
pub fn horizontal(mut self) -> Self {
self.axis = Axis::Horizontal;
self
}
/// Set the menu to be scrollable to show vertical scrollbar.
///
/// NOTE: If this is true, the sub-menus will cannot be support.
pub fn scrollable(mut self, scrollable: bool) -> Self {
self.scrollable = scrollable;
self
}
/// Add Menu Item
pub fn menu(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
self.menu_with_disabled(label, action, false)
}
/// Add Menu Item with disabled state
pub fn menu_with_disabled(
mut self,
label: impl Into<SharedString>,
action: Box<dyn Action>,
disabled: bool,
) -> Self {
self.add_menu_item(label, None, action, disabled, false);
pub fn menu(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
self.add_menu_item(label, None, action, false, false);
self
}
@@ -399,19 +282,7 @@ impl PopupMenu {
/// Add a Submenu
pub fn submenu(
self,
label: impl Into<SharedString>,
window: &mut Window,
cx: &mut Context<Self>,
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Self {
self.submenu_with_icon(None, label, window, cx, f)
}
/// Add a Submenu item with icon
pub fn submenu_with_icon(
mut self,
icon: Option<Icon>,
label: impl Into<SharedString>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -423,9 +294,7 @@ impl PopupMenu {
view.parent_menu = Some(parent_menu);
});
self.menu_items.push(
PopupMenuItem::submenu(label, submenu).when_some(icon, |this, icon| this.icon(icon)),
);
self.menu_items.push(PopupMenuItem::submenu(label, submenu));
self
}
@@ -436,13 +305,6 @@ impl PopupMenu {
self
}
/// Use small size, the menu item will have smaller height.
#[allow(dead_code)]
pub(crate) fn small(mut self) -> Self {
self.size = Size::Small;
self
}
fn add_menu_item(
&mut self,
label: impl Into<SharedString>,
@@ -461,7 +323,7 @@ impl PopupMenu {
self
}
pub(crate) fn active_submenu(&self) -> Option<Entity<PopupMenu>> {
fn active_submenu(&self) -> Option<Entity<PopupMenu>> {
if let Some(ix) = self.selected_index
&& let Some(item) = self.menu_items.get(ix)
{
@@ -489,52 +351,24 @@ impl PopupMenu {
}
fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
if let Some(index) = self.selected_index {
let item = self.menu_items.get(index);
match item {
Some(PopupMenuItem::Item {
handler, action, ..
}) => {
if let Some(handler) = handler {
handler(&ClickEvent::default(), window, cx);
} else if let Some(action) = action.as_ref() {
self.dispatch_confirm_action(action.as_ref(), window, cx);
}
self.dismiss(&Cancel, window, cx)
}
Some(PopupMenuItem::ElementItem {
handler, action, ..
}) => {
if let Some(handler) = handler {
handler(&ClickEvent::default(), window, cx);
} else if let Some(action) = action.as_ref() {
self.dispatch_confirm_action(action.as_ref(), window, cx);
}
self.dismiss(&Cancel, window, cx)
}
_ => {}
if let Some(index) = self.selected_index
&& let Some(PopupMenuItem::Item {
handler, action, ..
}) = self.menu_items.get(index)
{
if let Some(handler) = handler {
handler(&ClickEvent::default(), window, cx);
} else if let Some(action) = action.as_ref() {
window.dispatch_action(action.boxed_clone(), cx);
}
}
}
fn dispatch_confirm_action(
&self,
action: &dyn Action,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(context) = self.action_context.as_ref() {
context.focus(window, cx);
self.dismiss(&Cancel, window, cx)
}
window.dispatch_action(action.boxed_clone(), cx);
}
fn set_selected_index(&mut self, ix: usize, cx: &mut Context<Self>) {
if self.selected_index != Some(ix) {
self.selected_index = Some(ix);
self.scroll_handle.scroll_to_item(ix);
cx.notify();
}
}
@@ -675,6 +509,7 @@ impl PopupMenu {
}
}
#[expect(clippy::only_used_in_recursion)]
fn dismiss(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
if self.active_submenu().is_some() {
return;
@@ -682,20 +517,17 @@ impl PopupMenu {
cx.emit(DismissEvent);
// Focus back to the previous focused handle.
if let Some(action_context) = self.action_context.as_ref() {
window.focus(action_context, cx);
}
let Some(parent_menu) = self.parent_menu.clone() else {
return;
};
// Dismiss parent menu, when this menu is dismissed
_ = parent_menu.update(cx, |view, cx| {
if let Err(error) = parent_menu.update(cx, |view, cx| {
view.selected_index = None;
view.dismiss(&Cancel, window, cx);
});
}) {
log::warn!("Failed to dismiss parent menu: {error}");
}
}
fn handle_dismiss(
@@ -732,16 +564,7 @@ impl PopupMenu {
) -> Option<Kbd> {
let action = action?;
match self
.action_context
.as_ref()
.and_then(|handle| Kbd::binding_for_action_in(action.as_ref(), handle, window))
{
Some(kbd) => Some(kbd),
// Fallback to App level key binding
None => Kbd::binding_for_action(action.as_ref(), None, window),
}
.map(|this| {
Kbd::binding_for_action(action.as_ref(), None, window).map(|this| {
this.p_0()
.flex_nowrap()
.border_0()
@@ -771,16 +594,10 @@ impl PopupMenu {
Some(icon)
}
#[inline]
fn max_width(&self) -> Pixels {
self.max_width.unwrap_or(px(500.))
}
/// Calculate the anchor corner and left offset for child submenu
fn update_submenu_menu_anchor(&mut self, window: &Window) {
let bounds = self.bounds;
let max_width = self.max_width();
let (anchor, left) = if max_width + bounds.origin.x > window.bounds().size.width {
let (anchor, left) = if MAX_WIDTH + bounds.origin.x > window.bounds().size.width {
(Anchor::TopRight, -px(16.))
} else {
(Anchor::TopLeft, bounds.size.width - px(8.))
@@ -803,12 +620,7 @@ impl PopupMenu {
cx: &mut Context<Self>,
) -> MenuItemElement {
let has_left_icon = options.has_left_icon;
let is_left_check = options.check_side.is_left() && item.is_checked();
let right_check_icon = if options.check_side.is_right() && item.is_checked() {
Some(Icon::new(IconName::Check).xsmall())
} else {
None
};
let is_checked = item.is_checked();
let selected = self.selected_index == Some(ix);
const EDGE_PADDING: Pixels = px(4.);
@@ -817,17 +629,14 @@ impl PopupMenu {
let is_submenu = matches!(item, PopupMenuItem::Submenu { .. });
let group_name = format!("{}:item-{}", cx.entity().entity_id(), ix);
let (item_height, radius) = match self.size {
Size::Small => (px(20.), options.radius.half()),
_ => (px(26.), options.radius),
};
let item_height = px(26.);
let this = MenuItemElement::new(ix, &group_name)
.relative()
.text_sm()
.py_0()
.px(INNER_PADDING)
.rounded(radius)
.rounded(options.radius)
.items_center()
.selected(selected)
.on_hover(cx.listener(move |this, hovered, _, cx| {
@@ -860,33 +669,13 @@ impl PopupMenu {
.child(label.clone()),
),
),
PopupMenuItem::ElementItem {
render, disabled, ..
} => this
.when(!disabled, |this| {
this.on_click(
cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)),
)
})
.disabled(*disabled)
.child(
h_flex()
.flex_1()
.min_h(item_height)
.items_center()
.gap_x_2()
.child((render)(window, cx))
.children(right_check_icon.map(|icon| icon.ml_3())),
),
PopupMenuItem::Item {
icon,
label,
action,
disabled,
is_link,
..
} => {
let show_link_icon = *is_link && self.external_link_icon;
let action = action.as_ref().map(|action| action.boxed_clone());
let key = self.render_key_binding(action, window, cx);
@@ -900,7 +689,7 @@ impl PopupMenu {
.gap_x_2()
.children(Self::render_icon(
has_left_icon,
is_left_check,
is_checked,
icon.clone(),
window,
cx,
@@ -911,22 +700,7 @@ impl PopupMenu {
.gap_3()
.items_center()
.justify_between()
.when(!show_link_icon, |this| this.child(label.clone()))
.children(right_check_icon)
.when(show_link_icon, |this| {
this.child(
h_flex()
.w_full()
.justify_between()
.gap_2()
.child(label.clone())
.child(
Icon::new(IconName::Link)
.xsmall()
.text_color(cx.theme().text_muted),
),
)
})
.child(label.clone())
.children(key),
)
}
@@ -1002,7 +776,6 @@ impl Focusable for PopupMenu {
#[derive(Clone, Copy)]
struct RenderOptions {
has_left_icon: bool,
check_side: Side,
radius: Pixels,
}
@@ -1013,20 +786,10 @@ impl Render for PopupMenu {
let view = cx.entity().clone();
let items_count = self.menu_items.len();
let max_width = self.max_width();
let max_height = self.max_height.unwrap_or_else(|| {
let window_half_height = window.window_bounds().get_bounds().size.height * 0.5;
window_half_height.min(px(450.))
});
let has_left_icon = self
.menu_items
.iter()
.any(|item| item.has_left_icon(self.check_side));
let has_left_icon = self.menu_items.iter().any(|item| item.has_left_icon());
let options = RenderOptions {
has_left_icon,
check_side: self.check_side,
radius: cx.theme().radius.min(px(8.)),
};
@@ -1051,17 +814,11 @@ impl Render for PopupMenu {
.p_1()
.gap_y_0p5()
.min_w(rems(8.))
.when_some(self.min_width, |this, min_width| this.min_w(min_width))
.max_w(max_width)
.max_w(MAX_WIDTH)
.map(|this| match self.axis {
Axis::Horizontal => this.flex().flex_row().items_center(),
Axis::Vertical => this.flex().flex_col(),
})
.when(self.scrollable, |this| {
this.max_h(max_height)
.overflow_y_scroll()
.track_scroll(&self.scroll_handle)
})
.children(
self.menu_items
.iter()
@@ -1072,9 +829,5 @@ impl Render for PopupMenu {
)
.on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds)),
)
.when(self.scrollable, |this| {
// TODO: When the menu is limited by `overflow_y_scroll`, the sub-menu will cannot be displayed.
this.vertical_scrollbar(&self.scroll_handle)
})
}
}
-11
View File
@@ -225,7 +225,6 @@ pub struct WelcomeMessage {
icon: Option<AnyElement>,
title: Option<SharedString>,
message: Option<SharedString>,
children: SmallVec<[AnyElement; 2]>,
}
impl WelcomeMessage {
@@ -237,7 +236,6 @@ impl WelcomeMessage {
icon: None,
title: None,
message: None,
children: SmallVec::new(),
}
}
@@ -261,13 +259,6 @@ impl WelcomeMessage {
self.message = Some(message.into());
self
}
/// Append an element below the body text.
#[must_use]
pub fn child(mut self, child: impl IntoElement) -> Self {
self.children.push(child.into_any_element());
self
}
}
impl Styled for WelcomeMessage {
@@ -284,7 +275,6 @@ impl RenderOnce for WelcomeMessage {
icon,
title,
message,
children,
} = self;
v_flex()
@@ -320,6 +310,5 @@ impl RenderOnce for WelcomeMessage {
)
}),
)
.children(children)
}
}
+1 -2
View File
@@ -44,8 +44,7 @@ impl Nav {
}
}
/// Marks the row as clickable even when it has no `on_click` handler, so it
/// still shows the hover affordance. Used by rows whose click opens a menu.
/// Marks the row as clickable even without an `on_click` handler, keeping the hover affordance.
pub fn clickable(mut self, clickable: bool) -> Self {
self.clickable = clickable;
self
+1 -10
View File
@@ -21,7 +21,6 @@ pub struct NavItem {
label: SharedString,
suffix: Option<AnyElement>,
selected: bool,
clickable: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
}
@@ -43,18 +42,10 @@ impl NavItem {
label: label.into(),
suffix: None,
selected: false,
clickable: false,
on_click: None,
}
}
/// Marks the item as clickable even when it has no `on_click` handler, so it
/// still shows the hover affordance. Used by items whose click opens a menu.
pub fn clickable(mut self, clickable: bool) -> Self {
self.clickable = clickable;
self
}
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
self.suffix = Some(suffix.into_any_element());
self
@@ -94,7 +85,7 @@ impl Styled for NavItem {
impl RenderOnce for NavItem {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let clickable = self.on_click.is_some() || self.clickable;
let clickable = self.on_click.is_some();
h_flex()
.id(self.id)
+20 -61
View File
@@ -40,20 +40,13 @@ pub enum NotificationKind {
impl NotificationKind {
fn icon(&self, cx: &App) -> Icon {
match self {
Self::Info => Icon::new(IconName::Info)
.with_size(Size::Medium)
.text_color(cx.theme().icon),
Self::Success => Icon::new(IconName::CheckCircle)
.with_size(Size::Medium)
.text_color(cx.theme().icon_accent),
Self::Warning => Icon::new(IconName::Warning)
.with_size(Size::Medium)
.text_color(cx.theme().text_warning),
Self::Error => Icon::new(IconName::CloseCircle)
.with_size(Size::Medium)
.text_color(cx.theme().danger_foreground),
}
let (name, color) = match self {
Self::Info => (IconName::Info, cx.theme().icon),
Self::Success => (IconName::CheckCircle, cx.theme().icon_accent),
Self::Warning => (IconName::Warning, cx.theme().text_warning),
Self::Error => (IconName::CloseCircle, cx.theme().danger_foreground),
};
Icon::new(name).with_size(Size::Medium).text_color(color)
}
}
@@ -78,10 +71,7 @@ impl From<(TypeId, ElementId)> for NotificationId {
#[allow(clippy::type_complexity)]
/// A notification element.
pub struct Notification {
/// The id is used make the notification unique.
/// Then you push a notification with the same id, the previous notification will be replaced.
///
/// None means the notification will be added to the end of the list.
/// Pushing a notification with the same id replaces the previous one.
id: NotificationId,
style: StyleRefinement,
kind: Option<NotificationKind>,
@@ -128,9 +118,7 @@ impl From<(NotificationKind, SharedString)> for Notification {
struct DefaultIdType;
impl Notification {
/// Create a new notification.
///
/// The default id is a random UUID.
/// Creates a notification with a random UUID id.
pub fn new() -> Self {
let id: SharedString = uuid::Uuid::new_v4().to_string().into();
let id = (TypeId::of::<DefaultIdType>(), id.into());
@@ -177,12 +165,7 @@ impl Notification {
.with_kind(NotificationKind::Error)
}
/// Set the type for unique identification of the notification.
///
/// ```rs
/// struct MyNotificationKind;
/// let notification = Notification::new("Hello").id::<MyNotificationKind>();
/// ```
/// Sets the type used to uniquely identify the notification.
pub fn id<T: Sized + 'static>(mut self) -> Self {
self.id = TypeId::of::<T>().into();
self
@@ -194,15 +177,13 @@ impl Notification {
self
}
/// Set the title of the notification, default is None.
///
/// If title is None, the notification will not have a title.
/// Sets the title of the notification, default is None.
pub fn title(mut self, title: impl Into<SharedString>) -> Self {
self.title = Some(title.into());
self
}
/// Set the type of the notification, default is NotificationType::Info.
/// Sets the kind of the notification, default is `NotificationKind::Info`.
pub fn with_kind(mut self, kind: NotificationKind) -> Self {
self.kind = Some(kind);
self
@@ -214,9 +195,7 @@ impl Notification {
self
}
/// Set the action button of the notification.
///
/// When an action is set, the notification will not autohide.
/// Sets an action button; the notification will not autohide when set.
pub fn action<F>(mut self, action: F) -> Self
where
F: Fn(&mut Self, &mut Window, &mut Context<Self>) -> Button + 'static,
@@ -226,8 +205,8 @@ impl Notification {
self
}
/// Dismiss the notification.
pub fn dismiss(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
/// Emits a request for the notification list to dismiss this notification.
pub(crate) fn dismiss(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissRequest);
}
@@ -401,10 +380,7 @@ impl Render for Notification {
move |this, delta| {
if closing {
let opacity = 1. - delta;
let that = this
.shadow_none()
.opacity(opacity)
.when(opacity < 0.85, |this| this.shadow_none());
let that = this.shadow_none().opacity(opacity);
match placement {
Anchor::TopRight | Anchor::BottomRight => {
let x_offset = px(0.) + delta * px(45.);
@@ -446,8 +422,7 @@ impl Render for Notification {
/// A list of notifications.
pub(crate) struct NotificationList {
/// Notifications that will be auto hidden.
pub(crate) notifications: ToastManager<NotificationId, Entity<Notification>>,
notifications: ToastManager<NotificationId, Entity<Notification>>,
/// Measured geometry and interaction state of the visible stack.
stack_state: ToastStackState,
@@ -455,12 +430,11 @@ pub(crate) struct NotificationList {
/// Whether the lifecycle clock is running. The loop clears it as it exits.
is_advancing: bool,
/// Subscriptions
_subscriptions: HashMap<NotificationId, Subscription>,
}
impl NotificationList {
pub fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
pub(crate) fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
Self {
notifications: ToastManager::new(ToastMotion::default()),
stack_state: ToastStackState::default(),
@@ -469,10 +443,7 @@ impl NotificationList {
}
}
/// Tick the toast lifecycle until the last notification is unmounted.
///
/// The stack expansion is sampled here because it reaches the list through
/// no event, and an idle window should arm no timer.
/// Ticks the toast lifecycle, including stack expansion which arrives through no event.
fn start_advancing(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.is_advancing {
return;
@@ -520,7 +491,7 @@ impl NotificationList {
}
}
pub fn push(
pub(crate) fn push(
&mut self,
notification: impl Into<Notification>,
window: &mut Window,
@@ -575,18 +546,6 @@ impl NotificationList {
}
cx.notify();
}
pub fn clear(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
for id in self
.notifications
.dismiss_all(cx.background_executor().now())
{
if let Some(note) = self.notifications.get(&id) {
note.update(cx, |note, cx| note.begin_close(cx));
}
}
cx.notify();
}
}
impl Render for NotificationList {
+34 -52
View File
@@ -6,13 +6,13 @@ use gpui::{
MouseButton, ParentElement, RenderOnce, Stateful, StyleRefinement, Styled, Window,
};
use gpui_base::Popover as BasePopover;
pub use gpui_base::PopoverState;
pub(crate) use gpui_base::PopoverState;
use crate::{Selectable, StyledExt as _, v_flex};
/// A popover element that can be triggered by a button or any other element.
#[derive(IntoElement)]
pub struct Popover {
pub(crate) struct Popover {
id: ElementId,
style: StyleRefinement,
anchor: Anchor,
@@ -26,8 +26,7 @@ pub struct Popover {
>,
>,
children: Vec<AnyElement>,
/// Style for trigger element.
/// This is used for hotfix the trigger element style to support w_full.
/// Style refinement applied to the trigger element.
trigger_style: Option<StyleRefinement>,
mouse_button: MouseButton,
appearance: bool,
@@ -35,8 +34,8 @@ pub struct Popover {
}
impl Popover {
/// Create a new Popover with `view` mode.
pub fn new(id: impl Into<ElementId>) -> Self {
/// Creates a new Popover with the given id.
pub(crate) fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
style: StyleRefinement::default(),
@@ -51,23 +50,20 @@ impl Popover {
}
}
/// Set the anchor corner of the popover, default is `Corner::TopLeft`.
///
/// This method is kept for backward compatibility with `Corner` type.
/// Internally, it converts `Corner` to `Anchor`.
pub fn anchor(mut self, anchor: impl Into<Anchor>) -> Self {
/// Sets the anchor corner of the popover, default is `Anchor::TopLeft`.
pub(crate) fn anchor(mut self, anchor: impl Into<Anchor>) -> Self {
self.anchor = anchor.into();
self
}
/// Set the mouse button to trigger the popover, default is `MouseButton::Left`.
pub fn mouse_button(mut self, mouse_button: MouseButton) -> Self {
/// Sets the mouse button that triggers the popover, default is `MouseButton::Left`.
pub(crate) fn mouse_button(mut self, mouse_button: MouseButton) -> Self {
self.mouse_button = mouse_button;
self
}
/// Set the trigger element of the popover.
pub fn trigger<T>(mut self, trigger: T) -> Self
/// Sets the trigger element, marked as selected while the popover is open.
pub(crate) fn trigger<T>(mut self, trigger: T) -> Self
where
T: Selectable + IntoElement + 'static,
{
@@ -78,12 +74,8 @@ impl Popover {
self
}
/// Set the trigger from a builder, for elements that have no selected state.
///
/// [`Self::trigger`] marks the trigger as selected while the popover is
/// open, so it cannot be used with elements whose selection carries a
/// different meaning, such as a row that indicates the current room.
pub fn trigger_with<F>(mut self, trigger: F) -> Self
/// Sets the trigger from a builder, for elements that have no selected state.
pub(crate) fn trigger_with<F>(mut self, trigger: F) -> Self
where
F: FnOnce(bool, &Window, &App) -> AnyElement + 'static,
{
@@ -91,23 +83,20 @@ impl Popover {
self
}
/// Set the style for the trigger element.
pub fn trigger_style(mut self, style: StyleRefinement) -> Self {
/// Sets the style for the trigger element.
pub(crate) fn trigger_style(mut self, style: StyleRefinement) -> Self {
self.trigger_style = Some(style);
self
}
/// Set whether clicking outside the popover will dismiss it, default is `true`.
pub fn overlay_closable(mut self, closable: bool) -> Self {
/// Sets whether clicking outside the popover dismisses it, default is `true`.
pub(crate) fn overlay_closable(mut self, closable: bool) -> Self {
self.overlay_closable = closable;
self
}
/// Set the content builder for content of the Popover.
///
/// This callback will called every time on render the popover.
/// So, you should avoid creating new elements or entities in the content closure.
pub fn content<F, E>(mut self, content: F) -> Self
/// Sets the content builder, called on every render of the popover.
pub(crate) fn content<F, E>(mut self, content: F) -> Self
where
E: IntoElement,
F: Fn(&mut PopoverState, &mut Window, &mut Context<PopoverState>) -> E + 'static,
@@ -118,32 +107,13 @@ impl Popover {
self
}
/// Set whether the popover no style, default is `false`.
///
/// If no style:
///
/// - The popover will not have a bg, border, shadow, or padding.
/// - The click out of the popover will not dismiss it.
pub fn appearance(mut self, appearance: bool) -> Self {
/// Sets whether the popover renders with default styling, default is `true`.
pub(crate) fn appearance(mut self, appearance: bool) -> Self {
self.appearance = appearance;
self
}
}
impl ParentElement for Popover {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl Styled for Popover {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl Popover {
pub(crate) fn render_popover_content(
fn render_popover_content(
anchor: Anchor,
appearance: bool,
_: &mut Window,
@@ -162,6 +132,18 @@ impl Popover {
}
}
impl ParentElement for Popover {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl Styled for Popover {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for Popover {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let anchor = self.anchor;
-7
View File
@@ -230,13 +230,6 @@ impl Root {
});
cx.notify();
}
/// Clear all notifications from the notification layer.
pub fn clear_notifications(&mut self, window: &mut Window, cx: &mut Context<'_, Root>) {
self.notification
.update(cx, |view, cx| view.clear(window, cx));
cx.notify();
}
}
impl Render for Root {
+1 -1
View File
@@ -1,4 +1,4 @@
mod scrollable;
pub use gpui_base::{Scrollbar, ScrollbarAxis, ScrollbarHandle};
pub use gpui_base::Scrollbar;
pub use scrollable::*;
+3 -77
View File
@@ -3,7 +3,7 @@ use std::panic::Location;
use gpui::prelude::FluentBuilder;
use gpui::{
App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,
ScrollHandle, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, px,
ScrollHandle, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, px,
};
use gpui_base::{Scrollbar, ScrollbarAxis, ScrollbarHandle};
use theme::ActiveTheme as _;
@@ -12,26 +12,6 @@ use crate::StyledExt as _;
/// A trait for elements that can be made scrollable with scrollbars.
pub trait ScrollableElement: InteractiveElement + Styled + ParentElement + Element {
/// Adds a scrollbar to the element.
#[track_caller]
fn scrollbar<H: ScrollbarHandle + Clone>(
self,
scroll_handle: &H,
axis: impl Into<ScrollbarAxis>,
) -> Self {
self.child(ScrollbarLayer {
id: "scrollbar_layer".into(),
axis: axis.into(),
scroll_handle: scroll_handle.clone(),
})
}
/// Adds a vertical scrollbar to the element.
#[track_caller]
fn vertical_scrollbar<H: ScrollbarHandle + Clone>(self, scroll_handle: &H) -> Self {
self.scrollbar(scroll_handle, ScrollbarAxis::Vertical)
}
/// Almost equivalent to [`StatefulInteractiveElement::overflow_y_scroll`], but adds Vertical scrollbar.
#[track_caller]
fn overflow_y_scrollbar(self) -> Scrollable<Self> {
@@ -39,8 +19,7 @@ pub trait ScrollableElement: InteractiveElement + Styled + ParentElement + Eleme
}
}
/// The scrollbar the application's theme describes: a 10px rail holding a 6px
/// rounded thumb that grows to 8px under the pointer.
/// The scrollbar the application's theme describes: a 10px rail holding a 6px rounded thumb that grows to 8px under the pointer.
fn scrollbar<H: ScrollbarHandle + Clone>(
scroll_handle: &H,
axis: ScrollbarAxis,
@@ -87,24 +66,6 @@ fn scrollbar<H: ScrollbarHandle + Clone>(
})
}
/// A scrollbar child that resolves the theme while rendering, which the
/// [`ScrollableElement`] helpers cannot do at the call site.
#[derive(IntoElement)]
struct ScrollbarLayer<H: ScrollbarHandle + Clone> {
id: ElementId,
axis: ScrollbarAxis,
scroll_handle: H,
}
impl<H> RenderOnce for ScrollbarLayer<H>
where
H: ScrollbarHandle + Clone + 'static,
{
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
scrollbar(&self.scroll_handle, self.axis, cx).id(self.id)
}
}
/// A scrollable element wrapper that adds scrollbars to an interactive element.
#[derive(IntoElement)]
pub struct Scrollable<E: InteractiveElement + Styled + ParentElement + Element> {
@@ -128,15 +89,6 @@ where
}
}
impl<E> Styled for Scrollable<E>
where
E: InteractiveElement + Styled + ParentElement + Element,
{
fn style(&mut self) -> &mut StyleRefinement {
self.element.style()
}
}
impl<E> ParentElement for Scrollable<E>
where
E: InteractiveElement + Styled + ParentElement + Element,
@@ -146,18 +98,6 @@ where
}
}
impl InteractiveElement for Scrollable<Div> {
fn interactivity(&mut self) -> &mut gpui::Interactivity {
self.element.interactivity()
}
}
impl InteractiveElement for Scrollable<Stateful<Div>> {
fn interactivity(&mut self) -> &mut gpui::Interactivity {
self.element.interactivity()
}
}
impl<E> RenderOnce for Scrollable<E>
where
E: InteractiveElement + Styled + ParentElement + Element + 'static,
@@ -167,8 +107,6 @@ where
.use_keyed_state(self.id.clone(), cx, |_, _| ScrollHandle::default())
.read(cx)
.clone();
// Inherit the size from the element style.
let style = StyleRefinement {
size: self.element.style().size.clone(),
..Default::default()
@@ -190,22 +128,10 @@ where
ScrollbarAxis::Horizontal => this.flex_row().overflow_x_scroll(),
ScrollbarAxis::Both => this.overflow_scroll(),
})
.child(
self.element
// Refine element size to `flex_1`.
.size_auto()
.flex_1(),
),
.child(self.element.size_auto().flex_1()),
)
.child(scrollbar(&scroll_handle, self.axis, cx).id("scrollbar"))
}
}
impl ScrollableElement for Div {}
impl<E> ScrollableElement for Stateful<E>
where
E: ParentElement + Styled + Element,
Self: InteractiveElement,
{
}
+2 -6
View File
@@ -1,6 +1,5 @@
use gpui::{App, Div, Pixels, Refineable, StyleRefinement, Styled, div, px};
pub use gpui_base::component_traits::{Disableable, Selectable};
use serde::{Deserialize, Serialize};
use theme::ActiveTheme;
/// Returns a `Div` as horizontal flex layout.
@@ -65,7 +64,7 @@ pub trait StyledExt: Styled + Sized {
impl<E: Styled> StyledExt for E {}
/// A size for elements.
#[derive(Clone, Default, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
#[derive(Clone, Default, Copy, PartialEq, Eq, Debug)]
pub enum Size {
Size(Pixels),
XSmall,
@@ -83,10 +82,7 @@ impl From<Pixels> for Size {
/// A trait for setting the size of an element.
pub trait Sizable: Sized {
/// Set the ui::Size of this element.
///
/// Also can receive a `ButtonSize` to convert to `IconSize`,
/// Or a `Pixels` to set a custom size: `px(30.)`
/// Set the element's [`Size`], or a custom pixel size (`px(30.)`).
fn with_size(self, size: impl Into<Size>) -> Self;
/// Set to Size::XSmall
+11 -48
View File
@@ -7,9 +7,7 @@ use gpui::{
div, px, white,
};
use gpui_base::{Spring, Switch as BaseSwitch, SwitchThumb, SwitchTrack, spring};
use theme::{ActiveTheme, Side};
use crate::{Disableable, Sizable, Size};
use theme::ActiveTheme;
type OnClick = Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>;
@@ -17,12 +15,9 @@ type OnClick = Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>;
pub struct Switch {
id: ElementId,
checked: bool,
disabled: bool,
label: Option<SharedString>,
description: Option<SharedString>,
label_side: Side,
on_click: OnClick,
size: Size,
}
impl Switch {
@@ -32,12 +27,9 @@ impl Switch {
Self {
id: id.clone(),
checked: false,
disabled: false,
label: None,
description: None,
on_click: None,
label_side: Side::Left,
size: Size::Medium,
}
}
@@ -65,45 +57,21 @@ impl Switch {
}
}
impl Sizable for Switch {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
}
impl Disableable for Switch {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl RenderOnce for Switch {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let checked = self.checked;
let on_click = self.on_click.clone();
let (bg, toggle_bg) = match checked {
true => (cx.theme().element_background, white()),
false => (cx.theme().elevated_surface_background, white()),
};
let (bg, toggle_bg) = match self.disabled {
true => (bg.opacity(0.3), toggle_bg.opacity(0.8)),
false => (bg, toggle_bg),
};
let (bg_width, bg_height) = match self.size {
Size::XSmall | Size::Small => (px(28.), px(16.)),
_ => (px(36.), px(20.)),
};
let bar_width = match self.size {
Size::XSmall | Size::Small => px(12.),
_ => px(16.),
let bg = if checked {
cx.theme().element_background
} else {
cx.theme().elevated_surface_background
};
let toggle_bg = white();
let bg_width = px(36.);
let bg_height = px(20.);
let bar_width = px(16.);
let inset = px(2.);
let thumb_left = spring(
@@ -124,14 +92,13 @@ impl RenderOnce for Switch {
div().child(
BaseSwitch::new(self.id.clone())
.checked(checked)
.disabled(self.disabled)
.when_some(accessibility_label, |this, label| {
this.accessibility_label(label)
})
.when_some(on_click, |this, on_click| {
this.on_change(move |next, _event, window, cx| on_click(&next, window, cx))
})
.when(self.label_side.is_left(), |this| this.flex_row_reverse())
.flex_row_reverse()
.child(
div()
.w_full()
@@ -140,14 +107,11 @@ impl RenderOnce for Switch {
.items_center()
.gap_4()
.when_some(label, |this, label| {
// Label
this.child(div().text_sm().text_color(cx.theme().text).child(label))
})
.child(
// Switch Bar
SwitchTrack::new((self.id.clone(), "track"))
.checked(checked)
.disabled(self.disabled)
.flex_shrink_0()
.w(bg_width)
.h(bg_height)
@@ -157,9 +121,8 @@ impl RenderOnce for Switch {
.border(inset)
.border_color(cx.theme().border_transparent)
.bg(bg)
.when(!self.disabled, |this| this.cursor_pointer())
.cursor_pointer()
.child(
// Switch Toggle
SwitchThumb::new(checked)
.rounded_full()
.when(cx.theme().shadow, |this| this.shadow_sm())
+16 -69
View File
@@ -3,11 +3,11 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder as _;
use gpui::{
AnyElement, App, ClickEvent, Div, InteractiveElement, IntoElement, MouseButton, ParentElement,
RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px,
RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div,
};
use theme::ActiveTheme;
use crate::{Icon, IconName, Selectable, h_flex};
use crate::{Selectable, h_flex};
pub mod tab_bar;
@@ -17,61 +17,27 @@ pub mod tab_bar;
pub struct Tab {
ix: usize,
base: Div,
pub(super) label: Option<SharedString>,
icon: Option<Icon>,
prefix: Option<AnyElement>,
pub(super) tab_bar_prefix: Option<bool>,
suffix: Option<AnyElement>,
label: Option<SharedString>,
children: Vec<AnyElement>,
pub(super) disabled: bool,
pub(super) selected: bool,
pub(super) segmented: bool,
tab_bar_prefix: Option<bool>,
suffix: Option<AnyElement>,
disabled: bool,
selected: bool,
segmented: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
}
impl From<&'static str> for Tab {
fn from(label: &'static str) -> Self {
Self::new().label(label)
}
}
impl From<String> for Tab {
fn from(label: String) -> Self {
Self::new().label(label)
}
}
impl From<SharedString> for Tab {
fn from(label: SharedString) -> Self {
Self::new().label(label)
}
}
impl From<Icon> for Tab {
fn from(icon: Icon) -> Self {
Self::default().icon(icon)
}
}
impl From<IconName> for Tab {
fn from(icon_name: IconName) -> Self {
Self::default().icon(Icon::new(icon_name))
}
}
impl Default for Tab {
fn default() -> Self {
Self {
ix: 0,
base: div(),
label: None,
icon: None,
tab_bar_prefix: None,
children: Vec::new(),
disabled: false,
selected: false,
segmented: false,
prefix: None,
suffix: None,
on_click: None,
}
@@ -90,32 +56,20 @@ impl Tab {
self
}
/// Set icon for the tab.
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
self.icon = Some(icon.into());
self
}
/// Set the left side of the tab
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
self.prefix = Some(prefix.into_any_element());
self
}
/// Set the right side of the tab
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
pub(crate) fn suffix(mut self, suffix: impl IntoElement) -> Self {
self.suffix = Some(suffix.into_any_element());
self
}
/// Set disabled state to the tab, default false.
pub fn disabled(mut self, disabled: bool) -> Self {
pub(crate) fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
/// Set the click handler for the tab.
pub fn on_click(
pub(crate) fn on_click(
mut self,
on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
@@ -179,8 +133,6 @@ impl RenderOnce for Tab {
ix,
base,
label,
icon,
prefix,
suffix,
children,
disabled,
@@ -206,15 +158,11 @@ impl RenderOnce for Tab {
.overflow_hidden()
.when(segmented, |this| this.justify_center().px_1())
.when(!segmented, |this| this.justify_start())
.map(|this| match icon {
Some(icon) => this.w(px(38.)).child(icon.size_4()),
None => this
.map(|this| match label {
Some(label) => this.child(label),
None => this,
})
.children(children),
});
.map(|this| match label {
Some(label) => this.child(label),
None => this,
})
.children(children);
base.id(ix)
.group("tab")
@@ -253,7 +201,6 @@ impl RenderOnce for Tab {
})
})
})
.when_some(prefix, |this, prefix| this.child(prefix))
.child(content)
.when_some(suffix, |this, suffix| {
this.child(
+6 -8
View File
@@ -52,25 +52,25 @@ impl TabBar {
}
/// Track the scroll of the TabBar.
pub fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
pub(crate) fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
self.scroll_handle = Some(scroll_handle.clone());
self
}
/// Set the prefix element of the TabBar
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
pub(crate) fn prefix(mut self, prefix: impl IntoElement) -> Self {
self.prefix = Some(prefix.into_any_element());
self
}
/// Set the suffix element of the TabBar
pub fn suffix(mut self, suffix: impl IntoElement) -> Self {
pub(crate) fn suffix(mut self, suffix: impl IntoElement) -> Self {
self.suffix = Some(suffix.into_any_element());
self
}
/// Add children of the TabBar.
pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Tab>>) -> Self {
pub(crate) fn children(mut self, children: impl IntoIterator<Item = impl Into<Tab>>) -> Self {
self.children.extend(children.into_iter().map(Into::into));
self
}
@@ -88,14 +88,12 @@ impl TabBar {
}
/// Set the last empty space element of the TabBar.
pub fn last_empty_space(mut self, last_empty_space: impl IntoElement) -> Self {
pub(crate) fn last_empty_space(mut self, last_empty_space: impl IntoElement) -> Self {
self.last_empty_space = last_empty_space.into_any_element();
self
}
/// Set the on_click callback of the TabBar, the first parameter is the index of the clicked tab.
///
/// When this is set, the children's on_click will be ignored.
/// Set the on_click callback of the TabBar; when set, the children's on_click is ignored.
pub fn on_click<F>(mut self, on_click: F) -> Self
where
F: Fn(&usize, &mut Window, &mut App) + 'static,
+16 -47
View File
@@ -1,31 +1,23 @@
use std::rc::Rc;
use gpui::prelude::FluentBuilder as _;
use gpui::{
App, ClickEvent, Context, Div, Hsla, InteractiveElement as _, IntoElement, MouseButton,
ParentElement as _, Pixels, Render, RenderOnce, Stateful, StatefulInteractiveElement as _,
Styled, Window, WindowControlArea, div, px,
App, Div, Hsla, InteractiveElement as _, IntoElement, MouseButton, ParentElement as _, Pixels,
RenderOnce, Stateful, StatefulInteractiveElement as _, Styled, Window, WindowControlArea, div,
px,
};
use theme::ActiveTheme;
use crate::{Icon, IconName, Sizable as _, h_flex};
pub const TITLE_BAR_HEIGHT: Pixels = px(34.);
const TITLE_BAR_HEIGHT: Pixels = px(34.);
pub const TRAFFIC_LIGHT_PADDING: f32 = 80.;
// The Windows control buttons have a fixed width of 35px.
//
// We don't need implementation the click event for the control buttons.
// If user clicked in the bounds, the window event will be triggered.
// Windows control buttons rely on the native window event, so no click handlers are needed.
#[derive(IntoElement, Clone)]
#[allow(clippy::type_complexity)]
enum ControlIcon {
Minimize,
Restore,
Maximize,
Close {
on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>,
},
Close,
}
impl ControlIcon {
@@ -41,9 +33,8 @@ impl ControlIcon {
Self::Maximize
}
#[allow(clippy::type_complexity)]
fn close(on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>) -> Self {
Self::Close { on_close_window }
fn close() -> Self {
Self::Close
}
fn id(&self) -> &'static str {
@@ -51,7 +42,7 @@ impl ControlIcon {
Self::Minimize => "minimize",
Self::Restore => "restore",
Self::Maximize => "maximize",
Self::Close { .. } => "close",
Self::Close => "close",
}
}
@@ -60,7 +51,7 @@ impl ControlIcon {
Self::Minimize => IconName::WindowMinimize,
Self::Restore => IconName::WindowRestore,
Self::Maximize => IconName::WindowMaximize,
Self::Close { .. } => IconName::WindowClose,
Self::Close => IconName::WindowClose,
}
}
@@ -68,12 +59,12 @@ impl ControlIcon {
match self {
Self::Minimize => WindowControlArea::Min,
Self::Restore | Self::Maximize => WindowControlArea::Max,
Self::Close { .. } => WindowControlArea::Close,
Self::Close => WindowControlArea::Close,
}
}
fn is_close(&self) -> bool {
matches!(self, Self::Close { .. })
matches!(self, Self::Close)
}
#[inline]
@@ -114,11 +105,6 @@ impl RenderOnce for ControlIcon {
let hover_bg = self.hover_bg(cx);
let active_bg = self.active_bg(cx);
let on_close_window = match &self {
ControlIcon::Close { on_close_window } => on_close_window.clone(),
_ => None,
};
div()
.id(self.id())
.flex()
@@ -144,13 +130,7 @@ impl RenderOnce for ControlIcon {
match icon {
Self::Minimize => window.minimize_window(),
Self::Restore | Self::Maximize => window.zoom_window(),
Self::Close { .. } => {
if let Some(f) = on_close_window.clone() {
f(&ClickEvent::default(), window, cx);
} else {
window.remove_window();
}
}
Self::Close => window.remove_window(),
}
})
})
@@ -159,15 +139,10 @@ impl RenderOnce for ControlIcon {
}
#[derive(IntoElement)]
#[allow(clippy::type_complexity)]
pub(crate) struct WindowControls {
on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>>>,
}
pub(crate) struct WindowControls;
pub(crate) fn window_controls() -> WindowControls {
WindowControls {
on_close_window: None,
}
WindowControls
}
pub fn title_bar_drag_handlers(
@@ -232,16 +207,10 @@ impl RenderOnce for WindowControls {
} else {
ControlIcon::maximize()
})
.child(ControlIcon::close(self.on_close_window))
.child(ControlIcon::close())
}
}
struct TitleBarState {
should_move: bool,
}
impl Render for TitleBarState {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
}
}
+14 -16
View File
@@ -1,7 +1,7 @@
use gpui::prelude::FluentBuilder;
use gpui::{
App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, Styled,
Window, div, relative,
Window, relative,
};
use gpui_base::Tooltip as BaseTooltip;
use theme::ActiveTheme;
@@ -18,20 +18,18 @@ impl Tooltip {
impl Render for Tooltip {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div().child(
BaseTooltip::new("tooltip")
.font_family(".SystemUIFont")
.m_3()
.p_1p5()
.border_1()
.border_color(cx.theme().border)
.bg(cx.theme().surface_background)
.when(cx.theme().shadow, |this| this.shadow_sm())
.rounded(cx.theme().radius)
.text_xs()
.text_color(cx.theme().text)
.line_height(relative(1.25))
.child(self.text.clone()),
)
BaseTooltip::new("tooltip")
.font_family(".SystemUIFont")
.m_3()
.p_1p5()
.border_1()
.border_color(cx.theme().border)
.bg(cx.theme().surface_background)
.when(cx.theme().shadow, |this| this.shadow_sm())
.rounded(cx.theme().radius)
.text_xs()
.text_color(cx.theme().text)
.line_height(relative(1.25))
.child(self.text.clone())
}
}
-10
View File
@@ -31,9 +31,6 @@ pub trait WindowExtension: Sized {
key: impl Into<ElementId>,
cx: &mut App,
);
/// Clear all notifications
fn clear_notifications(&mut self, cx: &mut App);
}
impl WindowExtension for Window {
@@ -90,11 +87,4 @@ impl WindowExtension for Window {
root.clear_notification_by_id::<T>(key, window, cx);
})
}
#[inline]
fn clear_notifications(&mut self, cx: &mut App) {
Root::update(self, cx, move |root, window, cx| {
root.clear_notifications(window, cx);
})
}
}