editor2 rendering (#3244)
- Make tab bar visible - Fix tab text colors - Ensure panes cover the available space - Make the close button close - Fix bug when unsubscribe called after remove - WIP: start on editor element - Add hover behaviour to tabs - Add PointingHand on tabs - gpui2: Add on_hover events - Tooltip on tabs - MOAR TOOLTIPS - Use an `IconButton` for the tab close button - Remove unneeded type qualification - Tooltips in mouse event handler & fix executor timer - Move more tooltip logic into gpui2 & fix tooltip moving on paint - Update tooltip code a bit - Allow multiple subscriptions from one entity handle Release Notes: - N/A
This commit is contained in:
commit
eb325fb387
22 changed files with 577 additions and 164 deletions
|
@ -37,8 +37,8 @@ use futures::FutureExt;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter,
|
div, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter,
|
||||||
FocusHandle, Hsla, Model, Pixels, Render, Subscription, Task, TextStyle, View, ViewContext,
|
FocusHandle, Hsla, Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View,
|
||||||
VisualContext, WeakView, WindowContext,
|
ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||||
use hover_popover::{hide_hover, HoverState};
|
use hover_popover::{hide_hover, HoverState};
|
||||||
|
@ -68,6 +68,7 @@ use scroll::{
|
||||||
use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
|
use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -8347,51 +8348,51 @@ impl Editor {
|
||||||
// .text()
|
// .text()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
|
pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
|
||||||
// let mut wrap_guides = smallvec::smallvec![];
|
let mut wrap_guides = smallvec::smallvec![];
|
||||||
|
|
||||||
// if self.show_wrap_guides == Some(false) {
|
if self.show_wrap_guides == Some(false) {
|
||||||
// return wrap_guides;
|
return wrap_guides;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// let settings = self.buffer.read(cx).settings_at(0, cx);
|
let settings = self.buffer.read(cx).settings_at(0, cx);
|
||||||
// if settings.show_wrap_guides {
|
if settings.show_wrap_guides {
|
||||||
// if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
|
if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
|
||||||
// wrap_guides.push((soft_wrap as usize, true));
|
wrap_guides.push((soft_wrap as usize, true));
|
||||||
// }
|
}
|
||||||
// wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
|
wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
|
||||||
// }
|
}
|
||||||
|
|
||||||
// wrap_guides
|
wrap_guides
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
|
pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
|
||||||
// let settings = self.buffer.read(cx).settings_at(0, cx);
|
let settings = self.buffer.read(cx).settings_at(0, cx);
|
||||||
// let mode = self
|
let mode = self
|
||||||
// .soft_wrap_mode_override
|
.soft_wrap_mode_override
|
||||||
// .unwrap_or_else(|| settings.soft_wrap);
|
.unwrap_or_else(|| settings.soft_wrap);
|
||||||
// match mode {
|
match mode {
|
||||||
// language_settings::SoftWrap::None => SoftWrap::None,
|
language_settings::SoftWrap::None => SoftWrap::None,
|
||||||
// language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
|
language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
|
||||||
// language_settings::SoftWrap::PreferredLineLength => {
|
language_settings::SoftWrap::PreferredLineLength => {
|
||||||
// SoftWrap::Column(settings.preferred_line_length)
|
SoftWrap::Column(settings.preferred_line_length)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn set_soft_wrap_mode(
|
pub fn set_soft_wrap_mode(
|
||||||
// &mut self,
|
&mut self,
|
||||||
// mode: language_settings::SoftWrap,
|
mode: language_settings::SoftWrap,
|
||||||
// cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
// ) {
|
) {
|
||||||
// self.soft_wrap_mode_override = Some(mode);
|
self.soft_wrap_mode_override = Some(mode);
|
||||||
// cx.notify();
|
cx.notify();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut AppContext) -> bool {
|
pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
|
||||||
// self.display_map
|
self.display_map
|
||||||
// .update(cx, |map, cx| map.set_wrap_width(width, cx))
|
.update(cx, |map, cx| map.set_wrap_width(width, cx))
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
|
// pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
|
||||||
// if self.soft_wrap_mode_override.is_some() {
|
// if self.soft_wrap_mode_override.is_some() {
|
||||||
|
@ -9321,11 +9322,14 @@ impl EventEmitter for Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Editor {
|
impl Render for Editor {
|
||||||
type Element = Div<Self>;
|
type Element = EditorElement;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
// todo!()
|
EditorElement::new(EditorStyle {
|
||||||
div()
|
text: cx.text_style(),
|
||||||
|
line_height_scalar: 1.,
|
||||||
|
theme_id: 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,18 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{BlockStyle, DisplaySnapshot},
|
display_map::{BlockStyle, DisplaySnapshot},
|
||||||
EditorStyle,
|
EditorMode, EditorStyle, SoftWrap,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
black, px, relative, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style, TextRun,
|
black, point, px, relative, size, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style,
|
||||||
TextSystem,
|
TextRun, TextSystem, ViewContext,
|
||||||
};
|
};
|
||||||
use language::{CursorShape, Selection};
|
use language::{CursorShape, Selection};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{cmp, ops::Range, sync::Arc};
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
enum FoldMarkers {}
|
enum FoldMarkers {}
|
||||||
|
|
||||||
|
@ -1321,29 +1322,31 @@ impl EditorElement {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> f32 {
|
fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels {
|
||||||
// let style = &self.style;
|
let style = &self.style;
|
||||||
|
let font_size = style.text.font_size * cx.rem_size();
|
||||||
|
let layout = cx
|
||||||
|
.text_system()
|
||||||
|
.layout_text(
|
||||||
|
" ".repeat(column).as_str(),
|
||||||
|
font_size,
|
||||||
|
&[TextRun {
|
||||||
|
len: column,
|
||||||
|
font: style.text.font(),
|
||||||
|
color: Hsla::default(),
|
||||||
|
underline: None,
|
||||||
|
}],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// cx.text_layout_cache()
|
layout[0].width
|
||||||
// .layout_str(
|
}
|
||||||
// " ".repeat(column).as_str(),
|
|
||||||
// style.text.font_size,
|
|
||||||
// &[(
|
|
||||||
// column,
|
|
||||||
// RunStyle {
|
|
||||||
// font_id: style.text.font_id,
|
|
||||||
// color: Color::black(),
|
|
||||||
// underline: Default::default(),
|
|
||||||
// },
|
|
||||||
// )],
|
|
||||||
// )
|
|
||||||
// .width()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
|
fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> Pixels {
|
||||||
// let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
|
let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
|
||||||
// self.column_pixels(digit_count, cx)
|
self.column_pixels(digit_count, cx)
|
||||||
// }
|
}
|
||||||
|
|
||||||
//Folds contained in a hunk are ignored apart from shrinking visual size
|
//Folds contained in a hunk are ignored apart from shrinking visual size
|
||||||
//If a fold contains any hunks then that fold line is marked as modified
|
//If a fold contains any hunks then that fold line is marked as modified
|
||||||
|
@ -2002,6 +2005,7 @@ impl Element<Editor> for EditorElement {
|
||||||
element_state: &mut Self::ElementState,
|
element_state: &mut Self::ElementState,
|
||||||
cx: &mut gpui::ViewContext<Editor>,
|
cx: &mut gpui::ViewContext<Editor>,
|
||||||
) -> gpui::LayoutId {
|
) -> gpui::LayoutId {
|
||||||
|
let rem_size = cx.rem_size();
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.size.width = relative(1.).into();
|
style.size.width = relative(1.).into();
|
||||||
style.size.height = relative(1.).into();
|
style.size.height = relative(1.).into();
|
||||||
|
@ -2011,18 +2015,125 @@ impl Element<Editor> for EditorElement {
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<gpui::Pixels>,
|
bounds: Bounds<gpui::Pixels>,
|
||||||
view_state: &mut Editor,
|
editor: &mut Editor,
|
||||||
element_state: &mut Self::ElementState,
|
element_state: &mut Self::ElementState,
|
||||||
cx: &mut gpui::ViewContext<Editor>,
|
cx: &mut gpui::ViewContext<Editor>,
|
||||||
) {
|
) {
|
||||||
let text_style = cx.text_style();
|
// let mut size = constraint.max;
|
||||||
|
// if size.x().is_infinite() {
|
||||||
|
// unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
|
||||||
|
// }
|
||||||
|
|
||||||
let layout_text = cx.text_system().layout_text(
|
let snapshot = editor.snapshot(cx);
|
||||||
"hello world",
|
let style = self.style.clone();
|
||||||
text_style.font_size * cx.rem_size(),
|
let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
|
||||||
&[text_style.to_run("hello world".len())],
|
let font_size = style.text.font_size * cx.rem_size();
|
||||||
None,
|
let line_height = (font_size * style.line_height_scalar).round();
|
||||||
);
|
let em_width = cx
|
||||||
|
.text_system()
|
||||||
|
.typographic_bounds(font_id, font_size, 'm')
|
||||||
|
.unwrap()
|
||||||
|
.size
|
||||||
|
.width;
|
||||||
|
let em_advance = cx
|
||||||
|
.text_system()
|
||||||
|
.advance(font_id, font_size, 'm')
|
||||||
|
.unwrap()
|
||||||
|
.width;
|
||||||
|
|
||||||
|
let gutter_padding;
|
||||||
|
let gutter_width;
|
||||||
|
let gutter_margin;
|
||||||
|
if snapshot.show_gutter {
|
||||||
|
let descent = cx.text_system().descent(font_id, font_size).unwrap();
|
||||||
|
|
||||||
|
let gutter_padding_factor = 3.5;
|
||||||
|
gutter_padding = (em_width * gutter_padding_factor).round();
|
||||||
|
gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
|
||||||
|
gutter_margin = -descent;
|
||||||
|
} else {
|
||||||
|
gutter_padding = px(0.0);
|
||||||
|
gutter_width = px(0.0);
|
||||||
|
gutter_margin = px(0.0);
|
||||||
|
};
|
||||||
|
|
||||||
|
let text_width = bounds.size.width - gutter_width;
|
||||||
|
let overscroll = point(em_width, px(0.));
|
||||||
|
let snapshot = {
|
||||||
|
editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
|
||||||
|
|
||||||
|
let editor_width = text_width - gutter_margin - overscroll.x - em_width;
|
||||||
|
let wrap_width = match editor.soft_wrap_mode(cx) {
|
||||||
|
SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
|
||||||
|
SoftWrap::EditorWidth => editor_width,
|
||||||
|
SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
|
||||||
|
};
|
||||||
|
|
||||||
|
if editor.set_wrap_width(Some(wrap_width), cx) {
|
||||||
|
editor.snapshot(cx)
|
||||||
|
} else {
|
||||||
|
snapshot
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let wrap_guides = editor
|
||||||
|
.wrap_guides(cx)
|
||||||
|
.iter()
|
||||||
|
.map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
|
||||||
|
.collect::<SmallVec<[_; 2]>>();
|
||||||
|
|
||||||
|
let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
|
||||||
|
// todo!("this should happen during layout")
|
||||||
|
if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
|
||||||
|
todo!()
|
||||||
|
// size.set_y(
|
||||||
|
// scroll_height
|
||||||
|
// .min(constraint.max_along(Axis::Vertical))
|
||||||
|
// .max(constraint.min_along(Axis::Vertical))
|
||||||
|
// .max(line_height)
|
||||||
|
// .min(line_height * max_lines as f32),
|
||||||
|
// )
|
||||||
|
} else if let EditorMode::SingleLine = snapshot.mode {
|
||||||
|
todo!()
|
||||||
|
// size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
|
||||||
|
}
|
||||||
|
// todo!()
|
||||||
|
// else if size.y().is_infinite() {
|
||||||
|
// // size.set_y(scroll_height);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
let gutter_size = size(gutter_width, bounds.size.height);
|
||||||
|
let text_size = size(text_width, bounds.size.height);
|
||||||
|
|
||||||
|
let autoscroll_horizontally =
|
||||||
|
editor.autoscroll_vertically(bounds.size.height, line_height, cx);
|
||||||
|
let mut snapshot = editor.snapshot(cx);
|
||||||
|
|
||||||
|
let scroll_position = snapshot.scroll_position();
|
||||||
|
// The scroll position is a fractional point, the whole number of which represents
|
||||||
|
// the top of the window in terms of display rows.
|
||||||
|
let start_row = scroll_position.y as u32;
|
||||||
|
let height_in_lines = f32::from(bounds.size.height / line_height);
|
||||||
|
let max_row = snapshot.max_point().row();
|
||||||
|
|
||||||
|
// Add 1 to ensure selections bleed off screen
|
||||||
|
let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
|
||||||
|
|
||||||
|
dbg!(start_row..end_row);
|
||||||
|
// let text_style = cx.text_style();
|
||||||
|
// let layout_text = cx.text_system().layout_text(
|
||||||
|
// "hello world",
|
||||||
|
// text_style.font_size * cx.rem_size(),
|
||||||
|
// &[text_style.to_run("hello world".len())],
|
||||||
|
// None,
|
||||||
|
// );
|
||||||
|
// let line_height = text_style
|
||||||
|
// .line_height
|
||||||
|
// .to_pixels(text_style.font_size.into(), cx.rem_size());
|
||||||
|
|
||||||
|
// layout_text.unwrap()[0]
|
||||||
|
// .paint(bounds.origin, line_height, cx)
|
||||||
|
// .unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -578,18 +578,24 @@ impl Item for Editor {
|
||||||
|
|
||||||
fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> {
|
fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
|
|
||||||
AnyElement::new(
|
AnyElement::new(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_row()
|
.flex_row()
|
||||||
.items_center()
|
.items_center()
|
||||||
.bg(gpui::white())
|
.gap_2()
|
||||||
.text_color(gpui::white())
|
|
||||||
.child(self.title(cx).to_string())
|
.child(self.title(cx).to_string())
|
||||||
.children(detail.and_then(|detail| {
|
.children(detail.and_then(|detail| {
|
||||||
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
|
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
|
||||||
let description = path.to_string_lossy();
|
let description = path.to_string_lossy();
|
||||||
Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN))
|
|
||||||
|
Some(
|
||||||
|
div()
|
||||||
|
.text_color(theme.colors().text_muted)
|
||||||
|
.text_xs()
|
||||||
|
.child(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)),
|
||||||
|
)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -625,8 +631,7 @@ impl Item for Editor {
|
||||||
|
|
||||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let selection = self.selections.newest_anchor();
|
let selection = self.selections.newest_anchor();
|
||||||
todo!()
|
self.push_to_nav_history(selection.head(), None, cx);
|
||||||
// self.push_to_nav_history(selection.head(), None, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
|
|
@ -303,20 +303,20 @@ impl Editor {
|
||||||
self.scroll_manager.visible_line_count
|
self.scroll_manager.visible_line_count
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
|
pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
|
||||||
// let opened_first_time = self.scroll_manager.visible_line_count.is_none();
|
let opened_first_time = self.scroll_manager.visible_line_count.is_none();
|
||||||
// self.scroll_manager.visible_line_count = Some(lines);
|
self.scroll_manager.visible_line_count = Some(lines);
|
||||||
// if opened_first_time {
|
if opened_first_time {
|
||||||
// cx.spawn(|editor, mut cx| async move {
|
cx.spawn(|editor, mut cx| async move {
|
||||||
// editor
|
editor
|
||||||
// .update(&mut cx, |editor, cx| {
|
.update(&mut cx, |editor, cx| {
|
||||||
// editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
|
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
|
||||||
// })
|
})
|
||||||
// .ok()
|
.ok()
|
||||||
// })
|
})
|
||||||
// .detach()
|
.detach()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
pub fn set_scroll_position(
|
pub fn set_scroll_position(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -48,11 +48,11 @@ impl AutoscrollStrategy {
|
||||||
impl Editor {
|
impl Editor {
|
||||||
pub fn autoscroll_vertically(
|
pub fn autoscroll_vertically(
|
||||||
&mut self,
|
&mut self,
|
||||||
viewport_height: f32,
|
viewport_height: Pixels,
|
||||||
line_height: f32,
|
line_height: Pixels,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let visible_lines = viewport_height / line_height;
|
let visible_lines = f32::from(viewport_height / line_height);
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
|
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
|
||||||
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ fn generate_dispatch_bindings() {
|
||||||
.header("src/platform/mac/dispatch.h")
|
.header("src/platform/mac/dispatch.h")
|
||||||
.allowlist_var("_dispatch_main_q")
|
.allowlist_var("_dispatch_main_q")
|
||||||
.allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
|
.allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
|
||||||
|
.allowlist_var("DISPATCH_TIME_NOW")
|
||||||
.allowlist_function("dispatch_get_global_queue")
|
.allowlist_function("dispatch_get_global_queue")
|
||||||
.allowlist_function("dispatch_async_f")
|
.allowlist_function("dispatch_async_f")
|
||||||
.allowlist_function("dispatch_after_f")
|
.allowlist_function("dispatch_after_f")
|
||||||
|
|
|
@ -161,6 +161,7 @@ pub struct AppContext {
|
||||||
flushing_effects: bool,
|
flushing_effects: bool,
|
||||||
pending_updates: usize,
|
pending_updates: usize,
|
||||||
pub(crate) active_drag: Option<AnyDrag>,
|
pub(crate) active_drag: Option<AnyDrag>,
|
||||||
|
pub(crate) active_tooltip: Option<AnyTooltip>,
|
||||||
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
||||||
pub(crate) frame_consumers: HashMap<DisplayId, Task<()>>,
|
pub(crate) frame_consumers: HashMap<DisplayId, Task<()>>,
|
||||||
pub(crate) background_executor: BackgroundExecutor,
|
pub(crate) background_executor: BackgroundExecutor,
|
||||||
|
@ -219,6 +220,7 @@ impl AppContext {
|
||||||
flushing_effects: false,
|
flushing_effects: false,
|
||||||
pending_updates: 0,
|
pending_updates: 0,
|
||||||
active_drag: None,
|
active_drag: None,
|
||||||
|
active_tooltip: None,
|
||||||
next_frame_callbacks: HashMap::default(),
|
next_frame_callbacks: HashMap::default(),
|
||||||
frame_consumers: HashMap::default(),
|
frame_consumers: HashMap::default(),
|
||||||
background_executor: executor,
|
background_executor: executor,
|
||||||
|
@ -900,3 +902,9 @@ pub(crate) struct AnyDrag {
|
||||||
pub view: AnyView,
|
pub view: AnyView,
|
||||||
pub cursor_offset: Point<Pixels>,
|
pub cursor_offset: Point<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct AnyTooltip {
|
||||||
|
pub view: AnyView,
|
||||||
|
pub cursor_offset: Point<Pixels>,
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
|
ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
|
||||||
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
|
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
|
||||||
Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
|
Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
|
||||||
StatelessInteractive, Style, StyleRefinement, Styled, ViewContext,
|
StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility,
|
||||||
};
|
};
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -249,11 +249,22 @@ where
|
||||||
cx: &mut ViewContext<V>,
|
cx: &mut ViewContext<V>,
|
||||||
) {
|
) {
|
||||||
self.with_element_id(cx, |this, _global_id, cx| {
|
self.with_element_id(cx, |this, _global_id, cx| {
|
||||||
|
let style = this.compute_style(bounds, element_state, cx);
|
||||||
|
if style.visibility == Visibility::Hidden {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mouse_cursor) = style.mouse_cursor {
|
||||||
|
let hovered = bounds.contains_point(&cx.mouse_position());
|
||||||
|
if hovered {
|
||||||
|
cx.set_cursor_style(mouse_cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(group) = this.group.clone() {
|
if let Some(group) = this.group.clone() {
|
||||||
GroupBounds::push(group, bounds, cx);
|
GroupBounds::push(group, bounds, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let style = this.compute_style(bounds, element_state, cx);
|
|
||||||
let z_index = style.z_index.unwrap_or(0);
|
let z_index = style.z_index.unwrap_or(0);
|
||||||
|
|
||||||
let mut child_min = point(Pixels::MAX, Pixels::MAX);
|
let mut child_min = point(Pixels::MAX, Pixels::MAX);
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + Debug + Default> Point<T> {
|
impl<T: Clone + Debug + Default> Point<T> {
|
||||||
pub fn new(x: T, y: T) -> Self {
|
pub const fn new(x: T, y: T) -> Self {
|
||||||
Self { x, y }
|
Self { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -825,6 +825,12 @@ impl From<Pixels> for u32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Pixels {
|
||||||
|
fn from(pixels: u32) -> Self {
|
||||||
|
Pixels(pixels as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Pixels> for usize {
|
impl From<Pixels> for usize {
|
||||||
fn from(pixels: Pixels) -> Self {
|
fn from(pixels: Pixels) -> Self {
|
||||||
pixels.0 as usize
|
pixels.0 as usize
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
div, point, px, Action, AnyDrag, AnyView, AppContext, BorrowWindow, Bounds, Component,
|
div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
|
||||||
DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, Keystroke,
|
Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
|
||||||
Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, View,
|
Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
|
||||||
ViewContext,
|
StyleRefinement, Task, View, ViewContext,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
@ -17,9 +17,12 @@ use std::{
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DRAG_THRESHOLD: f64 = 2.;
|
const DRAG_THRESHOLD: f64 = 2.;
|
||||||
|
const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
|
||||||
|
const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
|
||||||
|
|
||||||
pub trait StatelessInteractive<V: 'static>: Element<V> {
|
pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||||
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
|
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
|
||||||
|
@ -333,6 +336,37 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||||
}));
|
}));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
debug_assert!(
|
||||||
|
self.stateful_interaction().hover_listener.is_none(),
|
||||||
|
"calling on_hover more than once on the same element is not supported"
|
||||||
|
);
|
||||||
|
self.stateful_interaction().hover_listener = Some(Box::new(listener));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tooltip<W>(
|
||||||
|
mut self,
|
||||||
|
build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
W: 'static + Render,
|
||||||
|
{
|
||||||
|
debug_assert!(
|
||||||
|
self.stateful_interaction().tooltip_builder.is_none(),
|
||||||
|
"calling tooltip more than once on the same element is not supported"
|
||||||
|
);
|
||||||
|
self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| {
|
||||||
|
build_tooltip(view_state, cx).into()
|
||||||
|
}));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ElementInteraction<V: 'static>: 'static {
|
pub trait ElementInteraction<V: 'static>: 'static {
|
||||||
|
@ -568,6 +602,77 @@ pub trait ElementInteraction<V: 'static>: 'static {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(hover_listener) = stateful.hover_listener.take() {
|
||||||
|
let was_hovered = element_state.hover_state.clone();
|
||||||
|
let has_mouse_down = element_state.pending_mouse_down.clone();
|
||||||
|
|
||||||
|
cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
|
||||||
|
if phase != DispatchPhase::Bubble {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let is_hovered =
|
||||||
|
bounds.contains_point(&event.position) && has_mouse_down.lock().is_none();
|
||||||
|
let mut was_hovered = was_hovered.lock();
|
||||||
|
|
||||||
|
if is_hovered != was_hovered.clone() {
|
||||||
|
*was_hovered = is_hovered;
|
||||||
|
drop(was_hovered);
|
||||||
|
|
||||||
|
hover_listener(view_state, is_hovered, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tooltip_builder) = stateful.tooltip_builder.take() {
|
||||||
|
let active_tooltip = element_state.active_tooltip.clone();
|
||||||
|
let pending_mouse_down = element_state.pending_mouse_down.clone();
|
||||||
|
|
||||||
|
cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
|
||||||
|
if phase != DispatchPhase::Bubble {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_hovered = bounds.contains_point(&event.position)
|
||||||
|
&& pending_mouse_down.lock().is_none();
|
||||||
|
if !is_hovered {
|
||||||
|
active_tooltip.lock().take();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if active_tooltip.lock().is_none() {
|
||||||
|
let task = cx.spawn({
|
||||||
|
let active_tooltip = active_tooltip.clone();
|
||||||
|
let tooltip_builder = tooltip_builder.clone();
|
||||||
|
|
||||||
|
move |view, mut cx| async move {
|
||||||
|
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
||||||
|
view.update(&mut cx, move |view_state, cx| {
|
||||||
|
active_tooltip.lock().replace(ActiveTooltip {
|
||||||
|
waiting: None,
|
||||||
|
tooltip: Some(AnyTooltip {
|
||||||
|
view: tooltip_builder(view_state, cx),
|
||||||
|
cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
active_tooltip.lock().replace(ActiveTooltip {
|
||||||
|
waiting: Some(task),
|
||||||
|
tooltip: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() {
|
||||||
|
if active_tooltip.tooltip.is_some() {
|
||||||
|
cx.active_tooltip = active_tooltip.tooltip.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let active_state = element_state.active_state.clone();
|
let active_state = element_state.active_state.clone();
|
||||||
if active_state.lock().is_none() {
|
if active_state.lock().is_none() {
|
||||||
let active_group_bounds = stateful
|
let active_group_bounds = stateful
|
||||||
|
@ -639,6 +744,8 @@ pub struct StatefulInteraction<V> {
|
||||||
active_style: StyleRefinement,
|
active_style: StyleRefinement,
|
||||||
group_active_style: Option<GroupStyle>,
|
group_active_style: Option<GroupStyle>,
|
||||||
drag_listener: Option<DragListener<V>>,
|
drag_listener: Option<DragListener<V>>,
|
||||||
|
hover_listener: Option<HoverListener<V>>,
|
||||||
|
tooltip_builder: Option<TooltipBuilder<V>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> {
|
impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> {
|
||||||
|
@ -666,6 +773,8 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
|
||||||
stateless: StatelessInteraction::default(),
|
stateless: StatelessInteraction::default(),
|
||||||
click_listeners: SmallVec::new(),
|
click_listeners: SmallVec::new(),
|
||||||
drag_listener: None,
|
drag_listener: None,
|
||||||
|
hover_listener: None,
|
||||||
|
tooltip_builder: None,
|
||||||
active_style: StyleRefinement::default(),
|
active_style: StyleRefinement::default(),
|
||||||
group_active_style: None,
|
group_active_style: None,
|
||||||
}
|
}
|
||||||
|
@ -695,6 +804,8 @@ impl<V> StatelessInteraction<V> {
|
||||||
stateless: self,
|
stateless: self,
|
||||||
click_listeners: SmallVec::new(),
|
click_listeners: SmallVec::new(),
|
||||||
drag_listener: None,
|
drag_listener: None,
|
||||||
|
hover_listener: None,
|
||||||
|
tooltip_builder: None,
|
||||||
active_style: StyleRefinement::default(),
|
active_style: StyleRefinement::default(),
|
||||||
group_active_style: None,
|
group_active_style: None,
|
||||||
}
|
}
|
||||||
|
@ -746,8 +857,16 @@ impl ActiveState {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct InteractiveElementState {
|
pub struct InteractiveElementState {
|
||||||
active_state: Arc<Mutex<ActiveState>>,
|
active_state: Arc<Mutex<ActiveState>>,
|
||||||
|
hover_state: Arc<Mutex<bool>>,
|
||||||
pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
|
pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
|
||||||
scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
|
scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
|
||||||
|
active_tooltip: Arc<Mutex<Option<ActiveTooltip>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ActiveTooltip {
|
||||||
|
#[allow(unused)] // used to drop the task
|
||||||
|
waiting: Option<Task<()>>,
|
||||||
|
tooltip: Option<AnyTooltip>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InteractiveElementState {
|
impl InteractiveElementState {
|
||||||
|
@ -1097,6 +1216,10 @@ pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>)
|
||||||
pub(crate) type DragListener<V> =
|
pub(crate) type DragListener<V> =
|
||||||
Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
|
Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
|
||||||
|
|
||||||
|
pub(crate) type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
|
||||||
|
|
||||||
|
pub(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
|
||||||
|
|
||||||
pub type KeyListener<V> = Box<
|
pub type KeyListener<V> = Box<
|
||||||
dyn Fn(
|
dyn Fn(
|
||||||
&mut V,
|
&mut V,
|
||||||
|
|
|
@ -11,11 +11,7 @@ use objc::{
|
||||||
};
|
};
|
||||||
use parking::{Parker, Unparker};
|
use parking::{Parker, Unparker};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{ffi::c_void, sync::Arc, time::Duration};
|
||||||
ffi::c_void,
|
|
||||||
sync::Arc,
|
|
||||||
time::{Duration, SystemTime},
|
|
||||||
};
|
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
|
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
|
||||||
|
|
||||||
|
@ -62,16 +58,10 @@ impl PlatformDispatcher for MacDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
||||||
let now = SystemTime::now();
|
|
||||||
let after_duration = now
|
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_nanos() as u64
|
|
||||||
+ duration.as_nanos() as u64;
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let queue =
|
let queue =
|
||||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
|
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
|
||||||
let when = dispatch_time(0, after_duration as i64);
|
let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
|
||||||
dispatch_after_f(
|
dispatch_after_f(
|
||||||
when,
|
when,
|
||||||
queue,
|
queue,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
||||||
Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures,
|
Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
|
||||||
FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, Rgba,
|
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems,
|
||||||
SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
|
Result, Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
|
||||||
};
|
};
|
||||||
use refineable::{Cascade, Refineable};
|
use refineable::{Cascade, Refineable};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -19,6 +19,9 @@ pub struct Style {
|
||||||
/// What layout strategy should be used?
|
/// What layout strategy should be used?
|
||||||
pub display: Display,
|
pub display: Display,
|
||||||
|
|
||||||
|
/// Should the element be painted on screen?
|
||||||
|
pub visibility: Visibility,
|
||||||
|
|
||||||
// Overflow properties
|
// Overflow properties
|
||||||
/// How children overflowing their container should affect layout
|
/// How children overflowing their container should affect layout
|
||||||
#[refineable]
|
#[refineable]
|
||||||
|
@ -98,6 +101,9 @@ pub struct Style {
|
||||||
/// TEXT
|
/// TEXT
|
||||||
pub text: TextStyleRefinement,
|
pub text: TextStyleRefinement,
|
||||||
|
|
||||||
|
/// The mouse cursor style shown when the mouse pointer is over an element.
|
||||||
|
pub mouse_cursor: Option<CursorStyle>,
|
||||||
|
|
||||||
pub z_index: Option<u32>,
|
pub z_index: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +113,13 @@ impl Styled for StyleRefinement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Visibility {
|
||||||
|
#[default]
|
||||||
|
Visible,
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BoxShadow {
|
pub struct BoxShadow {
|
||||||
pub color: Hsla,
|
pub color: Hsla,
|
||||||
|
@ -297,6 +310,7 @@ impl Default for Style {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Style {
|
Style {
|
||||||
display: Display::Block,
|
display: Display::Block,
|
||||||
|
visibility: Visibility::Visible,
|
||||||
overflow: Point {
|
overflow: Point {
|
||||||
x: Overflow::Visible,
|
x: Overflow::Visible,
|
||||||
y: Overflow::Visible,
|
y: Overflow::Visible,
|
||||||
|
@ -328,6 +342,7 @@ impl Default for Style {
|
||||||
corner_radii: Corners::default(),
|
corner_radii: Corners::default(),
|
||||||
box_shadow: Default::default(),
|
box_shadow: Default::default(),
|
||||||
text: TextStyleRefinement::default(),
|
text: TextStyleRefinement::default(),
|
||||||
|
mouse_cursor: None,
|
||||||
z_index: None,
|
z_index: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
self as gpui2, hsla, point, px, relative, rems, AlignItems, DefiniteLength, Display, Fill,
|
self as gpui2, hsla, point, px, relative, rems, AlignItems, CursorStyle, DefiniteLength,
|
||||||
FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString, StyleRefinement,
|
Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString,
|
||||||
|
StyleRefinement, Visibility,
|
||||||
};
|
};
|
||||||
use crate::{BoxShadow, TextStyleRefinement};
|
use crate::{BoxShadow, TextStyleRefinement};
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
|
@ -60,6 +61,54 @@ pub trait Styled {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the visibility of the element to `visible`.
|
||||||
|
/// [Docs](https://tailwindcss.com/docs/visibility)
|
||||||
|
fn visible(mut self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.style().visibility = Some(Visibility::Visible);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the visibility of the element to `hidden`.
|
||||||
|
/// [Docs](https://tailwindcss.com/docs/visibility)
|
||||||
|
fn invisible(mut self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.style().visibility = Some(Visibility::Hidden);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor(mut self, cursor: CursorStyle) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.style().mouse_cursor = Some(cursor);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the cursor style when hovering an element to `default`.
|
||||||
|
/// [Docs](https://tailwindcss.com/docs/cursor)
|
||||||
|
fn cursor_default(mut self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.style().mouse_cursor = Some(CursorStyle::Arrow);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the cursor style when hovering an element to `pointer`.
|
||||||
|
/// [Docs](https://tailwindcss.com/docs/cursor)
|
||||||
|
fn cursor_pointer(mut self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.style().mouse_cursor = Some(CursorStyle::PointingHand);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the flex direction of the element to `column`.
|
/// Sets the flex direction of the element to `column`.
|
||||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#column)
|
/// [Docs](https://tailwindcss.com/docs/flex-direction#column)
|
||||||
fn flex_col(mut self) -> Self
|
fn flex_col(mut self) -> Self
|
||||||
|
|
|
@ -14,7 +14,7 @@ impl<EmitterKey, Callback> Clone for SubscriberSet<EmitterKey, Callback> {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SubscriberSetState<EmitterKey, Callback> {
|
struct SubscriberSetState<EmitterKey, Callback> {
|
||||||
subscribers: BTreeMap<EmitterKey, BTreeMap<usize, Callback>>,
|
subscribers: BTreeMap<EmitterKey, Option<BTreeMap<usize, Callback>>>,
|
||||||
dropped_subscribers: BTreeSet<(EmitterKey, usize)>,
|
dropped_subscribers: BTreeSet<(EmitterKey, usize)>,
|
||||||
next_subscriber_id: usize,
|
next_subscriber_id: usize,
|
||||||
}
|
}
|
||||||
|
@ -38,12 +38,18 @@ where
|
||||||
lock.subscribers
|
lock.subscribers
|
||||||
.entry(emitter_key.clone())
|
.entry(emitter_key.clone())
|
||||||
.or_default()
|
.or_default()
|
||||||
|
.get_or_insert_with(|| Default::default())
|
||||||
.insert(subscriber_id, callback);
|
.insert(subscriber_id, callback);
|
||||||
let this = self.0.clone();
|
let this = self.0.clone();
|
||||||
Subscription {
|
Subscription {
|
||||||
unsubscribe: Some(Box::new(move || {
|
unsubscribe: Some(Box::new(move || {
|
||||||
let mut lock = this.lock();
|
let mut lock = this.lock();
|
||||||
if let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) {
|
let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) else {
|
||||||
|
// remove was called with this emitter_key
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(subscribers) = subscribers {
|
||||||
subscribers.remove(&subscriber_id);
|
subscribers.remove(&subscriber_id);
|
||||||
if subscribers.is_empty() {
|
if subscribers.is_empty() {
|
||||||
lock.subscribers.remove(&emitter_key);
|
lock.subscribers.remove(&emitter_key);
|
||||||
|
@ -62,34 +68,43 @@ where
|
||||||
|
|
||||||
pub fn remove(&self, emitter: &EmitterKey) -> impl IntoIterator<Item = Callback> {
|
pub fn remove(&self, emitter: &EmitterKey) -> impl IntoIterator<Item = Callback> {
|
||||||
let subscribers = self.0.lock().subscribers.remove(&emitter);
|
let subscribers = self.0.lock().subscribers.remove(&emitter);
|
||||||
subscribers.unwrap_or_default().into_values()
|
subscribers
|
||||||
|
.unwrap_or_default()
|
||||||
|
.map(|s| s.into_values())
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
|
pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&mut Callback) -> bool,
|
F: FnMut(&mut Callback) -> bool,
|
||||||
{
|
{
|
||||||
let entry = self.0.lock().subscribers.remove_entry(emitter);
|
let Some(mut subscribers) = self
|
||||||
if let Some((emitter, mut subscribers)) = entry {
|
.0
|
||||||
subscribers.retain(|_, callback| f(callback));
|
.lock()
|
||||||
let mut lock = self.0.lock();
|
.subscribers
|
||||||
|
.get_mut(emitter)
|
||||||
|
.and_then(|s| s.take())
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// Add any new subscribers that were added while invoking the callback.
|
subscribers.retain(|_, callback| f(callback));
|
||||||
if let Some(new_subscribers) = lock.subscribers.remove(&emitter) {
|
let mut lock = self.0.lock();
|
||||||
subscribers.extend(new_subscribers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any dropped subscriptions that were dropped while invoking the callback.
|
// Add any new subscribers that were added while invoking the callback.
|
||||||
for (dropped_emitter, dropped_subscription_id) in
|
if let Some(Some(new_subscribers)) = lock.subscribers.remove(&emitter) {
|
||||||
mem::take(&mut lock.dropped_subscribers)
|
subscribers.extend(new_subscribers);
|
||||||
{
|
}
|
||||||
debug_assert_eq!(emitter, dropped_emitter);
|
|
||||||
subscribers.remove(&dropped_subscription_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !subscribers.is_empty() {
|
// Remove any dropped subscriptions that were dropped while invoking the callback.
|
||||||
lock.subscribers.insert(emitter, subscribers);
|
for (dropped_emitter, dropped_subscription_id) in mem::take(&mut lock.dropped_subscribers) {
|
||||||
}
|
debug_assert_eq!(*emitter, dropped_emitter);
|
||||||
|
subscribers.remove(&dropped_subscription_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !subscribers.is_empty() {
|
||||||
|
lock.subscribers.insert(emitter.clone(), Some(subscribers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
||||||
Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
|
Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId,
|
||||||
Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId,
|
Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
|
||||||
Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
|
GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
|
||||||
Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
|
KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
|
||||||
MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point,
|
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay,
|
||||||
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
|
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
|
||||||
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
|
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
|
||||||
Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
|
Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
|
||||||
WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -190,6 +190,7 @@ pub struct Window {
|
||||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||||
default_prevented: bool,
|
default_prevented: bool,
|
||||||
mouse_position: Point<Pixels>,
|
mouse_position: Point<Pixels>,
|
||||||
|
requested_cursor_style: Option<CursorStyle>,
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
bounds: WindowBounds,
|
bounds: WindowBounds,
|
||||||
bounds_observers: SubscriberSet<(), AnyObserver>,
|
bounds_observers: SubscriberSet<(), AnyObserver>,
|
||||||
|
@ -283,6 +284,7 @@ impl Window {
|
||||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||||
default_prevented: true,
|
default_prevented: true,
|
||||||
mouse_position,
|
mouse_position,
|
||||||
|
requested_cursor_style: None,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
bounds,
|
bounds,
|
||||||
bounds_observers: SubscriberSet::new(),
|
bounds_observers: SubscriberSet::new(),
|
||||||
|
@ -669,6 +671,10 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window.mouse_position
|
self.window.mouse_position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_cursor_style(&mut self, style: CursorStyle) {
|
||||||
|
self.window.requested_cursor_style = Some(style)
|
||||||
|
}
|
||||||
|
|
||||||
/// Called during painting to invoke the given closure in a new stacking context. The given
|
/// Called during painting to invoke the given closure in a new stacking context. The given
|
||||||
/// z-index is interpreted relative to the previous call to `stack`.
|
/// z-index is interpreted relative to the previous call to `stack`.
|
||||||
pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
|
@ -981,12 +987,27 @@ impl<'a> WindowContext<'a> {
|
||||||
cx.active_drag = Some(active_drag);
|
cx.active_drag = Some(active_drag);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else if let Some(active_tooltip) = self.app.active_tooltip.take() {
|
||||||
|
self.stack(1, |cx| {
|
||||||
|
cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| {
|
||||||
|
let available_space =
|
||||||
|
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||||
|
active_tooltip.view.draw(available_space, cx);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window.root_view = Some(root_view);
|
self.window.root_view = Some(root_view);
|
||||||
let scene = self.window.scene_builder.build();
|
let scene = self.window.scene_builder.build();
|
||||||
|
|
||||||
self.window.platform_window.draw(scene);
|
self.window.platform_window.draw(scene);
|
||||||
|
let cursor_style = self
|
||||||
|
.window
|
||||||
|
.requested_cursor_style
|
||||||
|
.take()
|
||||||
|
.unwrap_or(CursorStyle::Arrow);
|
||||||
|
self.platform.set_cursor_style(cursor_style);
|
||||||
|
|
||||||
self.window.dirty = false;
|
self.window.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ mod tab;
|
||||||
mod toast;
|
mod toast;
|
||||||
mod toggle;
|
mod toggle;
|
||||||
mod tool_divider;
|
mod tool_divider;
|
||||||
|
mod tooltip;
|
||||||
|
|
||||||
pub use avatar::*;
|
pub use avatar::*;
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
|
@ -49,3 +50,4 @@ pub use tab::*;
|
||||||
pub use toast::*;
|
pub use toast::*;
|
||||||
pub use toggle::*;
|
pub use toggle::*;
|
||||||
pub use tool_divider::*;
|
pub use tool_divider::*;
|
||||||
|
pub use tooltip::*;
|
||||||
|
|
|
@ -186,7 +186,6 @@ impl IconElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||||
let fill = self.color.color(cx);
|
|
||||||
let svg_size = match self.size {
|
let svg_size = match self.size {
|
||||||
IconSize::Small => rems(0.75),
|
IconSize::Small => rems(0.75),
|
||||||
IconSize::Medium => rems(0.9375),
|
IconSize::Medium => rems(0.9375),
|
||||||
|
@ -196,7 +195,7 @@ impl IconElement {
|
||||||
.size(svg_size)
|
.size(svg_size)
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.path(self.icon.path())
|
.path(self.icon.path())
|
||||||
.text_color(fill)
|
.text_color(self.color.color(cx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
crates/ui2/src/components/tooltip.rs
Normal file
31
crates/ui2/src/components/tooltip.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use gpui2::{div, px, Div, ParentElement, Render, SharedString, Styled, ViewContext};
|
||||||
|
use theme2::ActiveTheme;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TextTooltip {
|
||||||
|
title: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextTooltip {
|
||||||
|
pub fn new(str: SharedString) -> Self {
|
||||||
|
Self { title: str }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for TextTooltip {
|
||||||
|
type Element = Div<Self>;
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
let theme = cx.theme();
|
||||||
|
div()
|
||||||
|
.bg(theme.colors().background)
|
||||||
|
.rounded(px(8.))
|
||||||
|
.border()
|
||||||
|
.font("Zed Sans")
|
||||||
|
.border_color(theme.colors().border)
|
||||||
|
.text_color(theme.colors().text)
|
||||||
|
.pl_2()
|
||||||
|
.pr_2()
|
||||||
|
.child(self.title.clone())
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use ui::v_stack;
|
use ui::v_stack;
|
||||||
use ui::{prelude::*, Icon, IconButton, IconColor, IconElement};
|
use ui::{prelude::*, Icon, IconButton, IconColor, IconElement, TextTooltip};
|
||||||
use util::truncate_and_remove_front;
|
use util::truncate_and_remove_front;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
|
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
|
||||||
|
@ -1359,15 +1359,30 @@ impl Pane {
|
||||||
cx: &mut ViewContext<'_, Pane>,
|
cx: &mut ViewContext<'_, Pane>,
|
||||||
) -> impl Component<Self> {
|
) -> impl Component<Self> {
|
||||||
let label = item.tab_content(Some(detail), cx);
|
let label = item.tab_content(Some(detail), cx);
|
||||||
let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted);
|
let close_icon = || {
|
||||||
|
let id = item.id();
|
||||||
|
|
||||||
let (tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
|
div()
|
||||||
|
.id(item.id())
|
||||||
|
.invisible()
|
||||||
|
.group_hover("", |style| style.visible())
|
||||||
|
.child(IconButton::new("close_tab", Icon::Close).on_click(
|
||||||
|
move |pane: &mut Self, cx| {
|
||||||
|
pane.close_item_by_id(id, SaveIntent::Close, cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index {
|
||||||
false => (
|
false => (
|
||||||
|
cx.theme().colors().text_muted,
|
||||||
cx.theme().colors().tab_inactive_background,
|
cx.theme().colors().tab_inactive_background,
|
||||||
cx.theme().colors().ghost_element_hover,
|
cx.theme().colors().ghost_element_hover,
|
||||||
cx.theme().colors().ghost_element_active,
|
cx.theme().colors().ghost_element_active,
|
||||||
),
|
),
|
||||||
true => (
|
true => (
|
||||||
|
cx.theme().colors().text,
|
||||||
cx.theme().colors().tab_active_background,
|
cx.theme().colors().tab_active_background,
|
||||||
cx.theme().colors().element_hover,
|
cx.theme().colors().element_hover,
|
||||||
cx.theme().colors().element_active,
|
cx.theme().colors().element_active,
|
||||||
|
@ -1377,7 +1392,12 @@ impl Pane {
|
||||||
let close_right = ItemSettings::get_global(cx).close_position.right();
|
let close_right = ItemSettings::get_global(cx).close_position.right();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.group("")
|
||||||
.id(item.id())
|
.id(item.id())
|
||||||
|
.cursor_pointer()
|
||||||
|
.when_some(item.tab_tooltip_text(cx), |div, text| {
|
||||||
|
div.tooltip(move |_, cx| cx.build_view(|cx| TextTooltip::new(text.clone())))
|
||||||
|
})
|
||||||
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
|
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
|
||||||
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
|
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
|
||||||
// .on_drop(|_view, state: View<DraggedTab>, cx| {
|
// .on_drop(|_view, state: View<DraggedTab>, cx| {
|
||||||
|
@ -1397,6 +1417,7 @@ impl Pane {
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
|
.text_color(text_color)
|
||||||
.children(if item.has_conflict(cx) {
|
.children(if item.has_conflict(cx) {
|
||||||
Some(
|
Some(
|
||||||
IconElement::new(Icon::ExclamationTriangle)
|
IconElement::new(Icon::ExclamationTriangle)
|
||||||
|
@ -1457,7 +1478,7 @@ impl Pane {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div().w_0().flex_1().h_full().child(
|
div().flex_1().h_full().child(
|
||||||
div().id("tabs").flex().overflow_x_scroll().children(
|
div().id("tabs").flex().overflow_x_scroll().children(
|
||||||
self.items
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -1888,13 +1909,14 @@ impl Render for Pane {
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
v_stack()
|
v_stack()
|
||||||
|
.size_full()
|
||||||
.child(self.render_tab_bar(cx))
|
.child(self.render_tab_bar(cx))
|
||||||
.child(div() /* toolbar */)
|
.child(div() /* todo!(toolbar) */)
|
||||||
.child(if let Some(item) = self.active_item() {
|
.child(if let Some(item) = self.active_item() {
|
||||||
item.to_any().render()
|
div().flex_1().child(item.to_any())
|
||||||
} else {
|
} else {
|
||||||
// todo!()
|
// todo!()
|
||||||
div().child("Empty Pane").render()
|
div().child("Empty Pane")
|
||||||
})
|
})
|
||||||
|
|
||||||
// enum MouseNavigationHandler {}
|
// enum MouseNavigationHandler {}
|
||||||
|
|
|
@ -201,7 +201,7 @@ impl Member {
|
||||||
// Some(pane)
|
// Some(pane)
|
||||||
// };
|
// };
|
||||||
|
|
||||||
div().child(pane.clone()).render()
|
div().size_full().child(pane.clone()).render()
|
||||||
|
|
||||||
// Stack::new()
|
// Stack::new()
|
||||||
// .with_child(pane_element.contained().with_border(leader_border))
|
// .with_child(pane_element.contained().with_border(leader_border))
|
||||||
|
|
|
@ -44,6 +44,7 @@ impl Render for StatusBar {
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
.h_8()
|
||||||
.bg(cx.theme().colors().status_bar_background)
|
.bg(cx.theme().colors().status_bar_background)
|
||||||
.child(self.render_left_tools(cx))
|
.child(self.render_left_tools(cx))
|
||||||
.child(self.render_right_tools(cx))
|
.child(self.render_right_tools(cx))
|
||||||
|
|
|
@ -208,7 +208,6 @@ fn main() {
|
||||||
if stdout_is_a_pty() {
|
if stdout_is_a_pty() {
|
||||||
cx.activate(true);
|
cx.activate(true);
|
||||||
let urls = collect_url_args();
|
let urls = collect_url_args();
|
||||||
dbg!(&urls);
|
|
||||||
if !urls.is_empty() {
|
if !urls.is_empty() {
|
||||||
listener.open_urls(urls)
|
listener.open_urls(urls)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue