fix clippy issues

This commit is contained in:
2024-12-11 09:11:30 +07:00
parent 516eb0e8bc
commit 10f042acab
49 changed files with 661 additions and 319 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
#[allow(clippy::module_inception)]
mod list; mod list;
mod list_item; mod list_item;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
#[allow(clippy::module_inception)]
mod tab; mod tab;
mod tab_bar; mod tab_bar;

View File

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

View File

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

View File

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