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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -2,6 +2,7 @@ mod blink_cursor;
mod change;
mod clear_button;
mod element;
#[allow(clippy::module_inception)]
mod input;
mod otp_input;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.
#[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

View File

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

View File

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

View File

@@ -184,6 +184,12 @@ impl SvgImg {
}
}
impl Default for SvgImg {
fn default() -> Self {
Self::new()
}
}
impl IntoElement for SvgImg {
type Element = Self;

View File

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

View File

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

View File

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

View File

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

View File

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