Compare commits
22 commits
main
...
ui-scrollb
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0e8696df84 | ||
![]() |
c0891800dc | ||
![]() |
b4223d318f | ||
![]() |
2a171a971c | ||
![]() |
a067729525 | ||
![]() |
1f2d18e691 | ||
![]() |
76842eed31 | ||
![]() |
da9084226d | ||
![]() |
fd8fb1ed16 | ||
![]() |
8b458b2a7a | ||
![]() |
351db21ff9 | ||
![]() |
7622ba09ee | ||
![]() |
91cdf69924 | ||
![]() |
6414589243 | ||
![]() |
3da0c0aa60 | ||
![]() |
a9dbfce8f9 | ||
![]() |
fd33832609 | ||
![]() |
40084aa94c | ||
![]() |
afcfd0979a | ||
![]() |
3b611313e1 | ||
![]() |
bfd71db0a3 | ||
![]() |
c28d873a2f |
36 changed files with 1510 additions and 2417 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -17521,6 +17521,7 @@ dependencies = [
|
|||
"icons",
|
||||
"itertools 0.14.0",
|
||||
"menu",
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, _)| {
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue