Merge pull request #1089 from zed-industries/jump-to-diagnostic
Jump to diagnostic
This commit is contained in:
commit
f1964cf2a0
15 changed files with 491 additions and 106 deletions
3
assets/icons/jump.svg
Normal file
3
assets/icons/jump.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.5 1.02501V6.27499C7.5 6.56484 7.26484 6.79999 6.975 6.79999C6.68516 6.79999 6.45 6.56484 6.45 6.27499V2.29157L1.3969 7.34468C1.29365 7.44968 1.15934 7.49999 1.02503 7.49999C0.890713 7.49999 0.756401 7.44871 0.653808 7.34619C0.448731 7.14111 0.448731 6.80894 0.653808 6.60375L5.70844 1.55001H1.72502C1.43518 1.55001 1.20002 1.31595 1.20002 1.02501C1.20002 0.734077 1.43518 0.500015 1.72502 0.500015H6.975C7.26594 0.500015 7.5 0.736264 7.5 1.02501Z" fill="white" fill-opacity="0.6"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 593 B |
|
@ -8,12 +8,13 @@ use editor::{
|
||||||
highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, MultiBuffer, ToOffset,
|
highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, MultiBuffer, ToOffset,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity,
|
actions, elements::*, fonts::TextStyle, impl_internal_actions, platform::CursorStyle,
|
||||||
ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
|
serde_json, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext,
|
||||||
WeakViewHandle,
|
Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
|
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
|
||||||
|
SelectionGoal, ToPoint,
|
||||||
};
|
};
|
||||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -27,15 +28,18 @@ use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use util::TryFutureExt;
|
use util::{ResultExt, TryFutureExt};
|
||||||
use workspace::{ItemHandle as _, ItemNavHistory, Workspace};
|
use workspace::{ItemHandle as _, ItemNavHistory, Workspace};
|
||||||
|
|
||||||
actions!(diagnostics, [Deploy]);
|
actions!(diagnostics, [Deploy]);
|
||||||
|
|
||||||
|
impl_internal_actions!(diagnostics, [Jump]);
|
||||||
|
|
||||||
const CONTEXT_LINE_COUNT: u32 = 1;
|
const CONTEXT_LINE_COUNT: u32 = 1;
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(ProjectDiagnosticsEditor::deploy);
|
cx.add_action(ProjectDiagnosticsEditor::deploy);
|
||||||
|
cx.add_action(ProjectDiagnosticsEditor::jump);
|
||||||
items::init(cx);
|
items::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +60,13 @@ struct PathState {
|
||||||
diagnostic_groups: Vec<DiagnosticGroupState>,
|
diagnostic_groups: Vec<DiagnosticGroupState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Jump {
|
||||||
|
path: ProjectPath,
|
||||||
|
position: Point,
|
||||||
|
anchor: Anchor,
|
||||||
|
}
|
||||||
|
|
||||||
struct DiagnosticGroupState {
|
struct DiagnosticGroupState {
|
||||||
primary_diagnostic: DiagnosticEntry<language::Anchor>,
|
primary_diagnostic: DiagnosticEntry<language::Anchor>,
|
||||||
primary_excerpt_ix: usize,
|
primary_excerpt_ix: usize,
|
||||||
|
@ -177,6 +188,30 @@ impl ProjectDiagnosticsEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn jump(workspace: &mut Workspace, action: &Jump, cx: &mut ViewContext<Workspace>) {
|
||||||
|
let editor = workspace.open_path(action.path.clone(), true, cx);
|
||||||
|
let position = action.position;
|
||||||
|
let anchor = action.anchor;
|
||||||
|
cx.spawn_weak(|_, mut cx| async move {
|
||||||
|
let editor = editor.await.log_err()?.downcast::<Editor>()?;
|
||||||
|
editor.update(&mut cx, |editor, cx| {
|
||||||
|
let buffer = editor.buffer().read(cx).as_singleton()?;
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
let cursor = if buffer.can_resolve(&anchor) {
|
||||||
|
anchor.to_point(buffer)
|
||||||
|
} else {
|
||||||
|
buffer.clip_point(position, Bias::Left)
|
||||||
|
};
|
||||||
|
editor.change_selections(Some(Autoscroll::Newest), cx, |s| {
|
||||||
|
s.select_ranges([cursor..cursor]);
|
||||||
|
});
|
||||||
|
Some(())
|
||||||
|
})?;
|
||||||
|
Some(())
|
||||||
|
})
|
||||||
|
.detach()
|
||||||
|
}
|
||||||
|
|
||||||
fn update_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
fn update_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let paths = mem::take(&mut self.paths_to_update);
|
let paths = mem::take(&mut self.paths_to_update);
|
||||||
let project = self.project.clone();
|
let project = self.project.clone();
|
||||||
|
@ -312,13 +347,20 @@ impl ProjectDiagnosticsEditor {
|
||||||
is_first_excerpt_for_group = false;
|
is_first_excerpt_for_group = false;
|
||||||
let mut primary =
|
let mut primary =
|
||||||
group.entries[group.primary_ix].diagnostic.clone();
|
group.entries[group.primary_ix].diagnostic.clone();
|
||||||
|
let anchor = group.entries[group.primary_ix].range.start;
|
||||||
|
let position = anchor.to_point(&snapshot);
|
||||||
primary.message =
|
primary.message =
|
||||||
primary.message.split('\n').next().unwrap().to_string();
|
primary.message.split('\n').next().unwrap().to_string();
|
||||||
group_state.block_count += 1;
|
group_state.block_count += 1;
|
||||||
blocks_to_add.push(BlockProperties {
|
blocks_to_add.push(BlockProperties {
|
||||||
position: header_position,
|
position: header_position,
|
||||||
height: 2,
|
height: 2,
|
||||||
render: diagnostic_header_renderer(primary),
|
render: diagnostic_header_renderer(
|
||||||
|
primary,
|
||||||
|
path.clone(),
|
||||||
|
position,
|
||||||
|
anchor,
|
||||||
|
),
|
||||||
disposition: BlockDisposition::Above,
|
disposition: BlockDisposition::Above,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -575,12 +617,20 @@ impl workspace::Item for ProjectDiagnosticsEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
fn diagnostic_header_renderer(
|
||||||
|
diagnostic: Diagnostic,
|
||||||
|
path: ProjectPath,
|
||||||
|
position: Point,
|
||||||
|
anchor: Anchor,
|
||||||
|
) -> RenderBlock {
|
||||||
|
enum JumpIcon {}
|
||||||
|
|
||||||
let (message, highlights) = highlight_diagnostic_message(&diagnostic.message);
|
let (message, highlights) = highlight_diagnostic_message(&diagnostic.message);
|
||||||
Arc::new(move |cx| {
|
Arc::new(move |cx| {
|
||||||
let settings = cx.global::<Settings>();
|
let settings = cx.global::<Settings>();
|
||||||
|
let tooltip_style = settings.theme.tooltip.clone();
|
||||||
let theme = &settings.theme.editor;
|
let theme = &settings.theme.editor;
|
||||||
let style = &theme.diagnostic_header;
|
let style = theme.diagnostic_header.clone();
|
||||||
let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
|
let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
|
||||||
let icon_width = cx.em_width * style.icon_width_factor;
|
let icon_width = cx.em_width * style.icon_width_factor;
|
||||||
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
|
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
|
||||||
|
@ -591,6 +641,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
||||||
.with_color(theme.warning_diagnostic.message.text.color)
|
.with_color(theme.warning_diagnostic.message.text.color)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let x_padding = cx.gutter_padding + cx.scroll_x * cx.em_width;
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
icon.constrained()
|
icon.constrained()
|
||||||
|
@ -618,9 +669,47 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
||||||
.aligned()
|
.aligned()
|
||||||
.boxed()
|
.boxed()
|
||||||
}))
|
}))
|
||||||
|
.with_child(
|
||||||
|
MouseEventHandler::new::<JumpIcon, _, _>(diagnostic.group_id, cx, |state, _| {
|
||||||
|
let style = style.jump_icon.style_for(state, false);
|
||||||
|
Svg::new("icons/jump.svg")
|
||||||
|
.with_color(style.color)
|
||||||
|
.constrained()
|
||||||
|
.with_width(style.icon_width)
|
||||||
|
.aligned()
|
||||||
|
.contained()
|
||||||
|
.with_style(style.container)
|
||||||
|
.constrained()
|
||||||
|
.with_width(style.button_width)
|
||||||
|
.with_height(style.button_width)
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click({
|
||||||
|
let path = path.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
cx.dispatch_action(Jump {
|
||||||
|
path: path.clone(),
|
||||||
|
position,
|
||||||
|
anchor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_tooltip(
|
||||||
|
diagnostic.group_id,
|
||||||
|
"Jump to diagnostic".to_string(),
|
||||||
|
Some(Box::new(editor::OpenExcerpts)),
|
||||||
|
tooltip_style,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.aligned()
|
||||||
|
.flex_float()
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(style.container)
|
.with_style(style.container)
|
||||||
.with_padding_left(cx.gutter_padding + cx.scroll_x * cx.em_width)
|
.with_padding_left(x_padding)
|
||||||
|
.with_padding_right(x_padding)
|
||||||
.expanded()
|
.expanded()
|
||||||
.named("diagnostic header")
|
.named("diagnostic header")
|
||||||
})
|
})
|
||||||
|
@ -702,7 +791,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::{BlockContext, TransformBlock},
|
display_map::{BlockContext, TransformBlock},
|
||||||
DisplayPoint, EditorSnapshot,
|
DisplayPoint,
|
||||||
};
|
};
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
|
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
|
||||||
|
@ -835,10 +924,8 @@ mod tests {
|
||||||
|
|
||||||
view.next_notification(&cx).await;
|
view.next_notification(&cx).await;
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&view.editor, cx),
|
||||||
[
|
[
|
||||||
(0, "path header block".into()),
|
(0, "path header block".into()),
|
||||||
(2, "diagnostic header".into()),
|
(2, "diagnostic header".into()),
|
||||||
|
@ -848,7 +935,7 @@ mod tests {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(),
|
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
concat!(
|
concat!(
|
||||||
//
|
//
|
||||||
// main.rs
|
// main.rs
|
||||||
|
@ -923,10 +1010,8 @@ mod tests {
|
||||||
|
|
||||||
view.next_notification(&cx).await;
|
view.next_notification(&cx).await;
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&view.editor, cx),
|
||||||
[
|
[
|
||||||
(0, "path header block".into()),
|
(0, "path header block".into()),
|
||||||
(2, "diagnostic header".into()),
|
(2, "diagnostic header".into()),
|
||||||
|
@ -938,7 +1023,7 @@ mod tests {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(),
|
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
concat!(
|
concat!(
|
||||||
//
|
//
|
||||||
// consts.rs
|
// consts.rs
|
||||||
|
@ -1038,10 +1123,8 @@ mod tests {
|
||||||
|
|
||||||
view.next_notification(&cx).await;
|
view.next_notification(&cx).await;
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&view.editor, cx),
|
||||||
[
|
[
|
||||||
(0, "path header block".into()),
|
(0, "path header block".into()),
|
||||||
(2, "diagnostic header".into()),
|
(2, "diagnostic header".into()),
|
||||||
|
@ -1055,7 +1138,7 @@ mod tests {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(),
|
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
concat!(
|
concat!(
|
||||||
//
|
//
|
||||||
// consts.rs
|
// consts.rs
|
||||||
|
@ -1115,36 +1198,44 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editor_blocks(editor: &EditorSnapshot, cx: &AppContext) -> Vec<(u32, String)> {
|
fn editor_blocks(
|
||||||
editor
|
editor: &ViewHandle<Editor>,
|
||||||
.blocks_in_range(0..editor.max_point().row())
|
cx: &mut MutableAppContext,
|
||||||
.filter_map(|(row, block)| {
|
) -> Vec<(u32, String)> {
|
||||||
let name = match block {
|
let mut presenter = cx.build_presenter(editor.id(), 0.);
|
||||||
TransformBlock::Custom(block) => block
|
let mut cx = presenter.build_layout_context(Default::default(), false, cx);
|
||||||
.render(&BlockContext {
|
cx.render(editor, |editor, cx| {
|
||||||
cx,
|
let snapshot = editor.snapshot(cx);
|
||||||
anchor_x: 0.,
|
snapshot
|
||||||
scroll_x: 0.,
|
.blocks_in_range(0..snapshot.max_point().row())
|
||||||
gutter_padding: 0.,
|
.filter_map(|(row, block)| {
|
||||||
gutter_width: 0.,
|
let name = match block {
|
||||||
line_height: 0.,
|
TransformBlock::Custom(block) => block
|
||||||
em_width: 0.,
|
.render(&mut BlockContext {
|
||||||
})
|
cx,
|
||||||
.name()?
|
anchor_x: 0.,
|
||||||
.to_string(),
|
scroll_x: 0.,
|
||||||
TransformBlock::ExcerptHeader {
|
gutter_padding: 0.,
|
||||||
starts_new_buffer, ..
|
gutter_width: 0.,
|
||||||
} => {
|
line_height: 0.,
|
||||||
if *starts_new_buffer {
|
em_width: 0.,
|
||||||
"path header block".to_string()
|
})
|
||||||
} else {
|
.name()?
|
||||||
"collapsed context".to_string()
|
.to_string(),
|
||||||
|
TransformBlock::ExcerptHeader {
|
||||||
|
starts_new_buffer, ..
|
||||||
|
} => {
|
||||||
|
if *starts_new_buffer {
|
||||||
|
"path header block".to_string()
|
||||||
|
} else {
|
||||||
|
"collapsed context".to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
Some((row, name))
|
Some((row, name))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,14 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::{Anchor, ToPoint as _};
|
use crate::{Anchor, ToPoint as _};
|
||||||
use collections::{Bound, HashMap, HashSet};
|
use collections::{Bound, HashMap, HashSet};
|
||||||
use gpui::{AppContext, ElementBox};
|
use gpui::{ElementBox, RenderContext};
|
||||||
use language::{BufferSnapshot, Chunk, Patch};
|
use language::{BufferSnapshot, Chunk, Patch};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
ops::{Deref, Range},
|
ops::{Deref, DerefMut, Range},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
Arc,
|
Arc,
|
||||||
|
@ -50,7 +50,7 @@ struct BlockRow(u32);
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
struct WrapRow(u32);
|
struct WrapRow(u32);
|
||||||
|
|
||||||
pub type RenderBlock = Arc<dyn Fn(&BlockContext) -> ElementBox>;
|
pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> ElementBox>;
|
||||||
|
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
id: BlockId,
|
id: BlockId,
|
||||||
|
@ -67,12 +67,12 @@ where
|
||||||
{
|
{
|
||||||
pub position: P,
|
pub position: P,
|
||||||
pub height: u8,
|
pub height: u8,
|
||||||
pub render: Arc<dyn Fn(&BlockContext) -> ElementBox>,
|
pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>,
|
||||||
pub disposition: BlockDisposition,
|
pub disposition: BlockDisposition,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BlockContext<'a> {
|
pub struct BlockContext<'a, 'b> {
|
||||||
pub cx: &'a AppContext,
|
pub cx: &'b mut RenderContext<'a, crate::Editor>,
|
||||||
pub anchor_x: f32,
|
pub anchor_x: f32,
|
||||||
pub scroll_x: f32,
|
pub scroll_x: f32,
|
||||||
pub gutter_width: f32,
|
pub gutter_width: f32,
|
||||||
|
@ -916,16 +916,22 @@ impl BlockDisposition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for BlockContext<'a> {
|
impl<'a, 'b> Deref for BlockContext<'a, 'b> {
|
||||||
type Target = AppContext;
|
type Target = RenderContext<'a, crate::Editor>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.cx
|
self.cx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> DerefMut for BlockContext<'a, 'b> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.cx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Block {
|
impl Block {
|
||||||
pub fn render(&self, cx: &BlockContext) -> ElementBox {
|
pub fn render(&self, cx: &mut BlockContext) -> ElementBox {
|
||||||
self.render.lock()(cx)
|
self.render.lock()(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1008,7 +1014,7 @@ mod tests {
|
||||||
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
|
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
|
||||||
|
|
||||||
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
|
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
|
||||||
writer.insert(vec![
|
let block_ids = writer.insert(vec![
|
||||||
BlockProperties {
|
BlockProperties {
|
||||||
position: buffer_snapshot.anchor_after(Point::new(1, 0)),
|
position: buffer_snapshot.anchor_after(Point::new(1, 0)),
|
||||||
height: 1,
|
height: 1,
|
||||||
|
@ -1036,22 +1042,7 @@ mod tests {
|
||||||
.blocks_in_range(0..8)
|
.blocks_in_range(0..8)
|
||||||
.map(|(start_row, block)| {
|
.map(|(start_row, block)| {
|
||||||
let block = block.as_custom().unwrap();
|
let block = block.as_custom().unwrap();
|
||||||
(
|
(start_row..start_row + block.height as u32, block.id)
|
||||||
start_row..start_row + block.height as u32,
|
|
||||||
block
|
|
||||||
.render(&BlockContext {
|
|
||||||
cx,
|
|
||||||
anchor_x: 0.,
|
|
||||||
gutter_padding: 0.,
|
|
||||||
scroll_x: 0.,
|
|
||||||
gutter_width: 0.,
|
|
||||||
line_height: 0.,
|
|
||||||
em_width: 0.,
|
|
||||||
})
|
|
||||||
.name()
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -1059,9 +1050,9 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
blocks,
|
blocks,
|
||||||
&[
|
&[
|
||||||
(1..2, "block 1".to_string()),
|
(1..2, block_ids[0]),
|
||||||
(2..4, "block 2".to_string()),
|
(2..4, block_ids[1]),
|
||||||
(7..10, "block 3".to_string()),
|
(7..10, block_ids[2]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4745,7 +4745,7 @@ impl Editor {
|
||||||
height: 1,
|
height: 1,
|
||||||
render: Arc::new({
|
render: Arc::new({
|
||||||
let editor = rename_editor.clone();
|
let editor = rename_editor.clone();
|
||||||
move |cx: &BlockContext| {
|
move |cx: &mut BlockContext| {
|
||||||
ChildView::new(editor.clone())
|
ChildView::new(editor.clone())
|
||||||
.contained()
|
.contained()
|
||||||
.with_padding_left(cx.anchor_x)
|
.with_padding_left(cx.anchor_x)
|
||||||
|
@ -5866,7 +5866,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
|
||||||
highlighted_lines.push(highlight_diagnostic_message(line));
|
highlighted_lines.push(highlight_diagnostic_message(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
Arc::new(move |cx: &BlockContext| {
|
Arc::new(move |cx: &mut BlockContext| {
|
||||||
let settings = cx.global::<Settings>();
|
let settings = cx.global::<Settings>();
|
||||||
let theme = &settings.theme.editor;
|
let theme = &settings.theme.editor;
|
||||||
let style = diagnostic_style(diagnostic.severity, is_valid, theme);
|
let style = diagnostic_style(diagnostic.severity, is_valid, theme);
|
||||||
|
|
|
@ -755,6 +755,12 @@ impl EditorElement {
|
||||||
line_layouts: &[text_layout::Line],
|
line_layouts: &[text_layout::Line],
|
||||||
cx: &mut LayoutContext,
|
cx: &mut LayoutContext,
|
||||||
) -> Vec<(u32, ElementBox)> {
|
) -> Vec<(u32, ElementBox)> {
|
||||||
|
let editor = if let Some(editor) = self.view.upgrade(cx) {
|
||||||
|
editor
|
||||||
|
} else {
|
||||||
|
return Default::default();
|
||||||
|
};
|
||||||
|
|
||||||
let scroll_x = snapshot.scroll_position.x();
|
let scroll_x = snapshot.scroll_position.x();
|
||||||
snapshot
|
snapshot
|
||||||
.blocks_in_range(rows.clone())
|
.blocks_in_range(rows.clone())
|
||||||
|
@ -774,14 +780,16 @@ impl EditorElement {
|
||||||
.x_for_index(align_to.column() as usize)
|
.x_for_index(align_to.column() as usize)
|
||||||
};
|
};
|
||||||
|
|
||||||
block.render(&BlockContext {
|
cx.render(&editor, |_, cx| {
|
||||||
cx,
|
block.render(&mut BlockContext {
|
||||||
anchor_x,
|
cx,
|
||||||
gutter_padding,
|
anchor_x,
|
||||||
line_height,
|
gutter_padding,
|
||||||
scroll_x,
|
line_height,
|
||||||
gutter_width,
|
scroll_x,
|
||||||
em_width,
|
gutter_width,
|
||||||
|
em_width,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
TransformBlock::ExcerptHeader {
|
TransformBlock::ExcerptHeader {
|
||||||
|
@ -1611,7 +1619,7 @@ mod tests {
|
||||||
|
|
||||||
// Don't panic.
|
// Don't panic.
|
||||||
let bounds = RectF::new(Default::default(), size);
|
let bounds = RectF::new(Default::default(), size);
|
||||||
let mut paint_cx = presenter.build_paint_context(&mut scene, cx);
|
let mut paint_cx = presenter.build_paint_context(&mut scene, bounds.size(), cx);
|
||||||
element.paint(bounds, bounds, &mut state, &mut paint_cx);
|
element.paint(bounds, bounds, &mut state, &mut paint_cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,14 @@ mod overlay;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod svg;
|
mod svg;
|
||||||
mod text;
|
mod text;
|
||||||
|
mod tooltip;
|
||||||
mod uniform_list;
|
mod uniform_list;
|
||||||
|
|
||||||
use self::expanded::Expanded;
|
use self::expanded::Expanded;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
align::*, canvas::*, constrained_box::*, container::*, empty::*, event_handler::*, flex::*,
|
align::*, canvas::*, constrained_box::*, container::*, empty::*, event_handler::*, flex::*,
|
||||||
hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
|
hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
|
||||||
stack::*, svg::*, text::*, uniform_list::*,
|
stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
|
||||||
};
|
};
|
||||||
pub use crate::presenter::ChildView;
|
pub use crate::presenter::ChildView;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -30,7 +31,8 @@ use crate::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
json, DebugContext, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
json, Action, DebugContext, Event, EventContext, LayoutContext, PaintContext, RenderContext,
|
||||||
|
SizeConstraint, View,
|
||||||
};
|
};
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use json::ToJson;
|
use json::ToJson;
|
||||||
|
@ -154,6 +156,20 @@ pub trait Element {
|
||||||
{
|
{
|
||||||
FlexItem::new(self.boxed()).float()
|
FlexItem::new(self.boxed()).float()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_tooltip<T: View>(
|
||||||
|
self,
|
||||||
|
id: usize,
|
||||||
|
text: String,
|
||||||
|
action: Option<Box<dyn Action>>,
|
||||||
|
style: TooltipStyle,
|
||||||
|
cx: &mut RenderContext<T>,
|
||||||
|
) -> Tooltip
|
||||||
|
where
|
||||||
|
Self: 'static + Sized,
|
||||||
|
{
|
||||||
|
Tooltip::new(id, text, action, style, self.boxed(), cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Lifecycle<T: Element> {
|
pub enum Lifecycle<T: Element> {
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub struct MouseEventHandler {
|
||||||
mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
||||||
right_mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
right_mouse_down_out: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
||||||
drag: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
drag: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
||||||
|
hover: Option<Rc<dyn Fn(Vector2F, bool, &mut EventContext)>>,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ impl MouseEventHandler {
|
||||||
mouse_down_out: None,
|
mouse_down_out: None,
|
||||||
right_mouse_down_out: None,
|
right_mouse_down_out: None,
|
||||||
drag: None,
|
drag: None,
|
||||||
|
hover: None,
|
||||||
padding: Default::default(),
|
padding: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +111,14 @@ impl MouseEventHandler {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_hover(
|
||||||
|
mut self,
|
||||||
|
handler: impl Fn(Vector2F, bool, &mut EventContext) + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.hover = Some(Rc::new(handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_padding(mut self, padding: Padding) -> Self {
|
pub fn with_padding(mut self, padding: Padding) -> Self {
|
||||||
self.padding = padding;
|
self.padding = padding;
|
||||||
self
|
self
|
||||||
|
@ -153,7 +163,7 @@ impl Element for MouseEventHandler {
|
||||||
view_id: cx.current_view_id(),
|
view_id: cx.current_view_id(),
|
||||||
discriminant: Some((self.tag, self.id)),
|
discriminant: Some((self.tag, self.id)),
|
||||||
bounds: self.hit_bounds(bounds),
|
bounds: self.hit_bounds(bounds),
|
||||||
hover: None,
|
hover: self.hover.clone(),
|
||||||
click: self.click.clone(),
|
click: self.click.clone(),
|
||||||
mouse_down: self.mouse_down.clone(),
|
mouse_down: self.mouse_down.clone(),
|
||||||
right_click: self.right_click.clone(),
|
right_click: self.right_click.clone(),
|
||||||
|
|
217
crates/gpui/src/elements/tooltip.rs
Normal file
217
crates/gpui/src/elements/tooltip.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
use super::{
|
||||||
|
ContainerStyle, Element, ElementBox, Flex, KeystrokeLabel, MouseEventHandler, ParentElement,
|
||||||
|
Text,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
fonts::TextStyle,
|
||||||
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
|
json::json,
|
||||||
|
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
|
||||||
|
Task, View,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
rc::Rc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
|
||||||
|
|
||||||
|
pub struct Tooltip {
|
||||||
|
child: ElementBox,
|
||||||
|
tooltip: Option<ElementBox>,
|
||||||
|
state: ElementStateHandle<Rc<TooltipState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TooltipState {
|
||||||
|
visible: Cell<bool>,
|
||||||
|
position: Cell<Vector2F>,
|
||||||
|
debounce: RefCell<Option<Task<()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct TooltipStyle {
|
||||||
|
#[serde(flatten)]
|
||||||
|
container: ContainerStyle,
|
||||||
|
text: TextStyle,
|
||||||
|
keystroke: KeystrokeStyle,
|
||||||
|
max_text_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct KeystrokeStyle {
|
||||||
|
#[serde(flatten)]
|
||||||
|
container: ContainerStyle,
|
||||||
|
#[serde(flatten)]
|
||||||
|
text: TextStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tooltip {
|
||||||
|
pub fn new<T: View>(
|
||||||
|
id: usize,
|
||||||
|
text: String,
|
||||||
|
action: Option<Box<dyn Action>>,
|
||||||
|
style: TooltipStyle,
|
||||||
|
child: ElementBox,
|
||||||
|
cx: &mut RenderContext<T>,
|
||||||
|
) -> Self {
|
||||||
|
let state_handle = cx.element_state::<TooltipState, Rc<TooltipState>>(id);
|
||||||
|
let state = state_handle.read(cx).clone();
|
||||||
|
let tooltip = if state.visible.get() {
|
||||||
|
let mut collapsed_tooltip = Self::render_tooltip(
|
||||||
|
text.clone(),
|
||||||
|
style.clone(),
|
||||||
|
action.as_ref().map(|a| a.boxed_clone()),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.boxed();
|
||||||
|
Some(
|
||||||
|
Self::render_tooltip(text, style, action, false)
|
||||||
|
.constrained()
|
||||||
|
.dynamically(move |constraint, cx| {
|
||||||
|
SizeConstraint::strict_along(
|
||||||
|
Axis::Vertical,
|
||||||
|
collapsed_tooltip.layout(constraint, cx).y(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let child = MouseEventHandler::new::<Self, _, _>(id, cx, |_, _| child)
|
||||||
|
.on_hover(move |position, hover, cx| {
|
||||||
|
let window_id = cx.window_id();
|
||||||
|
if let Some(view_id) = cx.view_id() {
|
||||||
|
if hover {
|
||||||
|
if !state.visible.get() {
|
||||||
|
state.position.set(position);
|
||||||
|
|
||||||
|
let mut debounce = state.debounce.borrow_mut();
|
||||||
|
if debounce.is_none() {
|
||||||
|
*debounce = Some(cx.spawn({
|
||||||
|
let state = state.clone();
|
||||||
|
|mut cx| async move {
|
||||||
|
cx.background().timer(DEBOUNCE_TIMEOUT).await;
|
||||||
|
state.visible.set(true);
|
||||||
|
cx.update(|cx| cx.notify_view(window_id, view_id));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.visible.set(false);
|
||||||
|
state.debounce.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.boxed();
|
||||||
|
Self {
|
||||||
|
child,
|
||||||
|
tooltip,
|
||||||
|
state: state_handle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_tooltip(
|
||||||
|
text: String,
|
||||||
|
style: TooltipStyle,
|
||||||
|
action: Option<Box<dyn Action>>,
|
||||||
|
measure: bool,
|
||||||
|
) -> impl Element {
|
||||||
|
Flex::row()
|
||||||
|
.with_child({
|
||||||
|
let text = Text::new(text, style.text)
|
||||||
|
.constrained()
|
||||||
|
.with_max_width(style.max_text_width);
|
||||||
|
if measure {
|
||||||
|
text.flex(1., false).boxed()
|
||||||
|
} else {
|
||||||
|
text.flex(1., false).aligned().boxed()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_children(action.map(|action| {
|
||||||
|
let keystroke_label =
|
||||||
|
KeystrokeLabel::new(action, style.keystroke.container, style.keystroke.text);
|
||||||
|
if measure {
|
||||||
|
keystroke_label.boxed()
|
||||||
|
} else {
|
||||||
|
keystroke_label.aligned().boxed()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.contained()
|
||||||
|
.with_style(style.container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for Tooltip {
|
||||||
|
type LayoutState = ();
|
||||||
|
type PaintState = ();
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
constraint: SizeConstraint,
|
||||||
|
cx: &mut LayoutContext,
|
||||||
|
) -> (Vector2F, Self::LayoutState) {
|
||||||
|
let size = self.child.layout(constraint, cx);
|
||||||
|
if let Some(tooltip) = self.tooltip.as_mut() {
|
||||||
|
tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
|
||||||
|
}
|
||||||
|
(size, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: RectF,
|
||||||
|
visible_bounds: RectF,
|
||||||
|
_: &mut Self::LayoutState,
|
||||||
|
cx: &mut PaintContext,
|
||||||
|
) {
|
||||||
|
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||||
|
if let Some(tooltip) = self.tooltip.as_mut() {
|
||||||
|
let origin = self.state.read(cx).position.get();
|
||||||
|
let mut bounds = RectF::new(origin, tooltip.size());
|
||||||
|
|
||||||
|
// Align tooltip to the left if its bounds overflow the window width.
|
||||||
|
if bounds.lower_right().x() > cx.window_size.x() {
|
||||||
|
bounds.set_origin_x(bounds.origin_x() - bounds.width());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Align tooltip to the top if its bounds overflow the window height.
|
||||||
|
if bounds.lower_right().y() > cx.window_size.y() {
|
||||||
|
bounds.set_origin_y(bounds.origin_y() - bounds.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.scene.push_stacking_context(None);
|
||||||
|
tooltip.paint(bounds.origin(), bounds, cx);
|
||||||
|
cx.scene.pop_stacking_context();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_event(
|
||||||
|
&mut self,
|
||||||
|
event: &crate::Event,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &mut Self::LayoutState,
|
||||||
|
_: &mut Self::PaintState,
|
||||||
|
cx: &mut crate::EventContext,
|
||||||
|
) -> bool {
|
||||||
|
self.child.dispatch_event(event, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &crate::DebugContext,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"child": self.child.debug(cx),
|
||||||
|
"tooltip": self.tooltip.as_ref().map(|t| t.debug(cx)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -148,7 +148,7 @@ impl Presenter {
|
||||||
|
|
||||||
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
||||||
self.layout(window_size, refreshing, cx);
|
self.layout(window_size, refreshing, cx);
|
||||||
let mut paint_cx = self.build_paint_context(&mut scene, cx);
|
let mut paint_cx = self.build_paint_context(&mut scene, window_size, cx);
|
||||||
paint_cx.paint(
|
paint_cx.paint(
|
||||||
root_view_id,
|
root_view_id,
|
||||||
Vector2F::zero(),
|
Vector2F::zero(),
|
||||||
|
@ -205,10 +205,12 @@ impl Presenter {
|
||||||
pub fn build_paint_context<'a>(
|
pub fn build_paint_context<'a>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
scene: &'a mut Scene,
|
scene: &'a mut Scene,
|
||||||
|
window_size: Vector2F,
|
||||||
cx: &'a mut MutableAppContext,
|
cx: &'a mut MutableAppContext,
|
||||||
) -> PaintContext {
|
) -> PaintContext {
|
||||||
PaintContext {
|
PaintContext {
|
||||||
scene,
|
scene,
|
||||||
|
window_size,
|
||||||
font_cache: &self.font_cache,
|
font_cache: &self.font_cache,
|
||||||
text_layout_cache: &self.text_layout_cache,
|
text_layout_cache: &self.text_layout_cache,
|
||||||
rendered_views: &mut self.rendered_views,
|
rendered_views: &mut self.rendered_views,
|
||||||
|
@ -311,7 +313,7 @@ impl Presenter {
|
||||||
if let Some(region_id) = region.id() {
|
if let Some(region_id) = region.id() {
|
||||||
if !self.hovered_region_ids.contains(®ion_id) {
|
if !self.hovered_region_ids.contains(®ion_id) {
|
||||||
invalidated_views.push(region.view_id);
|
invalidated_views.push(region.view_id);
|
||||||
hovered_regions.push(region.clone());
|
hovered_regions.push((region.clone(), position));
|
||||||
self.hovered_region_ids.insert(region_id);
|
self.hovered_region_ids.insert(region_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,7 +321,7 @@ impl Presenter {
|
||||||
if let Some(region_id) = region.id() {
|
if let Some(region_id) = region.id() {
|
||||||
if self.hovered_region_ids.contains(®ion_id) {
|
if self.hovered_region_ids.contains(®ion_id) {
|
||||||
invalidated_views.push(region.view_id);
|
invalidated_views.push(region.view_id);
|
||||||
unhovered_regions.push(region.clone());
|
unhovered_regions.push((region.clone(), position));
|
||||||
self.hovered_region_ids.remove(®ion_id);
|
self.hovered_region_ids.remove(®ion_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,20 +350,20 @@ impl Presenter {
|
||||||
|
|
||||||
let mut event_cx = self.build_event_context(cx);
|
let mut event_cx = self.build_event_context(cx);
|
||||||
let mut handled = false;
|
let mut handled = false;
|
||||||
for unhovered_region in unhovered_regions {
|
for (unhovered_region, position) in unhovered_regions {
|
||||||
handled = true;
|
handled = true;
|
||||||
if let Some(hover_callback) = unhovered_region.hover {
|
if let Some(hover_callback) = unhovered_region.hover {
|
||||||
event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
|
event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
|
||||||
hover_callback(false, event_cx);
|
hover_callback(position, false, event_cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for hovered_region in hovered_regions {
|
for (hovered_region, position) in hovered_regions {
|
||||||
handled = true;
|
handled = true;
|
||||||
if let Some(hover_callback) = hovered_region.hover {
|
if let Some(hover_callback) = hovered_region.hover {
|
||||||
event_cx.with_current_view(hovered_region.view_id, |event_cx| {
|
event_cx.with_current_view(hovered_region.view_id, |event_cx| {
|
||||||
hover_callback(true, event_cx);
|
hover_callback(position, true, event_cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -449,6 +451,7 @@ impl Presenter {
|
||||||
view_stack: Default::default(),
|
view_stack: Default::default(),
|
||||||
invalidated_views: Default::default(),
|
invalidated_views: Default::default(),
|
||||||
notify_count: 0,
|
notify_count: 0,
|
||||||
|
window_id: self.window_id,
|
||||||
app: cx,
|
app: cx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -591,6 +594,7 @@ impl<'a> UpgradeViewHandle for LayoutContext<'a> {
|
||||||
pub struct PaintContext<'a> {
|
pub struct PaintContext<'a> {
|
||||||
rendered_views: &'a mut HashMap<usize, ElementBox>,
|
rendered_views: &'a mut HashMap<usize, ElementBox>,
|
||||||
view_stack: Vec<usize>,
|
view_stack: Vec<usize>,
|
||||||
|
pub window_size: Vector2F,
|
||||||
pub scene: &'a mut Scene,
|
pub scene: &'a mut Scene,
|
||||||
pub font_cache: &'a FontCache,
|
pub font_cache: &'a FontCache,
|
||||||
pub text_layout_cache: &'a TextLayoutCache,
|
pub text_layout_cache: &'a TextLayoutCache,
|
||||||
|
@ -626,6 +630,7 @@ pub struct EventContext<'a> {
|
||||||
pub font_cache: &'a FontCache,
|
pub font_cache: &'a FontCache,
|
||||||
pub text_layout_cache: &'a TextLayoutCache,
|
pub text_layout_cache: &'a TextLayoutCache,
|
||||||
pub app: &'a mut MutableAppContext,
|
pub app: &'a mut MutableAppContext,
|
||||||
|
pub window_id: usize,
|
||||||
pub notify_count: usize,
|
pub notify_count: usize,
|
||||||
view_stack: Vec<usize>,
|
view_stack: Vec<usize>,
|
||||||
invalidated_views: HashSet<usize>,
|
invalidated_views: HashSet<usize>,
|
||||||
|
@ -653,6 +658,14 @@ impl<'a> EventContext<'a> {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn window_id(&self) -> usize {
|
||||||
|
self.window_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_id(&self) -> Option<usize> {
|
||||||
|
self.view_stack.last().copied()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
|
pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
|
||||||
self.dispatched_actions.push(DispatchDirective {
|
self.dispatched_actions.push(DispatchDirective {
|
||||||
dispatcher_view_id: self.view_stack.last().copied(),
|
dispatcher_view_id: self.view_stack.last().copied(),
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub struct MouseRegion {
|
||||||
pub view_id: usize,
|
pub view_id: usize,
|
||||||
pub discriminant: Option<(TypeId, usize)>,
|
pub discriminant: Option<(TypeId, usize)>,
|
||||||
pub bounds: RectF,
|
pub bounds: RectF,
|
||||||
pub hover: Option<Rc<dyn Fn(bool, &mut EventContext)>>,
|
pub hover: Option<Rc<dyn Fn(Vector2F, bool, &mut EventContext)>>,
|
||||||
pub mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
pub mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
||||||
pub click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
|
pub click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
|
||||||
pub right_mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
pub right_mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod theme_registry;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::{ContainerStyle, ImageStyle, LabelStyle},
|
elements::{ContainerStyle, ImageStyle, LabelStyle, TooltipStyle},
|
||||||
fonts::{HighlightStyle, TextStyle},
|
fonts::{HighlightStyle, TextStyle},
|
||||||
Border, MouseState,
|
Border, MouseState,
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,7 @@ pub struct Theme {
|
||||||
pub project_diagnostics: ProjectDiagnostics,
|
pub project_diagnostics: ProjectDiagnostics,
|
||||||
pub breadcrumbs: ContainedText,
|
pub breadcrumbs: ContainedText,
|
||||||
pub contact_notification: ContactNotification,
|
pub contact_notification: ContactNotification,
|
||||||
|
pub tooltip: TooltipStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
|
@ -317,7 +318,7 @@ pub struct Icon {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Clone, Deserialize, Default)]
|
||||||
pub struct IconButton {
|
pub struct IconButton {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub container: ContainerStyle,
|
pub container: ContainerStyle,
|
||||||
|
@ -461,6 +462,7 @@ pub struct DiagnosticHeader {
|
||||||
pub code: ContainedText,
|
pub code: ContainedText,
|
||||||
pub text_scale_factor: f32,
|
pub text_scale_factor: f32,
|
||||||
pub icon_width_factor: f32,
|
pub icon_width_factor: f32,
|
||||||
|
pub jump_icon: Interactive<IconButton>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Default)]
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
|
|
@ -299,7 +299,9 @@ impl Pane {
|
||||||
) -> Box<dyn ItemHandle> {
|
) -> Box<dyn ItemHandle> {
|
||||||
let existing_item = pane.update(cx, |pane, cx| {
|
let existing_item = pane.update(cx, |pane, cx| {
|
||||||
for (ix, item) in pane.items.iter().enumerate() {
|
for (ix, item) in pane.items.iter().enumerate() {
|
||||||
if item.project_entry_ids(cx).as_slice() == &[project_entry_id] {
|
if item.project_path(cx).is_some()
|
||||||
|
&& item.project_entry_ids(cx).as_slice() == &[project_entry_id]
|
||||||
|
{
|
||||||
let item = item.boxed_clone();
|
let item = item.boxed_clone();
|
||||||
pane.activate_item(ix, true, focus_item, cx);
|
pane.activate_item(ix, true, focus_item, cx);
|
||||||
return Some(item);
|
return Some(item);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import workspace from "./workspace";
|
||||||
import contextMenu from "./contextMenu";
|
import contextMenu from "./contextMenu";
|
||||||
import projectDiagnostics from "./projectDiagnostics";
|
import projectDiagnostics from "./projectDiagnostics";
|
||||||
import contactNotification from "./contactNotification";
|
import contactNotification from "./contactNotification";
|
||||||
|
import tooltip from "./tooltip";
|
||||||
|
|
||||||
export const panel = {
|
export const panel = {
|
||||||
padding: { top: 12, bottom: 12 },
|
padding: { top: 12, bottom: 12 },
|
||||||
|
@ -37,5 +38,6 @@ export default function app(theme: Theme): Object {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
contactNotification: contactNotification(theme),
|
contactNotification: contactNotification(theme),
|
||||||
|
tooltip: tooltip(theme),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,14 @@ export default function editor(theme: Theme) {
|
||||||
background: backgroundColor(theme, 300),
|
background: backgroundColor(theme, 300),
|
||||||
iconWidthFactor: 1.5,
|
iconWidthFactor: 1.5,
|
||||||
textScaleFactor: 0.857, // NateQ: Will we need dynamic sizing for text? If so let's create tokens for these.
|
textScaleFactor: 0.857, // NateQ: Will we need dynamic sizing for text? If so let's create tokens for these.
|
||||||
|
jumpIcon: {
|
||||||
|
color: iconColor(theme, "primary"),
|
||||||
|
iconWidth: 10,
|
||||||
|
buttonWidth: 10,
|
||||||
|
hover: {
|
||||||
|
color: iconColor(theme, "active")
|
||||||
|
}
|
||||||
|
},
|
||||||
border: border(theme, "secondary", {
|
border: border(theme, "secondary", {
|
||||||
bottom: true,
|
bottom: true,
|
||||||
top: true,
|
top: true,
|
||||||
|
|
22
styles/src/styleTree/tooltip.ts
Normal file
22
styles/src/styleTree/tooltip.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Theme from "../themes/common/theme";
|
||||||
|
import { backgroundColor, border, shadow, text } from "./components";
|
||||||
|
|
||||||
|
export default function tooltip(theme: Theme) {
|
||||||
|
return {
|
||||||
|
background: backgroundColor(theme, 500),
|
||||||
|
border: border(theme, "secondary"),
|
||||||
|
padding: { top: 4, bottom: 4, left: 8, right: 8 },
|
||||||
|
margin: { top: 6, left: 6 },
|
||||||
|
shadow: shadow(theme),
|
||||||
|
cornerRadius: 6,
|
||||||
|
text: text(theme, "sans", "secondary", { size: "xs", weight: "bold" }),
|
||||||
|
keystroke: {
|
||||||
|
background: backgroundColor(theme, "on500"),
|
||||||
|
cornerRadius: 4,
|
||||||
|
margin: { left: 6 },
|
||||||
|
padding: { left: 3, right: 3 },
|
||||||
|
...text(theme, "mono", "muted", { size: "xs", weight: "bold" })
|
||||||
|
},
|
||||||
|
maxTextWidth: 200,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue