clean up components
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
|
||||
* Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
|
||||
* Do not write organizational or comments that summarize the code. Comments should only be written in order to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious.
|
||||
* Keep doc comments and comments simple, one line, straightforward, and unbloated.
|
||||
* Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files.
|
||||
* Prefer `impl` blocks over standalone functions: place functionality as methods or associated functions on the relevant type, extending the type's existing `impl` block instead of creating a duplicate one in another file.
|
||||
* Bind a global store handle to a local before using it (`let state = GlobalState::global(cx);` then `state.update(...)`) instead of chaining on `GlobalState::global(cx)` inline.
|
||||
* Avoid using functions that panic like `unwrap()`, instead use mechanisms like `?` to propagate errors.
|
||||
* Be careful with operations like indexing which may panic if the indexes are out of bounds.
|
||||
* Never silently discard errors with `let _ =` on fallible operations. Always handle errors appropriately:
|
||||
@@ -81,6 +84,8 @@ Both `cx.spawn` and `cx.background_spawn` return a `Task<R>`, which is a future
|
||||
|
||||
A task which doesn't do anything but provide a value can be created with `Task::ready(value)`.
|
||||
|
||||
Prefer keeping a task in a field over `.detach()` when the work belongs to a view or store. A detached task outlives the view that started it (for example, a repository panel the user has closed), while a task stored in a field is cancelled when that struct drops. A finished `Task` held in a container is not reaped by GPUI - it stays alive until its handle is dropped - so if the container can accumulate many runs, either drop the finished handles before pushing a new one, or hold a single `Option<Task<_>>` and replace it.
|
||||
|
||||
## Elements
|
||||
|
||||
The `Render` trait is used to render some state into an element tree that is laid out using flexbox layout. An `Entity<T>` where `T` implements `Render` is sometimes called a "view".
|
||||
|
||||
Generated
+1
-1
@@ -7958,7 +7958,7 @@ dependencies = [
|
||||
"gpui-base",
|
||||
"gpui-pre",
|
||||
"instant",
|
||||
"serde",
|
||||
"log",
|
||||
"smallvec",
|
||||
"theme",
|
||||
"uuid",
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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"))]
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
mod scrollable;
|
||||
|
||||
pub use gpui_base::{Scrollbar, ScrollbarAxis, ScrollbarHandle};
|
||||
pub use gpui_base::Scrollbar;
|
||||
pub use scrollable::*;
|
||||
|
||||
@@ -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,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user