fix clippy issues
This commit is contained in:
@@ -8,6 +8,8 @@ use gpui::{
|
|||||||
|
|
||||||
use crate::{h_flex, theme::ActiveTheme as _, v_flex, Icon, IconName, Sizable, Size};
|
use crate::{h_flex, theme::ActiveTheme as _, v_flex, Icon, IconName, Sizable, Size};
|
||||||
|
|
||||||
|
type OnToggleClick = Option<Arc<dyn Fn(&[usize], &mut WindowContext) + Send + Sync>>;
|
||||||
|
|
||||||
/// An AccordionGroup is a container for multiple Accordion elements.
|
/// An AccordionGroup is a container for multiple Accordion elements.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Accordion {
|
pub struct Accordion {
|
||||||
@@ -18,7 +20,7 @@ pub struct Accordion {
|
|||||||
bordered: bool,
|
bordered: bool,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
children: Vec<AccordionItem>,
|
children: Vec<AccordionItem>,
|
||||||
on_toggle_click: Option<Arc<dyn Fn(&[usize], &mut WindowContext) + Send + Sync>>,
|
on_toggle_click: OnToggleClick,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Accordion {
|
impl Accordion {
|
||||||
@@ -138,6 +140,8 @@ impl RenderOnce for Accordion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnToggle = Option<Arc<dyn Fn(&bool, &mut WindowContext)>>;
|
||||||
|
|
||||||
/// An Accordion is a vertically stacked list of items, each of which can be expanded to reveal the content associated with it.
|
/// An Accordion is a vertically stacked list of items, each of which can be expanded to reveal the content associated with it.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct AccordionItem {
|
pub struct AccordionItem {
|
||||||
@@ -148,7 +152,7 @@ pub struct AccordionItem {
|
|||||||
size: Size,
|
size: Size,
|
||||||
bordered: bool,
|
bordered: bool,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
on_toggle_click: Option<Arc<dyn Fn(&bool, &mut WindowContext)>>,
|
on_toggle_click: OnToggle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccordionItem {
|
impl AccordionItem {
|
||||||
@@ -204,6 +208,12 @@ impl AccordionItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for AccordionItem {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Sizable for AccordionItem {
|
impl Sizable for AccordionItem {
|
||||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||||
self.size = size.into();
|
self.size = size.into();
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 {
|
|||||||
|
|
||||||
// The Bezier curve function for x and y, where x0 = 0, y0 = 0, x3 = 1, y3 = 1
|
// The Bezier curve function for x and y, where x0 = 0, y0 = 0, x3 = 1, y3 = 1
|
||||||
let _x = 3.0 * x1 * one_t2 * t + 3.0 * x2 * one_t * t2 + t3;
|
let _x = 3.0 * x1 * one_t2 * t + 3.0 * x2 * one_t * t2 + t3;
|
||||||
let y = 3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3;
|
|
||||||
|
|
||||||
y
|
3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ pub struct Breadcrumb {
|
|||||||
items: Vec<BreadcrumbItem>,
|
items: Vec<BreadcrumbItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnClick = Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>;
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct BreadcrumbItem {
|
pub struct BreadcrumbItem {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
text: SharedString,
|
text: SharedString,
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
on_click: OnClick,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
is_last: bool,
|
is_last: bool,
|
||||||
}
|
}
|
||||||
@@ -46,6 +48,7 @@ impl BreadcrumbItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// For internal use only.
|
/// For internal use only.
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
fn is_last(mut self, is_last: bool) -> Self {
|
fn is_last(mut self, is_last: bool) -> Self {
|
||||||
self.is_last = is_last;
|
self.is_last = is_last;
|
||||||
self
|
self
|
||||||
@@ -84,6 +87,12 @@ impl Breadcrumb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Breadcrumb {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
struct BreadcrumbSeparator;
|
struct BreadcrumbSeparator;
|
||||||
impl RenderOnce for BreadcrumbSeparator {
|
impl RenderOnce for BreadcrumbSeparator {
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ impl ButtonVariant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>;
|
||||||
|
|
||||||
/// A Button element.
|
/// A Button element.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
@@ -167,7 +169,7 @@ pub struct Button {
|
|||||||
size: Size,
|
size: Size,
|
||||||
compact: bool,
|
compact: bool,
|
||||||
tooltip: Option<SharedString>,
|
tooltip: Option<SharedString>,
|
||||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_click: OnClick,
|
||||||
pub(crate) stop_propagation: bool,
|
pub(crate) stop_propagation: bool,
|
||||||
loading: bool,
|
loading: bool,
|
||||||
loading_icon: Option<Icon>,
|
loading_icon: Option<Icon>,
|
||||||
@@ -509,10 +511,7 @@ impl ButtonVariant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn underline(&self, _: &WindowContext) -> bool {
|
fn underline(&self, _: &WindowContext) -> bool {
|
||||||
match self {
|
matches!(self, ButtonVariant::Link)
|
||||||
ButtonVariant::Link => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shadow(&self, _: &WindowContext) -> bool {
|
fn shadow(&self, _: &WindowContext) -> bool {
|
||||||
@@ -576,7 +575,11 @@ impl ButtonVariant {
|
|||||||
let bg = match self {
|
let bg = match self {
|
||||||
ButtonVariant::Primary => cx.theme().primary_active,
|
ButtonVariant::Primary => cx.theme().primary_active,
|
||||||
ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => {
|
ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => {
|
||||||
cx.theme().secondary_active
|
if cx.theme().mode.is_dark() {
|
||||||
|
cx.theme().secondary.lighten(0.2).opacity(0.8)
|
||||||
|
} else {
|
||||||
|
cx.theme().secondary.darken(0.2).opacity(0.8)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ButtonVariant::Danger => cx.theme().destructive_active,
|
ButtonVariant::Danger => cx.theme().destructive_active,
|
||||||
ButtonVariant::Link => cx.theme().transparent,
|
ButtonVariant::Link => cx.theme().transparent,
|
||||||
@@ -605,7 +608,11 @@ impl ButtonVariant {
|
|||||||
let bg = match self {
|
let bg = match self {
|
||||||
ButtonVariant::Primary => cx.theme().primary_active,
|
ButtonVariant::Primary => cx.theme().primary_active,
|
||||||
ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => {
|
ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => {
|
||||||
cx.theme().secondary_active
|
if cx.theme().mode.is_dark() {
|
||||||
|
cx.theme().secondary.lighten(0.2).opacity(0.8)
|
||||||
|
} else {
|
||||||
|
cx.theme().secondary.darken(0.2).opacity(0.8)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ButtonVariant::Danger => cx.theme().destructive_active,
|
ButtonVariant::Danger => cx.theme().destructive_active,
|
||||||
ButtonVariant::Link => cx.theme().transparent,
|
ButtonVariant::Link => cx.theme().transparent,
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ use crate::{
|
|||||||
Disableable, Sizable, Size,
|
Disableable, Sizable, Size,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OnClick = Option<Box<dyn Fn(&Vec<usize>, &mut WindowContext) + 'static>>;
|
||||||
|
|
||||||
/// A ButtonGroup element, to wrap multiple buttons in a group.
|
/// A ButtonGroup element, to wrap multiple buttons in a group.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ButtonGroup {
|
pub struct ButtonGroup {
|
||||||
@@ -23,7 +25,7 @@ pub struct ButtonGroup {
|
|||||||
variant: Option<ButtonVariant>,
|
variant: Option<ButtonVariant>,
|
||||||
size: Option<Size>,
|
size: Option<Size>,
|
||||||
|
|
||||||
on_click: Option<Box<dyn Fn(&Vec<usize>, &mut WindowContext) + 'static>>,
|
on_click: OnClick,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Disableable for ButtonGroup {
|
impl Disableable for ButtonGroup {
|
||||||
@@ -118,7 +120,8 @@ impl RenderOnce for ButtonGroup {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(child_index, child)| {
|
.map(|(child_index, child)| {
|
||||||
let state = Rc::clone(&state);
|
let state = Rc::clone(&state);
|
||||||
let child = if children_len == 1 {
|
|
||||||
|
if children_len == 1 {
|
||||||
child
|
child
|
||||||
} else if child_index == 0 {
|
} else if child_index == 0 {
|
||||||
// First
|
// First
|
||||||
@@ -167,9 +170,7 @@ impl RenderOnce for ButtonGroup {
|
|||||||
.when_some(self.compact, |this, _| this.compact())
|
.when_some(self.compact, |this, _| this.compact())
|
||||||
.on_click(move |_, _| {
|
.on_click(move |_, _| {
|
||||||
state.set(Some(child_index));
|
state.set(Some(child_index));
|
||||||
});
|
})
|
||||||
|
|
||||||
child
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.when_some(
|
.when_some(
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use gpui::{
|
|||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OnClick = Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>;
|
||||||
|
|
||||||
/// A Checkbox element.
|
/// A Checkbox element.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Checkbox {
|
pub struct Checkbox {
|
||||||
@@ -12,7 +14,7 @@ pub struct Checkbox {
|
|||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
on_click: Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>,
|
on_click: OnClick,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Checkbox {
|
impl Checkbox {
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ use crate::{
|
|||||||
h_flex, IconName, Sizable as _,
|
h_flex, IconName, Sizable as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ContentBuilder = Option<Box<dyn Fn(&mut WindowContext) -> AnyElement>>;
|
||||||
|
type CopiedCallback = Option<Rc<dyn Fn(SharedString, &mut WindowContext)>>;
|
||||||
|
|
||||||
pub struct Clipboard {
|
pub struct Clipboard {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
value: SharedString,
|
value: SharedString,
|
||||||
content_builder: Option<Box<dyn Fn(&mut WindowContext) -> AnyElement>>,
|
content_builder: ContentBuilder,
|
||||||
copied_callback: Option<Rc<dyn Fn(SharedString, &mut WindowContext)>>,
|
copied_callback: CopiedCallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clipboard {
|
impl Clipboard {
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ pub trait ContextMenuExt: ParentElement + Sized {
|
|||||||
impl<E> ContextMenuExt for Stateful<E> where E: ParentElement {}
|
impl<E> ContextMenuExt for Stateful<E> where E: ParentElement {}
|
||||||
impl<E> ContextMenuExt for Focusable<E> where E: ParentElement {}
|
impl<E> ContextMenuExt for Focusable<E> where E: ParentElement {}
|
||||||
|
|
||||||
|
type Menu<M> = Option<Box<dyn Fn(PopupMenu, &mut ViewContext<M>) -> PopupMenu + 'static>>;
|
||||||
|
|
||||||
/// A context menu that can be shown on right-click.
|
/// A context menu that can be shown on right-click.
|
||||||
pub struct ContextMenu {
|
pub struct ContextMenu {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
menu: Option<Box<dyn Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static>>,
|
menu: Menu<PopupMenu>,
|
||||||
anchor: AnchorCorner,
|
anchor: AnchorCorner,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,13 +101,17 @@ impl Element for ContextMenu {
|
|||||||
id: Option<&gpui::GlobalElementId>,
|
id: Option<&gpui::GlobalElementId>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||||
let mut style = Style::default();
|
|
||||||
// Set the layout style relative to the table view to get same size.
|
// Set the layout style relative to the table view to get same size.
|
||||||
style.position = Position::Absolute;
|
let style = Style {
|
||||||
style.flex_grow = 1.0;
|
position: Position::Absolute,
|
||||||
style.flex_shrink = 1.0;
|
flex_grow: 1.0,
|
||||||
style.size.width = relative(1.).into();
|
flex_shrink: 1.0,
|
||||||
style.size.height = relative(1.).into();
|
size: gpui::Size {
|
||||||
|
width: relative(1.).into(),
|
||||||
|
height: relative(1.).into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let anchor = self.anchor;
|
let anchor = self.anchor;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod dock;
|
mod dock;
|
||||||
mod invalid_panel;
|
mod invalid_panel;
|
||||||
mod panel;
|
mod panel;
|
||||||
@@ -6,7 +7,7 @@ mod state;
|
|||||||
mod tab_panel;
|
mod tab_panel;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
pub use dock::*;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds,
|
actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds,
|
||||||
Edges, Entity as _, EntityId, EventEmitter, InteractiveElement as _, IntoElement,
|
Edges, Entity as _, EntityId, EventEmitter, InteractiveElement as _, IntoElement,
|
||||||
@@ -15,11 +16,14 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub use dock::*;
|
||||||
pub use panel::*;
|
pub use panel::*;
|
||||||
pub use stack_panel::*;
|
pub use stack_panel::*;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
pub use tab_panel::*;
|
pub use tab_panel::*;
|
||||||
|
|
||||||
|
use crate::theme::ActiveTheme;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.set_global(PanelRegistry::new());
|
cx.set_global(PanelRegistry::new());
|
||||||
}
|
}
|
||||||
@@ -232,7 +236,7 @@ impl DockItem {
|
|||||||
}
|
}
|
||||||
Self::Split { view, items, .. } => {
|
Self::Split { view, items, .. } => {
|
||||||
// Iter items to add panel to the first tabs
|
// Iter items to add panel to the first tabs
|
||||||
for item in items.into_iter() {
|
for item in items.iter_mut() {
|
||||||
if let DockItem::Tabs { view, .. } = item {
|
if let DockItem::Tabs { view, .. } = item {
|
||||||
view.update(cx, |tab_panel, cx| {
|
view.update(cx, |tab_panel, cx| {
|
||||||
tab_panel.add_panel(panel.clone(), cx);
|
tab_panel.add_panel(panel.clone(), cx);
|
||||||
@@ -636,12 +640,12 @@ impl DockArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self._subscriptions
|
self._subscriptions
|
||||||
.push(cx.subscribe(view, move |_, _, event, cx| match event {
|
.push(cx.subscribe(view, move |_, _, event, cx| {
|
||||||
PanelEvent::LayoutChanged => {
|
if let PanelEvent::LayoutChanged = event {
|
||||||
let dock_area = cx.view().clone();
|
let dock_area = cx.view().clone();
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
let _ = cx.update(|cx| {
|
let _ = cx.update(|cx| {
|
||||||
let _ = dock_area.update(cx, |view, cx| {
|
dock_area.update(cx, |view, cx| {
|
||||||
view.update_toggle_button_tab_panels(cx)
|
view.update_toggle_button_tab_panels(cx)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -649,7 +653,6 @@ impl DockArea {
|
|||||||
.detach();
|
.detach();
|
||||||
cx.emit(DockEvent::LayoutChanged);
|
cx.emit(DockEvent::LayoutChanged);
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
DockItem::Tabs { .. } => {
|
DockItem::Tabs { .. } => {
|
||||||
@@ -673,7 +676,7 @@ impl DockArea {
|
|||||||
let panel = panel.clone();
|
let panel = panel.clone();
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
let _ = cx.update(|cx| {
|
let _ = cx.update(|cx| {
|
||||||
let _ = dock_area.update(cx, |dock, cx| {
|
dock_area.update(cx, |dock, cx| {
|
||||||
dock.set_zoomed_in(panel, cx);
|
dock.set_zoomed_in(panel, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
@@ -685,7 +688,7 @@ impl DockArea {
|
|||||||
let dock_area = cx.view().clone();
|
let dock_area = cx.view().clone();
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
let _ = cx.update(|cx| {
|
let _ = cx.update(|cx| {
|
||||||
let _ = dock_area.update(cx, |view, cx| view.set_zoomed_out(cx));
|
dock_area.update(cx, |view, cx| view.set_zoomed_out(cx));
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.detach()
|
.detach()
|
||||||
@@ -694,8 +697,7 @@ impl DockArea {
|
|||||||
let dock_area = cx.view().clone();
|
let dock_area = cx.view().clone();
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
let _ = cx.update(|cx| {
|
let _ = cx.update(|cx| {
|
||||||
let _ = dock_area
|
dock_area.update(cx, |view, cx| view.update_toggle_button_tab_panels(cx));
|
||||||
.update(cx, |view, cx| view.update_toggle_button_tab_panels(cx));
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
@@ -780,6 +782,8 @@ impl Render for DockArea {
|
|||||||
// Left dock
|
// Left dock
|
||||||
.when_some(self.left_dock.clone(), |this, dock| {
|
.when_some(self.left_dock.clone(), |this, dock| {
|
||||||
this.child(div().flex().flex_none().child(dock))
|
this.child(div().flex().flex_none().child(dock))
|
||||||
|
.bg(cx.theme().sidebar)
|
||||||
|
.text_color(cx.theme().sidebar_foreground)
|
||||||
})
|
})
|
||||||
// Center
|
// Center
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -143,8 +143,7 @@ impl PartialEq for dyn PanelView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PanelRegistry {
|
type PanelRegistryItem = HashMap<
|
||||||
pub(super) items: HashMap<
|
|
||||||
String,
|
String,
|
||||||
Arc<
|
Arc<
|
||||||
dyn Fn(
|
dyn Fn(
|
||||||
@@ -154,8 +153,12 @@ pub struct PanelRegistry {
|
|||||||
&mut WindowContext,
|
&mut WindowContext,
|
||||||
) -> Box<dyn PanelView>,
|
) -> Box<dyn PanelView>,
|
||||||
>,
|
>,
|
||||||
>,
|
>;
|
||||||
|
|
||||||
|
pub struct PanelRegistry {
|
||||||
|
pub(super) items: PanelRegistryItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PanelRegistry {
|
impl PanelRegistry {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -163,6 +166,13 @@ impl PanelRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PanelRegistry {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Global for PanelRegistry {}
|
impl Global for PanelRegistry {}
|
||||||
|
|
||||||
/// Register the Panel init by panel_name to global registry.
|
/// Register the Panel init by panel_name to global registry.
|
||||||
@@ -176,7 +186,7 @@ where
|
|||||||
) -> Box<dyn PanelView>
|
) -> Box<dyn PanelView>
|
||||||
+ 'static,
|
+ 'static,
|
||||||
{
|
{
|
||||||
if let None = cx.try_global::<PanelRegistry>() {
|
if cx.try_global::<PanelRegistry>().is_none() {
|
||||||
cx.set_global(PanelRegistry::new());
|
cx.set_global(PanelRegistry::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ impl StackPanel {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
// If the panel is already in the stack, return.
|
// If the panel is already in the stack, return.
|
||||||
if let Some(_) = self.index_of_panel(panel.clone()) {
|
if self.index_of_panel(panel.clone()).is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ impl TabPanel {
|
|||||||
let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, cx);
|
let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, cx);
|
||||||
|
|
||||||
if self.panels.len() == 1 && panel_style == PanelStyle::Default {
|
if self.panels.len() == 1 && panel_style == PanelStyle::Default {
|
||||||
let panel = self.panels.get(0).unwrap();
|
let panel = self.panels.first().unwrap();
|
||||||
let title_style = panel.title_style(cx);
|
let title_style = panel.title_style(cx);
|
||||||
|
|
||||||
return h_flex()
|
return h_flex()
|
||||||
@@ -694,6 +694,7 @@ impl TabPanel {
|
|||||||
|
|
||||||
// If target is same tab, and it is only one panel, do nothing.
|
// If target is same tab, and it is only one panel, do nothing.
|
||||||
if is_same_tab && ix.is_none() {
|
if is_same_tab && ix.is_none() {
|
||||||
|
#[allow(clippy::if_same_then_else)]
|
||||||
if self.will_split_placement.is_none() {
|
if self.will_split_placement.is_none() {
|
||||||
return;
|
return;
|
||||||
} else if self.panels.len() == 1 {
|
} else if self.panels.len() == 1 {
|
||||||
@@ -708,7 +709,7 @@ impl TabPanel {
|
|||||||
if is_same_tab {
|
if is_same_tab {
|
||||||
self.detach_panel(panel.clone(), cx);
|
self.detach_panel(panel.clone(), cx);
|
||||||
} else {
|
} else {
|
||||||
let _ = drag.tab_panel.update(cx, |view, cx| {
|
drag.tab_panel.update(cx, |view, cx| {
|
||||||
view.detach_panel(panel.clone(), cx);
|
view.detach_panel(panel.clone(), cx);
|
||||||
view.remove_self_if_empty(cx);
|
view.remove_self_if_empty(cx);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,17 +21,20 @@ use crate::{
|
|||||||
actions!(drawer, [Escape]);
|
actions!(drawer, [Escape]);
|
||||||
|
|
||||||
const CONTEXT: &str = "Drawer";
|
const CONTEXT: &str = "Drawer";
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnClose = Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Drawer {
|
pub struct Drawer {
|
||||||
pub(crate) focus_handle: FocusHandle,
|
pub(crate) focus_handle: FocusHandle,
|
||||||
placement: Placement,
|
placement: Placement,
|
||||||
size: DefiniteLength,
|
size: DefiniteLength,
|
||||||
resizable: bool,
|
resizable: bool,
|
||||||
on_close: Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
|
on_close: OnClose,
|
||||||
title: Option<AnyElement>,
|
title: Option<AnyElement>,
|
||||||
footer: Option<AnyElement>,
|
footer: Option<AnyElement>,
|
||||||
content: Div,
|
content: Div,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ impl DropdownItem for String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn value(&self) -> &Self::Value {
|
fn value(&self) -> &Self::Value {
|
||||||
&self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ impl DropdownItem for SharedString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn value(&self) -> &Self::Value {
|
fn value(&self) -> &Self::Value {
|
||||||
&self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +211,8 @@ pub enum DropdownEvent<D: DropdownDelegate + 'static> {
|
|||||||
Confirm(Option<<D::Item as DropdownItem>::Value>),
|
Confirm(Option<<D::Item as DropdownItem>::Value>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Empty = Option<Box<dyn Fn(&WindowContext) -> AnyElement + 'static>>;
|
||||||
|
|
||||||
/// A Dropdown element.
|
/// A Dropdown element.
|
||||||
pub struct Dropdown<D: DropdownDelegate + 'static> {
|
pub struct Dropdown<D: DropdownDelegate + 'static> {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
@@ -223,7 +225,7 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
|
|||||||
placeholder: Option<SharedString>,
|
placeholder: Option<SharedString>,
|
||||||
title_prefix: Option<SharedString>,
|
title_prefix: Option<SharedString>,
|
||||||
selected_value: Option<<D::Item as DropdownItem>::Value>,
|
selected_value: Option<<D::Item as DropdownItem>::Value>,
|
||||||
empty: Option<Box<dyn Fn(&WindowContext) -> AnyElement + 'static>>,
|
empty: Empty,
|
||||||
width: Length,
|
width: Length,
|
||||||
menu_width: Length,
|
menu_width: Length,
|
||||||
/// Store the bounds of the input
|
/// Store the bounds of the input
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ pub trait FocusableCycle {
|
|||||||
let target_focus_handle = handles
|
let target_focus_handle = handles
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.skip_while(|handle| Some(handle) != focused_handle.as_ref())
|
.skip_while(|handle| Some(handle) != focused_handle.as_ref())
|
||||||
.skip(1)
|
.nth(1)
|
||||||
.next()
|
|
||||||
.unwrap_or(fallback_handle);
|
.unwrap_or(fallback_handle);
|
||||||
|
|
||||||
target_focus_handle.focus(cx);
|
target_focus_handle.focus(cx);
|
||||||
|
|||||||
@@ -124,6 +124,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<I> Default for History<I>
|
||||||
|
where
|
||||||
|
I: HistoryItem,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -192,6 +201,6 @@ mod tests {
|
|||||||
let changes = history.undo().unwrap();
|
let changes = history.undo().unwrap();
|
||||||
assert_eq!(changes[0].tab_index, 0);
|
assert_eq!(changes[0].tab_index, 0);
|
||||||
|
|
||||||
assert_eq!(history.undo().is_none(), true);
|
assert!(history.undo().is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ impl Indicator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Indicator {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Sizable for Indicator {
|
impl Sizable for Indicator {
|
||||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||||
self.size = size.into();
|
self.size = size.into();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
pub(crate) struct ClearButton {}
|
pub(crate) struct ClearButton {}
|
||||||
|
|
||||||
impl ClearButton {
|
impl ClearButton {
|
||||||
|
#[allow(clippy::new_ret_no_self)]
|
||||||
pub fn new(cx: &mut WindowContext) -> Button {
|
pub fn new(cx: &mut WindowContext) -> Button {
|
||||||
Button::new("clean")
|
Button::new("clean")
|
||||||
.icon(Icon::new(IconName::CircleX).text_color(cx.theme().muted_foreground))
|
.icon(Icon::new(IconName::CircleX).text_color(cx.theme().muted_foreground))
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ impl TextElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds.origin = bounds.origin + scroll_offset;
|
bounds.origin += scroll_offset;
|
||||||
|
|
||||||
if input.show_cursor(cx) {
|
if input.show_cursor(cx) {
|
||||||
// cursor blink
|
// cursor blink
|
||||||
@@ -268,7 +268,7 @@ impl TextElement {
|
|||||||
|
|
||||||
// print_points_as_svg_path(&line_corners, &points);
|
// print_points_as_svg_path(&line_corners, &points);
|
||||||
|
|
||||||
let first_p = *points.get(0).unwrap();
|
let first_p = *points.first().unwrap();
|
||||||
let mut path = gpui::Path::new(bounds.origin + first_p);
|
let mut path = gpui::Path::new(bounds.origin + first_p);
|
||||||
for p in points.iter().skip(1) {
|
for p in points.iter().skip(1) {
|
||||||
path.line_to(bounds.origin + *p);
|
path.line_to(bounds.origin + *p);
|
||||||
@@ -295,10 +295,7 @@ impl IntoElement for TextElement {
|
|||||||
|
|
||||||
/// A debug function to print points as SVG path.
|
/// A debug function to print points as SVG path.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn print_points_as_svg_path(
|
fn print_points_as_svg_path(line_corners: &Vec<Corners<Point<Pixels>>>, points: &[Point<Pixels>]) {
|
||||||
line_corners: &Vec<Corners<Point<Pixels>>>,
|
|
||||||
points: &Vec<Point<Pixels>>,
|
|
||||||
) {
|
|
||||||
for corners in line_corners {
|
for corners in line_corners {
|
||||||
println!(
|
println!(
|
||||||
"tl: ({}, {}), tr: ({}, {}), bl: ({}, {}), br: ({}, {})",
|
"tl: ({}, {}), tr: ({}, {}), bl: ({}, {}), br: ({}, {})",
|
||||||
@@ -313,7 +310,7 @@ fn print_points_as_svg_path(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if points.len() > 0 {
|
if !points.is_empty() {
|
||||||
println!("M{},{}", points[0].x.0 as i32, points[0].y.0 as i32);
|
println!("M{},{}", points[0].x.0 as i32, points[0].y.0 as i32);
|
||||||
for p in points.iter().skip(1) {
|
for p in points.iter().skip(1) {
|
||||||
println!("L{},{}", p.x.0 as i32, p.y.0 as i32);
|
println!("L{},{}", p.x.0 as i32, p.y.0 as i32);
|
||||||
|
|||||||
@@ -145,14 +145,17 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Affixes<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>;
|
||||||
|
type Validate = Option<Box<dyn Fn(&str) -> bool + 'static>>;
|
||||||
|
|
||||||
pub struct TextInput {
|
pub struct TextInput {
|
||||||
pub(super) focus_handle: FocusHandle,
|
pub(super) focus_handle: FocusHandle,
|
||||||
pub(super) text: SharedString,
|
pub(super) text: SharedString,
|
||||||
multi_line: bool,
|
multi_line: bool,
|
||||||
pub(super) history: History<Change>,
|
pub(super) history: History<Change>,
|
||||||
pub(super) blink_cursor: Model<BlinkCursor>,
|
pub(super) blink_cursor: Model<BlinkCursor>,
|
||||||
pub(super) prefix: Option<Box<dyn Fn(&mut ViewContext<Self>) -> AnyElement + 'static>>,
|
pub(super) prefix: Affixes<Self>,
|
||||||
pub(super) suffix: Option<Box<dyn Fn(&mut ViewContext<Self>) -> AnyElement + 'static>>,
|
pub(super) suffix: Affixes<Self>,
|
||||||
pub(super) loading: bool,
|
pub(super) loading: bool,
|
||||||
pub(super) placeholder: SharedString,
|
pub(super) placeholder: SharedString,
|
||||||
pub(super) selected_range: Range<usize>,
|
pub(super) selected_range: Range<usize>,
|
||||||
@@ -177,7 +180,7 @@ pub struct TextInput {
|
|||||||
pub(super) size: Size,
|
pub(super) size: Size,
|
||||||
pub(super) rows: usize,
|
pub(super) rows: usize,
|
||||||
pattern: Option<regex::Regex>,
|
pattern: Option<regex::Regex>,
|
||||||
validate: Option<Box<dyn Fn(&str) -> bool + 'static>>,
|
validate: Validate,
|
||||||
pub(crate) scroll_handle: ScrollHandle,
|
pub(crate) scroll_handle: ScrollHandle,
|
||||||
scrollbar_state: Rc<Cell<ScrollbarState>>,
|
scrollbar_state: Rc<Cell<ScrollbarState>>,
|
||||||
/// The size of the scrollable content.
|
/// The size of the scrollable content.
|
||||||
@@ -506,13 +509,12 @@ impl TextInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let offset = self.previous_boundary(self.cursor_offset());
|
let offset = self.previous_boundary(self.cursor_offset());
|
||||||
let line = self
|
|
||||||
.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, cx)
|
self.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, cx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.rfind('\n')
|
.rfind('\n')
|
||||||
.map(|i| i + 1)
|
.map(|i| i + 1)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0)
|
||||||
line
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get end of line
|
/// Get end of line
|
||||||
@@ -531,8 +533,7 @@ impl TextInput {
|
|||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = self
|
self.text_for_range(
|
||||||
.text_for_range(
|
|
||||||
self.range_to_utf16(&(offset..self.text.len())),
|
self.range_to_utf16(&(offset..self.text.len())),
|
||||||
&mut None,
|
&mut None,
|
||||||
cx,
|
cx,
|
||||||
@@ -540,8 +541,7 @@ impl TextInput {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.find('\n')
|
.find('\n')
|
||||||
.map(|i| i + offset)
|
.map(|i| i + offset)
|
||||||
.unwrap_or(self.text.len());
|
.unwrap_or(self.text.len())
|
||||||
line
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||||
@@ -675,7 +675,7 @@ impl TextInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let old_text = self
|
let old_text = self
|
||||||
.text_for_range(self.range_to_utf16(&range), &mut None, cx)
|
.text_for_range(self.range_to_utf16(range), &mut None, cx)
|
||||||
.unwrap_or("".to_string());
|
.unwrap_or("".to_string());
|
||||||
|
|
||||||
let new_range = range.start..range.start + new_text.len();
|
let new_range = range.start..range.start + new_text.len();
|
||||||
@@ -753,7 +753,7 @@ impl TextInput {
|
|||||||
let mut y_offset = px(0.);
|
let mut y_offset = px(0.);
|
||||||
|
|
||||||
for line in lines.iter() {
|
for line in lines.iter() {
|
||||||
let line_origin = self.line_origin_with_y_offset(&mut y_offset, &line, line_height);
|
let line_origin = self.line_origin_with_y_offset(&mut y_offset, line, line_height);
|
||||||
let mut pos = inner_position - line_origin;
|
let mut pos = inner_position - line_origin;
|
||||||
// Ignore the y position in single line mode, only check x position.
|
// Ignore the y position in single line mode, only check x position.
|
||||||
if self.is_single_line() {
|
if self.is_single_line() {
|
||||||
@@ -765,7 +765,10 @@ impl TextInput {
|
|||||||
// Add 1 for place cursor after the character.
|
// Add 1 for place cursor after the character.
|
||||||
index += v + 1;
|
index += v + 1;
|
||||||
break;
|
break;
|
||||||
} else if let Ok(_) = line.index_for_position(point(px(0.), pos.y), line_height) {
|
} else if line
|
||||||
|
.index_for_position(point(px(0.), pos.y), line_height)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
// Click in the this line but not in the text, move cursor to the end of the line.
|
// Click in the this line but not in the text, move cursor to the end of the line.
|
||||||
// The fallback index is saved in Err from `index_for_position` method.
|
// The fallback index is saved in Err from `index_for_position` method.
|
||||||
index += index_result.unwrap_err();
|
index += index_result.unwrap_err();
|
||||||
@@ -809,7 +812,7 @@ impl TextInput {
|
|||||||
if self.is_multi_line() {
|
if self.is_multi_line() {
|
||||||
let p = point(px(0.), *y_offset);
|
let p = point(px(0.), *y_offset);
|
||||||
let height = line_height + line.wrap_boundaries.len() as f32 * line_height;
|
let height = line_height + line.wrap_boundaries.len() as f32 * line_height;
|
||||||
*y_offset = *y_offset + height;
|
*y_offset += height;
|
||||||
p
|
p
|
||||||
} else {
|
} else {
|
||||||
point(px(0.), px(0.))
|
point(px(0.), px(0.))
|
||||||
@@ -859,7 +862,7 @@ impl TextInput {
|
|||||||
let prev_chars = prev_text.chars().rev().peekable();
|
let prev_chars = prev_text.chars().rev().peekable();
|
||||||
let next_chars = next_text.chars().peekable();
|
let next_chars = next_text.chars().peekable();
|
||||||
|
|
||||||
for (_, c) in prev_chars.enumerate() {
|
for c in prev_chars {
|
||||||
if !is_word(c) {
|
if !is_word(c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -867,7 +870,7 @@ impl TextInput {
|
|||||||
start -= c.len_utf16();
|
start -= c.len_utf16();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_, c) in next_chars.enumerate() {
|
for c in next_chars {
|
||||||
if !is_word(c) {
|
if !is_word(c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1177,12 +1180,8 @@ impl Render for TextInput {
|
|||||||
.on_action(cx.listener(Self::delete_to_end_of_line))
|
.on_action(cx.listener(Self::delete_to_end_of_line))
|
||||||
.on_action(cx.listener(Self::enter))
|
.on_action(cx.listener(Self::enter))
|
||||||
})
|
})
|
||||||
.on_action(cx.listener(Self::up))
|
|
||||||
.on_action(cx.listener(Self::down))
|
|
||||||
.on_action(cx.listener(Self::left))
|
.on_action(cx.listener(Self::left))
|
||||||
.on_action(cx.listener(Self::right))
|
.on_action(cx.listener(Self::right))
|
||||||
.on_action(cx.listener(Self::select_up))
|
|
||||||
.on_action(cx.listener(Self::select_down))
|
|
||||||
.on_action(cx.listener(Self::select_left))
|
.on_action(cx.listener(Self::select_left))
|
||||||
.on_action(cx.listener(Self::select_right))
|
.on_action(cx.listener(Self::select_right))
|
||||||
.on_action(cx.listener(Self::select_all))
|
.on_action(cx.listener(Self::select_all))
|
||||||
@@ -1206,7 +1205,13 @@ impl Render for TextInput {
|
|||||||
.input_py(self.size)
|
.input_py(self.size)
|
||||||
.input_h(self.size)
|
.input_h(self.size)
|
||||||
.cursor_text()
|
.cursor_text()
|
||||||
.when(self.multi_line, |this| this.h_auto())
|
.when(self.multi_line, |this| {
|
||||||
|
this.on_action(cx.listener(Self::up))
|
||||||
|
.on_action(cx.listener(Self::down))
|
||||||
|
.on_action(cx.listener(Self::select_up))
|
||||||
|
.on_action(cx.listener(Self::select_down))
|
||||||
|
.h_auto()
|
||||||
|
})
|
||||||
.when(self.appearance, |this| {
|
.when(self.appearance, |this| {
|
||||||
this.bg(if self.disabled {
|
this.bg(if self.disabled {
|
||||||
cx.theme().muted
|
cx.theme().muted
|
||||||
@@ -1249,7 +1254,7 @@ impl Render for TextInput {
|
|||||||
.absolute()
|
.absolute()
|
||||||
.top_0()
|
.top_0()
|
||||||
.left_0()
|
.left_0()
|
||||||
.right_0()
|
.right(px(1.))
|
||||||
.bottom_0()
|
.bottom_0()
|
||||||
.child(
|
.child(
|
||||||
Scrollbar::vertical(
|
Scrollbar::vertical(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ mod blink_cursor;
|
|||||||
mod change;
|
mod change;
|
||||||
mod clear_button;
|
mod clear_button;
|
||||||
mod element;
|
mod element;
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod input;
|
mod input;
|
||||||
mod otp_input;
|
mod otp_input;
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ impl OtpInput {
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let c = key.chars().next().unwrap();
|
let c = key.chars().next().unwrap();
|
||||||
if !matches!(c, '0'..='9') {
|
if !c.is_ascii_digit() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ix >= self.length {
|
if ix >= self.length {
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ use gpui::{
|
|||||||
|
|
||||||
use crate::theme::ActiveTheme as _;
|
use crate::theme::ActiveTheme as _;
|
||||||
|
|
||||||
|
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut gpui::WindowContext) + 'static>>;
|
||||||
|
|
||||||
/// A Link element like a `<a>` tag in HTML.
|
/// A Link element like a `<a>` tag in HTML.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
base: Stateful<Div>,
|
base: Stateful<Div>,
|
||||||
href: Option<SharedString>,
|
href: Option<SharedString>,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut gpui::WindowContext) + 'static>>,
|
on_click: OnClick,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Link {
|
impl Link {
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
type OnClick = Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>;
|
||||||
|
type OnMouseEnter = Option<Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + 'static>>;
|
||||||
|
type Suffix = Option<Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>>;
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ListItem {
|
pub struct ListItem {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
@@ -14,9 +18,9 @@ pub struct ListItem {
|
|||||||
selected: bool,
|
selected: bool,
|
||||||
confirmed: bool,
|
confirmed: bool,
|
||||||
check_icon: Option<Icon>,
|
check_icon: Option<Icon>,
|
||||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_click: OnClick,
|
||||||
on_mouse_enter: Option<Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + 'static>>,
|
on_mouse_enter: OnMouseEnter,
|
||||||
suffix: Option<Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>>,
|
suffix: Suffix,
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod list;
|
mod list;
|
||||||
mod list_item;
|
mod list_item;
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,13 @@ use crate::{
|
|||||||
actions!(modal, [Escape]);
|
actions!(modal, [Escape]);
|
||||||
|
|
||||||
const CONTEXT: &str = "Modal";
|
const CONTEXT: &str = "Modal";
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnClose = Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Modal {
|
pub struct Modal {
|
||||||
base: Div,
|
base: Div,
|
||||||
@@ -30,8 +33,7 @@ pub struct Modal {
|
|||||||
width: Pixels,
|
width: Pixels,
|
||||||
max_width: Option<Pixels>,
|
max_width: Option<Pixels>,
|
||||||
margin_top: Option<Pixels>,
|
margin_top: Option<Pixels>,
|
||||||
|
on_close: OnClose,
|
||||||
on_close: Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
|
|
||||||
show_close: bool,
|
show_close: bool,
|
||||||
overlay: bool,
|
overlay: bool,
|
||||||
keyboard: bool,
|
keyboard: bool,
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ impl From<(TypeId, ElementId)> for NotificationId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnClick = Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext)>>;
|
||||||
|
|
||||||
/// A notification element.
|
/// A notification element.
|
||||||
pub struct Notification {
|
pub struct Notification {
|
||||||
/// The id is used make the notification unique.
|
/// The id is used make the notification unique.
|
||||||
@@ -52,7 +54,7 @@ pub struct Notification {
|
|||||||
message: SharedString,
|
message: SharedString,
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
autohide: bool,
|
autohide: bool,
|
||||||
on_click: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
on_click: OnClick,
|
||||||
closing: bool,
|
closing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Content<T> = Rc<dyn Fn(&mut ViewContext<T>) -> AnyElement>;
|
||||||
|
|
||||||
pub struct PopoverContent {
|
pub struct PopoverContent {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
content: Rc<dyn Fn(&mut ViewContext<Self>) -> AnyElement>,
|
content: Content<Self>,
|
||||||
max_width: Option<Pixels>,
|
max_width: Option<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +64,14 @@ impl Render for PopoverContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Trigger = Option<Box<dyn FnOnce(bool, &WindowContext) -> AnyElement + 'static>>;
|
||||||
|
type ViewContent<M> = Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>;
|
||||||
|
|
||||||
pub struct Popover<M: ManagedView> {
|
pub struct Popover<M: ManagedView> {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
anchor: AnchorCorner,
|
anchor: AnchorCorner,
|
||||||
trigger: Option<Box<dyn FnOnce(bool, &WindowContext) -> AnyElement + 'static>>,
|
trigger: Trigger,
|
||||||
content: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
|
content: ViewContent<M>,
|
||||||
/// Style for trigger element.
|
/// Style for trigger element.
|
||||||
/// This is used for hotfix the trigger element style to support w_full.
|
/// This is used for hotfix the trigger element style to support w_full.
|
||||||
trigger_style: Option<StyleRefinement>,
|
trigger_style: Option<StyleRefinement>,
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ impl PopupMenu {
|
|||||||
// If the actions are listened on the `PanelContent`,
|
// If the actions are listened on the `PanelContent`,
|
||||||
// it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`.
|
// it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`.
|
||||||
if let Some(handle) = action_focus_handle.as_ref() {
|
if let Some(handle) = action_focus_handle.as_ref() {
|
||||||
cx.focus(&handle);
|
cx.focus(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.dispatch_action(action.boxed_clone());
|
cx.dispatch_action(action.boxed_clone());
|
||||||
@@ -367,8 +367,7 @@ impl PopupMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||||
match self.selected_index {
|
if let Some(index) = self.selected_index {
|
||||||
Some(index) => {
|
|
||||||
let item = self.menu_items.get(index);
|
let item = self.menu_items.get(index);
|
||||||
match item {
|
match item {
|
||||||
Some(PopupMenuItem::Item { handler, .. }) => {
|
Some(PopupMenuItem::Item { handler, .. }) => {
|
||||||
@@ -382,8 +381,6 @@ impl PopupMenu {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||||
@@ -435,7 +432,7 @@ impl PopupMenu {
|
|||||||
let el = div().text_color(cx.theme().muted_foreground).children(
|
let el = div().text_color(cx.theme().muted_foreground).children(
|
||||||
keybinding
|
keybinding
|
||||||
.keystrokes()
|
.keystrokes()
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|key| key_shortcut(key.clone())),
|
.map(|key| key_shortcut(key.clone())),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -443,7 +440,7 @@ impl PopupMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return None;
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_icon(
|
fn render_icon(
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ impl Progress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Progress {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Progress {
|
impl RenderOnce for Progress {
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
let rounded = px(self.height / 2.);
|
let rounded = px(self.height / 2.);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use gpui::{
|
|||||||
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WindowContext,
|
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OnClick = Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>;
|
||||||
|
|
||||||
/// A Radio element.
|
/// A Radio element.
|
||||||
///
|
///
|
||||||
/// This is not included the Radio group implementation, you can manage the group by yourself.
|
/// This is not included the Radio group implementation, you can manage the group by yourself.
|
||||||
@@ -13,7 +15,7 @@ pub struct Radio {
|
|||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
on_click: Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>,
|
on_click: OnClick,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Radio {
|
impl Radio {
|
||||||
|
|||||||
@@ -274,6 +274,8 @@ impl Render for ResizablePanelGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContentBuilder = Option<Rc<dyn Fn(&mut WindowContext) -> AnyElement>>;
|
||||||
|
|
||||||
pub struct ResizablePanel {
|
pub struct ResizablePanel {
|
||||||
group: Option<WeakView<ResizablePanelGroup>>,
|
group: Option<WeakView<ResizablePanelGroup>>,
|
||||||
/// Initial size is the size that the panel has when it is created.
|
/// Initial size is the size that the panel has when it is created.
|
||||||
@@ -283,7 +285,7 @@ pub struct ResizablePanel {
|
|||||||
/// the size ratio that the panel has relative to its group
|
/// the size ratio that the panel has relative to its group
|
||||||
size_ratio: Option<f32>,
|
size_ratio: Option<f32>,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
content_builder: Option<Rc<dyn Fn(&mut WindowContext) -> AnyElement>>,
|
content_builder: ContentBuilder,
|
||||||
content_view: Option<AnyView>,
|
content_view: Option<AnyView>,
|
||||||
/// The bounds of the resizable panel, when render the bounds will be updated.
|
/// The bounds of the resizable panel, when render the bounds will be updated.
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
@@ -435,7 +437,6 @@ impl Element for ResizePanelGroupElement {
|
|||||||
_: &mut Self::RequestLayoutState,
|
_: &mut Self::RequestLayoutState,
|
||||||
_: &mut WindowContext,
|
_: &mut WindowContext,
|
||||||
) -> Self::PrepaintState {
|
) -> Self::PrepaintState {
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ impl ContextModal for WindowContext<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn has_active_drawer(&self) -> bool {
|
fn has_active_drawer(&self) -> bool {
|
||||||
Root::read(&self).active_drawer.is_some()
|
Root::read(self).active_drawer.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_drawer(&mut self) {
|
fn close_drawer(&mut self) {
|
||||||
@@ -87,7 +87,7 @@ impl ContextModal for WindowContext<'_> {
|
|||||||
Root::update(self, move |root, cx| {
|
Root::update(self, move |root, cx| {
|
||||||
// Only save focus handle if there are no active modals.
|
// Only save focus handle if there are no active modals.
|
||||||
// This is used to restore focus when all modals are closed.
|
// This is used to restore focus when all modals are closed.
|
||||||
if root.active_modals.len() == 0 {
|
if root.active_modals.is_empty() {
|
||||||
root.previous_focus_handle = cx.focused();
|
root.previous_focus_handle = cx.focused();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ impl ContextModal for WindowContext<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn has_active_modal(&self) -> bool {
|
fn has_active_modal(&self) -> bool {
|
||||||
Root::read(&self).active_modals.len() > 0
|
!Root::read(self).active_modals.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_modal(&mut self) {
|
fn close_modal(&mut self) {
|
||||||
@@ -145,7 +145,7 @@ impl ContextModal for WindowContext<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn notifications(&self) -> Rc<Vec<View<Notification>>> {
|
fn notifications(&self) -> Rc<Vec<View<Notification>>> {
|
||||||
Rc::new(Root::read(&self).notification.read(&self).notifications())
|
Rc::new(Root::read(self).notification.read(self).notifications())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<V> ContextModal for ViewContext<'_, V> {
|
impl<V> ContextModal for ViewContext<'_, V> {
|
||||||
@@ -211,16 +211,19 @@ pub struct Root {
|
|||||||
view: AnyView,
|
view: AnyView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DrawerBuilder = Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>;
|
||||||
|
type ModelBuilder = Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ActiveDrawer {
|
struct ActiveDrawer {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
builder: Rc<dyn Fn(Drawer, &mut WindowContext) -> Drawer + 'static>,
|
builder: DrawerBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ActiveModal {
|
struct ActiveModal {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
builder: Rc<dyn Fn(Modal, &mut WindowContext) -> Modal + 'static>,
|
builder: ModelBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Root {
|
impl Root {
|
||||||
|
|||||||
@@ -150,11 +150,15 @@ where
|
|||||||
id: Option<&gpui::GlobalElementId>,
|
id: Option<&gpui::GlobalElementId>,
|
||||||
cx: &mut gpui::WindowContext,
|
cx: &mut gpui::WindowContext,
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||||
let mut style = Style::default();
|
let style = Style {
|
||||||
style.flex_grow = 1.0;
|
flex_grow: 1.0,
|
||||||
style.position = Position::Relative;
|
position: Position::Relative,
|
||||||
style.size.width = relative(1.0).into();
|
size: Size {
|
||||||
style.size.height = relative(1.0).into();
|
width: relative(1.0).into(),
|
||||||
|
height: relative(1.0).into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let axis = self.axis;
|
let axis = self.axis;
|
||||||
let view_id = self.view_id;
|
let view_id = self.view_id;
|
||||||
|
|||||||
@@ -70,13 +70,17 @@ impl Element for ScrollableMask {
|
|||||||
_: Option<&GlobalElementId>,
|
_: Option<&GlobalElementId>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (LayoutId, Self::RequestLayoutState) {
|
) -> (LayoutId, Self::RequestLayoutState) {
|
||||||
let mut style = Style::default();
|
|
||||||
// Set the layout style relative to the table view to get same size.
|
// Set the layout style relative to the table view to get same size.
|
||||||
style.position = Position::Absolute;
|
let style = Style {
|
||||||
style.flex_grow = 1.0;
|
position: Position::Absolute,
|
||||||
style.flex_shrink = 1.0;
|
flex_grow: 1.0,
|
||||||
style.size.width = relative(1.).into();
|
flex_shrink: 1.0,
|
||||||
style.size.height = relative(1.).into();
|
size: gpui::Size {
|
||||||
|
width: relative(1.).into(),
|
||||||
|
height: relative(1.).into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
(cx.request_layout(style, None), ())
|
(cx.request_layout(style, None), ())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,31 @@ use std::{cell::Cell, rc::Rc, time::Instant};
|
|||||||
|
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
fill, point, px, relative, Bounds, ContentMask, Edges, Element, EntityId, Hitbox, IntoElement,
|
fill, point, px, relative, AppContext, Bounds, ContentMask, CursorStyle, Edges, Element,
|
||||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, Position, ScrollHandle,
|
EntityId, Hitbox, Hsla, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||||
ScrollWheelEvent, Style, UniformListScrollHandle,
|
Pixels, Point, Position, ScrollHandle, ScrollWheelEvent, Style, UniformListScrollHandle,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Scrollbar show mode.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
|
||||||
|
pub enum ScrollbarShow {
|
||||||
|
#[default]
|
||||||
|
Scrolling,
|
||||||
|
Hover,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollbarShow {
|
||||||
|
fn is_hover(&self) -> bool {
|
||||||
|
matches!(self, Self::Hover)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const MIN_THUMB_SIZE: f32 = 80.;
|
const MIN_THUMB_SIZE: f32 = 80.;
|
||||||
const THUMB_RADIUS: Pixels = Pixels(3.0);
|
const THUMB_RADIUS: Pixels = Pixels(3.0);
|
||||||
const THUMB_INSET: Pixels = Pixels(4.);
|
const THUMB_INSET: Pixels = Pixels(4.);
|
||||||
|
const FADE_OUT_DURATION: f32 = 3.0;
|
||||||
|
const FADE_OUT_DELAY: f32 = 2.0;
|
||||||
|
|
||||||
pub trait ScrollHandleOffsetable {
|
pub trait ScrollHandleOffsetable {
|
||||||
fn offset(&self) -> Point<Pixels>;
|
fn offset(&self) -> Point<Pixels>;
|
||||||
@@ -92,6 +109,9 @@ impl ScrollbarState {
|
|||||||
fn with_hovered(&self, axis: Option<ScrollbarAxis>) -> Self {
|
fn with_hovered(&self, axis: Option<ScrollbarAxis>) -> Self {
|
||||||
let mut state = *self;
|
let mut state = *self;
|
||||||
state.hovered_axis = axis;
|
state.hovered_axis = axis;
|
||||||
|
if self.is_scrollbar_visible() {
|
||||||
|
state.last_scroll_time = Some(Instant::now());
|
||||||
|
}
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +131,21 @@ impl ScrollbarState {
|
|||||||
state.last_scroll_time = last_scroll_time;
|
state.last_scroll_time = last_scroll_time;
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_last_scroll_time(&self, t: Option<Instant>) -> Self {
|
||||||
|
let mut state = *self;
|
||||||
|
state.last_scroll_time = t;
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_scrollbar_visible(&self) -> bool {
|
||||||
|
if let Some(last_time) = self.last_scroll_time {
|
||||||
|
let elapsed = Instant::now().duration_since(last_time).as_secs_f32();
|
||||||
|
elapsed < FADE_OUT_DURATION
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -257,6 +292,52 @@ impl Scrollbar {
|
|||||||
self.axis = axis;
|
self.axis = axis;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn style_for_active(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||||
|
(
|
||||||
|
cx.theme().scrollbar_thumb_hover,
|
||||||
|
cx.theme().scrollbar,
|
||||||
|
cx.theme().border,
|
||||||
|
THUMB_INSET - px(1.),
|
||||||
|
THUMB_RADIUS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_for_hovered_thumb(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||||
|
(
|
||||||
|
cx.theme().scrollbar_thumb_hover,
|
||||||
|
cx.theme().scrollbar,
|
||||||
|
cx.theme().border,
|
||||||
|
THUMB_INSET - px(1.),
|
||||||
|
THUMB_RADIUS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_for_hovered_bar(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||||
|
let (inset, radius) = if cx.theme().scrollbar_show.is_hover() {
|
||||||
|
(THUMB_INSET, THUMB_RADIUS - px(1.))
|
||||||
|
} else {
|
||||||
|
(THUMB_INSET - px(1.), THUMB_RADIUS)
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
cx.theme().scrollbar_thumb,
|
||||||
|
cx.theme().scrollbar,
|
||||||
|
gpui::transparent_black(),
|
||||||
|
inset,
|
||||||
|
radius,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_for_idle(_: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||||
|
(
|
||||||
|
gpui::transparent_black(),
|
||||||
|
gpui::transparent_black(),
|
||||||
|
gpui::transparent_black(),
|
||||||
|
THUMB_INSET,
|
||||||
|
THUMB_RADIUS - px(1.),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for Scrollbar {
|
impl IntoElement for Scrollbar {
|
||||||
@@ -267,10 +348,31 @@ impl IntoElement for Scrollbar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PrepaintState {
|
||||||
|
hitbox: Hitbox,
|
||||||
|
states: Vec<AxisPrepaintState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AxisPrepaintState {
|
||||||
|
axis: ScrollbarAxis,
|
||||||
|
bar_hitbox: Hitbox,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
border_width: Pixels,
|
||||||
|
radius: Pixels,
|
||||||
|
bg: Hsla,
|
||||||
|
border: Hsla,
|
||||||
|
thumb_bounds: Bounds<Pixels>,
|
||||||
|
thumb_bg: Hsla,
|
||||||
|
scroll_size: Pixels,
|
||||||
|
container_size: Pixels,
|
||||||
|
thumb_size: Pixels,
|
||||||
|
margin_end: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
impl Element for Scrollbar {
|
impl Element for Scrollbar {
|
||||||
type RequestLayoutState = ();
|
type RequestLayoutState = ();
|
||||||
|
|
||||||
type PrepaintState = Hitbox;
|
type PrepaintState = PrepaintState;
|
||||||
|
|
||||||
fn id(&self) -> Option<gpui::ElementId> {
|
fn id(&self) -> Option<gpui::ElementId> {
|
||||||
None
|
None
|
||||||
@@ -281,12 +383,16 @@ impl Element for Scrollbar {
|
|||||||
_: Option<&gpui::GlobalElementId>,
|
_: Option<&gpui::GlobalElementId>,
|
||||||
cx: &mut gpui::WindowContext,
|
cx: &mut gpui::WindowContext,
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||||
let mut style = Style::default();
|
let style = Style {
|
||||||
style.position = Position::Absolute;
|
position: Position::Absolute,
|
||||||
style.flex_grow = 1.0;
|
flex_grow: 1.0,
|
||||||
style.flex_shrink = 1.0;
|
flex_shrink: 1.0,
|
||||||
style.size.width = relative(1.).into();
|
size: gpui::Size {
|
||||||
style.size.height = relative(1.).into();
|
width: relative(1.).into(),
|
||||||
|
height: relative(1.).into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
(cx.request_layout(style, None), ())
|
(cx.request_layout(style, None), ())
|
||||||
}
|
}
|
||||||
@@ -298,36 +404,26 @@ impl Element for Scrollbar {
|
|||||||
_: &mut Self::RequestLayoutState,
|
_: &mut Self::RequestLayoutState,
|
||||||
cx: &mut gpui::WindowContext,
|
cx: &mut gpui::WindowContext,
|
||||||
) -> Self::PrepaintState {
|
) -> Self::PrepaintState {
|
||||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
let hitbox = cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
cx.insert_hitbox(bounds, false)
|
cx.insert_hitbox(bounds, false)
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
let mut states = vec![];
|
||||||
|
|
||||||
fn paint(
|
|
||||||
&mut self,
|
|
||||||
_: Option<&gpui::GlobalElementId>,
|
|
||||||
_: Bounds<Pixels>,
|
|
||||||
_: &mut Self::RequestLayoutState,
|
|
||||||
hitbox: &mut Self::PrepaintState,
|
|
||||||
cx: &mut gpui::WindowContext,
|
|
||||||
) {
|
|
||||||
let hitbox_bounds = hitbox.bounds;
|
|
||||||
let mut has_both = self.axis.is_both();
|
let mut has_both = self.axis.is_both();
|
||||||
|
|
||||||
for axis in self.axis.all().into_iter() {
|
for axis in self.axis.all().into_iter() {
|
||||||
const NORMAL_OPACITY: f32 = 0.6;
|
|
||||||
|
|
||||||
let is_vertical = axis.is_vertical();
|
let is_vertical = axis.is_vertical();
|
||||||
let (scroll_area_size, container_size, scroll_position) = if is_vertical {
|
let (scroll_area_size, container_size, scroll_position) = if is_vertical {
|
||||||
(
|
(
|
||||||
self.scroll_size.height,
|
self.scroll_size.height,
|
||||||
hitbox_bounds.size.height,
|
hitbox.size.height,
|
||||||
self.scroll_handle.offset().y,
|
self.scroll_handle.offset().y,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
self.scroll_size.width,
|
self.scroll_size.width,
|
||||||
hitbox_bounds.size.width,
|
hitbox.size.width,
|
||||||
self.scroll_handle.offset().x,
|
self.scroll_handle.offset().x,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -354,23 +450,23 @@ impl Element for Scrollbar {
|
|||||||
let bounds = Bounds {
|
let bounds = Bounds {
|
||||||
origin: if is_vertical {
|
origin: if is_vertical {
|
||||||
point(
|
point(
|
||||||
hitbox_bounds.origin.x + hitbox_bounds.size.width - self.width,
|
hitbox.origin.x + hitbox.size.width - self.width,
|
||||||
hitbox_bounds.origin.y,
|
hitbox.origin.y,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
point(
|
point(
|
||||||
hitbox_bounds.origin.x,
|
hitbox.origin.x,
|
||||||
hitbox_bounds.origin.y + hitbox_bounds.size.height - self.width,
|
hitbox.origin.y + hitbox.size.height - self.width,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
size: gpui::Size {
|
size: gpui::Size {
|
||||||
width: if is_vertical {
|
width: if is_vertical {
|
||||||
self.width
|
self.width
|
||||||
} else {
|
} else {
|
||||||
hitbox_bounds.size.width
|
hitbox.size.width
|
||||||
},
|
},
|
||||||
height: if is_vertical {
|
height: if is_vertical {
|
||||||
hitbox_bounds.size.height
|
hitbox.size.height
|
||||||
} else {
|
} else {
|
||||||
self.width
|
self.width
|
||||||
},
|
},
|
||||||
@@ -378,49 +474,46 @@ impl Element for Scrollbar {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
|
let is_hover_to_show = cx.theme().scrollbar_show.is_hover();
|
||||||
|
let is_hovered_on_bar = state.get().hovered_axis == Some(axis);
|
||||||
|
let is_hovered_on_thumb = state.get().hovered_on_thumb == Some(axis);
|
||||||
|
|
||||||
let (thumb_bg, bar_bg, bar_border, inset, radius) =
|
let (thumb_bg, bar_bg, bar_border, inset, radius) =
|
||||||
if state.get().dragged_axis == Some(axis) {
|
if state.get().dragged_axis == Some(axis) {
|
||||||
(
|
Self::style_for_active(cx)
|
||||||
cx.theme().scrollbar_thumb,
|
} else if is_hover_to_show && is_hovered_on_bar {
|
||||||
cx.theme().scrollbar,
|
if is_hovered_on_thumb {
|
||||||
cx.theme().border,
|
Self::style_for_hovered_thumb(cx)
|
||||||
THUMB_INSET - px(1.),
|
|
||||||
THUMB_RADIUS,
|
|
||||||
)
|
|
||||||
} else if state.get().hovered_axis == Some(axis) {
|
|
||||||
if state.get().hovered_on_thumb == Some(axis) {
|
|
||||||
(
|
|
||||||
cx.theme().scrollbar_thumb,
|
|
||||||
cx.theme().scrollbar,
|
|
||||||
cx.theme().border,
|
|
||||||
THUMB_INSET - px(1.),
|
|
||||||
THUMB_RADIUS,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(
|
Self::style_for_hovered_bar(cx)
|
||||||
cx.theme().scrollbar_thumb.opacity(NORMAL_OPACITY),
|
|
||||||
gpui::transparent_black(),
|
|
||||||
gpui::transparent_black(),
|
|
||||||
THUMB_INSET,
|
|
||||||
THUMB_RADIUS,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut idle_state = (
|
let mut idle_state = Self::style_for_idle(cx);
|
||||||
gpui::transparent_black(),
|
// Delay 2s to fade out the scrollbar thumb (in 1s)
|
||||||
gpui::transparent_black(),
|
|
||||||
gpui::transparent_black(),
|
|
||||||
THUMB_INSET,
|
|
||||||
THUMB_RADIUS - px(1.),
|
|
||||||
);
|
|
||||||
if let Some(last_time) = state.get().last_scroll_time {
|
if let Some(last_time) = state.get().last_scroll_time {
|
||||||
let elapsed = Instant::now().duration_since(last_time).as_secs_f32();
|
let elapsed = Instant::now().duration_since(last_time).as_secs_f32();
|
||||||
if elapsed < 1.0 {
|
if elapsed < FADE_OUT_DURATION {
|
||||||
let y_value = NORMAL_OPACITY - elapsed.powi(10); // y = 1 - x^10
|
if is_hovered_on_bar {
|
||||||
idle_state.0 = cx.theme().scrollbar_thumb.opacity(y_value);
|
state.set(state.get().with_last_scroll_time(Some(Instant::now())));
|
||||||
|
idle_state = if is_hovered_on_thumb {
|
||||||
|
Self::style_for_hovered_thumb(cx)
|
||||||
|
} else {
|
||||||
|
Self::style_for_hovered_bar(cx)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if elapsed < FADE_OUT_DELAY {
|
||||||
|
idle_state.0 = cx.theme().scrollbar_thumb;
|
||||||
|
} else {
|
||||||
|
// opacity = 1 - (x - 2)^10
|
||||||
|
let opacity = 1.0 - (elapsed - FADE_OUT_DELAY).powi(10);
|
||||||
|
idle_state.0 = cx.theme().scrollbar_thumb.opacity(opacity);
|
||||||
|
};
|
||||||
|
|
||||||
cx.request_animation_frame();
|
cx.request_animation_frame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
idle_state
|
idle_state
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -449,8 +542,57 @@ impl Element for Scrollbar {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let bar_hitbox = cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
|
cx.insert_hitbox(bounds, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
states.push(AxisPrepaintState {
|
||||||
|
axis,
|
||||||
|
bar_hitbox,
|
||||||
|
bounds,
|
||||||
|
border_width,
|
||||||
|
radius,
|
||||||
|
bg: bar_bg,
|
||||||
|
border: bar_border,
|
||||||
|
thumb_bounds,
|
||||||
|
thumb_bg,
|
||||||
|
scroll_size: scroll_area_size,
|
||||||
|
container_size,
|
||||||
|
thumb_size: thumb_length,
|
||||||
|
margin_end,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
PrepaintState { hitbox, states }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
_: Option<&gpui::GlobalElementId>,
|
||||||
|
_: Bounds<Pixels>,
|
||||||
|
_: &mut Self::RequestLayoutState,
|
||||||
|
prepaint: &mut Self::PrepaintState,
|
||||||
|
cx: &mut gpui::WindowContext,
|
||||||
|
) {
|
||||||
|
let hitbox_bounds = prepaint.hitbox.bounds;
|
||||||
|
let is_visible = self.state.get().is_scrollbar_visible();
|
||||||
|
let is_hover_to_show = cx.theme().scrollbar_show.is_hover();
|
||||||
|
|
||||||
|
for state in prepaint.states.iter() {
|
||||||
|
let axis = state.axis;
|
||||||
|
let radius = state.radius;
|
||||||
|
let bounds = state.bounds;
|
||||||
|
let thumb_bounds = state.thumb_bounds;
|
||||||
|
let scroll_area_size = state.scroll_size;
|
||||||
|
let container_size = state.container_size;
|
||||||
|
let thumb_size = state.thumb_size;
|
||||||
|
let margin_end = state.margin_end;
|
||||||
|
let is_vertical = axis.is_vertical();
|
||||||
|
|
||||||
|
cx.set_cursor_style(CursorStyle::default(), &state.bar_hitbox);
|
||||||
|
|
||||||
cx.paint_layer(hitbox_bounds, |cx| {
|
cx.paint_layer(hitbox_bounds, |cx| {
|
||||||
cx.paint_quad(fill(bounds, bar_bg));
|
cx.paint_quad(fill(state.bounds, state.bg));
|
||||||
|
|
||||||
cx.paint_quad(PaintQuad {
|
cx.paint_quad(PaintQuad {
|
||||||
bounds,
|
bounds,
|
||||||
@@ -461,20 +603,20 @@ impl Element for Scrollbar {
|
|||||||
top: px(0.),
|
top: px(0.),
|
||||||
right: px(0.),
|
right: px(0.),
|
||||||
bottom: px(0.),
|
bottom: px(0.),
|
||||||
left: border_width,
|
left: state.border_width,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Edges {
|
Edges {
|
||||||
top: border_width,
|
top: state.border_width,
|
||||||
right: px(0.),
|
right: px(0.),
|
||||||
bottom: px(0.),
|
bottom: px(0.),
|
||||||
left: px(0.),
|
left: px(0.),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
border_color: bar_border,
|
border_color: state.border,
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.paint_quad(fill(thumb_bounds, thumb_bg).corner_radii(radius));
|
cx.paint_quad(fill(thumb_bounds, state.thumb_bg).corner_radii(radius));
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
@@ -483,8 +625,10 @@ impl Element for Scrollbar {
|
|||||||
let scroll_handle = self.scroll_handle.clone();
|
let scroll_handle = self.scroll_handle.clone();
|
||||||
|
|
||||||
move |event: &ScrollWheelEvent, phase, cx| {
|
move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
if phase.bubble() && hitbox_bounds.contains(&event.position) {
|
if phase.bubble()
|
||||||
if scroll_handle.offset() != state.get().last_scroll_offset {
|
&& hitbox_bounds.contains(&event.position)
|
||||||
|
&& scroll_handle.offset() != state.get().last_scroll_offset
|
||||||
|
{
|
||||||
state.set(
|
state.set(
|
||||||
state
|
state
|
||||||
.get()
|
.get()
|
||||||
@@ -493,11 +637,11 @@ impl Element for Scrollbar {
|
|||||||
cx.notify(Some(view_id));
|
cx.notify(Some(view_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let safe_range = (-scroll_area_size + container_size)..px(0.);
|
let safe_range = (-scroll_area_size + container_size)..px(0.);
|
||||||
|
|
||||||
|
if is_hover_to_show || is_visible {
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
let view_id = self.view_id;
|
let view_id = self.view_id;
|
||||||
@@ -519,11 +663,11 @@ impl Element for Scrollbar {
|
|||||||
// Set the thumb bar center to the click position
|
// Set the thumb bar center to the click position
|
||||||
let offset = scroll_handle.offset();
|
let offset = scroll_handle.offset();
|
||||||
let percentage = if is_vertical {
|
let percentage = if is_vertical {
|
||||||
(event.position.y - thumb_length / 2. - bounds.origin.y)
|
(event.position.y - thumb_size / 2. - bounds.origin.y)
|
||||||
/ (bounds.size.height - thumb_length)
|
/ (bounds.size.height - thumb_size)
|
||||||
} else {
|
} else {
|
||||||
(event.position.x - thumb_length / 2. - bounds.origin.x)
|
(event.position.x - thumb_size / 2. - bounds.origin.x)
|
||||||
/ (bounds.size.width - thumb_length)
|
/ (bounds.size.width - thumb_size)
|
||||||
}
|
}
|
||||||
.min(1.);
|
.min(1.);
|
||||||
|
|
||||||
@@ -544,6 +688,7 @@ impl Element for Scrollbar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let scroll_handle = self.scroll_handle.clone();
|
let scroll_handle = self.scroll_handle.clone();
|
||||||
@@ -557,14 +702,12 @@ impl Element for Scrollbar {
|
|||||||
state.set(state.get().with_hovered(Some(axis)));
|
state.set(state.get().with_hovered(Some(axis)));
|
||||||
cx.notify(Some(view_id));
|
cx.notify(Some(view_id));
|
||||||
}
|
}
|
||||||
} else {
|
} else if state.get().hovered_axis == Some(axis)
|
||||||
if state.get().hovered_axis == Some(axis) {
|
&& state.get().hovered_axis.is_some()
|
||||||
if state.get().hovered_axis.is_some() {
|
{
|
||||||
state.set(state.get().with_hovered(None));
|
state.set(state.get().with_hovered(None));
|
||||||
cx.notify(Some(view_id));
|
cx.notify(Some(view_id));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update hovered state for scrollbar thumb
|
// Update hovered state for scrollbar thumb
|
||||||
if thumb_bounds.contains(&event.position) {
|
if thumb_bounds.contains(&event.position) {
|
||||||
@@ -572,12 +715,10 @@ impl Element for Scrollbar {
|
|||||||
state.set(state.get().with_hovered_on_thumb(Some(axis)));
|
state.set(state.get().with_hovered_on_thumb(Some(axis)));
|
||||||
cx.notify(Some(view_id));
|
cx.notify(Some(view_id));
|
||||||
}
|
}
|
||||||
} else {
|
} else if state.get().hovered_on_thumb == Some(axis) {
|
||||||
if state.get().hovered_on_thumb == Some(axis) {
|
|
||||||
state.set(state.get().with_hovered_on_thumb(None));
|
state.set(state.get().with_hovered_on_thumb(None));
|
||||||
cx.notify(Some(view_id));
|
cx.notify(Some(view_id));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Move thumb position on dragging
|
// Move thumb position on dragging
|
||||||
if state.get().dragged_axis == Some(axis) && event.dragging() {
|
if state.get().dragged_axis == Some(axis) && event.dragging() {
|
||||||
@@ -587,10 +728,10 @@ impl Element for Scrollbar {
|
|||||||
|
|
||||||
let percentage = (if is_vertical {
|
let percentage = (if is_vertical {
|
||||||
(event.position.y - drag_pos.y - bounds.origin.y)
|
(event.position.y - drag_pos.y - bounds.origin.y)
|
||||||
/ (bounds.size.height - thumb_length)
|
/ (bounds.size.height - thumb_size)
|
||||||
} else {
|
} else {
|
||||||
(event.position.x - drag_pos.x - bounds.origin.x)
|
(event.position.x - drag_pos.x - bounds.origin.x)
|
||||||
/ (bounds.size.width - thumb_length - margin_end)
|
/ (bounds.size.width - thumb_size - margin_end)
|
||||||
})
|
})
|
||||||
.clamp(0., 1.);
|
.clamp(0., 1.);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ impl SidebarFooter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SidebarFooter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Selectable for SidebarFooter {
|
impl Selectable for SidebarFooter {
|
||||||
fn selected(mut self, selected: bool) -> Self {
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
@@ -33,6 +40,7 @@ impl Selectable for SidebarFooter {
|
|||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collapsible for SidebarFooter {
|
impl Collapsible for SidebarFooter {
|
||||||
fn is_collapsed(&self) -> bool {
|
fn is_collapsed(&self) -> bool {
|
||||||
self.is_collapsed
|
self.is_collapsed
|
||||||
@@ -43,17 +51,21 @@ impl Collapsible for SidebarFooter {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for SidebarFooter {
|
impl ParentElement for SidebarFooter {
|
||||||
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
|
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
|
||||||
self.base.extend(elements);
|
self.base.extend(elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Styled for SidebarFooter {
|
impl Styled for SidebarFooter {
|
||||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||||
self.base.style()
|
self.base.style()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PopupMenuExt for SidebarFooter {}
|
impl PopupMenuExt for SidebarFooter {}
|
||||||
|
|
||||||
impl RenderOnce for SidebarFooter {
|
impl RenderOnce for SidebarFooter {
|
||||||
fn render(self, cx: &mut gpui::WindowContext) -> impl gpui::IntoElement {
|
fn render(self, cx: &mut gpui::WindowContext) -> impl gpui::IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ impl<E: Collapsible + IntoElement> SidebarGroup<E> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Collapsible + IntoElement> Collapsible for SidebarGroup<E> {
|
impl<E: Collapsible + IntoElement> Collapsible for SidebarGroup<E> {
|
||||||
fn is_collapsed(&self) -> bool {
|
fn is_collapsed(&self) -> bool {
|
||||||
self.is_collapsed
|
self.is_collapsed
|
||||||
@@ -43,6 +44,7 @@ impl<E: Collapsible + IntoElement> Collapsible for SidebarGroup<E> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Collapsible + IntoElement> RenderOnce for SidebarGroup<E> {
|
impl<E: Collapsible + IntoElement> RenderOnce for SidebarGroup<E> {
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ impl SidebarHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SidebarHeader {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Selectable for SidebarHeader {
|
impl Selectable for SidebarHeader {
|
||||||
fn selected(mut self, selected: bool) -> Self {
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
@@ -33,6 +40,7 @@ impl Selectable for SidebarHeader {
|
|||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collapsible for SidebarHeader {
|
impl Collapsible for SidebarHeader {
|
||||||
fn is_collapsed(&self) -> bool {
|
fn is_collapsed(&self) -> bool {
|
||||||
self.is_collapsed
|
self.is_collapsed
|
||||||
@@ -43,17 +51,21 @@ impl Collapsible for SidebarHeader {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for SidebarHeader {
|
impl ParentElement for SidebarHeader {
|
||||||
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
|
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
|
||||||
self.base.extend(elements);
|
self.base.extend(elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Styled for SidebarHeader {
|
impl Styled for SidebarHeader {
|
||||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||||
self.base.style()
|
self.base.style()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PopupMenuExt for SidebarHeader {}
|
impl PopupMenuExt for SidebarHeader {}
|
||||||
|
|
||||||
impl RenderOnce for SidebarHeader {
|
impl RenderOnce for SidebarHeader {
|
||||||
fn render(self, cx: &mut gpui::WindowContext) -> impl gpui::IntoElement {
|
fn render(self, cx: &mut gpui::WindowContext) -> impl gpui::IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|||||||
@@ -58,6 +58,13 @@ impl SidebarMenu {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SidebarMenu {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Collapsible for SidebarMenu {
|
impl Collapsible for SidebarMenu {
|
||||||
fn is_collapsed(&self) -> bool {
|
fn is_collapsed(&self) -> bool {
|
||||||
self.is_collapsed
|
self.is_collapsed
|
||||||
@@ -84,20 +91,22 @@ impl RenderOnce for SidebarMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Handler = Rc<dyn Fn(&ClickEvent, &mut WindowContext)>;
|
||||||
|
|
||||||
/// A sidebar menu item
|
/// A sidebar menu item
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
enum SidebarMenuItem {
|
enum SidebarMenuItem {
|
||||||
Item {
|
Item {
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
handler: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
|
handler: Handler,
|
||||||
active: bool,
|
active: bool,
|
||||||
is_collapsed: bool,
|
is_collapsed: bool,
|
||||||
},
|
},
|
||||||
Submenu {
|
Submenu {
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
handler: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
|
handler: Handler,
|
||||||
items: Vec<SidebarMenuItem>,
|
items: Vec<SidebarMenuItem>,
|
||||||
is_open: bool,
|
is_open: bool,
|
||||||
is_collapsed: bool,
|
is_collapsed: bool,
|
||||||
|
|||||||
@@ -106,13 +106,15 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnClick = Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>;
|
||||||
|
|
||||||
/// Sidebar collapse button with Icon.
|
/// Sidebar collapse button with Icon.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct SidebarToggleButton {
|
pub struct SidebarToggleButton {
|
||||||
btn: Button,
|
btn: Button,
|
||||||
is_collapsed: bool,
|
is_collapsed: bool,
|
||||||
side: Side,
|
side: Side,
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
on_click: OnClick,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SidebarToggleButton {
|
impl SidebarToggleButton {
|
||||||
@@ -158,12 +160,10 @@ impl RenderOnce for SidebarToggleButton {
|
|||||||
} else {
|
} else {
|
||||||
IconName::PanelRightOpen
|
IconName::PanelRightOpen
|
||||||
}
|
}
|
||||||
} else {
|
} else if self.side.is_left() {
|
||||||
if self.side.is_left() {
|
|
||||||
IconName::PanelLeftClose
|
IconName::PanelLeftClose
|
||||||
} else {
|
} else {
|
||||||
IconName::PanelRightClose
|
IconName::PanelRightClose
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.btn
|
self.btn
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ impl Skeleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Skeleton {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Styled for Skeleton {
|
impl Styled for Skeleton {
|
||||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||||
self.base.style()
|
self.base.style()
|
||||||
|
|||||||
@@ -350,17 +350,17 @@ impl<T: Styled> StyleSized<T> for T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait AxisExt {
|
pub trait AxisExt {
|
||||||
fn is_horizontal(self) -> bool;
|
fn is_horizontal(&self) -> bool;
|
||||||
fn is_vertical(self) -> bool;
|
fn is_vertical(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AxisExt for Axis {
|
impl AxisExt for Axis {
|
||||||
fn is_horizontal(self) -> bool {
|
fn is_horizontal(&self) -> bool {
|
||||||
self == Axis::Horizontal
|
self == &Axis::Horizontal
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_vertical(self) -> bool {
|
fn is_vertical(&self) -> bool {
|
||||||
self == Axis::Vertical
|
self == &Axis::Vertical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,17 +385,11 @@ impl Display for Placement {
|
|||||||
|
|
||||||
impl Placement {
|
impl Placement {
|
||||||
pub fn is_horizontal(&self) -> bool {
|
pub fn is_horizontal(&self) -> bool {
|
||||||
match self {
|
matches!(self, Placement::Left | Placement::Right)
|
||||||
Placement::Left | Placement::Right => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_vertical(&self) -> bool {
|
pub fn is_vertical(&self) -> bool {
|
||||||
match self {
|
matches!(self, Placement::Top | Placement::Bottom)
|
||||||
Placement::Top | Placement::Bottom => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn axis(&self) -> Axis {
|
pub fn axis(&self) -> Axis {
|
||||||
|
|||||||
@@ -184,6 +184,12 @@ impl SvgImg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SvgImg {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoElement for SvgImg {
|
impl IntoElement for SvgImg {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||||
|
|
||||||
|
type OnClick = Option<Rc<dyn Fn(&bool, &mut WindowContext)>>;
|
||||||
|
|
||||||
pub struct Switch {
|
pub struct Switch {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
label_side: Side,
|
label_side: Side,
|
||||||
on_click: Option<Rc<dyn Fn(&bool, &mut WindowContext)>>,
|
on_click: OnClick,
|
||||||
size: Size,
|
size: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod tab;
|
mod tab;
|
||||||
mod tab_bar;
|
mod tab_bar;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
hsla, point, AppContext, BoxShadow, Global, Hsla, ModelContext, Pixels, SharedString,
|
hsla, point, AppContext, BoxShadow, Global, Hsla, ModelContext, Pixels, SharedString,
|
||||||
ViewContext, WindowAppearance, WindowContext,
|
ViewContext, WindowAppearance, WindowContext,
|
||||||
};
|
};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use crate::scroll::ScrollbarShow;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
Theme::sync_system_appearance(cx)
|
Theme::sync_system_appearance(cx)
|
||||||
@@ -117,15 +118,19 @@ impl Colorize for Hsla {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return a new color with the lightness increased by the given factor.
|
/// Return a new color with the lightness increased by the given factor.
|
||||||
|
///
|
||||||
|
/// factor range: 0.0 .. 1.0
|
||||||
fn lighten(&self, factor: f32) -> Hsla {
|
fn lighten(&self, factor: f32) -> Hsla {
|
||||||
let l = self.l + (1.0 - self.l) * factor.clamp(0.0, 1.0).min(1.0);
|
let l = self.l * (1.0 + factor.clamp(0.0, 1.0));
|
||||||
|
|
||||||
Hsla { l, ..*self }
|
Hsla { l, ..*self }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a new color with the darkness increased by the given factor.
|
/// Return a new color with the darkness increased by the given factor.
|
||||||
|
///
|
||||||
|
/// factor range: 0.0 .. 1.0
|
||||||
fn darken(&self, factor: f32) -> Hsla {
|
fn darken(&self, factor: f32) -> Hsla {
|
||||||
let l = self.l * (1.0 - factor.clamp(0.0, 1.0).min(1.0));
|
let l = self.l * (1.0 - factor.clamp(0.0, 1.0));
|
||||||
|
|
||||||
Hsla { l, ..*self }
|
Hsla { l, ..*self }
|
||||||
}
|
}
|
||||||
@@ -181,6 +186,7 @@ pub struct ThemeColor {
|
|||||||
pub ring: Hsla,
|
pub ring: Hsla,
|
||||||
pub scrollbar: Hsla,
|
pub scrollbar: Hsla,
|
||||||
pub scrollbar_thumb: Hsla,
|
pub scrollbar_thumb: Hsla,
|
||||||
|
pub scrollbar_thumb_hover: Hsla,
|
||||||
pub secondary: Hsla,
|
pub secondary: Hsla,
|
||||||
pub secondary_active: Hsla,
|
pub secondary_active: Hsla,
|
||||||
pub secondary_foreground: Hsla,
|
pub secondary_foreground: Hsla,
|
||||||
@@ -253,8 +259,9 @@ impl ThemeColor {
|
|||||||
primary_hover: hsl(223.0, 5.9, 15.0),
|
primary_hover: hsl(223.0, 5.9, 15.0),
|
||||||
progress_bar: hsl(223.0, 5.9, 10.0),
|
progress_bar: hsl(223.0, 5.9, 10.0),
|
||||||
ring: hsl(240.0, 5.9, 65.0),
|
ring: hsl(240.0, 5.9, 65.0),
|
||||||
scrollbar: hsl(0., 0., 97.).opacity(0.3),
|
scrollbar: hsl(0., 0., 97.).opacity(0.75),
|
||||||
scrollbar_thumb: hsl(0., 0., 69.),
|
scrollbar_thumb: hsl(0., 0., 69.).opacity(0.9),
|
||||||
|
scrollbar_thumb_hover: hsl(0., 0., 59.),
|
||||||
secondary: hsl(240.0, 5.9, 96.9),
|
secondary: hsl(240.0, 5.9, 96.9),
|
||||||
secondary_active: hsl(240.0, 5.9, 93.),
|
secondary_active: hsl(240.0, 5.9, 93.),
|
||||||
secondary_foreground: hsl(240.0, 59.0, 10.),
|
secondary_foreground: hsl(240.0, 59.0, 10.),
|
||||||
@@ -327,8 +334,9 @@ impl ThemeColor {
|
|||||||
primary_hover: hsl(223.0, 0.0, 90.0),
|
primary_hover: hsl(223.0, 0.0, 90.0),
|
||||||
progress_bar: hsl(223.0, 0.0, 98.0),
|
progress_bar: hsl(223.0, 0.0, 98.0),
|
||||||
ring: hsl(240.0, 4.9, 83.9),
|
ring: hsl(240.0, 4.9, 83.9),
|
||||||
scrollbar: hsl(240., 1., 15.).opacity(0.3),
|
scrollbar: hsl(240., 1., 15.).opacity(0.75),
|
||||||
scrollbar_thumb: hsl(0., 0., 68.),
|
scrollbar_thumb: hsl(0., 0., 48.).opacity(0.9),
|
||||||
|
scrollbar_thumb_hover: hsl(0., 0., 68.),
|
||||||
secondary: hsl(240.0, 0., 13.0),
|
secondary: hsl(240.0, 0., 13.0),
|
||||||
secondary_active: hsl(240.0, 0., 10.),
|
secondary_active: hsl(240.0, 0., 10.),
|
||||||
secondary_foreground: hsl(0.0, 0.0, 78.0),
|
secondary_foreground: hsl(0.0, 0.0, 78.0),
|
||||||
@@ -373,6 +381,8 @@ pub struct Theme {
|
|||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
pub shadow: bool,
|
pub shadow: bool,
|
||||||
pub transparent: Hsla,
|
pub transparent: Hsla,
|
||||||
|
/// Show the scrollbar mode, default: Scrolling
|
||||||
|
pub scrollbar_show: ScrollbarShow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Theme {
|
impl Deref for Theme {
|
||||||
@@ -434,6 +444,7 @@ impl Theme {
|
|||||||
// self.selection = self.selection.apply(mask_color);
|
// self.selection = self.selection.apply(mask_color);
|
||||||
self.scrollbar = self.scrollbar.apply(mask_color);
|
self.scrollbar = self.scrollbar.apply(mask_color);
|
||||||
self.scrollbar_thumb = self.scrollbar_thumb.apply(mask_color);
|
self.scrollbar_thumb = self.scrollbar_thumb.apply(mask_color);
|
||||||
|
self.scrollbar_thumb_hover = self.scrollbar_thumb_hover.apply(mask_color);
|
||||||
self.panel = self.panel.apply(mask_color);
|
self.panel = self.panel.apply(mask_color);
|
||||||
self.drag_border = self.drag_border.apply(mask_color);
|
self.drag_border = self.drag_border.apply(mask_color);
|
||||||
self.drop_target = self.drop_target.apply(mask_color);
|
self.drop_target = self.drop_target.apply(mask_color);
|
||||||
@@ -518,6 +529,7 @@ impl From<ThemeColor> for Theme {
|
|||||||
},
|
},
|
||||||
radius: 4.0,
|
radius: 4.0,
|
||||||
shadow: true,
|
shadow: true,
|
||||||
|
scrollbar_show: ScrollbarShow::default(),
|
||||||
colors,
|
colors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,3 +547,28 @@ impl ThemeMode {
|
|||||||
matches!(self, Self::Dark)
|
matches!(self, Self::Dark)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::theme::Colorize as _;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lighten() {
|
||||||
|
let color = super::hsl(240.0, 5.0, 30.0);
|
||||||
|
let color = color.lighten(0.5);
|
||||||
|
assert_eq!(color.l, 0.45000002);
|
||||||
|
let color = color.lighten(0.5);
|
||||||
|
assert_eq!(color.l, 0.675);
|
||||||
|
let color = color.lighten(0.1);
|
||||||
|
assert_eq!(color.l, 0.7425);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_darken() {
|
||||||
|
let color = super::hsl(240.0, 5.0, 96.0);
|
||||||
|
let color = color.darken(0.5);
|
||||||
|
assert_eq!(color.l, 0.48);
|
||||||
|
let color = color.darken(0.5);
|
||||||
|
assert_eq!(color.l, 0.24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const TITLE_BAR_LEFT_PADDING: Pixels = px(80.);
|
|||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
const TITLE_BAR_LEFT_PADDING: Pixels = px(12.);
|
const TITLE_BAR_LEFT_PADDING: Pixels = px(12.);
|
||||||
|
|
||||||
|
type OnCloseWindow = Option<Rc<Box<dyn Fn(&ClickEvent, &mut WindowContext)>>>;
|
||||||
|
|
||||||
/// TitleBar used to customize the appearance of the title bar.
|
/// TitleBar used to customize the appearance of the title bar.
|
||||||
///
|
///
|
||||||
/// We can put some elements inside the title bar.
|
/// We can put some elements inside the title bar.
|
||||||
@@ -20,7 +22,7 @@ const TITLE_BAR_LEFT_PADDING: Pixels = px(12.);
|
|||||||
pub struct TitleBar {
|
pub struct TitleBar {
|
||||||
base: Stateful<Div>,
|
base: Stateful<Div>,
|
||||||
children: Vec<AnyElement>,
|
children: Vec<AnyElement>,
|
||||||
on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut WindowContext)>>>,
|
on_close_window: OnCloseWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TitleBar {
|
impl TitleBar {
|
||||||
@@ -45,6 +47,12 @@ impl TitleBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TitleBar {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The Windows control buttons have a fixed width of 35px.
|
// The Windows control buttons have a fixed width of 35px.
|
||||||
//
|
//
|
||||||
// We don't need implementation the click event for the control buttons.
|
// We don't need implementation the click event for the control buttons.
|
||||||
@@ -54,9 +62,7 @@ enum ControlIcon {
|
|||||||
Minimize,
|
Minimize,
|
||||||
Restore,
|
Restore,
|
||||||
Maximize,
|
Maximize,
|
||||||
Close {
|
Close { on_close_window: OnCloseWindow },
|
||||||
on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut WindowContext)>>>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControlIcon {
|
impl ControlIcon {
|
||||||
@@ -72,7 +78,7 @@ impl ControlIcon {
|
|||||||
Self::Maximize
|
Self::Maximize
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut WindowContext)>>>) -> Self {
|
fn close(on_close_window: OnCloseWindow) -> Self {
|
||||||
Self::Close { on_close_window }
|
Self::Close { on_close_window }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +179,7 @@ impl RenderOnce for ControlIcon {
|
|||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
struct WindowControls {
|
struct WindowControls {
|
||||||
on_close_window: Option<Rc<Box<dyn Fn(&ClickEvent, &mut WindowContext)>>>,
|
on_close_window: OnCloseWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for WindowControls {
|
impl RenderOnce for WindowControls {
|
||||||
@@ -230,9 +236,9 @@ impl RenderOnce for TitleBar {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.h(HEIGHT)
|
.h(HEIGHT)
|
||||||
.border_b_1()
|
|
||||||
.border_color(cx.theme().title_bar_border)
|
|
||||||
.bg(cx.theme().title_bar)
|
.bg(cx.theme().title_bar)
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().title_bar_border.opacity(0.7))
|
||||||
.when(cx.is_fullscreen(), |this| this.pl(px(12.)))
|
.when(cx.is_fullscreen(), |this| this.pl(px(12.)))
|
||||||
.on_double_click(|_, cx| cx.zoom_window())
|
.on_double_click(|_, cx| cx.zoom_window())
|
||||||
.child(
|
.child(
|
||||||
@@ -286,13 +292,18 @@ impl Element for TitleBarElement {
|
|||||||
_: Option<&gpui::GlobalElementId>,
|
_: Option<&gpui::GlobalElementId>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||||
let mut style = Style::default();
|
let style = Style {
|
||||||
style.flex_grow = 1.0;
|
flex_grow: 1.0,
|
||||||
style.flex_shrink = 1.0;
|
flex_shrink: 1.0,
|
||||||
style.size.width = relative(1.).into();
|
size: gpui::Size {
|
||||||
style.size.height = relative(1.).into();
|
width: relative(1.).into(),
|
||||||
|
height: relative(1.).into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let id = cx.request_layout(style, []);
|
let id = cx.request_layout(style, []);
|
||||||
|
|
||||||
(id, ())
|
(id, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub struct Tooltip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Tooltip {
|
impl Tooltip {
|
||||||
|
#[allow(clippy::new_ret_no_self)]
|
||||||
pub fn new(text: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
|
pub fn new(text: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
|
||||||
cx.new_view(|_| Self { text: text.into() }).into()
|
cx.new_view(|_| Self { text: text.into() }).into()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user