chore: Improve Request Screening (#101)
* open chat while screening * close panel on ignore * bypass screening * . * improve settings * refine modal * . * . * . * . * .
This commit is contained in:
@@ -41,11 +41,6 @@ pub trait ButtonVariants: Sized {
|
||||
self.with_variant(ButtonVariant::Danger)
|
||||
}
|
||||
|
||||
/// With the danger alternate style for the Button.
|
||||
fn danger_alt(self) -> Self {
|
||||
self.with_variant(ButtonVariant::DangerAlt)
|
||||
}
|
||||
|
||||
/// With the warning style for the Button.
|
||||
fn warning(self) -> Self {
|
||||
self.with_variant(ButtonVariant::Warning)
|
||||
@@ -104,7 +99,6 @@ pub enum ButtonVariant {
|
||||
Primary,
|
||||
Secondary,
|
||||
Danger,
|
||||
DangerAlt,
|
||||
Warning,
|
||||
Ghost,
|
||||
Transparent,
|
||||
@@ -286,6 +280,7 @@ impl RenderOnce for Button {
|
||||
let normal_style = style.normal(window, cx);
|
||||
let icon_size = match self.size {
|
||||
Size::Size(v) => Size::Size(v * 0.75),
|
||||
Size::Medium => Size::Small,
|
||||
_ => self.size,
|
||||
};
|
||||
|
||||
@@ -307,6 +302,7 @@ impl RenderOnce for Button {
|
||||
Size::Size(px) => this.size(px),
|
||||
Size::XSmall => this.size_5(),
|
||||
Size::Small => this.size_6(),
|
||||
Size::Medium => this.size_7(),
|
||||
_ => this.size_9(),
|
||||
}
|
||||
} else {
|
||||
@@ -321,8 +317,8 @@ impl RenderOnce for Button {
|
||||
this.h_7().px_3()
|
||||
}
|
||||
}
|
||||
Size::Medium => this.h_8().px_3(),
|
||||
Size::Large => this.h_10().px_4(),
|
||||
_ => this.h_9().px_2(),
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -342,21 +338,6 @@ impl RenderOnce for Button {
|
||||
this.bg(active_style.bg).text_color(active_style.fg)
|
||||
})
|
||||
})
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled && !self.loading),
|
||||
|this, on_click| {
|
||||
let stop_propagation = self.stop_propagation;
|
||||
this.on_mouse_down(MouseButton::Left, move |_, window, cx| {
|
||||
window.prevent_default();
|
||||
if stop_propagation {
|
||||
cx.stop_propagation();
|
||||
}
|
||||
})
|
||||
.on_click(move |event, window, cx| {
|
||||
(on_click)(event, window, cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
.when(self.disabled, |this| {
|
||||
let disabled_style = style.disabled(window, cx);
|
||||
this.cursor_not_allowed()
|
||||
@@ -403,6 +384,21 @@ impl RenderOnce for Button {
|
||||
.when_some(self.tooltip.clone(), |this, tooltip| {
|
||||
this.tooltip(move |window, cx| Tooltip::new(tooltip.clone(), window, cx).into())
|
||||
})
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled && !self.loading),
|
||||
|this, on_click| {
|
||||
let stop_propagation = self.stop_propagation;
|
||||
this.on_mouse_down(MouseButton::Left, move |_, window, cx| {
|
||||
window.prevent_default();
|
||||
if stop_propagation {
|
||||
cx.stop_propagation();
|
||||
}
|
||||
})
|
||||
.on_click(move |event, window, cx| {
|
||||
(on_click)(event, window, cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +431,6 @@ impl ButtonVariant {
|
||||
ButtonVariant::Primary => cx.theme().element_foreground,
|
||||
ButtonVariant::Secondary => cx.theme().text_muted,
|
||||
ButtonVariant::Danger => cx.theme().danger_foreground,
|
||||
ButtonVariant::DangerAlt => cx.theme().danger_background,
|
||||
ButtonVariant::Warning => cx.theme().warning_foreground,
|
||||
ButtonVariant::Transparent => cx.theme().text_placeholder,
|
||||
ButtonVariant::Ghost => cx.theme().text_muted,
|
||||
@@ -448,7 +443,6 @@ impl ButtonVariant {
|
||||
ButtonVariant::Primary => cx.theme().element_hover,
|
||||
ButtonVariant::Secondary => cx.theme().secondary_hover,
|
||||
ButtonVariant::Danger => cx.theme().danger_hover,
|
||||
ButtonVariant::DangerAlt => gpui::transparent_black(),
|
||||
ButtonVariant::Warning => cx.theme().warning_hover,
|
||||
ButtonVariant::Ghost => cx.theme().ghost_element_hover,
|
||||
ButtonVariant::Transparent => gpui::transparent_black(),
|
||||
@@ -470,7 +464,6 @@ impl ButtonVariant {
|
||||
ButtonVariant::Primary => cx.theme().element_active,
|
||||
ButtonVariant::Secondary => cx.theme().secondary_active,
|
||||
ButtonVariant::Danger => cx.theme().danger_active,
|
||||
ButtonVariant::DangerAlt => gpui::transparent_black(),
|
||||
ButtonVariant::Warning => cx.theme().warning_active,
|
||||
ButtonVariant::Ghost => cx.theme().ghost_element_active,
|
||||
ButtonVariant::Transparent => gpui::transparent_black(),
|
||||
@@ -491,7 +484,6 @@ impl ButtonVariant {
|
||||
ButtonVariant::Primary => cx.theme().element_selected,
|
||||
ButtonVariant::Secondary => cx.theme().secondary_selected,
|
||||
ButtonVariant::Danger => cx.theme().danger_selected,
|
||||
ButtonVariant::DangerAlt => gpui::transparent_black(),
|
||||
ButtonVariant::Warning => cx.theme().warning_selected,
|
||||
ButtonVariant::Ghost => cx.theme().ghost_element_selected,
|
||||
ButtonVariant::Transparent => gpui::transparent_black(),
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::sync::Arc;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
actions, canvas, div, px, AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, Edges,
|
||||
Entity, EntityId, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _,
|
||||
Pixels, Render, Styled, Subscription, WeakEntity, Window,
|
||||
Entity, EntityId, EventEmitter, Focusable, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, Pixels, Render, Styled, Subscription, WeakEntity, Window,
|
||||
};
|
||||
|
||||
use crate::dock_area::dock::{Dock, DockPlacement};
|
||||
@@ -39,7 +39,7 @@ pub enum DockEvent {
|
||||
pub struct DockArea {
|
||||
pub(crate) bounds: Bounds<Pixels>,
|
||||
/// The center view of the dockarea.
|
||||
items: DockItem,
|
||||
pub items: DockItem,
|
||||
/// The entity_id of the [`TabPanel`](TabPanel) where each toggle button should be displayed,
|
||||
toggle_button_panels: Edges<Option<EntityId>>,
|
||||
/// The left dock of the dock_area.
|
||||
@@ -73,7 +73,7 @@ pub enum DockItem {
|
||||
active_ix: usize,
|
||||
view: Entity<TabPanel>,
|
||||
},
|
||||
/// Panel layout
|
||||
/// Single panel layout
|
||||
Panel { view: Arc<dyn PanelView> },
|
||||
}
|
||||
|
||||
@@ -286,6 +286,12 @@ impl DockItem {
|
||||
DockItem::Panel { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn focus_tab_panel(&self, window: &mut Window, cx: &mut App) {
|
||||
if let DockItem::Tabs { view, .. } = self {
|
||||
window.focus(&view.read(cx).focus_handle(cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DockArea {
|
||||
@@ -572,12 +578,8 @@ impl DockArea {
|
||||
}
|
||||
}
|
||||
DockPlacement::Center => {
|
||||
let focus_handle = panel.focus_handle(cx);
|
||||
// Add panel
|
||||
self.items
|
||||
.add_panel(panel, &cx.entity().downgrade(), window, cx);
|
||||
// Focus to the newly added panel
|
||||
window.focus(&focus_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -707,6 +709,10 @@ impl DockArea {
|
||||
.and_then(|dock| dock.read(cx).panel.left_top_tab_panel(cx))
|
||||
.map(|view| view.entity_id());
|
||||
}
|
||||
|
||||
pub fn focus_tab_panel(&mut self, window: &mut Window, cx: &mut App) {
|
||||
self.items.focus_tab_panel(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DockEvent> for DockArea {}
|
||||
|
||||
@@ -13,7 +13,6 @@ use super::panel::PanelView;
|
||||
use super::stack_panel::StackPanel;
|
||||
use super::{ClosePanel, DockArea, PanelEvent, PanelStyle, ToggleZoom};
|
||||
use crate::button::{Button, ButtonVariants as _};
|
||||
use crate::dock_area::dock::DockPlacement;
|
||||
use crate::dock_area::panel::Panel;
|
||||
use crate::popup_menu::{PopupMenu, PopupMenuExt};
|
||||
use crate::tab::tab_bar::TabBar;
|
||||
@@ -454,89 +453,6 @@ impl TabPanel {
|
||||
)
|
||||
}
|
||||
|
||||
fn _render_dock_toggle_button(
|
||||
&self,
|
||||
placement: DockPlacement,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
if self.is_zoomed {
|
||||
return None;
|
||||
}
|
||||
|
||||
let dock_area = self.dock_area.upgrade()?.read(cx);
|
||||
|
||||
if !dock_area.is_dock_collapsible(placement, cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let view_entity_id = cx.entity().entity_id();
|
||||
let toggle_button_panels = dock_area.toggle_button_panels;
|
||||
|
||||
// Check if current TabPanel's entity_id matches the one stored in DockArea for this placement
|
||||
if !match placement {
|
||||
DockPlacement::Left => {
|
||||
dock_area.left_dock.is_some() && toggle_button_panels.left == Some(view_entity_id)
|
||||
}
|
||||
DockPlacement::Right => {
|
||||
dock_area.right_dock.is_some() && toggle_button_panels.right == Some(view_entity_id)
|
||||
}
|
||||
DockPlacement::Bottom => {
|
||||
dock_area.bottom_dock.is_some()
|
||||
&& toggle_button_panels.bottom == Some(view_entity_id)
|
||||
}
|
||||
DockPlacement::Center => unreachable!(),
|
||||
} {
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_open = dock_area.is_dock_open(placement, cx);
|
||||
|
||||
let icon = match placement {
|
||||
DockPlacement::Left => {
|
||||
if is_open {
|
||||
IconName::PanelLeft
|
||||
} else {
|
||||
IconName::PanelLeftOpen
|
||||
}
|
||||
}
|
||||
DockPlacement::Right => {
|
||||
if is_open {
|
||||
IconName::PanelRight
|
||||
} else {
|
||||
IconName::PanelRightOpen
|
||||
}
|
||||
}
|
||||
DockPlacement::Bottom => {
|
||||
if is_open {
|
||||
IconName::PanelBottom
|
||||
} else {
|
||||
IconName::PanelBottomOpen
|
||||
}
|
||||
}
|
||||
DockPlacement::Center => unreachable!(),
|
||||
};
|
||||
|
||||
Some(
|
||||
Button::new(SharedString::from(format!("toggle-dock:{placement:?}")))
|
||||
.icon(icon)
|
||||
.small()
|
||||
.ghost()
|
||||
.tooltip(match is_open {
|
||||
true => "Collapse",
|
||||
false => "Expand",
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
let dock_area = self.dock_area.clone();
|
||||
move |_, _, window, cx| {
|
||||
_ = dock_area.update(cx, |dock_area, cx| {
|
||||
dock_area.toggle_dock(placement, window, cx);
|
||||
});
|
||||
}
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_title_bar(
|
||||
&self,
|
||||
state: &TabState,
|
||||
@@ -1038,6 +954,7 @@ impl Render for TabPanel {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
let active_panel = self.active_panel(cx);
|
||||
|
||||
let mut state = TabState {
|
||||
closable: self.closable(cx),
|
||||
draggable: self.draggable(cx),
|
||||
|
||||
@@ -57,6 +57,7 @@ pub enum IconName {
|
||||
Relays,
|
||||
ResizeCorner,
|
||||
Reply,
|
||||
Report,
|
||||
Forward,
|
||||
Search,
|
||||
SearchFill,
|
||||
@@ -128,6 +129,7 @@ impl IconName {
|
||||
Self::Relays => "icons/relays.svg",
|
||||
Self::ResizeCorner => "icons/resize-corner.svg",
|
||||
Self::Reply => "icons/reply.svg",
|
||||
Self::Report => "icons/report.svg",
|
||||
Self::Forward => "icons/forward.svg",
|
||||
Self::Search => "icons/search.svg",
|
||||
Self::SearchFill => "icons/search-fill.svg",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::cell::Cell;
|
||||
use std::ops::{Deref, Range};
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
@@ -358,7 +357,7 @@ pub struct InputState {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(super) validate: Option<Box<dyn Fn(&str) -> bool + 'static>>,
|
||||
pub(crate) scroll_handle: ScrollHandle,
|
||||
pub(super) scrollbar_state: Rc<Cell<ScrollbarState>>,
|
||||
pub(super) scrollbar_state: ScrollbarState,
|
||||
/// The size of the scrollable content.
|
||||
pub(crate) scroll_size: gpui::Size<Pixels>,
|
||||
pub(crate) line_number_width: Pixels,
|
||||
@@ -434,7 +433,7 @@ impl InputState {
|
||||
last_selected_range: None,
|
||||
last_cursor_offset: None,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
scrollbar_state: Rc::new(Cell::new(ScrollbarState::default())),
|
||||
scrollbar_state: ScrollbarState::default(),
|
||||
scroll_size: gpui::size(px(0.), px(0.)),
|
||||
line_number_width: px(0.),
|
||||
preferred_x_offset: None,
|
||||
|
||||
@@ -276,8 +276,6 @@ impl RenderOnce for TextInput {
|
||||
.children(suffix),
|
||||
)
|
||||
.when(state.is_multi_line(), |this| {
|
||||
let entity_id = self.state.entity_id();
|
||||
|
||||
if state.last_layout.is_some() {
|
||||
this.relative().child(
|
||||
div()
|
||||
@@ -287,13 +285,8 @@ impl RenderOnce for TextInput {
|
||||
.right(px(1.))
|
||||
.bottom_0()
|
||||
.child(
|
||||
Scrollbar::vertical(
|
||||
entity_id,
|
||||
state.scrollbar_state.clone(),
|
||||
state.scroll_handle.clone(),
|
||||
state.scroll_size,
|
||||
)
|
||||
.axis(ScrollbarAxis::Vertical),
|
||||
Scrollbar::vertical(&state.scrollbar_state, &state.scroll_handle)
|
||||
.axis(ScrollbarAxis::Vertical),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::cell::Cell;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::prelude::FluentBuilder;
|
||||
@@ -154,7 +152,7 @@ pub struct List<D: ListDelegate> {
|
||||
querying: bool,
|
||||
scrollbar_visible: bool,
|
||||
vertical_scroll_handle: UniformListScrollHandle,
|
||||
scrollbar_state: Rc<Cell<ScrollbarState>>,
|
||||
scrollbar_state: ScrollbarState,
|
||||
pub(crate) size: Size,
|
||||
selected_index: Option<usize>,
|
||||
right_clicked_index: Option<usize>,
|
||||
@@ -181,7 +179,7 @@ where
|
||||
selected_index: None,
|
||||
right_clicked_index: None,
|
||||
vertical_scroll_handle: UniformListScrollHandle::new(),
|
||||
scrollbar_state: Rc::new(Cell::new(ScrollbarState::new())),
|
||||
scrollbar_state: ScrollbarState::default(),
|
||||
max_height: None,
|
||||
scrollbar_visible: true,
|
||||
selectable: true,
|
||||
@@ -265,15 +263,18 @@ where
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn render_scrollbar(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<impl IntoElement> {
|
||||
fn render_scrollbar(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
if !self.scrollbar_visible {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Scrollbar::uniform_scroll(
|
||||
cx.entity().entity_id(),
|
||||
self.scrollbar_state.clone(),
|
||||
self.vertical_scroll_handle.clone(),
|
||||
&self.scrollbar_state,
|
||||
&self.vertical_scroll_handle,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ use std::time::Duration;
|
||||
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
anchored, div, hsla, point, px, relative, Animation, AnimationExt as _, AnyElement, App,
|
||||
Bounds, BoxShadow, ClickEvent, Div, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
|
||||
MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, Styled, Window,
|
||||
anchored, div, hsla, point, px, Animation, AnimationExt as _, AnyElement, App, Axis, Bounds,
|
||||
BoxShadow, ClickEvent, Div, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
|
||||
MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, StyleRefinement, Styled,
|
||||
Window,
|
||||
};
|
||||
use theme::ActiveTheme;
|
||||
|
||||
@@ -77,7 +78,7 @@ impl ModalButtonProps {
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Modal {
|
||||
base: Div,
|
||||
style: StyleRefinement,
|
||||
title: Option<AnyElement>,
|
||||
footer: Option<FooterFn>,
|
||||
content: Div,
|
||||
@@ -88,11 +89,12 @@ pub struct Modal {
|
||||
on_close: OnClose,
|
||||
on_ok: OnOk,
|
||||
on_cancel: OnCancel,
|
||||
button_props: ModalButtonProps,
|
||||
show_close: bool,
|
||||
|
||||
overlay: bool,
|
||||
overlay_closable: bool,
|
||||
keyboard: bool,
|
||||
show_close: bool,
|
||||
button_props: ModalButtonProps,
|
||||
|
||||
/// This will be change when open the modal, the focus handle is create when open the modal.
|
||||
pub(crate) focus_handle: FocusHandle,
|
||||
@@ -102,18 +104,8 @@ pub struct Modal {
|
||||
|
||||
impl Modal {
|
||||
pub fn new(_window: &mut Window, cx: &mut App) -> Self {
|
||||
let radius = (cx.theme().radius * 2.).min(px(20.));
|
||||
|
||||
let base = v_flex()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.rounded(radius)
|
||||
.shadow_xl()
|
||||
.min_h_24();
|
||||
|
||||
Self {
|
||||
base,
|
||||
style: StyleRefinement::default(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
title: None,
|
||||
footer: None,
|
||||
@@ -276,7 +268,7 @@ impl ParentElement for Modal {
|
||||
|
||||
impl Styled for Modal {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
self.base.style()
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,6 +342,7 @@ impl RenderOnce for Modal {
|
||||
});
|
||||
|
||||
let window_paddings = crate::window_border::window_paddings(window, cx);
|
||||
let radius = (cx.theme().radius * 2.).min(px(20.));
|
||||
|
||||
let view_size = window.viewport_size()
|
||||
- gpui::size(
|
||||
@@ -366,6 +359,17 @@ impl RenderOnce for Modal {
|
||||
let y = self.margin_top.unwrap_or(view_size.height / 10.) + offset_top;
|
||||
let x = bounds.center().x - self.width / 2.;
|
||||
|
||||
let mut padding_right = px(16.);
|
||||
let mut padding_left = px(16.);
|
||||
|
||||
if let Some(pl) = self.style.padding.left {
|
||||
padding_left = pl.to_pixels(self.width.into(), window.rem_size());
|
||||
}
|
||||
|
||||
if let Some(pr) = self.style.padding.right {
|
||||
padding_right = pr.to_pixels(self.width.into(), window.rem_size());
|
||||
}
|
||||
|
||||
let animation = Animation::new(Duration::from_secs_f64(0.25))
|
||||
.with_easing(cubic_bezier(0.32, 0.72, 0., 1.));
|
||||
|
||||
@@ -374,6 +378,7 @@ impl RenderOnce for Modal {
|
||||
.snap_to_window()
|
||||
.child(
|
||||
div()
|
||||
.id("modal")
|
||||
.w(view_size.width)
|
||||
.h(view_size.height)
|
||||
.when(self.overlay_visible, |this| {
|
||||
@@ -396,10 +401,17 @@ impl RenderOnce for Modal {
|
||||
})
|
||||
})
|
||||
.child(
|
||||
self.base
|
||||
.id(SharedString::from(format!("modal-{layer_ix}")))
|
||||
v_flex()
|
||||
.id(layer_ix)
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border.alpha(0.4))
|
||||
.rounded(radius)
|
||||
.shadow_xl()
|
||||
.min_h_24()
|
||||
.key_context(CONTEXT)
|
||||
.track_focus(&self.focus_handle)
|
||||
.refine_style(&self.style)
|
||||
.when(self.keyboard, |this| {
|
||||
this.on_action({
|
||||
let on_cancel = on_cancel.clone();
|
||||
@@ -430,6 +442,7 @@ impl RenderOnce for Modal {
|
||||
}
|
||||
})
|
||||
})
|
||||
// There style is high priority, can't be overridden.
|
||||
.absolute()
|
||||
.occlude()
|
||||
.relative()
|
||||
@@ -437,57 +450,59 @@ impl RenderOnce for Modal {
|
||||
.top(y)
|
||||
.w(self.width)
|
||||
.when_some(self.max_width, |this, w| this.max_w(w))
|
||||
.when_some(self.title, |this, title| {
|
||||
this.child(
|
||||
div()
|
||||
.h_12()
|
||||
.px_3()
|
||||
.mb_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.font_semibold()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().border)
|
||||
.line_height(relative(1.))
|
||||
.child(title),
|
||||
)
|
||||
})
|
||||
.child(h_flex().h_4().px_3().justify_center().when_some(
|
||||
self.title,
|
||||
|this, title| {
|
||||
this.h_12().font_semibold().text_center().child(title)
|
||||
},
|
||||
))
|
||||
.when(self.show_close, |this| {
|
||||
this.child(
|
||||
Button::new(SharedString::from(format!(
|
||||
"modal-close-{layer_ix}"
|
||||
)))
|
||||
.icon(IconName::CloseCircleFill)
|
||||
.absolute()
|
||||
.top_1p5()
|
||||
.right_2()
|
||||
.custom(
|
||||
ButtonCustomVariant::new(window, cx)
|
||||
.foreground(cx.theme().icon_muted)
|
||||
.color(cx.theme().ghost_element_background)
|
||||
.hover(cx.theme().ghost_element_background)
|
||||
.active(cx.theme().ghost_element_background),
|
||||
)
|
||||
.on_click(
|
||||
move |_, window, cx| {
|
||||
Button::new("close")
|
||||
.icon(IconName::CloseCircleFill)
|
||||
.absolute()
|
||||
.top_1p5()
|
||||
.right_2()
|
||||
.custom(
|
||||
ButtonCustomVariant::new(window, cx)
|
||||
.foreground(cx.theme().icon_muted)
|
||||
.color(cx.theme().ghost_element_background)
|
||||
.hover(cx.theme().ghost_element_background)
|
||||
.active(cx.theme().ghost_element_background),
|
||||
)
|
||||
.on_click(move |_, window, cx| {
|
||||
on_cancel(&ClickEvent::default(), window, cx);
|
||||
on_close(&ClickEvent::default(), window, cx);
|
||||
window.close_modal(cx);
|
||||
},
|
||||
),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(div().relative().w_full().flex_1().child(self.content))
|
||||
.when(self.footer.is_some(), |this| {
|
||||
let footer = self.footer.unwrap();
|
||||
|
||||
.child(
|
||||
div()
|
||||
.pt_px()
|
||||
.w_full()
|
||||
.h_auto()
|
||||
.flex_1()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
v_flex()
|
||||
.pr(padding_right)
|
||||
.pl(padding_left)
|
||||
.scrollable(Axis::Vertical)
|
||||
.child(self.content),
|
||||
),
|
||||
)
|
||||
.when_some(self.footer, |this, footer| {
|
||||
this.child(
|
||||
h_flex().p_4().gap_1p5().justify_center().children(footer(
|
||||
render_ok,
|
||||
render_cancel,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.pt(padding_left)
|
||||
.pr(padding_right)
|
||||
.pb(padding_left)
|
||||
.pl(padding_right)
|
||||
.justify_end()
|
||||
.children(footer(render_ok, render_cancel, window, cx)),
|
||||
)
|
||||
})
|
||||
.with_animation("slide-down", animation.clone(), move |this, delta| {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::cell::Cell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -125,7 +124,7 @@ pub struct PopupMenu {
|
||||
|
||||
scrollable: bool,
|
||||
scroll_handle: ScrollHandle,
|
||||
scroll_state: Rc<Cell<ScrollbarState>>,
|
||||
scroll_state: ScrollbarState,
|
||||
|
||||
action_focus_handle: Option<FocusHandle>,
|
||||
#[allow(dead_code)]
|
||||
@@ -159,7 +158,7 @@ impl PopupMenu {
|
||||
bounds: Bounds::default(),
|
||||
scrollable: false,
|
||||
scroll_handle: ScrollHandle::default(),
|
||||
scroll_state: Rc::new(Cell::new(ScrollbarState::default())),
|
||||
scroll_state: ScrollbarState::default(),
|
||||
subscriptions,
|
||||
};
|
||||
|
||||
@@ -714,12 +713,7 @@ impl Render for PopupMenu {
|
||||
.left_0()
|
||||
.right_0p5()
|
||||
.bottom_0()
|
||||
.child(Scrollbar::vertical(
|
||||
cx.entity_id(),
|
||||
self.scroll_state.clone(),
|
||||
self.scroll_handle.clone(),
|
||||
self.bounds.size,
|
||||
)),
|
||||
.child(Scrollbar::vertical(&self.scroll_state, &self.scroll_handle)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
canvas, div, relative, AnyElement, App, Div, Element, ElementId, EntityId, GlobalElementId,
|
||||
InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString,
|
||||
Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, Window,
|
||||
div, relative, AnyElement, App, Bounds, Div, Element, ElementId, GlobalElementId,
|
||||
InspectorElementId, InteractiveElement, Interactivity, IntoElement, LayoutId, ParentElement,
|
||||
Pixels, Position, ScrollHandle, SharedString, Size, Stateful, StatefulInteractiveElement,
|
||||
Style, StyleRefinement, Styled, Window,
|
||||
};
|
||||
|
||||
use super::{Scrollbar, ScrollbarAxis, ScrollbarState};
|
||||
@@ -13,7 +11,6 @@ use super::{Scrollbar, ScrollbarAxis, ScrollbarState};
|
||||
pub struct Scrollable<E> {
|
||||
id: ElementId,
|
||||
element: Option<E>,
|
||||
view_id: EntityId,
|
||||
axis: ScrollbarAxis,
|
||||
/// This is a fake element to handle Styled, InteractiveElement, not used.
|
||||
_element: Stateful<Div>,
|
||||
@@ -23,19 +20,16 @@ impl<E> Scrollable<E>
|
||||
where
|
||||
E: Element,
|
||||
{
|
||||
pub(crate) fn new(view_id: EntityId, element: E, axis: ScrollbarAxis) -> Self {
|
||||
let id = ElementId::Name(SharedString::from(format!(
|
||||
"ScrollView:{}-{:?}",
|
||||
view_id,
|
||||
element.id(),
|
||||
)));
|
||||
pub(crate) fn new(axis: impl Into<ScrollbarAxis>, element: E) -> Self {
|
||||
let id = ElementId::Name(SharedString::from(
|
||||
format!("scrollable-{:?}", element.id(),),
|
||||
));
|
||||
|
||||
Self {
|
||||
element: Some(element),
|
||||
_element: div().id("fake"),
|
||||
id,
|
||||
view_id,
|
||||
axis,
|
||||
axis: axis.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +47,8 @@ where
|
||||
}
|
||||
|
||||
/// Set the axis of the scroll view.
|
||||
pub fn set_axis(&mut self, axis: ScrollbarAxis) {
|
||||
self.axis = axis;
|
||||
pub fn set_axis(&mut self, axis: impl Into<ScrollbarAxis>) {
|
||||
self.axis = axis.into();
|
||||
}
|
||||
|
||||
fn with_element_state<R>(
|
||||
@@ -76,8 +70,7 @@ where
|
||||
}
|
||||
|
||||
pub struct ScrollViewState {
|
||||
scroll_size: Rc<Cell<Size<Pixels>>>,
|
||||
state: Rc<Cell<ScrollbarState>>,
|
||||
state: ScrollbarState,
|
||||
handle: ScrollHandle,
|
||||
}
|
||||
|
||||
@@ -85,8 +78,7 @@ impl Default for ScrollViewState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
handle: ScrollHandle::new(),
|
||||
scroll_size: Rc::new(Cell::new(Size::default())),
|
||||
state: Rc::new(Cell::new(ScrollbarState::default())),
|
||||
state: ScrollbarState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +111,7 @@ impl<E> InteractiveElement for Scrollable<E>
|
||||
where
|
||||
E: Element + InteractiveElement,
|
||||
{
|
||||
fn interactivity(&mut self) -> &mut gpui::Interactivity {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
if let Some(element) = &mut self.element {
|
||||
element.interactivity()
|
||||
} else {
|
||||
@@ -127,7 +119,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> StatefulInteractiveElement for Scrollable<E> where E: Element + StatefulInteractiveElement {}
|
||||
|
||||
impl<E> IntoElement for Scrollable<E>
|
||||
@@ -148,7 +139,7 @@ where
|
||||
type PrepaintState = ScrollViewState;
|
||||
type RequestLayoutState = AnyElement;
|
||||
|
||||
fn id(&self) -> Option<gpui::ElementId> {
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
@@ -158,11 +149,11 @@ where
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
_: Option<&gpui::InspectorElementId>,
|
||||
id: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let style = Style {
|
||||
position: Position::Relative,
|
||||
flex_grow: 1.0,
|
||||
@@ -175,16 +166,10 @@ where
|
||||
};
|
||||
|
||||
let axis = self.axis;
|
||||
let view_id = self.view_id;
|
||||
|
||||
let scroll_id = self.id.clone();
|
||||
let content = self.element.take().map(|c| c.into_any_element());
|
||||
|
||||
self.with_element_state(id.unwrap(), window, cx, |_, element_state, window, cx| {
|
||||
let handle = element_state.handle.clone();
|
||||
let state = element_state.state.clone();
|
||||
let scroll_size = element_state.scroll_size.clone();
|
||||
|
||||
let mut element = div()
|
||||
.relative()
|
||||
.size_full()
|
||||
@@ -192,16 +177,11 @@ where
|
||||
.child(
|
||||
div()
|
||||
.id(scroll_id)
|
||||
.track_scroll(&handle)
|
||||
.track_scroll(&element_state.handle)
|
||||
.overflow_scroll()
|
||||
.relative()
|
||||
.size_full()
|
||||
.child(div().children(content).child({
|
||||
let scroll_size = element_state.scroll_size.clone();
|
||||
canvas(move |b, _, _| scroll_size.set(b.size), |_, _, _, _| {})
|
||||
.absolute()
|
||||
.size_full()
|
||||
})),
|
||||
.child(div().children(content)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -211,8 +191,7 @@ where
|
||||
.right_0()
|
||||
.bottom_0()
|
||||
.child(
|
||||
Scrollbar::both(view_id, state, handle.clone(), scroll_size.get())
|
||||
.axis(axis),
|
||||
Scrollbar::both(&element_state.state, &element_state.handle).axis(axis),
|
||||
),
|
||||
)
|
||||
.into_any_element();
|
||||
@@ -226,9 +205,9 @@ where
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: Option<&gpui::InspectorElementId>,
|
||||
_: gpui::Bounds<Pixels>,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -240,9 +219,9 @@ where
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: Option<&gpui::InspectorElementId>,
|
||||
_: gpui::Bounds<Pixels>,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{
|
||||
px, relative, App, Axis, BorderStyle, Bounds, ContentMask, Corners, Edges, Element, ElementId,
|
||||
EntityId, GlobalElementId, Hitbox, HitboxBehavior, Hsla, IntoElement, IsZero as _, LayoutId,
|
||||
PaintQuad, Pixels, Point, Position, ScrollHandle, ScrollWheelEvent, Size, Style, Window,
|
||||
EntityId, GlobalElementId, Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels,
|
||||
Point, Position, ScrollHandle, ScrollWheelEvent, Size, Style, Window,
|
||||
};
|
||||
|
||||
use crate::AxisExt;
|
||||
@@ -96,7 +96,7 @@ impl Element for ScrollableMask {
|
||||
size: bounds.size,
|
||||
};
|
||||
|
||||
window.insert_hitbox(cover_bounds, HitboxBehavior::Normal)
|
||||
window.insert_hitbox(cover_bounds, gpui::HitboxBehavior::Normal)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
@@ -118,9 +118,9 @@ impl Element for ScrollableMask {
|
||||
bounds,
|
||||
border_widths: Edges::all(px(1.0)),
|
||||
border_color: color,
|
||||
border_style: BorderStyle::Solid,
|
||||
background: gpui::transparent_white().into(),
|
||||
corner_radii: Corners::all(px(0.)),
|
||||
border_style: BorderStyle::default(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
use std::cell::Cell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use gpui::{
|
||||
fill, point, px, relative, App, BorderStyle, Bounds, ContentMask, CursorStyle, Edges, Element,
|
||||
EntityId, Hitbox, HitboxBehavior, Hsla, IntoElement, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, PaintQuad, Pixels, Point, Position, ScrollHandle, ScrollWheelEvent,
|
||||
UniformListScrollHandle, Window,
|
||||
fill, point, px, relative, size, App, Axis, BorderStyle, Bounds, ContentMask, Corner,
|
||||
CursorStyle, Edges, Element, GlobalElementId, Hitbox, HitboxBehavior, Hsla, InspectorElementId,
|
||||
IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
|
||||
Position, ScrollHandle, ScrollWheelEvent, Size, UniformListScrollHandle, Window,
|
||||
};
|
||||
use theme::ActiveTheme;
|
||||
use theme::{ActiveTheme, ScrollBarMode};
|
||||
|
||||
const WIDTH: Pixels = px(12.);
|
||||
const BORDER_WIDTH: Pixels = px(0.);
|
||||
const MIN_THUMB_SIZE: f32 = 80.;
|
||||
const THUMB_RADIUS: Pixels = Pixels(4.0);
|
||||
const THUMB_INSET: Pixels = Pixels(3.);
|
||||
const FADE_OUT_DURATION: f32 = 2.0;
|
||||
const FADE_OUT_DELAY: f32 = 1.2;
|
||||
use crate::AxisExt;
|
||||
|
||||
const WIDTH: Pixels = px(2. * 2. + 8.);
|
||||
const MIN_THUMB_SIZE: f32 = 48.;
|
||||
|
||||
const THUMB_WIDTH: Pixels = px(6.);
|
||||
const THUMB_RADIUS: Pixels = Pixels(6. / 2.);
|
||||
const THUMB_INSET: Pixels = Pixels(2.);
|
||||
|
||||
const THUMB_ACTIVE_WIDTH: Pixels = px(8.);
|
||||
const THUMB_ACTIVE_RADIUS: Pixels = Pixels(8. / 2.);
|
||||
const THUMB_ACTIVE_INSET: Pixels = Pixels(2.);
|
||||
|
||||
const FADE_OUT_DURATION: f32 = 3.0;
|
||||
const FADE_OUT_DELAY: f32 = 2.0;
|
||||
|
||||
pub trait ScrollHandleOffsetable {
|
||||
fn offset(&self) -> Point<Pixels>;
|
||||
@@ -24,6 +33,8 @@ pub trait ScrollHandleOffsetable {
|
||||
fn is_uniform_list(&self) -> bool {
|
||||
false
|
||||
}
|
||||
/// The full size of the content, including padding.
|
||||
fn content_size(&self) -> Size<Pixels>;
|
||||
}
|
||||
|
||||
impl ScrollHandleOffsetable for ScrollHandle {
|
||||
@@ -34,6 +45,10 @@ impl ScrollHandleOffsetable for ScrollHandle {
|
||||
fn set_offset(&self, offset: Point<Pixels>) {
|
||||
self.set_offset(offset);
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
self.max_offset() + self.bounds().size
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollHandleOffsetable for UniformListScrollHandle {
|
||||
@@ -48,13 +63,21 @@ impl ScrollHandleOffsetable for UniformListScrollHandle {
|
||||
fn is_uniform_list(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
let base_handle = &self.0.borrow().base_handle;
|
||||
base_handle.max_offset() + base_handle.bounds().size
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScrollbarState(Rc<Cell<ScrollbarStateInner>>);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ScrollbarState {
|
||||
hovered_axis: Option<ScrollbarAxis>,
|
||||
hovered_on_thumb: Option<ScrollbarAxis>,
|
||||
dragged_axis: Option<ScrollbarAxis>,
|
||||
pub struct ScrollbarStateInner {
|
||||
hovered_axis: Option<Axis>,
|
||||
hovered_on_thumb: Option<Axis>,
|
||||
dragged_axis: Option<Axis>,
|
||||
drag_pos: Point<Pixels>,
|
||||
last_scroll_offset: Point<Pixels>,
|
||||
last_scroll_time: Option<Instant>,
|
||||
@@ -64,7 +87,7 @@ pub struct ScrollbarState {
|
||||
|
||||
impl Default for ScrollbarState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
Self(Rc::new(Cell::new(ScrollbarStateInner {
|
||||
hovered_axis: None,
|
||||
hovered_on_thumb: None,
|
||||
dragged_axis: None,
|
||||
@@ -72,16 +95,20 @@ impl Default for ScrollbarState {
|
||||
last_scroll_offset: point(px(0.), px(0.)),
|
||||
last_scroll_time: None,
|
||||
last_update: Instant::now(),
|
||||
}
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollbarState {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
impl Deref for ScrollbarState {
|
||||
type Target = Rc<Cell<ScrollbarStateInner>>;
|
||||
|
||||
fn with_drag_pos(&self, axis: ScrollbarAxis, pos: Point<Pixels>) -> Self {
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollbarStateInner {
|
||||
fn with_drag_pos(&self, axis: Axis, pos: Point<Pixels>) -> Self {
|
||||
let mut state = *self;
|
||||
if axis.is_vertical() {
|
||||
state.drag_pos.y = pos.y;
|
||||
@@ -99,7 +126,7 @@ impl ScrollbarState {
|
||||
state
|
||||
}
|
||||
|
||||
fn with_hovered(&self, axis: Option<ScrollbarAxis>) -> Self {
|
||||
fn with_hovered(&self, axis: Option<Axis>) -> Self {
|
||||
let mut state = *self;
|
||||
state.hovered_axis = axis;
|
||||
if axis.is_some() {
|
||||
@@ -108,10 +135,10 @@ impl ScrollbarState {
|
||||
state
|
||||
}
|
||||
|
||||
fn with_hovered_on_thumb(&self, axis: Option<ScrollbarAxis>) -> Self {
|
||||
fn with_hovered_on_thumb(&self, axis: Option<Axis>) -> Self {
|
||||
let mut state = *self;
|
||||
state.hovered_on_thumb = axis;
|
||||
if axis.is_some() {
|
||||
if self.is_scrollbar_visible() && axis.is_some() {
|
||||
state.last_scroll_time = Some(std::time::Instant::now());
|
||||
}
|
||||
state
|
||||
@@ -162,14 +189,28 @@ pub enum ScrollbarAxis {
|
||||
Both,
|
||||
}
|
||||
|
||||
impl From<Axis> for ScrollbarAxis {
|
||||
fn from(axis: Axis) -> Self {
|
||||
match axis {
|
||||
Axis::Vertical => Self::Vertical,
|
||||
Axis::Horizontal => Self::Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollbarAxis {
|
||||
#[inline]
|
||||
fn is_vertical(&self) -> bool {
|
||||
/// Return true if the scrollbar axis is vertical.
|
||||
pub fn is_vertical(&self) -> bool {
|
||||
matches!(self, Self::Vertical)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_both(&self) -> bool {
|
||||
/// Return true if the scrollbar axis is horizontal.
|
||||
pub fn is_horizontal(&self) -> bool {
|
||||
matches!(self, Self::Horizontal)
|
||||
}
|
||||
|
||||
/// Return true if the scrollbar axis is both vertical and horizontal.
|
||||
pub fn is_both(&self) -> bool {
|
||||
matches!(self, Self::Both)
|
||||
}
|
||||
|
||||
@@ -184,24 +225,23 @@ impl ScrollbarAxis {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn all(&self) -> Vec<ScrollbarAxis> {
|
||||
fn all(&self) -> Vec<Axis> {
|
||||
match self {
|
||||
Self::Vertical => vec![Self::Vertical],
|
||||
Self::Horizontal => vec![Self::Horizontal],
|
||||
Self::Vertical => vec![Axis::Vertical],
|
||||
Self::Horizontal => vec![Axis::Horizontal],
|
||||
// This should keep Horizontal first, Vertical is the primary axis
|
||||
// if Vertical not need display, then Horizontal will not keep right margin.
|
||||
Self::Both => vec![Self::Horizontal, Self::Vertical],
|
||||
Self::Both => vec![Axis::Horizontal, Axis::Vertical],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scrollbar control for scroll-area or a uniform-list.
|
||||
pub struct Scrollbar {
|
||||
view_id: EntityId,
|
||||
axis: ScrollbarAxis,
|
||||
scroll_handle: Rc<Box<dyn ScrollHandleOffsetable>>,
|
||||
scroll_size: gpui::Size<Pixels>,
|
||||
state: Rc<Cell<ScrollbarState>>,
|
||||
state: ScrollbarState,
|
||||
scroll_size: Option<Size<Pixels>>,
|
||||
/// Maximum frames per second for scrolling by drag. Default is 120 FPS.
|
||||
///
|
||||
/// This is used to limit the update rate of the scrollbar when it is
|
||||
@@ -211,95 +251,62 @@ pub struct Scrollbar {
|
||||
|
||||
impl Scrollbar {
|
||||
fn new(
|
||||
view_id: EntityId,
|
||||
state: Rc<Cell<ScrollbarState>>,
|
||||
axis: ScrollbarAxis,
|
||||
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
||||
scroll_size: gpui::Size<Pixels>,
|
||||
axis: impl Into<ScrollbarAxis>,
|
||||
state: &ScrollbarState,
|
||||
scroll_handle: &(impl ScrollHandleOffsetable + Clone + 'static),
|
||||
) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
state,
|
||||
axis,
|
||||
scroll_size,
|
||||
scroll_handle: Rc::new(Box::new(scroll_handle)),
|
||||
state: state.clone(),
|
||||
axis: axis.into(),
|
||||
scroll_handle: Rc::new(Box::new(scroll_handle.clone())),
|
||||
max_fps: 120,
|
||||
scroll_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create with vertical and horizontal scrollbar.
|
||||
pub fn both(
|
||||
view_id: EntityId,
|
||||
state: Rc<Cell<ScrollbarState>>,
|
||||
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
||||
scroll_size: gpui::Size<Pixels>,
|
||||
state: &ScrollbarState,
|
||||
scroll_handle: &(impl ScrollHandleOffsetable + Clone + 'static),
|
||||
) -> Self {
|
||||
Self::new(
|
||||
view_id,
|
||||
state,
|
||||
ScrollbarAxis::Both,
|
||||
scroll_handle,
|
||||
scroll_size,
|
||||
)
|
||||
Self::new(ScrollbarAxis::Both, state, scroll_handle)
|
||||
}
|
||||
|
||||
/// Create with horizontal scrollbar.
|
||||
pub fn horizontal(
|
||||
view_id: EntityId,
|
||||
state: Rc<Cell<ScrollbarState>>,
|
||||
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
||||
scroll_size: gpui::Size<Pixels>,
|
||||
state: &ScrollbarState,
|
||||
scroll_handle: &(impl ScrollHandleOffsetable + Clone + 'static),
|
||||
) -> Self {
|
||||
Self::new(
|
||||
view_id,
|
||||
state,
|
||||
ScrollbarAxis::Horizontal,
|
||||
scroll_handle,
|
||||
scroll_size,
|
||||
)
|
||||
Self::new(ScrollbarAxis::Horizontal, state, scroll_handle)
|
||||
}
|
||||
|
||||
/// Create with vertical scrollbar.
|
||||
pub fn vertical(
|
||||
view_id: EntityId,
|
||||
state: Rc<Cell<ScrollbarState>>,
|
||||
scroll_handle: impl ScrollHandleOffsetable + 'static,
|
||||
scroll_size: gpui::Size<Pixels>,
|
||||
state: &ScrollbarState,
|
||||
scroll_handle: &(impl ScrollHandleOffsetable + Clone + 'static),
|
||||
) -> Self {
|
||||
Self::new(
|
||||
view_id,
|
||||
state,
|
||||
ScrollbarAxis::Vertical,
|
||||
scroll_handle,
|
||||
scroll_size,
|
||||
)
|
||||
Self::new(ScrollbarAxis::Vertical, state, scroll_handle)
|
||||
}
|
||||
|
||||
/// Create vertical scrollbar for uniform list.
|
||||
pub fn uniform_scroll(
|
||||
view_id: EntityId,
|
||||
state: Rc<Cell<ScrollbarState>>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
state: &ScrollbarState,
|
||||
scroll_handle: &(impl ScrollHandleOffsetable + Clone + 'static),
|
||||
) -> Self {
|
||||
let scroll_size = scroll_handle
|
||||
.0
|
||||
.borrow()
|
||||
.last_item_size
|
||||
.map(|size| size.contents)
|
||||
.unwrap_or_default();
|
||||
Self::new(ScrollbarAxis::Vertical, state, scroll_handle)
|
||||
}
|
||||
|
||||
Self::new(
|
||||
view_id,
|
||||
state,
|
||||
ScrollbarAxis::Vertical,
|
||||
scroll_handle,
|
||||
scroll_size,
|
||||
)
|
||||
/// Set a special scroll size of the content area, default is None.
|
||||
///
|
||||
/// Default will sync the `content_size` from `scroll_handle`.
|
||||
pub fn scroll_size(mut self, scroll_size: Size<Pixels>) -> Self {
|
||||
self.scroll_size = Some(scroll_size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set scrollbar axis.
|
||||
pub fn axis(mut self, axis: ScrollbarAxis) -> Self {
|
||||
self.axis = axis;
|
||||
pub fn axis(mut self, axis: impl Into<ScrollbarAxis>) -> Self {
|
||||
self.axis = axis.into();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -313,47 +320,54 @@ impl Scrollbar {
|
||||
self
|
||||
}
|
||||
|
||||
fn style_for_active(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
fn style_for_active(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {
|
||||
(
|
||||
cx.theme().scrollbar_thumb_hover_background,
|
||||
cx.theme().scrollbar_thumb_background,
|
||||
cx.theme().scrollbar_thumb_border,
|
||||
THUMB_INSET - px(1.),
|
||||
THUMB_RADIUS,
|
||||
THUMB_ACTIVE_WIDTH,
|
||||
THUMB_ACTIVE_INSET,
|
||||
THUMB_ACTIVE_RADIUS,
|
||||
)
|
||||
}
|
||||
|
||||
fn style_for_hovered_thumb(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
fn style_for_hovered_thumb(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {
|
||||
(
|
||||
cx.theme().scrollbar_thumb_hover_background,
|
||||
cx.theme().scrollbar_thumb_background,
|
||||
cx.theme().scrollbar_thumb_border,
|
||||
THUMB_INSET - px(1.),
|
||||
THUMB_RADIUS,
|
||||
THUMB_ACTIVE_WIDTH,
|
||||
THUMB_ACTIVE_INSET,
|
||||
THUMB_ACTIVE_RADIUS,
|
||||
)
|
||||
}
|
||||
|
||||
fn style_for_hovered_bar(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
let (inset, radius) = (THUMB_INSET - px(1.), THUMB_RADIUS);
|
||||
|
||||
fn style_for_hovered_bar(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {
|
||||
(
|
||||
cx.theme().scrollbar_thumb_background,
|
||||
cx.theme().scrollbar_thumb_border,
|
||||
gpui::transparent_black(),
|
||||
THUMB_ACTIVE_WIDTH,
|
||||
THUMB_ACTIVE_INSET,
|
||||
THUMB_ACTIVE_RADIUS,
|
||||
)
|
||||
}
|
||||
|
||||
fn style_for_idle(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {
|
||||
let (width, inset, radius) = match cx.theme().scrollbar_mode {
|
||||
ScrollBarMode::Scrolling => (THUMB_WIDTH, THUMB_INSET, THUMB_RADIUS),
|
||||
_ => (THUMB_ACTIVE_WIDTH, THUMB_ACTIVE_INSET, THUMB_ACTIVE_RADIUS),
|
||||
};
|
||||
|
||||
(
|
||||
gpui::transparent_black(),
|
||||
gpui::transparent_black(),
|
||||
gpui::transparent_black(),
|
||||
width,
|
||||
inset,
|
||||
radius,
|
||||
)
|
||||
}
|
||||
|
||||
fn style_for_idle(_: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
|
||||
(
|
||||
gpui::transparent_black(),
|
||||
gpui::transparent_black(),
|
||||
gpui::transparent_black(),
|
||||
THUMB_INSET,
|
||||
THUMB_RADIUS - px(1.),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for Scrollbar {
|
||||
@@ -370,7 +384,7 @@ pub struct PrepaintState {
|
||||
}
|
||||
|
||||
pub struct AxisPrepaintState {
|
||||
axis: ScrollbarAxis,
|
||||
axis: Axis,
|
||||
bar_hitbox: Hitbox,
|
||||
bounds: Bounds<Pixels>,
|
||||
radius: Pixels,
|
||||
@@ -400,11 +414,11 @@ impl Element for Scrollbar {
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: Option<&gpui::InspectorElementId>,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let style = gpui::Style {
|
||||
position: Position::Absolute,
|
||||
flex_grow: 1.0,
|
||||
@@ -421,8 +435,8 @@ impl Element for Scrollbar {
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: Option<&gpui::InspectorElementId>,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
@@ -434,26 +448,30 @@ impl Element for Scrollbar {
|
||||
|
||||
let mut states = vec![];
|
||||
let mut has_both = self.axis.is_both();
|
||||
let scroll_size = self
|
||||
.scroll_size
|
||||
.unwrap_or(self.scroll_handle.content_size());
|
||||
|
||||
for axis in self.axis.all().into_iter() {
|
||||
let is_vertical = axis.is_vertical();
|
||||
let (scroll_area_size, container_size, scroll_position) = if is_vertical {
|
||||
(
|
||||
self.scroll_size.height,
|
||||
scroll_size.height,
|
||||
hitbox.size.height,
|
||||
self.scroll_handle.offset().y,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
self.scroll_size.width,
|
||||
scroll_size.width,
|
||||
hitbox.size.width,
|
||||
self.scroll_handle.offset().x,
|
||||
)
|
||||
};
|
||||
|
||||
// The horizontal scrollbar is set avoid overlapping with the vertical scrollbar, if the vertical scrollbar is visible.
|
||||
|
||||
let margin_end = if has_both && !is_vertical {
|
||||
WIDTH
|
||||
THUMB_ACTIVE_WIDTH
|
||||
} else {
|
||||
px(0.)
|
||||
};
|
||||
@@ -494,12 +512,27 @@ impl Element for Scrollbar {
|
||||
};
|
||||
|
||||
let state = self.state.clone();
|
||||
let is_always_to_show = cx.theme().scrollbar_mode.is_always();
|
||||
let is_hover_to_show = cx.theme().scrollbar_mode.is_hover();
|
||||
let is_hovered_on_bar = state.get().hovered_axis == Some(axis);
|
||||
let is_hovered_on_thumb = state.get().hovered_on_thumb == Some(axis);
|
||||
|
||||
let (thumb_bg, bar_bg, bar_border, inset, radius) =
|
||||
let (thumb_bg, bar_bg, bar_border, thumb_width, inset, radius) =
|
||||
if state.get().dragged_axis == Some(axis) {
|
||||
Self::style_for_active(cx)
|
||||
} else if is_hover_to_show && (is_hovered_on_bar || is_hovered_on_thumb) {
|
||||
if is_hovered_on_thumb {
|
||||
Self::style_for_hovered_thumb(cx)
|
||||
} else {
|
||||
Self::style_for_hovered_bar(cx)
|
||||
}
|
||||
} else if is_always_to_show {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
if is_hovered_on_thumb {
|
||||
Self::style_for_hovered_thumb(cx)
|
||||
} else {
|
||||
Self::style_for_hovered_bar(cx)
|
||||
}
|
||||
} else {
|
||||
let mut idle_state = Self::style_for_idle(cx);
|
||||
// Delay 2s to fade out the scrollbar thumb (in 1s)
|
||||
@@ -531,43 +564,39 @@ impl Element for Scrollbar {
|
||||
idle_state
|
||||
};
|
||||
|
||||
// The clickable area of the thumb
|
||||
let thumb_length = thumb_end - thumb_start - inset * 2;
|
||||
let thumb_bounds = if is_vertical {
|
||||
Bounds::from_corners(
|
||||
point(bounds.origin.x, bounds.origin.y + thumb_start),
|
||||
point(bounds.origin.x + WIDTH, bounds.origin.y + thumb_end),
|
||||
Bounds::from_corner_and_size(
|
||||
Corner::TopRight,
|
||||
bounds.top_right() + point(-inset, inset + thumb_start),
|
||||
size(WIDTH, thumb_length),
|
||||
)
|
||||
} else {
|
||||
Bounds::from_corners(
|
||||
point(bounds.origin.x + thumb_start, bounds.origin.y),
|
||||
point(bounds.origin.x + thumb_end, bounds.origin.y + WIDTH),
|
||||
Bounds::from_corner_and_size(
|
||||
Corner::BottomLeft,
|
||||
bounds.bottom_left() + point(inset + thumb_start, -inset),
|
||||
size(thumb_length, WIDTH),
|
||||
)
|
||||
};
|
||||
|
||||
// The actual render area of the thumb
|
||||
let thumb_fill_bounds = if is_vertical {
|
||||
Bounds::from_corners(
|
||||
point(
|
||||
bounds.origin.x + inset + BORDER_WIDTH,
|
||||
bounds.origin.y + thumb_start + inset,
|
||||
),
|
||||
point(
|
||||
bounds.origin.x + WIDTH - inset,
|
||||
bounds.origin.y + thumb_end - inset,
|
||||
),
|
||||
Bounds::from_corner_and_size(
|
||||
Corner::TopRight,
|
||||
bounds.top_right() + point(-inset, inset + thumb_start),
|
||||
size(thumb_width, thumb_length),
|
||||
)
|
||||
} else {
|
||||
Bounds::from_corners(
|
||||
point(
|
||||
bounds.origin.x + thumb_start + inset,
|
||||
bounds.origin.y + inset + BORDER_WIDTH,
|
||||
),
|
||||
point(
|
||||
bounds.origin.x + thumb_end - inset,
|
||||
bounds.origin.y + WIDTH - inset,
|
||||
),
|
||||
Bounds::from_corner_and_size(
|
||||
Corner::BottomLeft,
|
||||
bounds.bottom_left() + point(inset + thumb_start, -inset),
|
||||
size(thumb_length, thumb_width),
|
||||
)
|
||||
};
|
||||
|
||||
let bar_hitbox = window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
window.insert_hitbox(bounds, HitboxBehavior::Normal)
|
||||
window.insert_hitbox(bounds, gpui::HitboxBehavior::Normal)
|
||||
});
|
||||
|
||||
states.push(AxisPrepaintState {
|
||||
@@ -592,16 +621,19 @@ impl Element for Scrollbar {
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
_: Option<&gpui::InspectorElementId>,
|
||||
_: Option<&GlobalElementId>,
|
||||
_: Option<&InspectorElementId>,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
_cx: &mut App,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let view_id = window.current_view();
|
||||
let hitbox_bounds = prepaint.hitbox.bounds;
|
||||
let is_visible = self.state.get().is_scrollbar_visible();
|
||||
let is_visible =
|
||||
self.state.get().is_scrollbar_visible() || cx.theme().scrollbar_mode.is_always();
|
||||
let is_hover_to_show = cx.theme().scrollbar_mode.is_hover();
|
||||
|
||||
// Update last_scroll_time when offset is changed.
|
||||
if self.scroll_handle.offset() != self.state.get().last_scroll_offset {
|
||||
@@ -637,23 +669,14 @@ impl Element for Scrollbar {
|
||||
bounds,
|
||||
corner_radii: (0.).into(),
|
||||
background: gpui::transparent_black().into(),
|
||||
border_widths: if is_vertical {
|
||||
Edges {
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: BORDER_WIDTH,
|
||||
}
|
||||
} else {
|
||||
Edges {
|
||||
top: BORDER_WIDTH,
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
}
|
||||
border_widths: Edges {
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
},
|
||||
border_color: state.border,
|
||||
border_style: BorderStyle::Solid,
|
||||
border_style: BorderStyle::default(),
|
||||
});
|
||||
|
||||
cx.paint_quad(
|
||||
@@ -663,7 +686,6 @@ impl Element for Scrollbar {
|
||||
|
||||
window.on_mouse_event({
|
||||
let state = self.state.clone();
|
||||
let view_id = self.view_id;
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
|
||||
move |event: &ScrollWheelEvent, phase, _, cx| {
|
||||
@@ -682,10 +704,9 @@ impl Element for Scrollbar {
|
||||
|
||||
let safe_range = (-scroll_area_size + container_size)..px(0.);
|
||||
|
||||
if is_visible {
|
||||
if is_hover_to_show || is_visible {
|
||||
window.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| {
|
||||
@@ -734,14 +755,13 @@ impl Element for Scrollbar {
|
||||
window.on_mouse_event({
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
let state = self.state.clone();
|
||||
let view_id = self.view_id;
|
||||
let max_fps_duration = Duration::from_millis((1000 / self.max_fps) as u64);
|
||||
|
||||
move |event: &MouseMoveEvent, _, _, cx| {
|
||||
let mut notify = false;
|
||||
// When is hover to show mode or it was visible,
|
||||
// we need to update the hovered state and increase the last_scroll_time.
|
||||
let need_hover_to_update = is_visible;
|
||||
let need_hover_to_update = is_hover_to_show || is_visible;
|
||||
// Update hovered state for scrollbar
|
||||
if bounds.contains(&event.position) && need_hover_to_update {
|
||||
state.set(state.get().with_hovered(Some(axis)));
|
||||
@@ -815,7 +835,6 @@ impl Element for Scrollbar {
|
||||
});
|
||||
|
||||
window.on_mouse_event({
|
||||
let view_id = self.view_id;
|
||||
let state = self.state.clone();
|
||||
|
||||
move |_event: &MouseUpEvent, phase, _, cx| {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use gpui::{
|
||||
div, px, App, Axis, Div, Element, ElementId, EntityId, Pixels, Refineable, StyleRefinement,
|
||||
Styled, Window,
|
||||
div, px, App, Axis, Div, Element, ElementId, Pixels, Refineable, StyleRefinement, Styled,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use theme::ActiveTheme;
|
||||
@@ -48,19 +47,15 @@ pub trait StyledExt: Styled + Sized {
|
||||
self.flex().flex_col()
|
||||
}
|
||||
|
||||
/// Render a border with a width of 1px, color ring color
|
||||
fn outline(self, _window: &Window, cx: &App) -> Self {
|
||||
self.border_color(cx.theme().ring)
|
||||
}
|
||||
|
||||
/// Wraps the element in a ScrollView.
|
||||
///
|
||||
/// Current this is only have a vertical scrollbar.
|
||||
fn scrollable(self, view_id: EntityId, axis: ScrollbarAxis) -> Scrollable<Self>
|
||||
#[inline]
|
||||
fn scrollable(self, axis: impl Into<ScrollbarAxis>) -> Scrollable<Self>
|
||||
where
|
||||
Self: Element,
|
||||
{
|
||||
Scrollable::new(view_id, self, axis)
|
||||
Scrollable::new(axis, self)
|
||||
}
|
||||
|
||||
font_weight!(font_thin, THIN);
|
||||
@@ -74,6 +69,7 @@ pub trait StyledExt: Styled + Sized {
|
||||
font_weight!(font_black, BLACK);
|
||||
|
||||
/// Set as Popover style
|
||||
#[inline]
|
||||
fn popover_style(self, cx: &mut App) -> Self {
|
||||
self.bg(cx.theme().background)
|
||||
.border_1()
|
||||
|
||||
Reference in New Issue
Block a user