outline panel: Add indent guides (#19719)
See #12673 | File | Search | |--------|--------| | <img width="302" alt="image" src="https://github.com/user-attachments/assets/44b8d5f9-8446-41b5-8c0f-e438050f0ac9"> | <img width="301" alt="image" src="https://github.com/user-attachments/assets/a2e6f77b-6d3b-4f1c-8fcb-16bd35274807"> | Release Notes: - Added indent guides to the outline panel
This commit is contained in:
parent
e86b096b92
commit
888fec9299
8 changed files with 236 additions and 86 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -7728,8 +7728,10 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
"theme",
|
"theme",
|
||||||
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
"workspace",
|
"workspace",
|
||||||
"worktree",
|
"worktree",
|
||||||
|
|
|
@ -388,6 +388,8 @@
|
||||||
"git_status": true,
|
"git_status": true,
|
||||||
// Amount of indentation for nested items.
|
// Amount of indentation for nested items.
|
||||||
"indent_size": 20,
|
"indent_size": 20,
|
||||||
|
// Whether to show indent guides in the outline panel.
|
||||||
|
"indent_guides": true,
|
||||||
// Whether to reveal it in the outline panel automatically,
|
// Whether to reveal it in the outline panel automatically,
|
||||||
// when a corresponding outline entry becomes active.
|
// when a corresponding outline entry becomes active.
|
||||||
// Gitignored entries are never auto revealed.
|
// Gitignored entries are never auto revealed.
|
||||||
|
|
|
@ -340,6 +340,7 @@ impl Element for UniformList {
|
||||||
visible_range.clone(),
|
visible_range.clone(),
|
||||||
bounds,
|
bounds,
|
||||||
item_height,
|
item_height,
|
||||||
|
self.item_count,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
let available_space = size(
|
let available_space = size(
|
||||||
|
@ -396,6 +397,7 @@ pub trait UniformListDecoration {
|
||||||
visible_range: Range<usize>,
|
visible_range: Range<usize>,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
item_height: Pixels,
|
item_height: Pixels,
|
||||||
|
item_count: usize,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> AnyElement;
|
) -> AnyElement;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,10 @@ search.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
smallvec.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
worktree.workspace = true
|
worktree.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
|
@ -24,12 +24,12 @@ use editor::{
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, deferred, div, impl_actions, px, uniform_list, Action, AnyElement,
|
actions, anchored, deferred, div, impl_actions, point, px, size, uniform_list, Action,
|
||||||
AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId,
|
AnyElement, AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent,
|
||||||
EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement, IntoElement,
|
Div, ElementId, EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement,
|
||||||
KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render,
|
IntoElement, KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
|
||||||
SharedString, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext,
|
Render, SharedString, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View,
|
||||||
VisualContext, WeakView, WindowContext,
|
ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{BufferId, BufferSnapshot, OffsetRangeExt, OutlineItem};
|
use language::{BufferId, BufferSnapshot, OffsetRangeExt, OutlineItem};
|
||||||
|
@ -42,6 +42,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use smol::channel;
|
use smol::channel;
|
||||||
use theme::{SyntaxTheme, ThemeSettings};
|
use theme::{SyntaxTheme, ThemeSettings};
|
||||||
|
use ui::{IndentGuideColors, IndentGuideLayout};
|
||||||
use util::{debug_panic, RangeExt, ResultExt, TryFutureExt};
|
use util::{debug_panic, RangeExt, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
|
@ -254,14 +255,14 @@ impl SearchState {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum SelectedEntry {
|
enum SelectedEntry {
|
||||||
Invalidated(Option<PanelEntry>),
|
Invalidated(Option<PanelEntry>),
|
||||||
Valid(PanelEntry),
|
Valid(PanelEntry, usize),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectedEntry {
|
impl SelectedEntry {
|
||||||
fn invalidate(&mut self) {
|
fn invalidate(&mut self) {
|
||||||
match std::mem::replace(self, SelectedEntry::None) {
|
match std::mem::replace(self, SelectedEntry::None) {
|
||||||
Self::Valid(entry) => *self = Self::Invalidated(Some(entry)),
|
Self::Valid(entry, _) => *self = Self::Invalidated(Some(entry)),
|
||||||
Self::None => *self = Self::Invalidated(None),
|
Self::None => *self = Self::Invalidated(None),
|
||||||
other => *self = other,
|
other => *self = other,
|
||||||
}
|
}
|
||||||
|
@ -3568,7 +3569,7 @@ impl OutlinePanel {
|
||||||
fn selected_entry(&self) -> Option<&PanelEntry> {
|
fn selected_entry(&self) -> Option<&PanelEntry> {
|
||||||
match &self.selected_entry {
|
match &self.selected_entry {
|
||||||
SelectedEntry::Invalidated(entry) => entry.as_ref(),
|
SelectedEntry::Invalidated(entry) => entry.as_ref(),
|
||||||
SelectedEntry::Valid(entry) => Some(entry),
|
SelectedEntry::Valid(entry, _) => Some(entry),
|
||||||
SelectedEntry::None => None,
|
SelectedEntry::None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3577,7 +3578,16 @@ impl OutlinePanel {
|
||||||
if focus {
|
if focus {
|
||||||
self.focus_handle.focus(cx);
|
self.focus_handle.focus(cx);
|
||||||
}
|
}
|
||||||
self.selected_entry = SelectedEntry::Valid(entry);
|
let ix = self
|
||||||
|
.cached_entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, cached_entry)| &cached_entry.entry == &entry)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
self.selected_entry = SelectedEntry::Valid(entry, ix);
|
||||||
|
|
||||||
self.autoscroll(cx);
|
self.autoscroll(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -3736,6 +3746,9 @@ impl Render for OutlinePanel {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let query = self.query(cx);
|
let query = self.query(cx);
|
||||||
let pinned = self.pinned;
|
let pinned = self.pinned;
|
||||||
|
let settings = OutlinePanelSettings::get_global(cx);
|
||||||
|
let indent_size = settings.indent_size;
|
||||||
|
let show_indent_guides = settings.indent_guides;
|
||||||
|
|
||||||
let outline_panel = v_flex()
|
let outline_panel = v_flex()
|
||||||
.id("outline-panel")
|
.id("outline-panel")
|
||||||
|
@ -3901,6 +3914,61 @@ impl Render for OutlinePanel {
|
||||||
})
|
})
|
||||||
.size_full()
|
.size_full()
|
||||||
.track_scroll(self.scroll_handle.clone())
|
.track_scroll(self.scroll_handle.clone())
|
||||||
|
.when(show_indent_guides, |list| {
|
||||||
|
list.with_decoration(
|
||||||
|
ui::indent_guides(
|
||||||
|
cx.view().clone(),
|
||||||
|
px(indent_size),
|
||||||
|
IndentGuideColors::panel(cx),
|
||||||
|
|outline_panel, range, _| {
|
||||||
|
let entries = outline_panel.cached_entries.get(range);
|
||||||
|
if let Some(entries) = entries {
|
||||||
|
entries.into_iter().map(|item| item.depth).collect()
|
||||||
|
} else {
|
||||||
|
smallvec::SmallVec::new()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_render_fn(
|
||||||
|
cx.view().clone(),
|
||||||
|
move |outline_panel, params, _| {
|
||||||
|
const LEFT_OFFSET: f32 = 14.;
|
||||||
|
|
||||||
|
let indent_size = params.indent_size;
|
||||||
|
let item_height = params.item_height;
|
||||||
|
let active_indent_guide_ix = find_active_indent_guide_ix(
|
||||||
|
outline_panel,
|
||||||
|
¶ms.indent_guides,
|
||||||
|
);
|
||||||
|
|
||||||
|
params
|
||||||
|
.indent_guides
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ix, layout)| {
|
||||||
|
let bounds = Bounds::new(
|
||||||
|
point(
|
||||||
|
px(layout.offset.x as f32) * indent_size
|
||||||
|
+ px(LEFT_OFFSET),
|
||||||
|
px(layout.offset.y as f32) * item_height,
|
||||||
|
),
|
||||||
|
size(
|
||||||
|
px(1.),
|
||||||
|
px(layout.length as f32) * item_height,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
ui::RenderedIndentGuide {
|
||||||
|
bounds,
|
||||||
|
layout,
|
||||||
|
is_active: active_indent_guide_ix == Some(ix),
|
||||||
|
hitbox: None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
||||||
|
@ -3945,6 +4013,40 @@ impl Render for OutlinePanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_active_indent_guide_ix(
|
||||||
|
outline_panel: &OutlinePanel,
|
||||||
|
candidates: &[IndentGuideLayout],
|
||||||
|
) -> Option<usize> {
|
||||||
|
let SelectedEntry::Valid(_, target_ix) = &outline_panel.selected_entry else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let target_depth = outline_panel
|
||||||
|
.cached_entries
|
||||||
|
.get(*target_ix)
|
||||||
|
.map(|cached_entry| cached_entry.depth)?;
|
||||||
|
|
||||||
|
let (target_ix, target_depth) = if let Some(target_depth) = outline_panel
|
||||||
|
.cached_entries
|
||||||
|
.get(target_ix + 1)
|
||||||
|
.filter(|cached_entry| cached_entry.depth > target_depth)
|
||||||
|
.map(|entry| entry.depth)
|
||||||
|
{
|
||||||
|
(target_ix + 1, target_depth.saturating_sub(1))
|
||||||
|
} else {
|
||||||
|
(*target_ix, target_depth.saturating_sub(1))
|
||||||
|
};
|
||||||
|
|
||||||
|
candidates
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, guide)| {
|
||||||
|
guide.offset.y <= target_ix
|
||||||
|
&& target_ix < guide.offset.y + guide.length
|
||||||
|
&& guide.offset.x == target_depth
|
||||||
|
})
|
||||||
|
.map(|(ix, _)| ix)
|
||||||
|
}
|
||||||
|
|
||||||
fn subscribe_for_editor_events(
|
fn subscribe_for_editor_events(
|
||||||
editor: &View<Editor>,
|
editor: &View<Editor>,
|
||||||
cx: &mut ViewContext<OutlinePanel>,
|
cx: &mut ViewContext<OutlinePanel>,
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub struct OutlinePanelSettings {
|
||||||
pub folder_icons: bool,
|
pub folder_icons: bool,
|
||||||
pub git_status: bool,
|
pub git_status: bool,
|
||||||
pub indent_size: f32,
|
pub indent_size: f32,
|
||||||
|
pub indent_guides: bool,
|
||||||
pub auto_reveal_entries: bool,
|
pub auto_reveal_entries: bool,
|
||||||
pub auto_fold_dirs: bool,
|
pub auto_fold_dirs: bool,
|
||||||
}
|
}
|
||||||
|
@ -53,6 +54,10 @@ pub struct OutlinePanelSettingsContent {
|
||||||
///
|
///
|
||||||
/// Default: 20
|
/// Default: 20
|
||||||
pub indent_size: Option<f32>,
|
pub indent_size: Option<f32>,
|
||||||
|
/// Whether to show indent guides in the outline panel.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub indent_guides: Option<bool>,
|
||||||
/// Whether to reveal it in the outline panel automatically,
|
/// Whether to reveal it in the outline panel automatically,
|
||||||
/// when a corresponding project entry becomes active.
|
/// when a corresponding project entry becomes active.
|
||||||
/// Gitignored entries are never auto revealed.
|
/// Gitignored entries are never auto revealed.
|
||||||
|
|
|
@ -140,13 +140,18 @@ mod uniform_list {
|
||||||
visible_range: Range<usize>,
|
visible_range: Range<usize>,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
item_height: Pixels,
|
item_height: Pixels,
|
||||||
|
item_count: usize,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let mut visible_range = visible_range.clone();
|
let mut visible_range = visible_range.clone();
|
||||||
visible_range.end += 1;
|
let includes_trailing_indent = visible_range.end < item_count;
|
||||||
|
// Check if we have entries after the visible range,
|
||||||
|
// if so extend the visible range so we can fetch a trailing indent,
|
||||||
|
// which is needed to compute indent guides correctly.
|
||||||
|
if includes_trailing_indent {
|
||||||
|
visible_range.end += 1;
|
||||||
|
}
|
||||||
let visible_entries = &(self.compute_indents_fn)(visible_range.clone(), cx);
|
let visible_entries = &(self.compute_indents_fn)(visible_range.clone(), cx);
|
||||||
// Check if we have an additional indent that is outside of the visible range
|
|
||||||
let includes_trailing_indent = visible_entries.len() == visible_range.len();
|
|
||||||
let indent_guides = compute_indent_guides(
|
let indent_guides = compute_indent_guides(
|
||||||
&visible_entries,
|
&visible_entries,
|
||||||
visible_range.start,
|
visible_range.start,
|
||||||
|
@ -198,8 +203,12 @@ mod uniform_list {
|
||||||
on_hovered_indent_guide_click: Option<Rc<dyn Fn(&IndentGuideLayout, &mut WindowContext)>>,
|
on_hovered_indent_guide_click: Option<Rc<dyn Fn(&IndentGuideLayout, &mut WindowContext)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IndentGuidesElementPrepaintState {
|
enum IndentGuidesElementPrepaintState {
|
||||||
hitboxes: SmallVec<[Hitbox; 12]>,
|
Static,
|
||||||
|
Interactive {
|
||||||
|
hitboxes: Rc<SmallVec<[Hitbox; 12]>>,
|
||||||
|
on_hovered_indent_guide_click: Rc<dyn Fn(&IndentGuideLayout, &mut WindowContext)>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for IndentGuidesElement {
|
impl Element for IndentGuidesElement {
|
||||||
|
@ -225,11 +234,21 @@ mod uniform_list {
|
||||||
_request_layout: &mut Self::RequestLayoutState,
|
_request_layout: &mut Self::RequestLayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Self::PrepaintState {
|
) -> Self::PrepaintState {
|
||||||
let mut hitboxes = SmallVec::new();
|
if let Some(on_hovered_indent_guide_click) = self.on_hovered_indent_guide_click.clone()
|
||||||
for guide in self.indent_guides.as_ref().iter() {
|
{
|
||||||
hitboxes.push(cx.insert_hitbox(guide.hitbox.unwrap_or(guide.bounds), false));
|
let hitboxes = self
|
||||||
|
.indent_guides
|
||||||
|
.as_ref()
|
||||||
|
.iter()
|
||||||
|
.map(|guide| cx.insert_hitbox(guide.hitbox.unwrap_or(guide.bounds), false))
|
||||||
|
.collect();
|
||||||
|
Self::PrepaintState::Interactive {
|
||||||
|
hitboxes: Rc::new(hitboxes),
|
||||||
|
on_hovered_indent_guide_click,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self::PrepaintState::Static
|
||||||
}
|
}
|
||||||
Self::PrepaintState { hitboxes }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
|
@ -240,81 +259,96 @@ mod uniform_list {
|
||||||
prepaint: &mut Self::PrepaintState,
|
prepaint: &mut Self::PrepaintState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
let callback = self.on_hovered_indent_guide_click.clone();
|
match prepaint {
|
||||||
if let Some(callback) = callback {
|
IndentGuidesElementPrepaintState::Static => {
|
||||||
cx.on_mouse_event({
|
for indent_guide in self.indent_guides.as_ref() {
|
||||||
let hitboxes = prepaint.hitboxes.clone();
|
let fill_color = if indent_guide.is_active {
|
||||||
let indent_guides = self.indent_guides.clone();
|
self.colors.active
|
||||||
move |event: &MouseDownEvent, phase, cx| {
|
} else {
|
||||||
if phase == DispatchPhase::Bubble && event.button == MouseButton::Left {
|
self.colors.default
|
||||||
let mut active_hitbox_ix = None;
|
};
|
||||||
for (i, hitbox) in hitboxes.iter().enumerate() {
|
|
||||||
|
cx.paint_quad(fill(indent_guide.bounds, fill_color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IndentGuidesElementPrepaintState::Interactive {
|
||||||
|
hitboxes,
|
||||||
|
on_hovered_indent_guide_click,
|
||||||
|
} => {
|
||||||
|
cx.on_mouse_event({
|
||||||
|
let hitboxes = hitboxes.clone();
|
||||||
|
let indent_guides = self.indent_guides.clone();
|
||||||
|
let on_hovered_indent_guide_click = on_hovered_indent_guide_click.clone();
|
||||||
|
move |event: &MouseDownEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble && event.button == MouseButton::Left {
|
||||||
|
let mut active_hitbox_ix = None;
|
||||||
|
for (i, hitbox) in hitboxes.iter().enumerate() {
|
||||||
|
if hitbox.is_hovered(cx) {
|
||||||
|
active_hitbox_ix = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(active_hitbox_ix) = active_hitbox_ix else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let active_indent_guide = &indent_guides[active_hitbox_ix].layout;
|
||||||
|
on_hovered_indent_guide_click(active_indent_guide, cx);
|
||||||
|
|
||||||
|
cx.stop_propagation();
|
||||||
|
cx.prevent_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let mut hovered_hitbox_id = None;
|
||||||
|
for (i, hitbox) in hitboxes.iter().enumerate() {
|
||||||
|
cx.set_cursor_style(gpui::CursorStyle::PointingHand, hitbox);
|
||||||
|
let indent_guide = &self.indent_guides[i];
|
||||||
|
let fill_color = if hitbox.is_hovered(cx) {
|
||||||
|
hovered_hitbox_id = Some(hitbox.id);
|
||||||
|
self.colors.hover
|
||||||
|
} else if indent_guide.is_active {
|
||||||
|
self.colors.active
|
||||||
|
} else {
|
||||||
|
self.colors.default
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.paint_quad(fill(indent_guide.bounds, fill_color));
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.on_mouse_event({
|
||||||
|
let prev_hovered_hitbox_id = hovered_hitbox_id;
|
||||||
|
let hitboxes = hitboxes.clone();
|
||||||
|
move |_: &MouseMoveEvent, phase, cx| {
|
||||||
|
let mut hovered_hitbox_id = None;
|
||||||
|
for hitbox in hitboxes.as_ref() {
|
||||||
if hitbox.is_hovered(cx) {
|
if hitbox.is_hovered(cx) {
|
||||||
active_hitbox_ix = Some(i);
|
hovered_hitbox_id = Some(hitbox.id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if phase == DispatchPhase::Capture {
|
||||||
let Some(active_hitbox_ix) = active_hitbox_ix else {
|
// If the hovered hitbox has changed, we need to re-paint the indent guides.
|
||||||
return;
|
match (prev_hovered_hitbox_id, hovered_hitbox_id) {
|
||||||
};
|
(Some(prev_id), Some(id)) => {
|
||||||
|
if prev_id != id {
|
||||||
let active_indent_guide = &indent_guides[active_hitbox_ix].layout;
|
cx.refresh();
|
||||||
callback(active_indent_guide, cx);
|
}
|
||||||
|
}
|
||||||
cx.stop_propagation();
|
(None, Some(_)) => {
|
||||||
cx.prevent_default();
|
cx.refresh();
|
||||||
}
|
}
|
||||||
}
|
(Some(_), None) => {
|
||||||
});
|
cx.refresh();
|
||||||
}
|
}
|
||||||
|
(None, None) => {}
|
||||||
let mut hovered_hitbox_id = None;
|
|
||||||
for (i, hitbox) in prepaint.hitboxes.iter().enumerate() {
|
|
||||||
cx.set_cursor_style(gpui::CursorStyle::PointingHand, hitbox);
|
|
||||||
let indent_guide = &self.indent_guides[i];
|
|
||||||
let fill_color = if hitbox.is_hovered(cx) {
|
|
||||||
hovered_hitbox_id = Some(hitbox.id);
|
|
||||||
self.colors.hover
|
|
||||||
} else if indent_guide.is_active {
|
|
||||||
self.colors.active
|
|
||||||
} else {
|
|
||||||
self.colors.default
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.paint_quad(fill(indent_guide.bounds, fill_color));
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.on_mouse_event({
|
|
||||||
let prev_hovered_hitbox_id = hovered_hitbox_id;
|
|
||||||
let hitboxes = prepaint.hitboxes.clone();
|
|
||||||
move |_: &MouseMoveEvent, phase, cx| {
|
|
||||||
let mut hovered_hitbox_id = None;
|
|
||||||
for hitbox in &hitboxes {
|
|
||||||
if hitbox.is_hovered(cx) {
|
|
||||||
hovered_hitbox_id = Some(hitbox.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if phase == DispatchPhase::Capture {
|
|
||||||
// If the hovered hitbox has changed, we need to re-paint the indent guides.
|
|
||||||
match (prev_hovered_hitbox_id, hovered_hitbox_id) {
|
|
||||||
(Some(prev_id), Some(id)) => {
|
|
||||||
if prev_id != id {
|
|
||||||
cx.refresh();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, Some(_)) => {
|
|
||||||
cx.refresh();
|
|
||||||
}
|
|
||||||
(Some(_), None) => {
|
|
||||||
cx.refresh();
|
|
||||||
}
|
|
||||||
(None, None) => {}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2237,6 +2237,7 @@ Run the `theme selector: toggle` action in the command palette to see a current
|
||||||
"folder_icons": true,
|
"folder_icons": true,
|
||||||
"git_status": true,
|
"git_status": true,
|
||||||
"indent_size": 20,
|
"indent_size": 20,
|
||||||
|
"indent_guides": true,
|
||||||
"auto_reveal_entries": true,
|
"auto_reveal_entries": true,
|
||||||
"auto_fold_dirs": true,
|
"auto_fold_dirs": true,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue