Thread view scrollbar (#35655)
This also adds a convenient `Scrollbar:auto_hide` function so that we don't have to handle that at the callsite. Release Notes: - N/A --------- Co-authored-by: David Kleingeld <davidsk@zed.dev>
This commit is contained in:
parent
3c602fecbf
commit
33f198fef1
5 changed files with 238 additions and 233 deletions
|
@ -21,10 +21,10 @@ use editor::{
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, Animation, AnimationExt, App, BorderStyle, EdgesRefinement, Empty, Entity, EntityId,
|
Action, Animation, AnimationExt, App, BorderStyle, EdgesRefinement, Empty, Entity, EntityId,
|
||||||
FocusHandle, Focusable, Hsla, Length, ListOffset, ListState, PlatformDisplay, SharedString,
|
FocusHandle, Focusable, Hsla, Length, ListOffset, ListState, MouseButton, PlatformDisplay,
|
||||||
StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation,
|
SharedString, Stateful, StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement,
|
||||||
UnderlineStyle, WeakEntity, Window, WindowHandle, div, linear_color_stop, linear_gradient,
|
Transformation, UnderlineStyle, WeakEntity, Window, WindowHandle, div, linear_color_stop,
|
||||||
list, percentage, point, prelude::*, pulsating_between,
|
linear_gradient, list, percentage, point, prelude::*, pulsating_between,
|
||||||
};
|
};
|
||||||
use language::language_settings::SoftWrap;
|
use language::language_settings::SoftWrap;
|
||||||
use language::{Buffer, Language};
|
use language::{Buffer, Language};
|
||||||
|
@ -34,7 +34,9 @@ use project::Project;
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use text::{Anchor, BufferSnapshot};
|
use text::{Anchor, BufferSnapshot};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{Disclosure, Divider, DividerColor, KeyBinding, Tooltip, prelude::*};
|
use ui::{
|
||||||
|
Disclosure, Divider, DividerColor, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*,
|
||||||
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{CollaboratorId, Workspace};
|
use workspace::{CollaboratorId, Workspace};
|
||||||
use zed_actions::agent::{Chat, NextHistoryMessage, PreviousHistoryMessage};
|
use zed_actions::agent::{Chat, NextHistoryMessage, PreviousHistoryMessage};
|
||||||
|
@ -69,6 +71,7 @@ pub struct AcpThreadView {
|
||||||
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
|
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
|
||||||
last_error: Option<Entity<Markdown>>,
|
last_error: Option<Entity<Markdown>>,
|
||||||
list_state: ListState,
|
list_state: ListState,
|
||||||
|
scrollbar_state: ScrollbarState,
|
||||||
auth_task: Option<Task<()>>,
|
auth_task: Option<Task<()>>,
|
||||||
expanded_tool_calls: HashSet<acp::ToolCallId>,
|
expanded_tool_calls: HashSet<acp::ToolCallId>,
|
||||||
expanded_thinking_blocks: HashSet<(usize, usize)>,
|
expanded_thinking_blocks: HashSet<(usize, usize)>,
|
||||||
|
@ -187,7 +190,8 @@ impl AcpThreadView {
|
||||||
notifications: Vec::new(),
|
notifications: Vec::new(),
|
||||||
notification_subscriptions: HashMap::default(),
|
notification_subscriptions: HashMap::default(),
|
||||||
diff_editors: Default::default(),
|
diff_editors: Default::default(),
|
||||||
list_state: list_state,
|
list_state: list_state.clone(),
|
||||||
|
scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
|
||||||
last_error: None,
|
last_error: None,
|
||||||
auth_task: None,
|
auth_task: None,
|
||||||
expanded_tool_calls: HashSet::default(),
|
expanded_tool_calls: HashSet::default(),
|
||||||
|
@ -2479,6 +2483,39 @@ impl AcpThreadView {
|
||||||
.child(open_as_markdown)
|
.child(open_as_markdown)
|
||||||
.child(scroll_to_top)
|
.child(scroll_to_top)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for AcpThreadView {
|
impl Focusable for AcpThreadView {
|
||||||
|
@ -2553,6 +2590,7 @@ impl Render for AcpThreadView {
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.into_any(),
|
.into_any(),
|
||||||
)
|
)
|
||||||
|
.child(self.render_vertical_scrollbar(cx))
|
||||||
.children(match thread_clone.read(cx).status() {
|
.children(match thread_clone.read(cx).status() {
|
||||||
ThreadStatus::Idle | ThreadStatus::WaitingForToolConfirmation => {
|
ThreadStatus::Idle | ThreadStatus::WaitingForToolConfirmation => {
|
||||||
None
|
None
|
||||||
|
|
|
@ -69,8 +69,6 @@ pub struct ActiveThread {
|
||||||
messages: Vec<MessageId>,
|
messages: Vec<MessageId>,
|
||||||
list_state: ListState,
|
list_state: ListState,
|
||||||
scrollbar_state: ScrollbarState,
|
scrollbar_state: ScrollbarState,
|
||||||
show_scrollbar: bool,
|
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
|
||||||
rendered_messages_by_id: HashMap<MessageId, RenderedMessage>,
|
rendered_messages_by_id: HashMap<MessageId, RenderedMessage>,
|
||||||
rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>,
|
rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>,
|
||||||
editing_message: Option<(MessageId, EditingMessageState)>,
|
editing_message: Option<(MessageId, EditingMessageState)>,
|
||||||
|
@ -805,9 +803,7 @@ impl ActiveThread {
|
||||||
expanded_thinking_segments: HashMap::default(),
|
expanded_thinking_segments: HashMap::default(),
|
||||||
expanded_code_blocks: HashMap::default(),
|
expanded_code_blocks: HashMap::default(),
|
||||||
list_state: list_state.clone(),
|
list_state: list_state.clone(),
|
||||||
scrollbar_state: ScrollbarState::new(list_state),
|
scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
|
||||||
show_scrollbar: false,
|
|
||||||
hide_scrollbar_task: None,
|
|
||||||
editing_message: None,
|
editing_message: None,
|
||||||
last_error: None,
|
last_error: None,
|
||||||
copied_code_block_ids: HashSet::default(),
|
copied_code_block_ids: HashSet::default(),
|
||||||
|
@ -3502,60 +3498,37 @@ impl ActiveThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
|
||||||
if !self.show_scrollbar && !self.scrollbar_state.is_dragging() {
|
div()
|
||||||
return None;
|
.occlude()
|
||||||
}
|
.id("active-thread-scrollbar")
|
||||||
|
.on_mouse_move(cx.listener(|_, _, _, cx| {
|
||||||
Some(
|
cx.notify();
|
||||||
div()
|
cx.stop_propagation()
|
||||||
.occlude()
|
}))
|
||||||
.id("active-thread-scrollbar")
|
.on_hover(|_, _, cx| {
|
||||||
.on_mouse_move(cx.listener(|_, _, _, cx| {
|
cx.stop_propagation();
|
||||||
cx.notify();
|
})
|
||||||
cx.stop_propagation()
|
.on_any_mouse_down(|_, _, cx| {
|
||||||
}))
|
cx.stop_propagation();
|
||||||
.on_hover(|_, _, cx| {
|
})
|
||||||
|
.on_mouse_up(
|
||||||
|
MouseButton::Left,
|
||||||
|
cx.listener(|_, _, _, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
})
|
}),
|
||||||
.on_any_mouse_down(|_, _, cx| {
|
)
|
||||||
cx.stop_propagation();
|
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
|
||||||
})
|
cx.notify();
|
||||||
.on_mouse_up(
|
}))
|
||||||
MouseButton::Left,
|
.h_full()
|
||||||
cx.listener(|_, _, _, cx| {
|
.absolute()
|
||||||
cx.stop_propagation();
|
.right_1()
|
||||||
}),
|
.top_1()
|
||||||
)
|
.bottom_0()
|
||||||
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
|
.w(px(12.))
|
||||||
cx.notify();
|
.cursor_default()
|
||||||
}))
|
.children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
|
||||||
.h_full()
|
|
||||||
.absolute()
|
|
||||||
.right_1()
|
|
||||||
.top_1()
|
|
||||||
.bottom_0()
|
|
||||||
.w(px(12.))
|
|
||||||
.cursor_default()
|
|
||||||
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hide_scrollbar_later(&mut self, cx: &mut Context<Self>) {
|
|
||||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
|
||||||
self.hide_scrollbar_task = Some(cx.spawn(async move |thread, cx| {
|
|
||||||
cx.background_executor()
|
|
||||||
.timer(SCROLLBAR_SHOW_INTERVAL)
|
|
||||||
.await;
|
|
||||||
thread
|
|
||||||
.update(cx, |thread, cx| {
|
|
||||||
if !thread.scrollbar_state.is_dragging() {
|
|
||||||
thread.show_scrollbar = false;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_codeblock_expanded(&self, message_id: MessageId, ix: usize) -> bool {
|
pub fn is_codeblock_expanded(&self, message_id: MessageId, ix: usize) -> bool {
|
||||||
|
@ -3596,26 +3569,8 @@ impl Render for ActiveThread {
|
||||||
.size_full()
|
.size_full()
|
||||||
.relative()
|
.relative()
|
||||||
.bg(cx.theme().colors().panel_background)
|
.bg(cx.theme().colors().panel_background)
|
||||||
.on_mouse_move(cx.listener(|this, _, _, cx| {
|
|
||||||
this.show_scrollbar = true;
|
|
||||||
this.hide_scrollbar_later(cx);
|
|
||||||
cx.notify();
|
|
||||||
}))
|
|
||||||
.on_scroll_wheel(cx.listener(|this, _, _, cx| {
|
|
||||||
this.show_scrollbar = true;
|
|
||||||
this.hide_scrollbar_later(cx);
|
|
||||||
cx.notify();
|
|
||||||
}))
|
|
||||||
.on_mouse_up(
|
|
||||||
MouseButton::Left,
|
|
||||||
cx.listener(|this, _, _, cx| {
|
|
||||||
this.hide_scrollbar_later(cx);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(list(self.list_state.clone(), cx.processor(Self::render_message)).flex_grow())
|
.child(list(self.list_state.clone(), cx.processor(Self::render_message)).flex_grow())
|
||||||
.when_some(self.render_vertical_scrollbar(cx), |this, scrollbar| {
|
.child(self.render_vertical_scrollbar(cx))
|
||||||
this.child(scrollbar)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ use ui::{
|
||||||
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Toggleable,
|
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Toggleable,
|
||||||
Tooltip, Window, div, h_flex, px, v_flex,
|
Tooltip, Window, div, h_flex, px, v_flex,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
|
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
|
||||||
|
|
||||||
|
@ -56,8 +55,6 @@ pub(crate) struct BreakpointList {
|
||||||
scrollbar_state: ScrollbarState,
|
scrollbar_state: ScrollbarState,
|
||||||
breakpoints: Vec<BreakpointEntry>,
|
breakpoints: Vec<BreakpointEntry>,
|
||||||
session: Option<Entity<Session>>,
|
session: Option<Entity<Session>>,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
|
||||||
show_scrollbar: bool,
|
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
selected_ix: Option<usize>,
|
selected_ix: Option<usize>,
|
||||||
|
@ -103,8 +100,6 @@ impl BreakpointList {
|
||||||
worktree_store,
|
worktree_store,
|
||||||
scrollbar_state,
|
scrollbar_state,
|
||||||
breakpoints: Default::default(),
|
breakpoints: Default::default(),
|
||||||
hide_scrollbar_task: None,
|
|
||||||
show_scrollbar: false,
|
|
||||||
workspace,
|
workspace,
|
||||||
session,
|
session,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
|
@ -565,21 +560,6 @@ impl BreakpointList {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
|
||||||
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 render_list(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_list(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let selected_ix = self.selected_ix;
|
let selected_ix = self.selected_ix;
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
@ -614,43 +594,39 @@ impl BreakpointList {
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
|
||||||
if !(self.show_scrollbar || self.scrollbar_state.is_dragging()) {
|
div()
|
||||||
return None;
|
.occlude()
|
||||||
}
|
.id("breakpoint-list-vertical-scrollbar")
|
||||||
Some(
|
.on_mouse_move(cx.listener(|_, _, _, cx| {
|
||||||
div()
|
cx.notify();
|
||||||
.occlude()
|
cx.stop_propagation()
|
||||||
.id("breakpoint-list-vertical-scrollbar")
|
}))
|
||||||
.on_mouse_move(cx.listener(|_, _, _, cx| {
|
.on_hover(|_, _, cx| {
|
||||||
cx.notify();
|
cx.stop_propagation();
|
||||||
cx.stop_propagation()
|
})
|
||||||
}))
|
.on_any_mouse_down(|_, _, cx| {
|
||||||
.on_hover(|_, _, cx| {
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_mouse_up(
|
||||||
|
MouseButton::Left,
|
||||||
|
cx.listener(|_, _, _, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
})
|
}),
|
||||||
.on_any_mouse_down(|_, _, cx| {
|
)
|
||||||
cx.stop_propagation();
|
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
|
||||||
})
|
cx.notify();
|
||||||
.on_mouse_up(
|
}))
|
||||||
MouseButton::Left,
|
.h_full()
|
||||||
cx.listener(|_, _, _, cx| {
|
.absolute()
|
||||||
cx.stop_propagation();
|
.right_1()
|
||||||
}),
|
.top_1()
|
||||||
)
|
.bottom_0()
|
||||||
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
|
.w(px(12.))
|
||||||
cx.notify();
|
.cursor_default()
|
||||||
}))
|
.children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
|
||||||
.h_full()
|
|
||||||
.absolute()
|
|
||||||
.right_1()
|
|
||||||
.top_1()
|
|
||||||
.bottom_0()
|
|
||||||
.w(px(12.))
|
|
||||||
.cursor_default()
|
|
||||||
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render_control_strip(&self) -> AnyElement {
|
pub(crate) fn render_control_strip(&self) -> AnyElement {
|
||||||
let selection_kind = self.selection_kind();
|
let selection_kind = self.selection_kind();
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
@ -819,15 +795,6 @@ impl Render for BreakpointList {
|
||||||
.id("breakpoint-list")
|
.id("breakpoint-list")
|
||||||
.key_context("BreakpointList")
|
.key_context("BreakpointList")
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.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_action(cx.listener(Self::select_next))
|
.on_action(cx.listener(Self::select_next))
|
||||||
.on_action(cx.listener(Self::select_previous))
|
.on_action(cx.listener(Self::select_previous))
|
||||||
.on_action(cx.listener(Self::select_first))
|
.on_action(cx.listener(Self::select_first))
|
||||||
|
@ -844,7 +811,7 @@ impl Render for BreakpointList {
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(self.render_list(cx))
|
.child(self.render_list(cx))
|
||||||
.children(self.render_vertical_scrollbar(cx)),
|
.child(self.render_vertical_scrollbar(cx)),
|
||||||
)
|
)
|
||||||
.when_some(self.strip_mode, |this, _| {
|
.when_some(self.strip_mode, |this, _| {
|
||||||
this.child(Divider::horizontal()).child(
|
this.child(Divider::horizontal()).child(
|
||||||
|
|
|
@ -23,7 +23,6 @@ use ui::{
|
||||||
ParentElement, Pixels, PopoverMenuHandle, Render, Scrollbar, ScrollbarState, SharedString,
|
ParentElement, Pixels, PopoverMenuHandle, Render, Scrollbar, ScrollbarState, SharedString,
|
||||||
StatefulInteractiveElement, Styled, TextSize, Tooltip, Window, div, h_flex, px, v_flex,
|
StatefulInteractiveElement, Styled, TextSize, Tooltip, Window, div, h_flex, px, v_flex,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::{ToggleDataBreakpoint, session::running::stack_frame_list::StackFrameList};
|
use crate::{ToggleDataBreakpoint, session::running::stack_frame_list::StackFrameList};
|
||||||
|
@ -34,9 +33,7 @@ pub(crate) struct MemoryView {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
scroll_state: ScrollbarState,
|
scroll_state: ScrollbarState,
|
||||||
show_scrollbar: bool,
|
|
||||||
stack_frame_list: WeakEntity<StackFrameList>,
|
stack_frame_list: WeakEntity<StackFrameList>,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
view_state: ViewState,
|
view_state: ViewState,
|
||||||
query_editor: Entity<Editor>,
|
query_editor: Entity<Editor>,
|
||||||
|
@ -150,8 +147,6 @@ impl MemoryView {
|
||||||
scroll_state,
|
scroll_state,
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
stack_frame_list,
|
stack_frame_list,
|
||||||
show_scrollbar: false,
|
|
||||||
hide_scrollbar_task: None,
|
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
view_state,
|
view_state,
|
||||||
query_editor,
|
query_editor,
|
||||||
|
@ -168,61 +163,42 @@ impl MemoryView {
|
||||||
.detach();
|
.detach();
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
|
||||||
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 render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
|
||||||
if !(self.show_scrollbar || self.scroll_state.is_dragging()) {
|
div()
|
||||||
return None;
|
.occlude()
|
||||||
}
|
.id("memory-view-vertical-scrollbar")
|
||||||
Some(
|
.on_drag_move(cx.listener(|this, evt, _, cx| {
|
||||||
div()
|
let did_handle = this.handle_scroll_drag(evt);
|
||||||
.occlude()
|
cx.notify();
|
||||||
.id("memory-view-vertical-scrollbar")
|
if did_handle {
|
||||||
.on_drag_move(cx.listener(|this, evt, _, cx| {
|
cx.stop_propagation()
|
||||||
let did_handle = this.handle_scroll_drag(evt);
|
}
|
||||||
cx.notify();
|
}))
|
||||||
if did_handle {
|
.on_drag(ScrollbarDragging, |_, _, _, cx| cx.new(|_| Empty))
|
||||||
cx.stop_propagation()
|
.on_hover(|_, _, cx| {
|
||||||
}
|
cx.stop_propagation();
|
||||||
}))
|
})
|
||||||
.on_drag(ScrollbarDragging, |_, _, _, cx| cx.new(|_| Empty))
|
.on_any_mouse_down(|_, _, cx| {
|
||||||
.on_hover(|_, _, cx| {
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_mouse_up(
|
||||||
|
MouseButton::Left,
|
||||||
|
cx.listener(|_, _, _, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
})
|
}),
|
||||||
.on_any_mouse_down(|_, _, cx| {
|
)
|
||||||
cx.stop_propagation();
|
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
|
||||||
})
|
cx.notify();
|
||||||
.on_mouse_up(
|
}))
|
||||||
MouseButton::Left,
|
.h_full()
|
||||||
cx.listener(|_, _, _, cx| {
|
.absolute()
|
||||||
cx.stop_propagation();
|
.right_1()
|
||||||
}),
|
.top_1()
|
||||||
)
|
.bottom_0()
|
||||||
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
|
.w(px(12.))
|
||||||
cx.notify();
|
.cursor_default()
|
||||||
}))
|
.children(Scrollbar::vertical(self.scroll_state.clone()).map(|s| s.auto_hide(cx)))
|
||||||
.h_full()
|
|
||||||
.absolute()
|
|
||||||
.right_1()
|
|
||||||
.top_1()
|
|
||||||
.bottom_0()
|
|
||||||
.w(px(12.))
|
|
||||||
.cursor_default()
|
|
||||||
.children(Scrollbar::vertical(self.scroll_state.clone())),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_memory(&self, cx: &mut Context<Self>) -> UniformList {
|
fn render_memory(&self, cx: &mut Context<Self>) -> UniformList {
|
||||||
|
@ -920,15 +896,6 @@ impl Render for MemoryView {
|
||||||
.on_action(cx.listener(Self::page_up))
|
.on_action(cx.listener(Self::page_up))
|
||||||
.size_full()
|
.size_full()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
@ -978,7 +945,7 @@ impl Render for MemoryView {
|
||||||
)
|
)
|
||||||
.with_priority(1)
|
.with_priority(1)
|
||||||
}))
|
}))
|
||||||
.children(self.render_vertical_scrollbar(cx)),
|
.child(self.render_vertical_scrollbar(cx)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
use std::{any::Any, cell::Cell, fmt::Debug, ops::Range, rc::Rc, sync::Arc};
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
fmt::Debug,
|
||||||
|
ops::Range,
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{IntoElement, prelude::*, px, relative};
|
use crate::{IntoElement, prelude::*, px, relative};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Along, App, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Corners, CursorStyle,
|
Along, App, Axis as ScrollbarAxis, BorderStyle, Bounds, ContentMask, Corners, CursorStyle,
|
||||||
Edges, Element, ElementId, Entity, EntityId, GlobalElementId, Hitbox, HitboxBehavior, Hsla,
|
Edges, Element, ElementId, Entity, EntityId, GlobalElementId, Hitbox, HitboxBehavior, Hsla,
|
||||||
IsZero, LayoutId, ListState, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
IsZero, LayoutId, ListState, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||||
Point, ScrollHandle, ScrollWheelEvent, Size, Style, UniformListScrollHandle, Window, quad,
|
Point, ScrollHandle, ScrollWheelEvent, Size, Style, Task, UniformListScrollHandle, Window,
|
||||||
|
quad,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Scrollbar {
|
pub struct Scrollbar {
|
||||||
|
@ -108,6 +117,25 @@ pub struct ScrollbarState {
|
||||||
thumb_state: Rc<Cell<ThumbState>>,
|
thumb_state: Rc<Cell<ThumbState>>,
|
||||||
parent_id: Option<EntityId>,
|
parent_id: Option<EntityId>,
|
||||||
scroll_handle: Arc<dyn ScrollableHandle>,
|
scroll_handle: Arc<dyn ScrollableHandle>,
|
||||||
|
auto_hide: Rc<RefCell<AutoHide>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AutoHide {
|
||||||
|
Disabled,
|
||||||
|
Hidden {
|
||||||
|
parent_id: EntityId,
|
||||||
|
},
|
||||||
|
Visible {
|
||||||
|
parent_id: EntityId,
|
||||||
|
_task: Task<()>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutoHide {
|
||||||
|
fn is_hidden(&self) -> bool {
|
||||||
|
matches!(self, AutoHide::Hidden { .. })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollbarState {
|
impl ScrollbarState {
|
||||||
|
@ -116,6 +144,7 @@ impl ScrollbarState {
|
||||||
thumb_state: Default::default(),
|
thumb_state: Default::default(),
|
||||||
parent_id: None,
|
parent_id: None,
|
||||||
scroll_handle: Arc::new(scroll),
|
scroll_handle: Arc::new(scroll),
|
||||||
|
auto_hide: Rc::new(RefCell::new(AutoHide::Disabled)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +203,38 @@ impl ScrollbarState {
|
||||||
let thumb_percentage_end = (start_offset + thumb_size) / viewport_size;
|
let thumb_percentage_end = (start_offset + thumb_size) / viewport_size;
|
||||||
Some(thumb_percentage_start..thumb_percentage_end)
|
Some(thumb_percentage_start..thumb_percentage_end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_temporarily(&self, parent_id: EntityId, cx: &mut App) {
|
||||||
|
const SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
|
|
||||||
|
let auto_hide = self.auto_hide.clone();
|
||||||
|
auto_hide.replace(AutoHide::Visible {
|
||||||
|
parent_id,
|
||||||
|
_task: cx.spawn({
|
||||||
|
let this = auto_hide.clone();
|
||||||
|
async move |cx| {
|
||||||
|
cx.background_executor().timer(SHOW_INTERVAL).await;
|
||||||
|
this.replace(AutoHide::Hidden { parent_id });
|
||||||
|
cx.update(|cx| {
|
||||||
|
cx.notify(parent_id);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unhide(&self, position: &Point<Pixels>, cx: &mut App) {
|
||||||
|
let parent_id = match &*self.auto_hide.borrow() {
|
||||||
|
AutoHide::Disabled => return,
|
||||||
|
AutoHide::Hidden { parent_id } => *parent_id,
|
||||||
|
AutoHide::Visible { parent_id, _task } => *parent_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.scroll_handle().viewport().contains(position) {
|
||||||
|
self.show_temporarily(parent_id, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scrollbar {
|
impl Scrollbar {
|
||||||
|
@ -189,6 +250,14 @@ impl Scrollbar {
|
||||||
let thumb = state.thumb_range(kind)?;
|
let thumb = state.thumb_range(kind)?;
|
||||||
Some(Self { thumb, state, kind })
|
Some(Self { thumb, state, kind })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Automatically hide the scrollbar when idle
|
||||||
|
pub fn auto_hide<V: 'static>(self, cx: &mut Context<V>) -> Self {
|
||||||
|
if matches!(*self.state.auto_hide.borrow(), AutoHide::Disabled) {
|
||||||
|
self.state.show_temporarily(cx.entity_id(), cx);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Scrollbar {
|
impl Element for Scrollbar {
|
||||||
|
@ -284,16 +353,18 @@ impl Element for Scrollbar {
|
||||||
.apply_along(axis.invert(), |width| width / 1.5),
|
.apply_along(axis.invert(), |width| width / 1.5),
|
||||||
);
|
);
|
||||||
|
|
||||||
let corners = Corners::all(thumb_bounds.size.along(axis.invert()) / 2.0);
|
if thumb_state.is_dragging() || !self.state.auto_hide.borrow().is_hidden() {
|
||||||
|
let corners = Corners::all(thumb_bounds.size.along(axis.invert()) / 2.0);
|
||||||
|
|
||||||
window.paint_quad(quad(
|
window.paint_quad(quad(
|
||||||
thumb_bounds,
|
thumb_bounds,
|
||||||
corners,
|
corners,
|
||||||
thumb_background,
|
thumb_background,
|
||||||
Edges::default(),
|
Edges::default(),
|
||||||
Hsla::transparent_black(),
|
Hsla::transparent_black(),
|
||||||
BorderStyle::default(),
|
BorderStyle::default(),
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if thumb_state.is_dragging() {
|
if thumb_state.is_dragging() {
|
||||||
window.set_window_cursor_style(CursorStyle::Arrow);
|
window.set_window_cursor_style(CursorStyle::Arrow);
|
||||||
|
@ -361,13 +432,18 @@ impl Element for Scrollbar {
|
||||||
});
|
});
|
||||||
|
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
|
let state = self.state.clone();
|
||||||
let scroll_handle = self.state.scroll_handle().clone();
|
let scroll_handle = self.state.scroll_handle().clone();
|
||||||
move |event: &ScrollWheelEvent, phase, window, _| {
|
move |event: &ScrollWheelEvent, phase, window, cx| {
|
||||||
if phase.bubble() && bounds.contains(&event.position) {
|
if phase.bubble() {
|
||||||
let current_offset = scroll_handle.offset();
|
state.unhide(&event.position, cx);
|
||||||
scroll_handle.set_offset(
|
|
||||||
current_offset + event.delta.pixel_delta(window.line_height()),
|
if bounds.contains(&event.position) {
|
||||||
);
|
let current_offset = scroll_handle.offset();
|
||||||
|
scroll_handle.set_offset(
|
||||||
|
current_offset + event.delta.pixel_delta(window.line_height()),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -376,6 +452,8 @@ impl Element for Scrollbar {
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
move |event: &MouseMoveEvent, phase, window, cx| {
|
move |event: &MouseMoveEvent, phase, window, cx| {
|
||||||
if phase.bubble() {
|
if phase.bubble() {
|
||||||
|
state.unhide(&event.position, cx);
|
||||||
|
|
||||||
match state.thumb_state.get() {
|
match state.thumb_state.get() {
|
||||||
ThumbState::Dragging(drag_state) if event.dragging() => {
|
ThumbState::Dragging(drag_state) if event.dragging() => {
|
||||||
let scroll_handle = state.scroll_handle();
|
let scroll_handle = state.scroll_handle();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue