Compare commits

...
Sign in to create a new pull request.

22 commits

Author SHA1 Message Date
MrSubidubi
0e8696df84 Improve padding 2025-08-26 11:37:39 +02:00
MrSubidubi
c0891800dc Improve state handling 2025-08-25 23:39:07 +02:00
MrSubidubi
b4223d318f Merge branch 'main' into ui-scrollbar-teardown 2025-08-25 22:54:12 +02:00
MrSubidubi
2a171a971c Use proper opacity 2025-08-25 22:45:12 +02:00
MrSubidubi
a067729525 Improve layout when two scrollbars are shown 2025-08-24 21:53:15 +02:00
MrSubidubi
1f2d18e691 Only update hover state on scroll 2025-08-20 16:48:52 +02:00
MrSubidubi
76842eed31 Merge branch 'main' into ui-scrollbar-teardown 2025-08-20 10:48:16 +02:00
MrSubidubi
da9084226d WIP 2025-08-20 10:41:52 +02:00
MrSubidubi
fd8fb1ed16 Dim if no space reserved and fix some issues 2025-08-18 17:23:31 +02:00
MrSubidubi
8b458b2a7a Some more fixes 2025-08-18 16:07:47 +02:00
MrSubidubi
351db21ff9 Merge branch 'main' into ui-scrollbar-teardown 2025-08-18 14:28:43 +02:00
MrSubidubi
7622ba09ee Clippy 2025-08-16 00:44:52 +02:00
MrSubidubi
91cdf69924 Merge branch 'main' into ui-scrollbar-teardown 2025-08-16 00:38:25 +02:00
MrSubidubi
6414589243 Merge branch 'main' into ui-scrollbar-teardown 2025-08-16 00:37:13 +02:00
MrSubidubi
3da0c0aa60 Some fixes, impl for uniformlist 2025-08-16 00:36:11 +02:00
MrSubidubi
a9dbfce8f9 Impl for uniformlist and notify cleanupds 2025-08-14 17:22:55 +02:00
MrSubidubi
fd33832609 Do not notify the parent unintentionally 2025-08-13 18:15:40 +02:00
MrSubidubi
40084aa94c Even less clones 2025-08-13 15:13:18 +02:00
MrSubidubi
afcfd0979a Reduce clones 2025-08-13 14:56:49 +02:00
MrSubidubi
3b611313e1 Resolve conflicts 2025-08-13 13:05:31 +02:00
MrSubidubi
bfd71db0a3 Use new div impl to reduce footprint 2025-08-13 12:18:47 +02:00
MrSubidubi
c28d873a2f WIP 2025-08-12 19:53:57 +02:00
36 changed files with 1510 additions and 2417 deletions

1
Cargo.lock generated
View file

@ -17521,6 +17521,7 @@ dependencies = [
"icons",
"itertools 0.14.0",
"menu",
"schemars",
"serde",
"settings",
"smallvec",

View file

@ -5,15 +5,15 @@ use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
use editor::{Editor, EditorEvent};
use fuzzy::StringMatchCandidate;
use gpui::{
App, Entity, EventEmitter, FocusHandle, Focusable, ScrollStrategy, Stateful, Task,
App, Entity, EventEmitter, FocusHandle, Focusable, ScrollStrategy, Task,
UniformListScrollHandle, WeakEntity, Window, uniform_list,
};
use std::{fmt::Display, ops::Range};
use text::Bias;
use time::{OffsetDateTime, UtcOffset};
use ui::{
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
Tooltip, prelude::*,
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tooltip, WithScrollbar,
prelude::*,
};
pub struct AcpThreadHistory {
@ -26,8 +26,6 @@ pub struct AcpThreadHistory {
visible_items: Vec<ListItemType>,
scrollbar_visibility: bool,
scrollbar_state: ScrollbarState,
local_timezone: UtcOffset,
_update_task: Task<()>,
@ -90,7 +88,6 @@ impl AcpThreadHistory {
});
let scroll_handle = UniformListScrollHandle::default();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
history_store,
@ -99,8 +96,6 @@ impl AcpThreadHistory {
hovered_index: None,
visible_items: Default::default(),
search_editor,
scrollbar_visibility: true,
scrollbar_state,
local_timezone: UtcOffset::from_whole_seconds(
chrono::Local::now().offset().local_minus_utc(),
)
@ -339,43 +334,6 @@ impl AcpThreadHistory {
task.detach_and_log_err(cx);
}
fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
if !(self.scrollbar_visibility || self.scrollbar_state.is_dragging()) {
return None;
}
Some(
div()
.occlude()
.id("thread-history-scroll")
.h_full()
.bg(cx.theme().colors().panel_background.opacity(0.8))
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.absolute()
.right_1()
.top_0()
.bottom_0()
.w_4()
.pl_1()
.cursor_default()
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
}
fn render_list_items(
&mut self,
range: Range<usize>,
@ -491,7 +449,7 @@ impl Focusable for AcpThreadHistory {
}
impl Render for AcpThreadHistory {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.key_context("ThreadHistory")
.size_full()
@ -542,8 +500,7 @@ impl Render for AcpThreadHistory {
),
)
} else {
view.pr_5()
.child(
view.pr_5().child(
uniform_list(
"thread-history",
self.visible_items.len(),
@ -553,11 +510,13 @@ impl Render for AcpThreadHistory {
)
.p_1()
.track_scroll(self.scroll_handle.clone())
.flex_grow(),
.flex_grow()
.vertical_scrollbar_for(
self.scroll_handle.clone(),
window,
cx,
),
)
.when_some(self.render_scrollbar(cx), |div, scrollbar| {
div.child(scrollbar)
})
}
})
}

View file

@ -21,10 +21,10 @@ use fs::Fs;
use gpui::{
Action, Animation, AnimationExt, AnyView, App, BorderStyle, ClickEvent, ClipboardItem,
EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length, ListOffset,
ListState, MouseButton, PlatformDisplay, SharedString, Stateful, StyleRefinement, Subscription,
Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, Window,
WindowHandle, div, ease_in_out, linear_color_stop, linear_gradient, list, percentage, point,
prelude::*, pulsating_between,
ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task, TextStyle,
TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, Window, WindowHandle, div,
ease_in_out, linear_color_stop, linear_gradient, list, percentage, point, prelude::*,
pulsating_between,
};
use language::Buffer;
@ -43,7 +43,7 @@ use text::Anchor;
use theme::ThemeSettings;
use ui::{
Callout, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle,
Scrollbar, ScrollbarState, SpinnerLabel, Tooltip, prelude::*,
SpinnerLabel, Tooltip, WithScrollbar, prelude::*,
};
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
use workspace::{CollaboratorId, Workspace};
@ -268,7 +268,6 @@ pub struct AcpThreadView {
thread_error: Option<ThreadError>,
thread_feedback: ThreadFeedbackState,
list_state: ListState,
scrollbar_state: ScrollbarState,
auth_task: Option<Task<()>>,
expanded_tool_calls: HashSet<acp::ToolCallId>,
expanded_thinking_blocks: HashSet<(usize, usize)>,
@ -375,8 +374,7 @@ impl AcpThreadView {
profile_selector: None,
notifications: Vec::new(),
notification_subscriptions: HashMap::default(),
list_state: list_state.clone(),
scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
list_state: list_state,
thread_retry_status: None,
thread_error: None,
thread_feedback: Default::default(),
@ -4320,39 +4318,6 @@ impl AcpThreadView {
cx.notify();
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.id("acp-thread-scrollbar")
.occlude()
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
}
fn render_token_limit_callout(
&self,
line_height: Pixels,
@ -4865,23 +4830,27 @@ impl Render for AcpThreadView {
configuration_view,
pending_auth_method,
..
} => self.render_auth_required_state(
} => self
.render_auth_required_state(
connection,
description.as_ref(),
configuration_view.as_ref(),
pending_auth_method.as_ref(),
window,
cx,
),
)
.into_any(),
ThreadState::Loading { .. } => v_flex()
.flex_1()
.child(self.render_recent_history(window, cx)),
.child(self.render_recent_history(window, cx))
.into_any(),
ThreadState::LoadError(e) => v_flex()
.flex_1()
.size_full()
.items_center()
.justify_end()
.child(self.render_load_error(e, cx)),
.child(self.render_load_error(e, cx))
.into_any(),
ThreadState::Ready { .. } => v_flex().flex_1().map(|this| {
if has_messages {
this.child(
@ -4901,9 +4870,11 @@ impl Render for AcpThreadView {
.flex_grow()
.into_any(),
)
.child(self.render_vertical_scrollbar(cx))
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.into_any()
} else {
this.child(self.render_recent_history(window, cx))
.into_any()
}
}),
})

View file

@ -22,10 +22,9 @@ use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, Selec
use gpui::{
AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry,
ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla,
ListAlignment, ListOffset, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful,
StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation,
UnderlineStyle, WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, percentage,
pulsating_between,
ListAlignment, ListOffset, ListState, PlatformDisplay, ScrollHandle, Stateful, StyleRefinement,
Subscription, Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity,
WindowHandle, linear_color_stop, linear_gradient, list, percentage, pulsating_between,
};
use language::{Buffer, Language, LanguageRegistry};
use language_model::{
@ -46,8 +45,7 @@ use std::time::Duration;
use text::ToPoint;
use theme::ThemeSettings;
use ui::{
Banner, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
Tooltip, prelude::*,
Banner, Disclosure, KeyBinding, PopoverMenuHandle, TextSize, Tooltip, WithScrollbar, prelude::*,
};
use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
@ -68,7 +66,6 @@ pub struct ActiveThread {
save_thread_task: Option<Task<()>>,
messages: Vec<MessageId>,
list_state: ListState,
scrollbar_state: ScrollbarState,
rendered_messages_by_id: HashMap<MessageId, RenderedMessage>,
rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>,
editing_message: Option<(MessageId, EditingMessageState)>,
@ -799,8 +796,7 @@ impl ActiveThread {
expanded_tool_uses: HashMap::default(),
expanded_thinking_segments: HashMap::default(),
expanded_code_blocks: HashMap::default(),
list_state: list_state.clone(),
scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
list_state,
editing_message: None,
last_error: None,
copied_code_block_ids: HashSet::default(),
@ -3491,39 +3487,6 @@ impl ActiveThread {
}
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("active-thread-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
}
pub fn is_codeblock_expanded(&self, message_id: MessageId, ix: usize) -> bool {
self.expanded_code_blocks
.get(&(message_id, ix))
@ -3557,13 +3520,13 @@ pub enum ActiveThreadEvent {
impl EventEmitter<ActiveThreadEvent> for ActiveThread {}
impl Render for ActiveThread {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.size_full()
.relative()
.bg(cx.theme().colors().panel_background)
.child(list(self.list_state.clone(), cx.processor(Self::render_message)).flex_grow())
.child(self.render_vertical_scrollbar(cx))
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
}
}

View file

@ -31,7 +31,7 @@ use project::{
use settings::{Settings, SettingsStore, update_settings_file};
use ui::{
Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip, prelude::*,
Switch, SwitchColor, SwitchField, Tooltip, WithScrollbar, prelude::*,
};
use util::ResultExt as _;
use workspace::Workspace;
@ -58,7 +58,6 @@ pub struct AgentConfiguration {
tools: Entity<ToolWorkingSet>,
_registry_subscription: Subscription,
scroll_handle: ScrollHandle,
scrollbar_state: ScrollbarState,
gemini_is_installed: bool,
_check_for_gemini: Task<()>,
}
@ -102,7 +101,6 @@ impl AgentConfiguration {
.detach();
let scroll_handle = ScrollHandle::new();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
fs,
@ -117,7 +115,6 @@ impl AgentConfiguration {
tools,
_registry_subscription: registry_subscription,
scroll_handle,
scrollbar_state,
gemini_is_installed: false,
_check_for_gemini: Task::ready(()),
};
@ -1209,32 +1206,7 @@ impl Render for AgentConfiguration {
.child(self.render_context_servers_section(window, cx))
.child(self.render_provider_configuration_section(cx)),
)
.child(
div()
.id("assistant-configuration-scrollbar")
.occlude()
.absolute()
.right(px(3.))
.top_0()
.bottom_0()
.pb_6()
.w(px(12.))
.cursor_default()
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
.vertical_scrollbar(window, cx)
}
}

View file

@ -4,14 +4,14 @@ use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
use editor::{Editor, EditorEvent};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
App, ClickEvent, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Stateful, Task,
App, ClickEvent, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Task,
UniformListScrollHandle, WeakEntity, Window, uniform_list,
};
use std::{fmt::Display, ops::Range, sync::Arc};
use time::{OffsetDateTime, UtcOffset};
use ui::{
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
Tooltip, prelude::*,
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, ScrollAxes, Scrollbars, Tooltip,
WithScrollbar, prelude::*,
};
use util::ResultExt;
@ -30,8 +30,6 @@ pub struct ThreadHistory {
separated_item_indexes: Vec<u32>,
_separated_items_task: Option<Task<()>>,
search_state: SearchState,
scrollbar_visibility: bool,
scrollbar_state: ScrollbarState,
_subscriptions: Vec<gpui::Subscription>,
}
@ -90,7 +88,6 @@ impl ThreadHistory {
});
let scroll_handle = UniformListScrollHandle::default();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
agent_panel,
@ -103,8 +100,6 @@ impl ThreadHistory {
separated_items: Default::default(),
separated_item_indexes: Default::default(),
search_editor,
scrollbar_visibility: true,
scrollbar_state,
_subscriptions: vec![search_editor_subscription, history_store_subscription],
_separated_items_task: None,
};
@ -363,43 +358,6 @@ impl ThreadHistory {
cx.notify();
}
fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
if !(self.scrollbar_visibility || self.scrollbar_state.is_dragging()) {
return None;
}
Some(
div()
.occlude()
.id("thread-history-scroll")
.h_full()
.bg(cx.theme().colors().panel_background.opacity(0.8))
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.absolute()
.right_1()
.top_0()
.bottom_0()
.w_4()
.pl_1()
.cursor_default()
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
}
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
if let Some(entry) = self.get_match(self.selected_index) {
let task_result = match entry {
@ -536,7 +494,7 @@ impl Focusable for ThreadHistory {
}
impl Render for ThreadHistory {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.key_context("ThreadHistory")
.size_full()
@ -601,9 +559,14 @@ impl Render for ThreadHistory {
.track_scroll(self.scroll_handle.clone())
.flex_grow(),
)
.when_some(self.render_scrollbar(cx), |div, scrollbar| {
div.child(scrollbar)
})
.custom_scrollbars(
Scrollbars::new(ScrollAxes::Vertical)
.tracked_scroll_handle(self.scroll_handle.clone())
.width_sm()
.with_track_along(ScrollAxes::Vertical),
window,
cx,
)
}
})
}

View file

@ -10,7 +10,7 @@ use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use gpui::{
Action, AppContext, ClickEvent, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy,
Stateful, Task, UniformListScrollHandle, WeakEntity, actions, uniform_list,
Task, UniformListScrollHandle, WeakEntity, actions, uniform_list,
};
use language::Point;
use project::{
@ -23,8 +23,8 @@ use project::{
worktree_store::WorktreeStore,
};
use ui::{
Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render, Scrollbar,
ScrollbarState, StatefulInteractiveElement, Tooltip, prelude::*,
Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render,
StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,
};
use workspace::Workspace;
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
@ -49,7 +49,6 @@ pub(crate) struct BreakpointList {
breakpoint_store: Entity<BreakpointStore>,
dap_store: Entity<DapStore>,
worktree_store: Entity<WorktreeStore>,
scrollbar_state: ScrollbarState,
breakpoints: Vec<BreakpointEntry>,
session: Option<Entity<Session>>,
focus_handle: FocusHandle,
@ -87,7 +86,6 @@ impl BreakpointList {
let dap_store = project.dap_store();
let focus_handle = cx.focus_handle();
let scroll_handle = UniformListScrollHandle::new();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let adapter_name = session.as_ref().map(|session| session.read(cx).adapter());
cx.new(|cx| {
@ -95,7 +93,6 @@ impl BreakpointList {
breakpoint_store,
dap_store,
worktree_store,
scrollbar_state,
breakpoints: Default::default(),
workspace,
session,
@ -576,39 +573,6 @@ impl BreakpointList {
.flex_1()
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("breakpoint-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
}
pub(crate) fn render_control_strip(&self) -> AnyElement {
let selection_kind = self.selection_kind();
let focus_handle = self.focus_handle.clone();
@ -789,7 +753,7 @@ impl Render for BreakpointList {
.size_full()
.pt_1()
.child(self.render_list(cx))
.child(self.render_vertical_scrollbar(cx))
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.when_some(self.strip_mode, |this, _| {
this.child(Divider::horizontal().color(DividerColor::Border))
.child(

View file

@ -9,7 +9,7 @@ use std::{
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{
Action, AppContext, DismissEvent, DragMoveEvent, Empty, Entity, FocusHandle, Focusable,
MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful, Subscription, Task, TextStyle,
MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Subscription, Task, TextStyle,
UniformList, UniformListScrollHandle, WeakEntity, actions, anchored, deferred, point,
uniform_list,
};
@ -19,7 +19,7 @@ use settings::Settings;
use theme::ThemeSettings;
use ui::{
ContextMenu, Divider, DropdownMenu, FluentBuilder, IntoElement, PopoverMenuHandle, Render,
Scrollbar, ScrollbarState, StatefulInteractiveElement, Tooltip, prelude::*,
ScrollableHandle, StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,
};
use workspace::Workspace;
@ -30,7 +30,6 @@ actions!(debugger, [GoToSelectedAddress]);
pub(crate) struct MemoryView {
workspace: WeakEntity<Workspace>,
scroll_handle: UniformListScrollHandle,
scroll_state: ScrollbarState,
stack_frame_list: WeakEntity<StackFrameList>,
focus_handle: FocusHandle,
view_state: ViewState,
@ -121,11 +120,10 @@ impl ViewState {
}
}
struct ScrollbarDragging;
static HEX_BYTES_MEMOIZED: LazyLock<[SharedString; 256]> =
LazyLock::new(|| std::array::from_fn(|byte| SharedString::from(format!("{byte:02X}"))));
static UNKNOWN_BYTE: SharedString = SharedString::new_static("??");
impl MemoryView {
pub(crate) fn new(
session: Entity<Session>,
@ -139,10 +137,8 @@ impl MemoryView {
let query_editor = cx.new(|cx| Editor::single_line(window, cx));
let scroll_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
workspace,
scroll_state,
scroll_handle,
stack_frame_list,
focus_handle: cx.focus_handle(),
@ -162,43 +158,6 @@ impl MemoryView {
this
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("memory-view-vertical-scrollbar")
.on_drag_move(cx.listener(|this, evt, _, cx| {
let did_handle = this.handle_scroll_drag(evt);
cx.notify();
if did_handle {
cx.stop_propagation()
}
}))
.on_drag(ScrollbarDragging, |_, _, _, cx| cx.new(|_| Empty))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scroll_state.clone()).map(|s| s.auto_hide(cx)))
}
fn render_memory(&self, cx: &mut Context<Self>) -> UniformList {
let weak = cx.weak_entity();
let session = self.session.clone();
@ -233,10 +192,9 @@ impl MemoryView {
.track_scroll(self.scroll_handle.clone())
.on_scroll_wheel(cx.listener(|this, evt: &ScrollWheelEvent, window, _| {
let delta = evt.delta.pixel_delta(window.line_height());
let scroll_handle = this.scroll_state.scroll_handle();
let size = scroll_handle.content_size();
let viewport = scroll_handle.viewport();
let current_offset = scroll_handle.offset();
let size = this.scroll_handle.content_size();
let viewport = this.scroll_handle.viewport();
let current_offset = this.scroll_handle.offset();
let first_entry_offset_boundary = size.height / this.view_state.row_count() as f32;
let last_entry_offset_boundary = size.height - first_entry_offset_boundary;
if first_entry_offset_boundary + viewport.size.height > current_offset.y.abs() {
@ -245,7 +203,8 @@ impl MemoryView {
} else if last_entry_offset_boundary < current_offset.y.abs() + viewport.size.height {
this.view_state.schedule_scroll_down();
}
scroll_handle.set_offset(current_offset + point(px(0.), delta.y));
this.scroll_handle
.set_offset(current_offset + point(px(0.), delta.y));
}))
}
fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement {
@ -297,7 +256,7 @@ impl MemoryView {
}
let row_count = self.view_state.row_count();
debug_assert!(row_count > 1);
let scroll_handle = self.scroll_state.scroll_handle();
let scroll_handle = &self.scroll_handle;
let viewport = scroll_handle.viewport();
if viewport.bottom() < evt.event.position.y {
@ -307,13 +266,15 @@ impl MemoryView {
}
}
fn handle_scroll_drag(&mut self, evt: &DragMoveEvent<ScrollbarDragging>) -> bool {
if !self.scroll_state.is_dragging() {
return false;
}
#[allow(unused)]
fn handle_scroll_drag(&mut self, evt: &DragMoveEvent<()>) -> bool {
// todo!
// if !self.scroll_state.is_dragging() {
// return false;
// }
let row_count = self.view_state.row_count();
debug_assert!(row_count > 1);
let scroll_handle = self.scroll_state.scroll_handle();
let scroll_handle = &self.scroll_handle;
let viewport = scroll_handle.viewport();
if viewport.bottom() < evt.event.position.y {
@ -943,7 +904,7 @@ impl Render for MemoryView {
)
.with_priority(1)
}))
.child(self.render_vertical_scrollbar(cx)),
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
)
}
}

View file

@ -1,15 +1,15 @@
use anyhow::anyhow;
use dap::Module;
use gpui::{
AnyElement, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful,
Subscription, Task, UniformListScrollHandle, WeakEntity, uniform_list,
AnyElement, Entity, FocusHandle, Focusable, ScrollStrategy, Subscription, Task,
UniformListScrollHandle, WeakEntity, uniform_list,
};
use project::{
ProjectItem as _, ProjectPath,
debugger::session::{Session, SessionEvent},
};
use std::{ops::Range, path::Path, sync::Arc};
use ui::{Scrollbar, ScrollbarState, prelude::*};
use ui::{WithScrollbar, prelude::*};
use workspace::Workspace;
pub struct ModuleList {
@ -18,7 +18,6 @@ pub struct ModuleList {
session: Entity<Session>,
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
scrollbar_state: ScrollbarState,
entries: Vec<Module>,
_rebuild_task: Option<Task<()>>,
_subscription: Subscription,
@ -44,7 +43,6 @@ impl ModuleList {
let scroll_handle = UniformListScrollHandle::new();
Self {
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
session,
workspace,
@ -167,38 +165,6 @@ impl ModuleList {
self.session
.update(cx, |session, cx| session.modules(cx).to_vec())
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("module-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
let Some(ix) = self.selected_ix else { return };
@ -313,6 +279,6 @@ impl Render for ModuleList {
.size_full()
.p_1()
.child(self.render_list(window, cx))
.child(self.render_vertical_scrollbar(cx))
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
}
}

View file

@ -5,8 +5,8 @@ use std::time::Duration;
use anyhow::{Context as _, Result, anyhow};
use dap::StackFrameId;
use gpui::{
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState, MouseButton,
Stateful, Subscription, Task, WeakEntity, list,
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState, Subscription,
Task, WeakEntity, list,
};
use util::debug_panic;
@ -15,7 +15,7 @@ use language::PointUtf16;
use project::debugger::breakpoint_store::ActiveStackFrame;
use project::debugger::session::{Session, SessionEvent, StackFrame};
use project::{ProjectItem, ProjectPath};
use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*};
use ui::{Tooltip, WithScrollbar, prelude::*};
use workspace::{ItemHandle, Workspace};
use super::RunningState;
@ -35,7 +35,6 @@ pub struct StackFrameList {
workspace: WeakEntity<Workspace>,
selected_ix: Option<usize>,
opened_stack_frame_id: Option<StackFrameId>,
scrollbar_state: ScrollbarState,
list_state: ListState,
error: Option<SharedString>,
_refresh_task: Task<()>,
@ -71,7 +70,6 @@ impl StackFrameList {
});
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
let scrollbar_state = ScrollbarState::new(list_state.clone());
let mut this = Self {
session,
@ -84,7 +82,6 @@ impl StackFrameList {
selected_ix: None,
opened_stack_frame_id: None,
list_state,
scrollbar_state,
_refresh_task: Task::ready(()),
};
this.schedule_refresh(true, window, cx);
@ -581,39 +578,6 @@ impl StackFrameList {
}
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("stack-frame-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
self.selected_ix = ix;
cx.notify();
@ -740,7 +704,7 @@ impl Render for StackFrameList {
)
})
.child(self.render_list(window, cx))
.child(self.render_vertical_scrollbar(cx))
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
}
}

View file

@ -8,9 +8,8 @@ use dap::{
use editor::Editor;
use gpui::{
Action, AnyElement, ClickEvent, ClipboardItem, Context, DismissEvent, Empty, Entity,
FocusHandle, Focusable, Hsla, MouseButton, MouseDownEvent, Point, Stateful, Subscription,
TextStyleRefinement, UniformListScrollHandle, WeakEntity, actions, anchored, deferred,
uniform_list,
FocusHandle, Focusable, Hsla, MouseDownEvent, Point, Subscription, TextStyleRefinement,
UniformListScrollHandle, WeakEntity, actions, anchored, deferred, uniform_list,
};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};
use project::debugger::{
@ -18,7 +17,7 @@ use project::debugger::{
session::{Session, SessionEvent, Watcher},
};
use std::{collections::HashMap, ops::Range, sync::Arc};
use ui::{ContextMenu, ListItem, ScrollableHandle, Scrollbar, ScrollbarState, Tooltip, prelude::*};
use ui::{ContextMenu, ListItem, ScrollableHandle, Tooltip, WithScrollbar, prelude::*};
use util::{debug_panic, maybe};
actions!(
@ -189,7 +188,6 @@ pub struct VariableList {
entry_states: HashMap<EntryPath, EntryState>,
selected_stack_frame_id: Option<StackFrameId>,
list_handle: UniformListScrollHandle,
scrollbar_state: ScrollbarState,
session: Entity<Session>,
selection: Option<EntryPath>,
open_context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
@ -235,7 +233,6 @@ impl VariableList {
let list_state = UniformListScrollHandle::default();
Self {
scrollbar_state: ScrollbarState::new(list_state.clone()),
list_handle: list_state,
session,
focus_handle,
@ -1500,39 +1497,6 @@ impl VariableList {
)
.into_any()
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("variable-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
impl Focusable for VariableList {
@ -1542,7 +1506,7 @@ impl Focusable for VariableList {
}
impl Render for VariableList {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.track_focus(&self.focus_handle)
.key_context("VariableList")
@ -1587,7 +1551,7 @@ impl Render for VariableList {
)
.with_priority(1)
}))
.child(self.render_vertical_scrollbar(cx))
.vertical_scrollbar_for(self.list_handle.clone(), window, cx)
}
}

View file

@ -55,7 +55,7 @@ pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPla
pub use edit_prediction::Direction;
pub use editor_settings::{
CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
};
pub use editor_settings_controls::*;
pub use element::{
@ -165,7 +165,7 @@ use project::{
};
use rand::{seq::SliceRandom, thread_rng};
use rpc::{ErrorCode, ErrorExt, proto::PeerId};
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
use selections_collection::{
MutableSelectionsCollection, SelectionsCollection, resolve_selections,
};
@ -198,7 +198,7 @@ use theme::{
};
use ui::{
ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
};
use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
use workspace::{

View file

@ -7,6 +7,7 @@ use project::project_settings::DiagnosticSeverity;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, VsCodeSettings};
use ui::scrollbars::{ScrollbarVisibilitySetting, ShowScrollbar};
use util::serde::default_true;
/// Imports from the VSCode settings at
@ -196,23 +197,6 @@ pub struct Gutter {
pub folds: bool,
}
/// When to show the scrollbar in the editor.
///
/// Default: auto
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ShowScrollbar {
/// Show the scrollbar if there's important information or
/// follow the system's configured behavior.
Auto,
/// Match the system's configured behavior.
System,
/// Always show the scrollbar.
Always,
/// Never show the scrollbar.
Never,
}
/// When to show the minimap in the editor.
///
/// Default: never
@ -735,6 +719,12 @@ impl EditorSettings {
}
}
impl ScrollbarVisibilitySetting for EditorSettings {
fn scrollbar_visibility(&self, _cx: &App) -> ShowScrollbar {
self.scrollbar.show
}
}
impl Settings for EditorSettings {
const KEY: Option<&'static str> = None;

View file

@ -18,7 +18,7 @@ use crate::{
editor_settings::{
CurrentLineHighlight, DocumentColorsRenderMode, DoubleClickInMultibuffer, Minimap,
MinimapThumb, MinimapThumbBorder, ScrollBeyondLastLine, ScrollbarAxes,
ScrollbarDiagnostics, ShowMinimap, ShowScrollbar,
ScrollbarDiagnostics, ShowMinimap,
},
git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
hover_popover::{
@ -84,7 +84,7 @@ use text::{BufferId, SelectionGoal};
use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
use ui::{
ButtonLike, ContextMenu, Indicator, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*,
right_click_menu,
right_click_menu, scrollbars::ShowScrollbar,
};
use unicode_segmentation::UnicodeSegmentation;
use util::post_inc;

View file

@ -9,8 +9,8 @@ use anyhow::Context as _;
use gpui::{
AnyElement, AsyncWindowContext, Context, Entity, Focusable as _, FontWeight, Hsla,
InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size,
Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Subscription, Task,
TextStyleRefinement, Window, div, px,
StatefulInteractiveElement, StyleRefinement, Styled, Subscription, Task, TextStyleRefinement,
Window, div, px,
};
use itertools::Itertools;
use language::{DiagnosticEntry, Language, LanguageRegistry};
@ -23,7 +23,7 @@ use std::{borrow::Cow, cell::RefCell};
use std::{ops::Range, sync::Arc, time::Duration};
use std::{path::PathBuf, rc::Rc};
use theme::ThemeSettings;
use ui::{Scrollbar, ScrollbarState, prelude::*, theme_is_transparent};
use ui::{Scrollbars, WithScrollbar, prelude::*, theme_is_transparent};
use url::Url;
use util::TryFutureExt;
use workspace::{OpenOptions, OpenVisible, Workspace};
@ -184,7 +184,6 @@ pub fn hover_at_inlay(
let hover_popover = InfoPopover {
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
parsed_content,
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(false)),
anchor: None,
@ -387,7 +386,6 @@ fn show_hover(
local_diagnostic,
markdown,
border_color,
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
background_color,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
@ -457,7 +455,6 @@ fn show_hover(
info_popovers.push(InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
@ -507,7 +504,6 @@ fn show_hover(
info_popovers.push(InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
@ -846,7 +842,6 @@ pub struct InfoPopover {
pub symbol_range: RangeInEditor,
pub parsed_content: Option<Entity<Markdown>>,
pub scroll_handle: ScrollHandle,
pub scrollbar_state: ScrollbarState,
pub keyboard_grace: Rc<RefCell<bool>>,
pub anchor: Option<Anchor>,
_subscription: Option<Subscription>,
@ -891,7 +886,12 @@ impl InfoPopover {
.on_url_click(open_markdown_url),
),
)
.child(self.render_vertical_scrollbar(cx))
.custom_scrollbars(
Scrollbars::for_settings::<EditorSettings>()
.tracked_scroll_handle(self.scroll_handle.clone()),
window,
cx,
)
})
.into_any_element()
}
@ -905,39 +905,6 @@ impl InfoPopover {
cx.notify();
self.scroll_handle.set_offset(current);
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Editor>) -> Stateful<Div> {
div()
.occlude()
.id("info-popover-vertical-scroll")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
pub struct DiagnosticPopover {
@ -949,7 +916,6 @@ pub struct DiagnosticPopover {
pub anchor: Anchor,
_subscription: Subscription,
pub scroll_handle: ScrollHandle,
pub scrollbar_state: ScrollbarState,
}
impl DiagnosticPopover {
@ -1013,43 +979,15 @@ impl DiagnosticPopover {
),
),
)
.child(self.render_vertical_scrollbar(cx)),
.custom_scrollbars(
Scrollbars::for_settings::<EditorSettings>()
.tracked_scroll_handle(self.scroll_handle.clone()),
window,
cx,
),
)
.into_any_element()
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Editor>) -> Stateful<Div> {
div()
.occlude()
.id("diagnostic-popover-vertical-scroll")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
#[cfg(test)]

View file

@ -12,7 +12,7 @@ use crate::{
};
pub use autoscroll::{Autoscroll, AutoscrollStrategy};
use core::fmt::Debug;
use gpui::{Along, App, Axis, Context, Global, Pixels, Task, Window, point, px};
use gpui::{Along, App, Axis, Context, Pixels, Task, Window, point, px};
use language::language_settings::{AllLanguageSettings, SoftWrap};
use language::{Bias, Point};
pub use scroll_amount::ScrollAmount;
@ -21,6 +21,7 @@ use std::{
cmp::Ordering,
time::{Duration, Instant},
};
use ui::scrollbars::ScrollbarAutoHide;
use util::ResultExt;
use workspace::{ItemId, WorkspaceId};
@ -29,11 +30,6 @@ const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
pub struct WasScrolled(pub(crate) bool);
#[derive(Default)]
pub struct ScrollbarAutoHide(pub bool);
impl Global for ScrollbarAutoHide {}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScrollAnchor {
pub offset: gpui::Point<f32>,
@ -327,7 +323,7 @@ impl ScrollManager {
cx.notify();
}
if cx.default_global::<ScrollbarAutoHide>().0 {
if cx.default_global::<ScrollbarAutoHide>().should_hide() {
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)

View file

@ -2,8 +2,8 @@ use crate::actions::ShowSignatureHelp;
use crate::hover_popover::open_markdown_url;
use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp, hover_markdown_style};
use gpui::{
App, Context, Div, Entity, HighlightStyle, MouseButton, ScrollHandle, Size, Stateful,
StyledText, Task, TextStyle, Window, combine_highlights,
App, Context, Entity, HighlightStyle, MouseButton, ScrollHandle, Size, StyledText, Task,
TextStyle, Window, combine_highlights,
};
use language::BufferSnapshot;
use markdown::{Markdown, MarkdownElement};
@ -15,8 +15,8 @@ use theme::ThemeSettings;
use ui::{
ActiveTheme, AnyElement, ButtonCommon, ButtonStyle, Clickable, FluentBuilder, IconButton,
IconButtonShape, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon,
LabelSize, ParentElement, Pixels, Scrollbar, ScrollbarState, SharedString,
StatefulInteractiveElement, Styled, StyledExt, div, px, relative,
LabelSize, ParentElement, Pixels, SharedString, StatefulInteractiveElement, Styled, StyledExt,
WithScrollbar, div, relative,
};
// Language-specific settings may define quotes as "brackets", so filter them out separately.
@ -243,7 +243,6 @@ impl Editor {
.min(signatures.len().saturating_sub(1));
let signature_help_popover = SignatureHelpPopover {
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
style,
signatures,
current_signature,
@ -330,7 +329,6 @@ pub struct SignatureHelpPopover {
pub signatures: Vec<SignatureHelp>,
pub current_signature: usize,
scroll_handle: ScrollHandle,
scrollbar_state: ScrollbarState,
}
impl SignatureHelpPopover {
@ -391,7 +389,8 @@ impl SignatureHelpPopover {
)
}),
)
.child(self.render_vertical_scrollbar(cx));
.vertical_scrollbar(window, cx);
let controls = if self.signatures.len() > 1 {
let prev_button = IconButton::new("signature_help_prev", IconName::ChevronUp)
.shape(IconButtonShape::Square)
@ -460,26 +459,4 @@ impl SignatureHelpPopover {
.child(main_content)
.into_any_element()
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Editor>) -> Stateful<Div> {
div()
.occlude()
.id("signature_help_scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| cx.stop_propagation())
.on_any_mouse_down(|_, _, cx| cx.stop_propagation())
.on_mouse_up(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.on_scroll_wheel(cx.listener(|_, _, _, cx| cx.notify()))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_1()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}

View file

@ -24,8 +24,8 @@ use settings::Settings;
use strum::IntoEnumIterator as _;
use theme::ThemeSettings;
use ui::{
CheckboxWithLabel, Chip, ContextMenu, PopoverMenu, ScrollableHandle, Scrollbar, ScrollbarState,
ToggleButton, Tooltip, prelude::*,
CheckboxWithLabel, Chip, ContextMenu, PopoverMenu, ScrollableHandle, ToggleButton, Tooltip,
WithScrollbar, prelude::*,
};
use vim_mode_setting::VimModeSetting;
use workspace::{
@ -290,7 +290,6 @@ pub struct ExtensionsPage {
_subscriptions: [gpui::Subscription; 2],
extension_fetch_task: Option<Task<()>>,
upsells: BTreeSet<Feature>,
scrollbar_state: ScrollbarState,
}
impl ExtensionsPage {
@ -339,7 +338,7 @@ impl ExtensionsPage {
let mut this = Self {
workspace: workspace.weak_handle(),
list: scroll_handle.clone(),
list: scroll_handle,
is_fetching_extensions: false,
filter: ExtensionFilter::All,
dev_extension_entries: Vec::new(),
@ -351,7 +350,6 @@ impl ExtensionsPage {
_subscriptions: subscriptions,
query_editor,
upsells: BTreeSet::default(),
scrollbar_state: ScrollbarState::new(scroll_handle),
};
this.fetch_extensions(
this.search_query(cx),
@ -1375,7 +1373,7 @@ impl ExtensionsPage {
}
impl Render for ExtensionsPage {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.size_full()
.bg(cx.theme().colors().editor_background)
@ -1520,25 +1518,24 @@ impl Render for ExtensionsPage {
}
if count == 0 {
return this.py_4().child(self.render_empty_state(cx));
}
this.py_4()
.child(self.render_empty_state(cx))
.into_any_element()
} else {
let scroll_handle = self.list.clone();
this.child(
uniform_list("entries", count, cx.processor(Self::render_extensions))
uniform_list(
"entries",
count,
cx.processor(Self::render_extensions),
)
.flex_grow()
.pb_4()
.track_scroll(scroll_handle),
)
.child(
div()
.absolute()
.right_1()
.top_0()
.bottom_0()
.w(px(12.))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
.track_scroll(scroll_handle.clone()),
)
.vertical_scrollbar_for(scroll_handle, window, cx)
.into_any_element()
}
}),
)
}

View file

@ -13,10 +13,7 @@ use agent_settings::AgentSettings;
use anyhow::Context as _;
use askpass::AskPassDelegate;
use db::kvp::KEY_VALUE_STORE;
use editor::{
Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar,
scroll::ScrollbarAutoHide,
};
use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
use futures::StreamExt as _;
use git::blame::ParsedCommitMessage;
use git::repository::{
@ -31,7 +28,7 @@ use git::{
UnstageAll,
};
use gpui::{
Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, Axis, ClickEvent, Corner,
Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, ClickEvent, Corner,
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext,
ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, MouseDownEvent, Point,
PromptLevel, ScrollStrategy, Subscription, Task, Transformation, UniformListScrollHandle,
@ -63,9 +60,10 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize};
use strum::{IntoEnumIterator, VariantNames};
use time::OffsetDateTime;
use ui::{
Checkbox, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu, Scrollbar,
ScrollbarState, SplitButton, Tooltip, prelude::*,
Checkbox, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu,
SplitButton, Tooltip, prelude::*,
};
use ui::{ScrollAxes, Scrollbars, WithScrollbar};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::SERIALIZATION_THROTTLE_TIME;
@ -276,61 +274,6 @@ struct PendingOperation {
op_id: usize,
}
// computed state related to how to render scrollbars
// one per axis
// on render we just read this off the panel
// we update it when
// - settings change
// - on focus in, on focus out, on hover, etc.
#[derive(Debug)]
struct ScrollbarProperties {
axis: Axis,
show_scrollbar: bool,
show_track: bool,
auto_hide: bool,
hide_task: Option<Task<()>>,
state: ScrollbarState,
}
impl ScrollbarProperties {
// Shows the scrollbar and cancels any pending hide task
fn show(&mut self, cx: &mut Context<GitPanel>) {
if !self.auto_hide {
return;
}
self.show_scrollbar = true;
self.hide_task.take();
cx.notify();
}
fn hide(&mut self, window: &mut Window, cx: &mut Context<GitPanel>) {
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
if !self.auto_hide {
return;
}
let axis = self.axis;
self.hide_task = Some(cx.spawn_in(window, async move |panel, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
.await;
if let Some(panel) = panel.upgrade() {
panel
.update(cx, |panel, cx| {
match axis {
Axis::Vertical => panel.vertical_scrollbar.show_scrollbar = false,
Axis::Horizontal => panel.horizontal_scrollbar.show_scrollbar = false,
}
cx.notify();
})
.log_err();
}
}));
}
}
pub struct GitPanel {
pub(crate) active_repository: Option<Entity<Repository>>,
pub(crate) commit_editor: Entity<Editor>,
@ -343,8 +286,6 @@ pub struct GitPanel {
single_tracked_entry: Option<GitStatusEntry>,
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
horizontal_scrollbar: ScrollbarProperties,
vertical_scrollbar: ScrollbarProperties,
new_count: usize,
entry_count: usize,
new_staged_count: usize,
@ -429,10 +370,6 @@ impl GitPanel {
cx.new(|cx| {
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, window, Self::focus_in).detach();
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
this.hide_scrollbars(window, cx);
})
.detach();
let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path;
cx.observe_global::<SettingsStore>(move |this, cx| {
@ -457,24 +394,6 @@ impl GitPanel {
let scroll_handle = UniformListScrollHandle::new();
let vertical_scrollbar = ScrollbarProperties {
axis: Axis::Vertical,
state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
show_scrollbar: false,
show_track: false,
auto_hide: false,
hide_task: None,
};
let horizontal_scrollbar = ScrollbarProperties {
axis: Axis::Horizontal,
state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
show_scrollbar: false,
show_track: false,
auto_hide: false,
hide_task: None,
};
let mut assistant_enabled = AgentSettings::get_global(cx).enabled;
let mut was_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
let _settings_subscription = cx.observe_global::<SettingsStore>(move |_, cx| {
@ -555,8 +474,6 @@ impl GitPanel {
workspace: workspace.weak_handle(),
modal_open: false,
entry_count: 0,
horizontal_scrollbar,
vertical_scrollbar,
bulk_staging: None,
_settings_subscription,
};
@ -566,86 +483,6 @@ impl GitPanel {
})
}
fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.horizontal_scrollbar.hide(window, cx);
self.vertical_scrollbar.hide(window, cx);
}
fn update_scrollbar_properties(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
// TODO: This PR should have defined Editor's `scrollbar.axis`
// as an Option<ScrollbarAxis>, not a ScrollbarAxes as it would allow you to
// `.unwrap_or(EditorSettings::get_global(cx).scrollbar.show)`.
//
// Once this is fixed we can extend the GitPanelSettings with a `scrollbar.axis`
// so we can show each axis based on the settings.
//
// We should fix this. PR: https://github.com/zed-industries/zed/pull/19495
let show_setting = GitPanelSettings::get_global(cx)
.scrollbar
.show
.unwrap_or(EditorSettings::get_global(cx).scrollbar.show);
let scroll_handle = self.scroll_handle.0.borrow();
let autohide = |show: ShowScrollbar, cx: &mut Context<Self>| match show {
ShowScrollbar::Auto => true,
ShowScrollbar::System => cx
.try_global::<ScrollbarAutoHide>()
.map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
ShowScrollbar::Always => false,
ShowScrollbar::Never => false,
};
let longest_item_width = scroll_handle.last_item_size.and_then(|size| {
(size.contents.width > size.item.width).then_some(size.contents.width)
});
// is there an item long enough that we should show a horizontal scrollbar?
let item_wider_than_container = if let Some(longest_item_width) = longest_item_width {
longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0)
} else {
true
};
let show_horizontal = match (show_setting, item_wider_than_container) {
(_, false) => false,
(ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always, true) => true,
(ShowScrollbar::Never, true) => false,
};
let show_vertical = match show_setting {
ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
};
let show_horizontal_track =
show_horizontal && matches!(show_setting, ShowScrollbar::Always);
// TODO: we probably should hide the scroll track when the list doesn't need to scroll
let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always);
self.vertical_scrollbar = ScrollbarProperties {
axis: self.vertical_scrollbar.axis,
state: self.vertical_scrollbar.state.clone(),
show_scrollbar: show_vertical,
show_track: show_vertical_track,
auto_hide: autohide(show_setting, cx),
hide_task: None,
};
self.horizontal_scrollbar = ScrollbarProperties {
axis: self.horizontal_scrollbar.axis,
state: self.horizontal_scrollbar.state.clone(),
show_scrollbar: show_horizontal,
show_track: show_horizontal_track,
auto_hide: autohide(show_setting, cx),
hide_task: None,
};
cx.notify();
}
pub fn entry_by_path(&self, path: &RepoPath, cx: &App) -> Option<usize> {
if GitPanelSettings::get_global(cx).sort_by_path {
return self
@ -2594,12 +2431,11 @@ impl GitPanel {
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
if let Some(git_panel) = handle.upgrade() {
git_panel
.update_in(cx, |git_panel, window, cx| {
.update(cx, |git_panel, cx| {
if clear_pending {
git_panel.clear_pending();
}
git_panel.update_visible_entries(cx);
git_panel.update_scrollbar_properties(window, cx);
})
.ok();
}
@ -3710,110 +3546,6 @@ impl GitPanel {
)
}
fn render_vertical_scrollbar(
&self,
show_horizontal_scrollbar_container: bool,
cx: &mut Context<Self>,
) -> impl IntoElement {
div()
.id("git-panel-vertical-scroll")
.occlude()
.flex_none()
.h_full()
.cursor_default()
.absolute()
.right_0()
.top_0()
.bottom_0()
.w(px(12.))
.when(show_horizontal_scrollbar_container, |this| {
this.pb_neg_3p5()
})
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
if !this.vertical_scrollbar.state.is_dragging()
&& !this.focus_handle.contains_focused(window, cx)
{
this.vertical_scrollbar.hide(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(
// percentage as f32..end_offset as f32,
self.vertical_scrollbar.state.clone(),
))
}
/// Renders the horizontal scrollbar.
///
/// The right offset is used to determine how far to the right the
/// scrollbar should extend to, useful for ensuring it doesn't collide
/// with the vertical scrollbar when visible.
fn render_horizontal_scrollbar(
&self,
right_offset: Pixels,
cx: &mut Context<Self>,
) -> impl IntoElement {
div()
.id("git-panel-horizontal-scroll")
.occlude()
.flex_none()
.w_full()
.cursor_default()
.absolute()
.bottom_neg_px()
.left_0()
.right_0()
.pr(right_offset)
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
if !this.horizontal_scrollbar.state.is_dragging()
&& !this.focus_handle.contains_focused(window, cx)
{
this.horizontal_scrollbar.hide(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.children(Scrollbar::horizontal(
// percentage as f32..end_offset as f32,
self.horizontal_scrollbar.state.clone(),
))
}
fn render_buffer_header_controls(
&self,
entity: &Entity<Self>,
@ -3861,33 +3593,16 @@ impl GitPanel {
fn render_entries(
&self,
has_write_access: bool,
_: &Window,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let entry_count = self.entries.len();
let scroll_track_size = px(16.);
let h_scroll_offset = if self.vertical_scrollbar.show_scrollbar {
// magic number
px(3.)
} else {
px(0.)
};
v_flex()
.flex_1()
.size_full()
.overflow_hidden()
.relative()
// Show a border on the top and bottom of the container when
// the vertical scrollbar container is visible so we don't have a
// floating left border in the panel.
.when(self.vertical_scrollbar.show_track, |this| {
this.border_t_1()
.border_b_1()
.border_color(cx.theme().colors().border)
})
.child(
h_flex()
.flex_1()
@ -3928,15 +3643,6 @@ impl GitPanel {
items
}),
)
.when(
!self.horizontal_scrollbar.show_track
&& self.horizontal_scrollbar.show_scrollbar,
|this| {
// when not showing the horizontal scrollbar track, make sure we don't
// obscure the last entry
this.pb(scroll_track_size)
},
)
.size_full()
.flex_grow()
.with_sizing_behavior(ListSizingBehavior::Auto)
@ -3952,72 +3658,14 @@ impl GitPanel {
this.deploy_panel_context_menu(event.position, window, cx)
}),
)
.when(self.vertical_scrollbar.show_track, |this| {
this.child(
v_flex()
.h_full()
.flex_none()
.w(scroll_track_size)
.bg(cx.theme().colors().panel_background)
.child(
div()
.size_full()
.flex_1()
.border_l_1()
.border_color(cx.theme().colors().border),
),
)
})
.when(self.vertical_scrollbar.show_scrollbar, |this| {
this.child(
self.render_vertical_scrollbar(
self.horizontal_scrollbar.show_track,
.custom_scrollbars(
Scrollbars::for_settings::<GitPanelSettings>()
.tracked_scroll_handle(self.scroll_handle.clone())
.with_track_along(ScrollAxes::Horizontal),
window,
cx,
),
)
}),
)
.when(self.horizontal_scrollbar.show_track, |this| {
this.child(
h_flex()
.w_full()
.h(scroll_track_size)
.flex_none()
.relative()
.child(
div()
.w_full()
.flex_1()
// for some reason the horizontal scrollbar is 1px
// taller than the vertical scrollbar??
.h(scroll_track_size - px(1.))
.bg(cx.theme().colors().panel_background)
.border_t_1()
.border_color(cx.theme().colors().border),
)
.when(self.vertical_scrollbar.show_track, |this| {
this.child(
div()
.flex_none()
// -1px prevents a missing pixel between the two container borders
.w(scroll_track_size - px(1.))
.h_full(),
)
.child(
// HACK: Fill the missing 1px 🥲
div()
.absolute()
.right(scroll_track_size - px(1.))
.bottom(scroll_track_size - px(1.))
.size_px()
.bg(cx.theme().colors().border),
)
}),
)
})
.when(self.horizontal_scrollbar.show_scrollbar, |this| {
this.child(self.render_horizontal_scrollbar(h_scroll_offset, cx))
})
}
fn entry_label(&self, label: impl Into<SharedString>, color: Color) -> Label {
@ -4526,15 +4174,6 @@ impl Render for GitPanel {
.when(has_write_access && has_co_authors, |git_panel| {
git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
})
.on_hover(cx.listener(move |this, hovered, window, cx| {
if *hovered {
this.horizontal_scrollbar.show(cx);
this.vertical_scrollbar.show(cx);
cx.notify();
} else if !this.focus_handle.contains_focused(window, cx) {
this.hide_scrollbars(window, cx);
}
}))
.size_full()
.overflow_hidden()
.bg(cx.theme().colors().panel_background)

View file

@ -1,8 +1,9 @@
use editor::ShowScrollbar;
use editor::EditorSettings;
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use ui::scrollbars::{ScrollbarVisibilitySetting, ShowScrollbar};
use workspace::dock::DockPosition;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -89,6 +90,22 @@ pub struct GitPanelSettings {
pub collapse_untracked_diff: bool,
}
impl ScrollbarVisibilitySetting for GitPanelSettings {
fn scrollbar_visibility(&self, cx: &ui::App) -> ShowScrollbar {
// TODO: This PR should have defined Editor's `scrollbar.axis`
// as an Option<ScrollbarAxis>, not a ScrollbarAxes as it would allow you to
// `.unwrap_or(EditorSettings::get_global(cx).scrollbar.show)`.
//
// Once this is fixed we can extend the GitPanelSettings with a `scrollbar.axis`
// so we can show each axis based on the settings.
//
// We should fix this. PR: https://github.com/zed-industries/zed/pull/19495
self.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show)
}
}
impl Settings for GitPanelSettings {
const KEY: Option<&'static str> = Some("git_panel");

View file

@ -16,10 +16,10 @@
//! constructed by combining these two systems into an all-in-one element.
use crate::{
Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, HitboxBehavior,
HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent,
KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent,
DispatchPhase, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox,
HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Overflow, ParentElement, Pixels,
Point, Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task,
TooltipId, Visibility, Window, WindowControlArea, point, px, size,
@ -1036,6 +1036,15 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
/// Set the space to be reserved for rendering the scrollbar.
///
/// This will only affect the layout of the element when overflow for this element is set to
/// `Overflow::Scroll`.
fn scrollbar_width(mut self, width: impl Into<AbsoluteLength>) -> Self {
self.interactivity().base_style.scrollbar_width = Some(width.into());
self
}
/// Track the scroll state of this element with the given handle.
fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());

View file

@ -5,10 +5,10 @@
//! elements with uniform height.
use crate::{
AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, GlobalElementId,
Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement, IsZero, LayoutId,
ListSizingBehavior, Overflow, Pixels, Point, ScrollHandle, Size, StyleRefinement, Styled,
Window, point, size,
AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, Entity,
GlobalElementId, Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement,
IsZero, LayoutId, ListSizingBehavior, Overflow, Pixels, Point, ScrollHandle, Size,
StyleRefinement, Styled, Window, point, size,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -71,7 +71,7 @@ pub struct UniformList {
/// Frame state used by the [UniformList].
pub struct UniformListFrameState {
items: SmallVec<[AnyElement; 32]>,
decorations: SmallVec<[AnyElement; 1]>,
decorations: SmallVec<[AnyElement; 2]>,
}
/// A handle for controlling the scroll position of a uniform list.
@ -529,6 +529,31 @@ pub trait UniformListDecoration {
) -> AnyElement;
}
impl<T: UniformListDecoration + 'static> UniformListDecoration for Entity<T> {
fn compute(
&self,
visible_range: Range<usize>,
bounds: Bounds<Pixels>,
scroll_offset: Point<Pixels>,
item_height: Pixels,
item_count: usize,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
self.update(cx, |inner, cx| {
inner.compute(
visible_range,
bounds,
scroll_offset,
item_height,
item_count,
window,
cx,
)
})
}
}
impl UniformList {
/// Selects a specific list item for measurement.
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {

View file

@ -153,7 +153,7 @@ pub struct Style {
#[refineable]
pub overflow: Point<Overflow>,
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
pub scrollbar_width: f32,
pub scrollbar_width: AbsoluteLength,
/// Whether both x and y axis should be scrollable at the same time.
pub allow_concurrent_scroll: bool,
/// Whether scrolling should be restricted to the axis indicated by the mouse wheel.
@ -745,7 +745,7 @@ impl Default for Style {
},
allow_concurrent_scroll: false,
restrict_scroll_to_axis: false,
scrollbar_width: 0.0,
scrollbar_width: AbsoluteLength::default(),
position: Position::Relative,
inset: Edges::auto(),
margin: Edges::<Length>::zero(),

View file

@ -277,7 +277,7 @@ impl ToTaffy<taffy::style::Style> for Style {
taffy::style::Style {
display: self.display.into(),
overflow: self.overflow.into(),
scrollbar_width: self.scrollbar_width,
scrollbar_width: self.scrollbar_width.to_taffy(rem_size),
position: self.position.into(),
inset: self.inset.to_taffy(rem_size),
size: self.size.to_taffy(rem_size),
@ -314,6 +314,15 @@ impl ToTaffy<taffy::style::Style> for Style {
}
}
impl ToTaffy<f32> for AbsoluteLength {
fn to_taffy(&self, rem_size: Pixels) -> f32 {
match self {
AbsoluteLength::Pixels(pixels) => pixels.into(),
AbsoluteLength::Rems(rems) => (*rems * rem_size).into(),
}
}
}
impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
match self {

View file

@ -2504,7 +2504,7 @@ impl Window {
&mut self,
key: impl Into<ElementId>,
cx: &mut App,
init: impl FnOnce(&mut Self, &mut App) -> S,
init: impl FnOnce(&mut Self, &mut Context<S>) -> S,
) -> Entity<S> {
let current_view = self.current_view();
self.with_global_id(key.into(), |global_id, window| {
@ -2537,7 +2537,7 @@ impl Window {
pub fn use_state<S: 'static>(
&mut self,
cx: &mut App,
init: impl FnOnce(&mut Self, &mut App) -> S,
init: impl FnOnce(&mut Self, &mut Context<S>) -> S,
) -> Entity<S> {
self.use_keyed_state(
ElementId::CodeLocation(*core::panic::Location::caller()),
@ -4838,6 +4838,12 @@ impl<T: Into<SharedString>> From<(ElementId, T)> for ElementId {
}
}
impl From<&'static core::panic::Location<'static>> for ElementId {
fn from(location: &'static core::panic::Location<'static>) -> Self {
ElementId::CodeLocation(*location)
}
}
/// A rectangle to be rendered in the window at the given position and size.
/// Passed as an argument [`Window::paint_quad`].
#[derive(Clone)]

View file

@ -4,11 +4,11 @@ use anyhow::Context as _;
use collections::{BTreeSet, HashMap, HashSet, hash_map};
use db::kvp::KEY_VALUE_STORE;
use editor::{
AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, EditorSettings, ExcerptId,
ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, SelectionEffects, ShowScrollbar,
AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, ExcerptId, ExcerptRange,
MultiBufferSnapshot, RangeToAnchorExt, SelectionEffects,
display_map::ToDisplayPoint,
items::{entry_git_aware_label_color, entry_label_color},
scroll::{Autoscroll, ScrollAnchor, ScrollbarAutoHide},
scroll::{Autoscroll, ScrollAnchor},
};
use file_icons::FileIcons;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
@ -45,19 +45,18 @@ use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use smol::channel;
use theme::{SyntaxTheme, ThemeSettings};
use ui::{DynamicSpacing, IndentGuideColors, IndentGuideLayout};
use ui::{
ActiveTheme, ButtonCommon, Clickable, Color, ContextMenu, DynamicSpacing, FluentBuilder,
HighlightedLabel, Icon, IconButton, IconButtonShape, IconName, IconSize, IndentGuideColors,
IndentGuideLayout, Label, LabelCommon, ListItem, ScrollAxes, Scrollbars, StyledExt,
StyledTypography, Toggleable, Tooltip, WithScrollbar, h_flex, v_flex,
};
use util::{RangeExt, ResultExt, TryFutureExt, debug_panic};
use workspace::{
OpenInTerminal, WeakItemHandle, Workspace,
dock::{DockPosition, Panel, PanelEvent},
item::ItemHandle,
searchable::{SearchEvent, SearchableItem},
ui::{
ActiveTheme, ButtonCommon, Clickable, Color, ContextMenu, FluentBuilder, HighlightedLabel,
Icon, IconButton, IconButtonShape, IconName, IconSize, Label, LabelCommon, ListItem,
Scrollbar, ScrollbarState, StyledExt, StyledTypography, Toggleable, Tooltip, h_flex,
v_flex,
},
};
use worktree::{Entry, ProjectEntryId, WorktreeId};
@ -125,10 +124,6 @@ pub struct OutlinePanel {
cached_entries: Vec<CachedEntry>,
filter_editor: Entity<Editor>,
mode: ItemsDisplayMode,
show_scrollbar: bool,
vertical_scrollbar_state: ScrollbarState,
horizontal_scrollbar_state: ScrollbarState,
hide_scrollbar_task: Option<Task<()>>,
max_width_item_index: Option<usize>,
preserve_selection_on_buffer_fold_toggles: HashSet<BufferId>,
pending_default_expansion_depth: Option<usize>,
@ -752,10 +747,6 @@ impl OutlinePanel {
let focus_handle = cx.focus_handle();
let focus_subscription = cx.on_focus(&focus_handle, window, Self::focus_in);
let focus_out_subscription =
cx.on_focus_out(&focus_handle, window, |outline_panel, _, window, cx| {
outline_panel.hide_scrollbar(window, cx);
});
let workspace_subscription = cx.subscribe_in(
&workspace
.weak_handle()
@ -868,12 +859,6 @@ impl OutlinePanel {
workspace: workspace_handle,
project,
fs: workspace.app_state().fs.clone(),
show_scrollbar: !Self::should_autohide_scrollbar(cx),
hide_scrollbar_task: None,
vertical_scrollbar_state: ScrollbarState::new(scroll_handle.clone())
.parent_entity(&cx.entity()),
horizontal_scrollbar_state: ScrollbarState::new(scroll_handle.clone())
.parent_entity(&cx.entity()),
max_width_item_index: None,
scroll_handle,
focus_handle,
@ -903,7 +888,6 @@ impl OutlinePanel {
settings_subscription,
icons_subscription,
focus_subscription,
focus_out_subscription,
workspace_subscription,
filter_update_subscription,
],
@ -4491,150 +4475,6 @@ impl OutlinePanel {
cx.notify();
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
if !Self::should_show_scrollbar(cx)
|| !(self.show_scrollbar || self.vertical_scrollbar_state.is_dragging())
{
return None;
}
Some(
div()
.occlude()
.id("project-panel-vertical-scroll")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|outline_panel, _, window, cx| {
if !outline_panel.vertical_scrollbar_state.is_dragging()
&& !outline_panel.focus_handle.contains_focused(window, cx)
{
outline_panel.hide_scrollbar(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.vertical_scrollbar_state.clone())),
)
}
fn render_horizontal_scrollbar(
&self,
_: &mut Window,
cx: &mut Context<Self>,
) -> Option<Stateful<Div>> {
if !Self::should_show_scrollbar(cx)
|| !(self.show_scrollbar || self.horizontal_scrollbar_state.is_dragging())
{
return None;
}
Scrollbar::horizontal(self.horizontal_scrollbar_state.clone()).map(|scrollbar| {
div()
.occlude()
.id("project-panel-horizontal-scroll")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|outline_panel, _, window, cx| {
if !outline_panel.horizontal_scrollbar_state.is_dragging()
&& !outline_panel.focus_handle.contains_focused(window, cx)
{
outline_panel.hide_scrollbar(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.w_full()
.absolute()
.right_1()
.left_1()
.bottom_0()
.h(px(12.))
.cursor_default()
.child(scrollbar)
})
}
fn should_show_scrollbar(cx: &App) -> bool {
let show = OutlinePanelSettings::get_global(cx)
.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show);
match show {
ShowScrollbar::Auto => true,
ShowScrollbar::System => true,
ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
}
}
fn should_autohide_scrollbar(cx: &App) -> bool {
let show = OutlinePanelSettings::get_global(cx)
.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show);
match show {
ShowScrollbar::Auto => true,
ShowScrollbar::System => cx
.try_global::<ScrollbarAutoHide>()
.map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
ShowScrollbar::Always => false,
ShowScrollbar::Never => true,
}
}
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
if !Self::should_autohide_scrollbar(cx) {
return;
}
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
.await;
panel
.update(cx, |panel, cx| {
panel.show_scrollbar = false;
cx.notify();
})
.log_err();
}))
}
fn width_estimate(&self, depth: usize, entry: &PanelEntry, cx: &App) -> u64 {
let item_text_chars = match entry {
PanelEntry::Fs(FsEntry::ExternalFile(external)) => self
@ -4690,7 +4530,7 @@ impl OutlinePanel {
indent_size: f32,
window: &mut Window,
cx: &mut Context<Self>,
) -> Div {
) -> impl IntoElement {
let contents = if self.cached_entries.is_empty() {
let header = if self.updating_fs_entries || self.updating_cached_entries {
None
@ -4844,17 +4684,20 @@ impl OutlinePanel {
}),
)
})
.custom_scrollbars(
Scrollbars::for_settings::<OutlinePanelSettings>()
.tracked_scroll_handle(self.scroll_handle.clone())
.with_track_along(ScrollAxes::Horizontal)
.notify_content(),
window,
cx,
)
};
v_flex()
.flex_shrink()
.size_full()
.child(list_contents.size_full().flex_shrink())
.children(self.render_vertical_scrollbar(cx))
.when_some(
self.render_horizontal_scrollbar(window, cx),
|this, scrollbar| this.pb_4().child(scrollbar),
)
}
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
deferred(
@ -5121,15 +4964,6 @@ impl Render for OutlinePanel {
.size_full()
.overflow_hidden()
.relative()
.on_hover(cx.listener(|this, hovered, window, cx| {
if *hovered {
this.show_scrollbar = true;
this.hide_scrollbar_task.take();
cx.notify();
} else if !this.focus_handle.contains_focused(window, cx) {
this.hide_scrollbar(window, cx);
}
}))
.key_context(self.dispatch_context(window, cx))
.on_action(cx.listener(Self::open_selected_entry))
.on_action(cx.listener(Self::cancel))

View file

@ -1,8 +1,9 @@
use editor::ShowScrollbar;
use gpui::Pixels;
use editor::EditorSettings;
use gpui::{App, Pixels};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use ui::scrollbars::{ScrollbarVisibilitySetting, ShowScrollbar};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
@ -115,6 +116,14 @@ pub struct OutlinePanelSettingsContent {
pub expand_outlines_with_depth: Option<usize>,
}
impl ScrollbarVisibilitySetting for OutlinePanelSettings {
fn scrollbar_visibility(&self, cx: &App) -> ShowScrollbar {
self.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show)
}
}
impl Settings for OutlinePanelSettings {
const KEY: Option<&'static str> = Some("outline_panel");

View file

@ -11,17 +11,17 @@ use editor::{
use gpui::{
Action, AnyElement, App, ClickEvent, Context, DismissEvent, Entity, EventEmitter, FocusHandle,
Focusable, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent, Render,
ScrollStrategy, Stateful, Task, UniformListScrollHandle, Window, actions, div, list,
prelude::*, uniform_list,
ScrollStrategy, Task, UniformListScrollHandle, Window, actions, div, list, prelude::*,
uniform_list,
};
use head::Head;
use schemars::JsonSchema;
use serde::Deserialize;
use std::{ops::Range, sync::Arc, time::Duration};
use ui::{
Color, Divider, Label, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, prelude::*, v_flex,
Color, Divider, Label, ListItem, ListItemSpacing, ScrollAxes, Scrollbars, WithScrollbar,
prelude::*, v_flex,
};
use util::ResultExt;
use workspace::ModalView;
enum ElementContainer {
@ -65,13 +65,8 @@ pub struct Picker<D: PickerDelegate> {
width: Option<Length>,
widest_item: Option<usize>,
max_height: Option<Length>,
focus_handle: FocusHandle,
/// An external control to display a scrollbar in the `Picker`.
show_scrollbar: bool,
/// An internal state that controls whether to show the scrollbar based on the user's focus.
scrollbar_visibility: bool,
scrollbar_state: ScrollbarState,
hide_scrollbar_task: Option<Task<()>>,
/// Whether the `Picker` is rendered as a self-contained modal.
///
/// Set this to `false` when rendering the `Picker` as part of a larger modal.
@ -293,13 +288,6 @@ impl<D: PickerDelegate> Picker<D> {
cx: &mut Context<Self>,
) -> Self {
let element_container = Self::create_element_container(container);
let scrollbar_state = match &element_container {
ElementContainer::UniformList(scroll_handle) => {
ScrollbarState::new(scroll_handle.clone())
}
ElementContainer::List(state) => ScrollbarState::new(state.clone()),
};
let focus_handle = cx.focus_handle();
let mut this = Self {
delegate,
head,
@ -309,12 +297,8 @@ impl<D: PickerDelegate> Picker<D> {
width: None,
widest_item: None,
max_height: Some(rems(18.).into()),
focus_handle,
show_scrollbar: false,
scrollbar_visibility: true,
scrollbar_state,
is_modal: true,
hide_scrollbar_task: None,
};
this.update_matches("".to_string(), window, cx);
// give the delegate 4ms to render the first set of suggestions.
@ -790,67 +774,6 @@ impl<D: PickerDelegate> Picker<D> {
}
}
}
fn hide_scrollbar(&mut self, cx: &mut Context<Self>) {
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
self.hide_scrollbar_task = Some(cx.spawn(async move |panel, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
.await;
panel
.update(cx, |panel, cx| {
panel.scrollbar_visibility = false;
cx.notify();
})
.log_err();
}))
}
fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
if !self.show_scrollbar
|| !(self.scrollbar_visibility || self.scrollbar_state.is_dragging())
{
return None;
}
Some(
div()
.occlude()
.id("picker-scroll")
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|picker, _, window, cx| {
if !picker.scrollbar_state.is_dragging()
&& !picker.focus_handle.contains_focused(window, cx)
{
picker.hide_scrollbar(cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
}
}
impl<D: PickerDelegate> EventEmitter<DismissEvent> for Picker<D> {}
@ -900,17 +823,12 @@ impl<D: PickerDelegate> Render for Picker<D> {
.overflow_hidden()
.children(self.delegate.render_header(window, cx))
.child(self.render_element_container(cx))
.on_hover(cx.listener(|this, hovered, window, cx| {
if *hovered {
this.scrollbar_visibility = true;
this.hide_scrollbar_task.take();
cx.notify();
} else if !this.focus_handle.contains_focused(window, cx) {
this.hide_scrollbar(cx);
}
}))
.when_some(self.render_scrollbar(cx), |div, scrollbar| {
div.child(scrollbar)
.when(self.show_scrollbar, |this| {
this.custom_scrollbars(
Scrollbars::new(ScrollAxes::Vertical).width_sm(),
window,
cx,
)
}),
)
})

View file

@ -7,12 +7,11 @@ use collections::{BTreeSet, HashMap, hash_map};
use command_palette_hooks::CommandPaletteFilter;
use db::kvp::KEY_VALUE_STORE;
use editor::{
Editor, EditorEvent, EditorSettings, ShowScrollbar,
Editor, EditorEvent,
items::{
entry_diagnostic_aware_icon_decoration_and_color,
entry_diagnostic_aware_icon_name_and_color, entry_git_aware_label_color,
},
scroll::ScrollbarAutoHide,
};
use file_icons::FileIcons;
use git::status::GitSummary;
@ -59,7 +58,8 @@ use theme::ThemeSettings;
use ui::{
Color, ContextMenu, DecoratedIcon, Divider, Icon, IconDecoration, IconDecorationKind,
IndentGuideColors, IndentGuideLayout, KeyBinding, Label, LabelSize, ListItem, ListItemSpacing,
ScrollableHandle, Scrollbar, ScrollbarState, StickyCandidate, Tooltip, prelude::*, v_flex,
ScrollAxes, ScrollableHandle, Scrollbars, StickyCandidate, Tooltip, WithScrollbar, prelude::*,
v_flex,
};
use util::{ResultExt, TakeUntilExt, TryFutureExt, maybe, paths::compare_paths};
use workspace::{
@ -109,10 +109,6 @@ pub struct ProjectPanel {
workspace: WeakEntity<Workspace>,
width: Option<Pixels>,
pending_serialization: Task<Option<()>>,
show_scrollbar: bool,
vertical_scrollbar_state: ScrollbarState,
horizontal_scrollbar_state: ScrollbarState,
hide_scrollbar_task: Option<Task<()>>,
diagnostics: HashMap<(WorktreeId, PathBuf), DiagnosticSeverity>,
max_width_item_index: Option<usize>,
diagnostic_summary_update: Task<()>,
@ -428,7 +424,6 @@ impl ProjectPanel {
cx.on_focus(&focus_handle, window, Self::focus_in).detach();
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
this.focus_out(window, cx);
this.hide_scrollbar(window, cx);
})
.detach();
@ -619,12 +614,6 @@ impl ProjectPanel {
workspace: workspace.weak_handle(),
width: None,
pending_serialization: Task::ready(None),
show_scrollbar: !Self::should_autohide_scrollbar(cx),
hide_scrollbar_task: None,
vertical_scrollbar_state: ScrollbarState::new(scroll_handle.clone())
.parent_entity(&cx.entity()),
horizontal_scrollbar_state: ScrollbarState::new(scroll_handle.clone())
.parent_entity(&cx.entity()),
max_width_item_index: None,
diagnostics: Default::default(),
diagnostic_summary_update: Task::ready(()),
@ -4707,103 +4696,6 @@ impl ProjectPanel {
}
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
if !Self::should_show_scrollbar(cx)
|| !(self.show_scrollbar || self.vertical_scrollbar_state.is_dragging())
{
return None;
}
Some(
div()
.occlude()
.id("project-panel-vertical-scroll")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
if !this.vertical_scrollbar_state.is_dragging()
&& !this.focus_handle.contains_focused(window, cx)
{
this.hide_scrollbar(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_1()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(
// percentage as f32..end_offset as f32,
self.vertical_scrollbar_state.clone(),
)),
)
}
fn render_horizontal_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
if !Self::should_show_scrollbar(cx)
|| !(self.show_scrollbar || self.horizontal_scrollbar_state.is_dragging())
{
return None;
}
Scrollbar::horizontal(self.horizontal_scrollbar_state.clone()).map(|scrollbar| {
div()
.occlude()
.id("project-panel-horizontal-scroll")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, window, cx| {
if !this.horizontal_scrollbar_state.is_dragging()
&& !this.focus_handle.contains_focused(window, cx)
{
this.hide_scrollbar(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.w_full()
.absolute()
.right_1()
.left_1()
.bottom_1()
.h(px(12.))
.cursor_default()
.child(scrollbar)
})
}
fn dispatch_context(&self, window: &Window, cx: &Context<Self>) -> KeyContext {
let mut dispatch_context = KeyContext::new_with_defaults();
dispatch_context.add("ProjectPanel");
@ -4819,52 +4711,6 @@ impl ProjectPanel {
dispatch_context
}
fn should_show_scrollbar(cx: &App) -> bool {
let show = ProjectPanelSettings::get_global(cx)
.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show);
match show {
ShowScrollbar::Auto => true,
ShowScrollbar::System => true,
ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
}
}
fn should_autohide_scrollbar(cx: &App) -> bool {
let show = ProjectPanelSettings::get_global(cx)
.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show);
match show {
ShowScrollbar::Auto => true,
ShowScrollbar::System => cx
.try_global::<ScrollbarAutoHide>()
.map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
ShowScrollbar::Always => false,
ShowScrollbar::Never => true,
}
}
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
if !Self::should_autohide_scrollbar(cx) {
return;
}
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
.await;
panel
.update(cx, |panel, cx| {
panel.show_scrollbar = false;
cx.notify();
})
.log_err();
}))
}
fn reveal_entry(
&mut self,
project: Entity<Project>,
@ -5214,15 +5060,6 @@ impl Render for ProjectPanel {
this.refresh_drag_cursor_style(&event.modifiers, window, cx);
},
))
.on_hover(cx.listener(|this, hovered, window, cx| {
if *hovered {
this.show_scrollbar = true;
this.hide_scrollbar_task.take();
cx.notify();
} else if !this.focus_handle.contains_focused(window, cx) {
this.hide_scrollbar(window, cx);
}
}))
.on_click(cx.listener(|this, event, _, cx| {
if matches!(event, gpui::ClickEvent::Keyboard(_)) {
return;
@ -5483,10 +5320,14 @@ impl Render for ProjectPanel {
.with_width_from_item(self.max_width_item_index)
.track_scroll(self.scroll_handle.clone()),
)
.children(self.render_vertical_scrollbar(cx))
.when_some(self.render_horizontal_scrollbar(cx), |this, scrollbar| {
this.pb_4().child(scrollbar)
})
.custom_scrollbars(
Scrollbars::for_settings::<ProjectPanelSettings>()
.tracked_scroll_handle(self.scroll_handle.clone())
.with_track_along(ScrollAxes::Horizontal)
.notify_content(),
window,
cx,
)
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
deferred(
anchored()

View file

@ -1,8 +1,9 @@
use editor::ShowScrollbar;
use editor::EditorSettings;
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use ui::scrollbars::{ScrollbarVisibilitySetting, ShowScrollbar};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
@ -162,6 +163,14 @@ pub struct ProjectPanelSettingsContent {
pub sticky_scroll: Option<bool>,
}
impl ScrollbarVisibilitySetting for ProjectPanelSettings {
fn scrollbar_visibility(&self, cx: &ui::App) -> ShowScrollbar {
self.scrollbar
.show
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show)
}
}
impl Settings for ProjectPanelSettings {
const KEY: Option<&'static str> = Some("project_panel");

View file

@ -1,4 +1,3 @@
use std::any::Any;
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::path::PathBuf;
@ -37,9 +36,10 @@ use settings::watch_config_file;
use smol::stream::StreamExt as _;
use ui::Navigable;
use ui::NavigableEntry;
use ui::WithScrollbar;
use ui::{
IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Scrollbar, ScrollbarState,
Section, Tooltip, prelude::*,
IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Section, Tooltip,
prelude::*,
};
use util::{
ResultExt,
@ -297,7 +297,7 @@ impl RemoteEntry {
#[derive(Clone)]
struct DefaultState {
scrollbar: ScrollbarState,
scroll_handle: ScrollHandle,
add_new_server: NavigableEntry,
servers: Vec<RemoteEntry>,
}
@ -305,7 +305,6 @@ struct DefaultState {
impl DefaultState {
fn new(ssh_config_servers: &BTreeSet<SharedString>, cx: &mut App) -> Self {
let handle = ScrollHandle::new();
let scrollbar = ScrollbarState::new(handle.clone());
let add_new_server = NavigableEntry::new(&handle, cx);
let ssh_settings = SshSettings::get_global(cx);
@ -346,7 +345,7 @@ impl DefaultState {
}
Self {
scrollbar,
scroll_handle: handle,
add_new_server,
servers,
}
@ -1449,7 +1448,6 @@ impl RemoteServerProjects {
}
}
let scroll_state = state.scrollbar.parent_entity(&cx.entity());
let connect_button = div()
.id("ssh-connect-new-server-container")
.track_focus(&state.add_new_server.focus_handle)
@ -1480,17 +1478,12 @@ impl RemoteServerProjects {
cx.notify();
}));
let handle = &**scroll_state.scroll_handle() as &dyn Any;
let Some(scroll_handle) = handle.downcast_ref::<ScrollHandle>() else {
unreachable!()
};
let mut modal_section = Navigable::new(
v_flex()
.track_focus(&self.focus_handle(cx))
.id("ssh-server-list")
.overflow_y_scroll()
.track_scroll(scroll_handle)
.track_scroll(&state.scroll_handle)
.size_full()
.child(connect_button)
.child(
@ -1585,17 +1578,7 @@ impl RemoteServerProjects {
)
.size_full(),
)
.child(
div()
.occlude()
.h_full()
.absolute()
.top_1()
.bottom_1()
.right_1()
.w(px(8.))
.children(Scrollbar::vertical(scroll_state)),
),
.vertical_scrollbar_for(state.scroll_handle.clone(), window, cx),
),
)
.into_any_element()

View file

@ -11,8 +11,8 @@ use editor::{CompletionProvider, Editor, EditorEvent};
use fs::Fs;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
Action, AppContext as _, AsyncApp, Axis, ClickEvent, Context, DismissEvent, Entity,
EventEmitter, FocusHandle, Focusable, Global, IsZero,
Action, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
FocusHandle, Focusable, Global, IsZero,
KeyBindingContextPredicate::{And, Descendant, Equal, Identifier, Not, NotEqual, Or},
KeyContext, Keystroke, MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful,
StyledText, Subscription, Task, TextStyleRefinement, WeakEntity, actions, anchored, deferred,
@ -426,7 +426,7 @@ impl KeymapEditor {
fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let _keymap_subscription =
cx.observe_global_in::<KeymapEventChannel>(window, Self::on_keymap_changed);
let table_interaction_state = TableInteractionState::new(window, cx);
let table_interaction_state = TableInteractionState::new(cx);
let keystroke_editor = cx.new(|cx| {
let mut keystroke_editor = KeystrokeInput::new(None, window, cx);
@ -780,9 +780,8 @@ impl KeymapEditor {
match previous_edit {
// should remove scroll from process_query
PreviousEdit::ScrollBarOffset(offset) => {
this.table_interaction_state.update(cx, |table, _| {
table.set_scrollbar_offset(Axis::Vertical, offset)
})
this.table_interaction_state
.update(cx, |table, _| table.set_scroll_offset(offset))
// set selected index and scroll
}
PreviousEdit::Keybinding {
@ -811,9 +810,8 @@ impl KeymapEditor {
cx,
);
} else {
this.table_interaction_state.update(cx, |table, _| {
table.set_scrollbar_offset(Axis::Vertical, fallback)
});
this.table_interaction_state
.update(cx, |table, _| table.set_scroll_offset(fallback));
}
cx.notify();
}
@ -1198,9 +1196,7 @@ impl KeymapEditor {
};
let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
self.previous_edit = Some(PreviousEdit::ScrollBarOffset(
self.table_interaction_state
.read(cx)
.get_scrollbar_offset(Axis::Vertical),
self.table_interaction_state.read(cx).scroll_offset(),
));
cx.spawn(async move |_, _| remove_keybinding(to_remove, &fs, tab_size).await)
.detach_and_notify_err(window, cx);
@ -2337,10 +2333,7 @@ impl KeybindingEditorModal {
keymap.previous_edit = Some(PreviousEdit::Keybinding {
action_mapping,
action_name,
fallback: keymap
.table_interaction_state
.read(cx)
.get_scrollbar_offset(Axis::Vertical),
fallback: keymap.table_interaction_state.read(cx).scroll_offset(),
});
let status_toast = StatusToast::new(
format!("Saved edits to the {} action.", humanized_action_name),

View file

@ -1,20 +1,20 @@
use std::{ops::Range, rc::Rc, time::Duration};
use std::{ops::Range, rc::Rc};
use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};
use editor::EditorSettings;
use gpui::{
AbsoluteLength, AppContext, Axis, Context, DefiniteLength, DragMoveEvent, Entity, EntityId,
FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, Point,
Stateful, Task, UniformListScrollHandle, WeakEntity, transparent_black, uniform_list,
AbsoluteLength, AppContext, Context, DefiniteLength, DragMoveEvent, Entity, EntityId,
FocusHandle, Length, ListHorizontalSizingBehavior, ListSizingBehavior, Point, Stateful,
UniformListScrollHandle, WeakEntity, transparent_black, uniform_list,
};
use itertools::intersperse_with;
use settings::Settings as _;
use ui::{
ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,
InteractiveElement, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, StyledExt as _,
StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,
ScrollableHandle, Scrollbars, SharedString, StatefulInteractiveElement, Styled, StyledExt as _,
StyledTypography, Window, WithScrollbar, div, example_group_with_title, h_flex, px,
single_example, v_flex,
};
const RESIZE_COLUMN_WIDTH: f32 = 8.0;
@ -56,136 +56,22 @@ impl<const COLS: usize> TableContents<COLS> {
pub struct TableInteractionState {
pub focus_handle: FocusHandle,
pub scroll_handle: UniformListScrollHandle,
pub horizontal_scrollbar: ScrollbarProperties,
pub vertical_scrollbar: ScrollbarProperties,
}
impl TableInteractionState {
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| {
let focus_handle = cx.focus_handle();
cx.on_focus_out(&focus_handle, window, |this: &mut Self, _, window, cx| {
this.hide_scrollbars(window, cx);
})
.detach();
let scroll_handle = UniformListScrollHandle::new();
let vertical_scrollbar = ScrollbarProperties {
axis: Axis::Vertical,
state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
show_scrollbar: false,
show_track: false,
auto_hide: false,
hide_task: None,
};
let horizontal_scrollbar = ScrollbarProperties {
axis: Axis::Horizontal,
state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
show_scrollbar: false,
show_track: false,
auto_hide: false,
hide_task: None,
};
let mut this = Self {
focus_handle,
scroll_handle,
horizontal_scrollbar,
vertical_scrollbar,
};
this.update_scrollbar_visibility(cx);
this
pub fn new(cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self {
focus_handle: cx.focus_handle(),
scroll_handle: UniformListScrollHandle::new(),
})
}
pub fn get_scrollbar_offset(&self, axis: Axis) -> Point<Pixels> {
match axis {
Axis::Vertical => self.vertical_scrollbar.state.scroll_handle().offset(),
Axis::Horizontal => self.horizontal_scrollbar.state.scroll_handle().offset(),
}
pub fn scroll_offset(&self) -> Point<Pixels> {
self.scroll_handle.offset()
}
pub fn set_scrollbar_offset(&self, axis: Axis, offset: Point<Pixels>) {
match axis {
Axis::Vertical => self
.vertical_scrollbar
.state
.scroll_handle()
.set_offset(offset),
Axis::Horizontal => self
.horizontal_scrollbar
.state
.scroll_handle()
.set_offset(offset),
}
}
fn update_scrollbar_visibility(&mut self, cx: &mut Context<Self>) {
let show_setting = EditorSettings::get_global(cx).scrollbar.show;
let scroll_handle = self.scroll_handle.0.borrow();
let autohide = |show: ShowScrollbar, cx: &mut Context<Self>| match show {
ShowScrollbar::Auto => true,
ShowScrollbar::System => cx
.try_global::<ScrollbarAutoHide>()
.map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
ShowScrollbar::Always => false,
ShowScrollbar::Never => false,
};
let longest_item_width = scroll_handle.last_item_size.and_then(|size| {
(size.contents.width > size.item.width).then_some(size.contents.width)
});
// is there an item long enough that we should show a horizontal scrollbar?
let item_wider_than_container = if let Some(longest_item_width) = longest_item_width {
longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0)
} else {
true
};
let show_scrollbar = match show_setting {
ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
};
let show_vertical = show_scrollbar;
let show_horizontal = item_wider_than_container && show_scrollbar;
let show_horizontal_track =
show_horizontal && matches!(show_setting, ShowScrollbar::Always);
// TODO: we probably should hide the scroll track when the list doesn't need to scroll
let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always);
self.vertical_scrollbar = ScrollbarProperties {
axis: self.vertical_scrollbar.axis,
state: self.vertical_scrollbar.state.clone(),
show_scrollbar: show_vertical,
show_track: show_vertical_track,
auto_hide: autohide(show_setting, cx),
hide_task: None,
};
self.horizontal_scrollbar = ScrollbarProperties {
axis: self.horizontal_scrollbar.axis,
state: self.horizontal_scrollbar.state.clone(),
show_scrollbar: show_horizontal,
show_track: show_horizontal_track,
auto_hide: autohide(show_setting, cx),
hide_task: None,
};
cx.notify();
}
fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.horizontal_scrollbar.hide(window, cx);
self.vertical_scrollbar.hide(window, cx);
pub fn set_scroll_offset(&self, offset: Point<Pixels>) {
self.scroll_handle.set_offset(offset);
}
pub fn listener<E: ?Sized>(
@ -280,183 +166,6 @@ impl TableInteractionState {
.children(dividers)
.into_any_element()
}
fn render_vertical_scrollbar_track(
this: &Entity<Self>,
parent: Div,
scroll_track_size: Pixels,
cx: &mut App,
) -> Div {
if !this.read(cx).vertical_scrollbar.show_track {
return parent;
}
let child = v_flex()
.h_full()
.flex_none()
.w(scroll_track_size)
.bg(cx.theme().colors().background)
.child(
div()
.size_full()
.flex_1()
.border_l_1()
.border_color(cx.theme().colors().border),
);
parent.child(child)
}
fn render_vertical_scrollbar(this: &Entity<Self>, parent: Div, cx: &mut App) -> Div {
if !this.read(cx).vertical_scrollbar.show_scrollbar {
return parent;
}
let child = div()
.id(("table-vertical-scrollbar", this.entity_id()))
.occlude()
.flex_none()
.h_full()
.cursor_default()
.absolute()
.right_0()
.top_0()
.bottom_0()
.w(px(12.))
.on_mouse_move(Self::listener(this, |_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
Self::listener(this, |this, _, window, cx| {
if !this.vertical_scrollbar.state.is_dragging()
&& !this.focus_handle.contains_focused(window, cx)
{
this.vertical_scrollbar.hide(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_scroll_wheel(Self::listener(this, |_, _, _, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(
this.read(cx).vertical_scrollbar.state.clone(),
));
parent.child(child)
}
/// Renders the horizontal scrollbar.
///
/// The right offset is used to determine how far to the right the
/// scrollbar should extend to, useful for ensuring it doesn't collide
/// with the vertical scrollbar when visible.
fn render_horizontal_scrollbar(
this: &Entity<Self>,
parent: Div,
right_offset: Pixels,
cx: &mut App,
) -> Div {
if !this.read(cx).horizontal_scrollbar.show_scrollbar {
return parent;
}
let child = div()
.id(("table-horizontal-scrollbar", this.entity_id()))
.occlude()
.flex_none()
.w_full()
.cursor_default()
.absolute()
.bottom_neg_px()
.left_0()
.right_0()
.pr(right_offset)
.on_mouse_move(Self::listener(this, |_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
Self::listener(this, |this, _, window, cx| {
if !this.horizontal_scrollbar.state.is_dragging()
&& !this.focus_handle.contains_focused(window, cx)
{
this.horizontal_scrollbar.hide(window, cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(Self::listener(this, |_, _, _, cx| {
cx.notify();
}))
.children(Scrollbar::horizontal(
// percentage as f32..end_offset as f32,
this.read(cx).horizontal_scrollbar.state.clone(),
));
parent.child(child)
}
fn render_horizontal_scrollbar_track(
this: &Entity<Self>,
parent: Div,
scroll_track_size: Pixels,
cx: &mut App,
) -> Div {
if !this.read(cx).horizontal_scrollbar.show_track {
return parent;
}
let child = h_flex()
.w_full()
.h(scroll_track_size)
.flex_none()
.relative()
.child(
div()
.w_full()
.flex_1()
// for some reason the horizontal scrollbar is 1px
// taller than the vertical scrollbar??
.h(scroll_track_size - px(1.))
.bg(cx.theme().colors().background)
.border_t_1()
.border_color(cx.theme().colors().border),
)
.when(this.read(cx).vertical_scrollbar.show_track, |parent| {
parent
.child(
div()
.flex_none()
// -1px prevents a missing pixel between the two container borders
.w(scroll_track_size - px(1.))
.h_full(),
)
.child(
// HACK: Fill the missing 1px 🥲
div()
.absolute()
.right(scroll_track_size - px(1.))
.bottom(scroll_track_size - px(1.))
.size_px()
.bg(cx.theme().colors().border),
)
});
parent.child(child)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
@ -1054,17 +763,6 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
.and_then(|widths| Some((widths.current.as_ref()?, widths.resizable, widths.initial)))
.map(|(curr, resize_behavior, initial)| (curr.downgrade(), resize_behavior, initial));
let scroll_track_size = px(16.);
let h_scroll_offset = if interaction_state
.as_ref()
.is_some_and(|state| state.read(cx).vertical_scrollbar.show_scrollbar)
{
// magic number
px(3.)
} else {
px(0.)
};
let width = self.width;
let no_rows_rendered = self.rows.is_empty();
@ -1115,8 +813,8 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
})
}
})
.child(
div()
.child({
let content = div()
.flex_grow()
.w_full()
.relative()
@ -1187,25 +885,21 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
)
}))
},
)
.when_some(interaction_state.as_ref(), |this, interaction_state| {
this.map(|this| {
TableInteractionState::render_vertical_scrollbar_track(
interaction_state,
this,
scroll_track_size,
);
if let Some(state) = interaction_state.as_ref() {
content
.custom_scrollbars(
Scrollbars::for_settings::<EditorSettings>()
.tracked_scroll_handle(state.read(cx).scroll_handle.clone()),
window,
cx,
)
.into_any_element()
} else {
content.into_any_element()
}
})
.map(|this| {
TableInteractionState::render_vertical_scrollbar(
interaction_state,
this,
cx,
)
})
}),
)
.when_some(
no_rows_rendered
.then_some(self.empty_table_callback)
@ -1220,52 +914,12 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
.child(callback(window, cx)),
)
},
)
.when_some(
width.and(interaction_state.as_ref()),
|this, interaction_state| {
this.map(|this| {
TableInteractionState::render_horizontal_scrollbar_track(
interaction_state,
this,
scroll_track_size,
cx,
)
})
.map(|this| {
TableInteractionState::render_horizontal_scrollbar(
interaction_state,
this,
h_scroll_offset,
cx,
)
})
},
);
if let Some(interaction_state) = interaction_state.as_ref() {
table
.track_focus(&interaction_state.read(cx).focus_handle)
.id(("table", interaction_state.entity_id()))
.on_hover({
let interaction_state = interaction_state.downgrade();
move |hovered, window, cx| {
interaction_state
.update(cx, |interaction_state, cx| {
if *hovered {
interaction_state.horizontal_scrollbar.show(cx);
interaction_state.vertical_scrollbar.show(cx);
cx.notify();
} else if !interaction_state
.focus_handle
.contains_focused(window, cx)
{
interaction_state.hide_scrollbars(window, cx);
}
})
.ok();
}
})
.into_any_element()
} else {
table.into_any_element()
@ -1273,65 +927,6 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
}
}
// computed state related to how to render scrollbars
// one per axis
// on render we just read this off the keymap editor
// we update it when
// - settings change
// - on focus in, on focus out, on hover, etc.
#[derive(Debug)]
pub struct ScrollbarProperties {
axis: Axis,
show_scrollbar: bool,
show_track: bool,
auto_hide: bool,
hide_task: Option<Task<()>>,
state: ScrollbarState,
}
impl ScrollbarProperties {
// Shows the scrollbar and cancels any pending hide task
fn show(&mut self, cx: &mut Context<TableInteractionState>) {
if !self.auto_hide {
return;
}
self.show_scrollbar = true;
self.hide_task.take();
cx.notify();
}
fn hide(&mut self, window: &mut Window, cx: &mut Context<TableInteractionState>) {
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
if !self.auto_hide {
return;
}
let axis = self.axis;
self.hide_task = Some(cx.spawn_in(window, async move |keymap_editor, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
.await;
if let Some(keymap_editor) = keymap_editor.upgrade() {
keymap_editor
.update(cx, |keymap_editor, cx| {
match axis {
Axis::Vertical => {
keymap_editor.vertical_scrollbar.show_scrollbar = false
}
Axis::Horizontal => {
keymap_editor.horizontal_scrollbar.show_scrollbar = false
}
}
cx.notify();
})
.ok();
}
}));
}
}
impl Component for Table<3> {
fn scope() -> ComponentScope {
ComponentScope::Layout

View file

@ -7,12 +7,11 @@ mod terminal_slash_command;
pub mod terminal_tab_tooltip;
use assistant_slash_command::SlashCommandRegistry;
use editor::{EditorSettings, actions::SelectAll, scroll::ScrollbarAutoHide};
use editor::{EditorSettings, actions::SelectAll};
use gpui::{
Action, AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
KeyContext, KeyDownEvent, Keystroke, MouseButton, MouseDownEvent, Pixels, Render,
ScrollWheelEvent, Stateful, Styled, Subscription, Task, WeakEntity, actions, anchored,
deferred, div,
ScrollWheelEvent, Styled, Subscription, Task, WeakEntity, actions, anchored, deferred, div,
};
use persistence::TERMINAL_DB;
use project::{Project, search::SearchQuery, terminals::TerminalKind};
@ -35,7 +34,9 @@ use terminal_scrollbar::TerminalScrollHandle;
use terminal_slash_command::TerminalSlashCommand;
use terminal_tab_tooltip::TerminalTooltip;
use ui::{
ContextMenu, Icon, IconName, Label, Scrollbar, ScrollbarState, Tooltip, h_flex, prelude::*,
ContextMenu, Icon, IconName, Label, ScrollAxes, Scrollbars, Tooltip, WithScrollbar, h_flex,
prelude::*,
scrollbars::{self, GlobalValue, ScrollbarVisibilitySetting},
};
use util::ResultExt;
use workspace::{
@ -63,7 +64,6 @@ use std::{
};
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const TERMINAL_SCROLLBAR_WIDTH: Pixels = px(12.);
/// Event to transmit the scroll from the element to the view
#[derive(Clone, Debug, PartialEq)]
@ -134,10 +134,7 @@ pub struct TerminalView {
show_breadcrumbs: bool,
block_below_cursor: Option<Rc<BlockProperties>>,
scroll_top: Pixels,
scrollbar_state: ScrollbarState,
scroll_handle: TerminalScrollHandle,
show_scrollbar: bool,
hide_scrollbar_task: Option<Task<()>>,
marked_text: Option<String>,
marked_range_utf16: Option<Range<usize>>,
_subscriptions: Vec<Subscription>,
@ -261,10 +258,7 @@ impl TerminalView {
show_breadcrumbs: TerminalSettings::get_global(cx).toolbar.breadcrumbs,
block_below_cursor: None,
scroll_top: Pixels::ZERO,
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
show_scrollbar: !Self::should_autohide_scrollbar(cx),
hide_scrollbar_task: None,
cwd_serialized: false,
marked_text: None,
marked_range_utf16: None,
@ -833,136 +827,6 @@ impl TerminalView {
self.terminal = terminal;
}
// Hack: Using editor in terminal causes cyclic dependency i.e. editor -> terminal -> project -> editor.
fn map_show_scrollbar_from_editor_to_terminal(
show_scrollbar: editor::ShowScrollbar,
) -> terminal_settings::ShowScrollbar {
match show_scrollbar {
editor::ShowScrollbar::Auto => terminal_settings::ShowScrollbar::Auto,
editor::ShowScrollbar::System => terminal_settings::ShowScrollbar::System,
editor::ShowScrollbar::Always => terminal_settings::ShowScrollbar::Always,
editor::ShowScrollbar::Never => terminal_settings::ShowScrollbar::Never,
}
}
fn should_show_scrollbar(cx: &App) -> bool {
let show = TerminalSettings::get_global(cx)
.scrollbar
.show
.unwrap_or_else(|| {
Self::map_show_scrollbar_from_editor_to_terminal(
EditorSettings::get_global(cx).scrollbar.show,
)
});
match show {
terminal_settings::ShowScrollbar::Auto => true,
terminal_settings::ShowScrollbar::System => true,
terminal_settings::ShowScrollbar::Always => true,
terminal_settings::ShowScrollbar::Never => false,
}
}
fn should_autohide_scrollbar(cx: &App) -> bool {
let show = TerminalSettings::get_global(cx)
.scrollbar
.show
.unwrap_or_else(|| {
Self::map_show_scrollbar_from_editor_to_terminal(
EditorSettings::get_global(cx).scrollbar.show,
)
});
match show {
terminal_settings::ShowScrollbar::Auto => true,
terminal_settings::ShowScrollbar::System => cx
.try_global::<ScrollbarAutoHide>()
.map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
terminal_settings::ShowScrollbar::Always => false,
terminal_settings::ShowScrollbar::Never => true,
}
}
fn hide_scrollbar(&mut self, cx: &mut Context<Self>) {
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
if !Self::should_autohide_scrollbar(cx) {
return;
}
self.hide_scrollbar_task = Some(cx.spawn(async move |panel, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
.await;
panel
.update(cx, |panel, cx| {
panel.show_scrollbar = false;
cx.notify();
})
.log_err();
}))
}
fn render_scrollbar(&self, window: &Window, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
if !Self::should_show_scrollbar(cx)
|| !(self.show_scrollbar || self.scrollbar_state.is_dragging())
|| !self.content_mode(window, cx).is_scrollable()
{
return None;
}
if self.terminal.read(cx).total_lines() == self.terminal.read(cx).viewport_lines() {
return None;
}
self.scroll_handle.update(self.terminal.read(cx));
if let Some(new_display_offset) = self.scroll_handle.future_display_offset.take() {
self.terminal.update(cx, |term, _| {
let delta = new_display_offset as i32 - term.last_content.display_offset as i32;
match delta.cmp(&0) {
std::cmp::Ordering::Greater => term.scroll_up_by(delta as usize),
std::cmp::Ordering::Less => term.scroll_down_by(-delta as usize),
std::cmp::Ordering::Equal => {}
}
});
}
Some(
div()
.occlude()
.id("terminal-view-scroll")
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|terminal_view, _, window, cx| {
if !terminal_view.scrollbar_state.is_dragging()
&& !terminal_view.focus_handle.contains_focused(window, cx)
{
terminal_view.hide_scrollbar(cx);
cx.notify();
}
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.absolute()
.top_0()
.bottom_0()
.right_0()
.h_full()
.w(TERMINAL_SCROLLBAR_WIDTH)
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
}
fn rerun_button(task: &TaskState) -> Option<IconButton> {
if !task.show_rerun {
return None;
@ -1117,6 +981,29 @@ fn regex_search_for_query(query: &project::search::SearchQuery) -> Option<RegexS
}
}
struct TerminalScrollbarSettingsWrapper;
impl GlobalValue for TerminalScrollbarSettingsWrapper {
fn get_value(_cx: &App) -> &Self {
&Self
}
}
impl ScrollbarVisibilitySetting for TerminalScrollbarSettingsWrapper {
fn scrollbar_visibility(&self, cx: &App) -> scrollbars::ShowScrollbar {
TerminalSettings::get_global(cx)
.scrollbar
.show
.map(|value| match value {
terminal_settings::ShowScrollbar::Auto => scrollbars::ShowScrollbar::Auto,
terminal_settings::ShowScrollbar::System => scrollbars::ShowScrollbar::System,
terminal_settings::ShowScrollbar::Always => scrollbars::ShowScrollbar::Always,
terminal_settings::ShowScrollbar::Never => scrollbars::ShowScrollbar::Never,
})
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show)
}
}
impl TerminalView {
fn key_down(&mut self, event: &KeyDownEvent, window: &mut Window, cx: &mut Context<Self>) {
self.clear_bell(cx);
@ -1148,28 +1035,31 @@ impl TerminalView {
terminal.focus_out();
terminal.set_cursor_shape(CursorShape::Hollow);
});
self.hide_scrollbar(cx);
cx.notify();
}
}
impl Render for TerminalView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// TODO: this should be moved out of render
self.scroll_handle.update(self.terminal.read(cx));
if let Some(new_display_offset) = self.scroll_handle.future_display_offset.take() {
self.terminal.update(cx, |term, _| {
let delta = new_display_offset as i32 - term.last_content.display_offset as i32;
match delta.cmp(&0) {
std::cmp::Ordering::Greater => term.scroll_up_by(delta as usize),
std::cmp::Ordering::Less => term.scroll_down_by(-delta as usize),
std::cmp::Ordering::Equal => {}
}
});
}
let terminal_handle = self.terminal.clone();
let terminal_view_handle = cx.entity();
let focused = self.focus_handle.is_focused(window);
// Always calculate scrollbar width to prevent layout shift
let scrollbar_width = if Self::should_show_scrollbar(cx)
&& self.content_mode(window, cx).is_scrollable()
&& self.terminal.read(cx).total_lines() > self.terminal.read(cx).viewport_lines()
{
TERMINAL_SCROLLBAR_WIDTH
} else {
px(0.)
};
div()
.id("terminal-view")
.size_full()
@ -1206,21 +1096,12 @@ impl Render for TerminalView {
}
}),
)
.on_hover(cx.listener(|this, hovered, window, cx| {
if *hovered {
this.show_scrollbar = true;
this.hide_scrollbar_task.take();
cx.notify();
} else if !this.focus_handle.contains_focused(window, cx) {
this.hide_scrollbar(cx);
}
}))
.child(
// TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu
div()
.id("terminal-view-container")
.size_full()
.bg(cx.theme().colors().editor_background)
.when(scrollbar_width > px(0.), |div| div.pr(scrollbar_width))
.child(TerminalElement::new(
terminal_handle,
terminal_view_handle,
@ -1231,8 +1112,15 @@ impl Render for TerminalView {
self.block_below_cursor.clone(),
self.mode.clone(),
))
.when_some(self.render_scrollbar(window, cx), |div, scrollbar| {
div.child(scrollbar)
.when(self.content_mode(window, cx).is_scrollable(), |div| {
div.custom_scrollbars(
Scrollbars::for_settings::<TerminalScrollbarSettingsWrapper>()
.show_along(ScrollAxes::Vertical)
.with_track_along(ScrollAxes::Vertical)
.tracked_scroll_handle(self.scroll_handle.clone()),
window,
cx,
)
}),
)
.children(self.context_menu.as_ref().map(|(menu, position, _)| {

View file

@ -21,6 +21,7 @@ gpui_macros.workspace = true
icons.workspace = true
itertools.workspace = true
menu.workspace = true
schemars.workspace = true
serde.workspace = true
settings.workspace = true
smallvec.workspace = true

File diff suppressed because it is too large Load diff