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