Fix flickering (#9012)
See https://zed.dev/channel/gpui-536 Fixes https://github.com/zed-industries/zed/issues/9010 Fixes https://github.com/zed-industries/zed/issues/8883 Fixes https://github.com/zed-industries/zed/issues/8640 Fixes https://github.com/zed-industries/zed/issues/8598 Fixes https://github.com/zed-industries/zed/issues/8579 Fixes https://github.com/zed-industries/zed/issues/8363 Fixes https://github.com/zed-industries/zed/issues/8207 ### Problem After transitioning Zed to GPUI 2, we started noticing that interacting with the mouse on many UI elements would lead to a pretty annoying flicker. The main issue with the old approach was that hover state was calculated based on the previous frame. That is, when computing whether a given element was hovered in the current frame, we would use information about the same element in the previous frame. However, inspecting the previous frame tells us very little about what should be hovered in the current frame, as elements in the current frame may have changed significantly. ### Solution This pull request's main contribution is the introduction of a new `after_layout` phase when redrawing the window. The key idea is that we'll give every element a chance to register a hitbox (see `ElementContext::insert_hitbox`) before painting anything. Then, during the `paint` phase, elements can determine whether they're the topmost and draw their hover state accordingly. We are also removing the ability to give an arbitrary z-index to elements. Instead, we will follow the much simpler painter's algorithm. That is, an element that gets painted after will be drawn on top of an element that got painted earlier. Elements can still escape their current "stacking context" by using the new `ElementContext::defer_draw` method (see `Overlay` for an example). Elements drawn using this method will still be logically considered as being children of their original parent (for keybinding, focus and cache invalidation purposes) but their layout and paint passes will be deferred until the currently-drawn element is done. With these changes we also reworked geometry batching within the `Scene`. The new approach uses an AABB tree to determine geometry occlusion, which allows the GPU to render non-overlapping geometry in parallel. ### Performance Performance is slightly better than on `main` even though this new approach is more correct and we're maintaining an extra data structure (the AABB tree).  Release Notes: - Fixed a bug that was causing popovers to flicker. --------- Co-authored-by: Nathan Sobo <nathan@zed.dev> Co-authored-by: Thorsten <thorsten@zed.dev>
This commit is contained in:
parent
9afd78b35e
commit
4700d33728
74 changed files with 6434 additions and 6301 deletions
|
@ -31,9 +31,9 @@ use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext,
|
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext,
|
||||||
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter,
|
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context, EventEmitter, FocusHandle,
|
||||||
FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement,
|
FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model,
|
||||||
IntoElement, Model, ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
|
ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
|
||||||
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
|
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
|
||||||
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
|
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
|
||||||
};
|
};
|
||||||
|
@ -1284,8 +1284,9 @@ impl Render for AssistantPanel {
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
let scroll_handle = self.saved_conversations_scroll_handle.clone();
|
let scroll_handle = self.saved_conversations_scroll_handle.clone();
|
||||||
let conversation_count = self.saved_conversations.len();
|
let conversation_count = self.saved_conversations.len();
|
||||||
canvas(move |bounds, cx| {
|
canvas(
|
||||||
uniform_list(
|
move |bounds, cx| {
|
||||||
|
let mut list = uniform_list(
|
||||||
view,
|
view,
|
||||||
"saved_conversations",
|
"saved_conversations",
|
||||||
conversation_count,
|
conversation_count,
|
||||||
|
@ -1296,13 +1297,12 @@ impl Render for AssistantPanel {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.track_scroll(scroll_handle)
|
.track_scroll(scroll_handle)
|
||||||
.into_any_element()
|
.into_any_element();
|
||||||
.draw(
|
list.layout(bounds.origin, bounds.size.into(), cx);
|
||||||
bounds.origin,
|
list
|
||||||
bounds.size.map(AvailableSpace::Definite),
|
},
|
||||||
cx,
|
|_bounds, mut list, cx| list.paint(cx),
|
||||||
);
|
)
|
||||||
})
|
|
||||||
.size_full()
|
.size_full()
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -156,7 +156,7 @@ mod linux {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows {
|
mod windows {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
|
@ -132,7 +132,7 @@ async fn main() -> Result<()> {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow!(e))?;
|
.map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
|
@ -600,11 +600,9 @@ impl ChatPanel {
|
||||||
) -> Div {
|
) -> Div {
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.z_index(1)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.z_index(1)
|
|
||||||
.right_8()
|
.right_8()
|
||||||
.w_6()
|
.w_6()
|
||||||
.rounded_tl_md()
|
.rounded_tl_md()
|
||||||
|
@ -638,7 +636,6 @@ impl ChatPanel {
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.z_index(1)
|
|
||||||
.right_2()
|
.right_2()
|
||||||
.w_6()
|
.w_6()
|
||||||
.rounded_tr_md()
|
.rounded_tr_md()
|
||||||
|
@ -855,7 +852,7 @@ impl Render for ChatPanel {
|
||||||
.size_full()
|
.size_full()
|
||||||
.on_action(cx.listener(Self::send))
|
.on_action(cx.listener(Self::send))
|
||||||
.child(
|
.child(
|
||||||
h_flex().z_index(1).child(
|
h_flex().child(
|
||||||
TabBar::new("chat_header").child(
|
TabBar::new("chat_header").child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
|
|
@ -989,7 +989,6 @@ impl CollabPanel {
|
||||||
.children(has_channel_buffer_changed.then(|| {
|
.children(has_channel_buffer_changed.then(|| {
|
||||||
div()
|
div()
|
||||||
.w_1p5()
|
.w_1p5()
|
||||||
.z_index(1)
|
|
||||||
.absolute()
|
.absolute()
|
||||||
.right(px(2.))
|
.right(px(2.))
|
||||||
.top(px(2.))
|
.top(px(2.))
|
||||||
|
@ -1022,7 +1021,6 @@ impl CollabPanel {
|
||||||
.children(has_messages_notification.then(|| {
|
.children(has_messages_notification.then(|| {
|
||||||
div()
|
div()
|
||||||
.w_1p5()
|
.w_1p5()
|
||||||
.z_index(1)
|
|
||||||
.absolute()
|
.absolute()
|
||||||
.right(px(2.))
|
.right(px(2.))
|
||||||
.top(px(4.))
|
.top(px(4.))
|
||||||
|
@ -2617,7 +2615,6 @@ impl CollabPanel {
|
||||||
.children(has_notes_notification.then(|| {
|
.children(has_notes_notification.then(|| {
|
||||||
div()
|
div()
|
||||||
.w_1p5()
|
.w_1p5()
|
||||||
.z_index(1)
|
|
||||||
.absolute()
|
.absolute()
|
||||||
.right(px(-1.))
|
.right(px(-1.))
|
||||||
.top(px(-1.))
|
.top(px(-1.))
|
||||||
|
@ -2632,12 +2629,7 @@ impl CollabPanel {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex().absolute().right(rems(0.)).h_full().child(
|
||||||
.absolute()
|
|
||||||
.right(rems(0.))
|
|
||||||
.z_index(1)
|
|
||||||
.h_full()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.h_full()
|
.h_full()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
@ -2720,7 +2712,9 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) ->
|
||||||
let thickness = px(1.);
|
let thickness = px(1.);
|
||||||
let color = cx.theme().colors().text;
|
let color = cx.theme().colors().text;
|
||||||
|
|
||||||
canvas(move |bounds, cx| {
|
canvas(
|
||||||
|
|_, _| {},
|
||||||
|
move |bounds, _, cx| {
|
||||||
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
|
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
|
||||||
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
|
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
|
||||||
let right = bounds.right();
|
let right = bounds.right();
|
||||||
|
@ -2744,7 +2738,8 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) ->
|
||||||
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
|
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
|
||||||
color,
|
color,
|
||||||
));
|
));
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.w(width)
|
.w(width)
|
||||||
.h(line_height)
|
.h(line_height)
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,8 +329,10 @@ impl Render for CollabTitlebarItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_color_ribbon(color: Hsla) -> gpui::Canvas {
|
fn render_color_ribbon(color: Hsla) -> impl Element {
|
||||||
canvas(move |bounds, cx| {
|
canvas(
|
||||||
|
move |_, _| {},
|
||||||
|
move |bounds, _, cx| {
|
||||||
let height = bounds.size.height;
|
let height = bounds.size.height;
|
||||||
let horizontal_offset = height;
|
let horizontal_offset = height;
|
||||||
let vertical_offset = px(height.0 / 2.0);
|
let vertical_offset = px(height.0 / 2.0);
|
||||||
|
@ -346,7 +348,8 @@ fn render_color_ribbon(color: Hsla) -> gpui::Canvas {
|
||||||
);
|
);
|
||||||
path.line_to(bounds.lower_left());
|
path.line_to(bounds.lower_left());
|
||||||
cx.paint_path(path, color);
|
cx.paint_path(path, color);
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.h_1()
|
.h_1()
|
||||||
.w_full()
|
.w_full()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,25 +14,25 @@ impl FacePile {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
|
pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
|
||||||
Self {
|
Self { base: div(), faces }
|
||||||
base: h_flex(),
|
|
||||||
faces,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for FacePile {
|
impl RenderOnce for FacePile {
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||||
let player_count = self.faces.len();
|
// Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
|
||||||
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
|
self.base
|
||||||
let isnt_last = ix < player_count - 1;
|
.flex()
|
||||||
|
.flex_row_reverse()
|
||||||
div()
|
.items_center()
|
||||||
.z_index((player_count - ix) as u16)
|
.justify_start()
|
||||||
.when(isnt_last, |div| div.neg_mr_1())
|
.children(
|
||||||
.child(player)
|
self.faces
|
||||||
});
|
.into_iter()
|
||||||
self.base.children(player_list)
|
.enumerate()
|
||||||
|
.rev()
|
||||||
|
.map(|(ix, player)| div().when(ix > 0, |div| div.neg_ml_1()).child(player)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -894,7 +894,7 @@ mod tests {
|
||||||
display_map::{BlockContext, TransformBlock},
|
display_map::{BlockContext, TransformBlock},
|
||||||
DisplayPoint, GutterDimensions,
|
DisplayPoint, GutterDimensions,
|
||||||
};
|
};
|
||||||
use gpui::{px, TestAppContext, VisualTestContext, WindowContext};
|
use gpui::{px, Stateful, TestAppContext, VisualTestContext, WindowContext};
|
||||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
||||||
use project::FakeFs;
|
use project::FakeFs;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -1600,8 +1600,7 @@ mod tests {
|
||||||
let name: SharedString = match block {
|
let name: SharedString = match block {
|
||||||
TransformBlock::Custom(block) => cx.with_element_context({
|
TransformBlock::Custom(block) => cx.with_element_context({
|
||||||
|cx| -> Option<SharedString> {
|
|cx| -> Option<SharedString> {
|
||||||
block
|
let mut element = block.render(&mut BlockContext {
|
||||||
.render(&mut BlockContext {
|
|
||||||
context: cx,
|
context: cx,
|
||||||
anchor_x: px(0.),
|
anchor_x: px(0.),
|
||||||
gutter_dimensions: &GutterDimensions::default(),
|
gutter_dimensions: &GutterDimensions::default(),
|
||||||
|
@ -1610,10 +1609,9 @@ mod tests {
|
||||||
max_width: px(0.),
|
max_width: px(0.),
|
||||||
block_id: ix,
|
block_id: ix,
|
||||||
editor_style: &editor::EditorStyle::default(),
|
editor_style: &editor::EditorStyle::default(),
|
||||||
})
|
});
|
||||||
.inner_id()?
|
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
|
||||||
.try_into()
|
element.interactivity().element_id.clone()?.try_into().ok()
|
||||||
.ok()
|
|
||||||
}
|
}
|
||||||
})?,
|
})?,
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub use block_map::{
|
||||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
|
BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::fold_map::{Fold, FoldPoint};
|
pub use self::fold_map::{Fold, FoldId, FoldPoint};
|
||||||
pub use self::inlay_map::{InlayOffset, InlayPoint};
|
pub use self::inlay_map::{InlayOffset, InlayPoint};
|
||||||
pub(crate) use inlay_map::Inlay;
|
pub(crate) use inlay_map::Inlay;
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,9 @@ pub use display_map::DisplayPoint;
|
||||||
use display_map::*;
|
use display_map::*;
|
||||||
pub use editor_settings::EditorSettings;
|
pub use editor_settings::EditorSettings;
|
||||||
use element::LineWithInvisibles;
|
use element::LineWithInvisibles;
|
||||||
pub use element::{Cursor, EditorElement, HighlightedRange, HighlightedRangeLine};
|
pub use element::{
|
||||||
|
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
|
||||||
|
};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use git::diff_hunk_to_display;
|
use git::diff_hunk_to_display;
|
||||||
|
@ -4202,14 +4204,14 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_fold_indicators(
|
pub fn render_fold_indicators(
|
||||||
&self,
|
&mut self,
|
||||||
fold_data: Vec<Option<(FoldStatus, u32, bool)>>,
|
fold_data: Vec<Option<(FoldStatus, u32, bool)>>,
|
||||||
_style: &EditorStyle,
|
_style: &EditorStyle,
|
||||||
gutter_hovered: bool,
|
gutter_hovered: bool,
|
||||||
_line_height: Pixels,
|
_line_height: Pixels,
|
||||||
_gutter_margin: Pixels,
|
_gutter_margin: Pixels,
|
||||||
editor_view: View<Editor>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Vec<Option<IconButton>> {
|
) -> Vec<Option<AnyElement>> {
|
||||||
fold_data
|
fold_data
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
@ -4218,24 +4220,20 @@ impl Editor {
|
||||||
.map(|(fold_status, buffer_row, active)| {
|
.map(|(fold_status, buffer_row, active)| {
|
||||||
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
|
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
|
||||||
IconButton::new(ix, ui::IconName::ChevronDown)
|
IconButton::new(ix, ui::IconName::ChevronDown)
|
||||||
.on_click({
|
.on_click(cx.listener(move |this, _e, cx| match fold_status {
|
||||||
let view = editor_view.clone();
|
|
||||||
move |_e, cx| {
|
|
||||||
view.update(cx, |editor, cx| match fold_status {
|
|
||||||
FoldStatus::Folded => {
|
FoldStatus::Folded => {
|
||||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
this.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||||
}
|
}
|
||||||
FoldStatus::Foldable => {
|
FoldStatus::Foldable => {
|
||||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
this.fold_at(&FoldAt { buffer_row }, cx);
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
|
||||||
})
|
|
||||||
.icon_color(ui::Color::Muted)
|
.icon_color(ui::Color::Muted)
|
||||||
.icon_size(ui::IconSize::Small)
|
.icon_size(ui::IconSize::Small)
|
||||||
.selected(fold_status == FoldStatus::Folded)
|
.selected(fold_status == FoldStatus::Folded)
|
||||||
.selected_icon(ui::IconName::ChevronRight)
|
.selected_icon(ui::IconName::ChevronRight)
|
||||||
.size(ui::ButtonSize::None)
|
.size(ui::ButtonSize::None)
|
||||||
|
.into_any_element()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
|
@ -9215,7 +9213,7 @@ impl Editor {
|
||||||
&self,
|
&self,
|
||||||
search_range: Range<Anchor>,
|
search_range: Range<Anchor>,
|
||||||
display_snapshot: &DisplaySnapshot,
|
display_snapshot: &DisplaySnapshot,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &WindowContext,
|
||||||
) -> Vec<Range<DisplayPoint>> {
|
) -> Vec<Range<DisplayPoint>> {
|
||||||
display_snapshot
|
display_snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
|
@ -9986,7 +9984,7 @@ impl EditorSnapshot {
|
||||||
self.is_focused
|
self.is_focused
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&Arc<str>> {
|
pub fn placeholder_text(&self) -> Option<&Arc<str>> {
|
||||||
self.placeholder_text.as_ref()
|
self.placeholder_text.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
element::PointForPosition,
|
|
||||||
hover_popover::{self, InlayHover},
|
hover_popover::{self, InlayHover},
|
||||||
Anchor, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, SelectPhase,
|
Anchor, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, PointForPosition,
|
||||||
|
SelectPhase,
|
||||||
};
|
};
|
||||||
use gpui::{px, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
|
use gpui::{px, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
|
||||||
use language::{Bias, ToOffset};
|
use language::{Bias, ToOffset};
|
||||||
|
|
|
@ -499,9 +499,10 @@ impl InfoPopover {
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.max_w(max_size.width)
|
.max_w(max_size.width)
|
||||||
.max_h(max_size.height)
|
.max_h(max_size.height)
|
||||||
// Prevent a mouse move on the popover from being propagated to the editor,
|
// Prevent a mouse down/move on the popover from being propagated to the editor,
|
||||||
// because that would dismiss the popover.
|
// because that would dismiss the popover.
|
||||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||||
.child(crate::render_parsed_markdown(
|
.child(crate::render_parsed_markdown(
|
||||||
"content",
|
"content",
|
||||||
&self.parsed_content,
|
&self.parsed_content,
|
||||||
|
@ -563,6 +564,7 @@ impl DiagnosticPopover {
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id("diagnostic")
|
.id("diagnostic")
|
||||||
|
.block()
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.px_2()
|
.px_2()
|
||||||
|
@ -602,11 +604,10 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
editor_tests::init_test,
|
editor_tests::init_test,
|
||||||
element::PointForPosition,
|
|
||||||
hover_links::update_inlay_link_and_hover_points,
|
hover_links::update_inlay_link_and_hover_points,
|
||||||
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
|
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
|
||||||
test::editor_lsp_test_context::EditorLspTestContext,
|
test::editor_lsp_test_context::EditorLspTestContext,
|
||||||
InlayId,
|
InlayId, PointForPosition,
|
||||||
};
|
};
|
||||||
use collections::BTreeSet;
|
use collections::BTreeSet;
|
||||||
use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
|
use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
DisplayPoint, Editor, EditorMode, MultiBuffer,
|
DisplayPoint, Editor, EditorMode, MultiBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
use gpui::{Context, Model, Pixels, ViewContext};
|
use gpui::{Context, Font, FontFeatures, FontStyle, FontWeight, Model, Pixels, ViewContext};
|
||||||
|
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use util::test::{marked_text_offsets, marked_text_ranges};
|
use util::test::{marked_text_offsets, marked_text_ranges};
|
||||||
|
@ -26,7 +26,12 @@ pub fn marked_display_snapshot(
|
||||||
) -> (DisplaySnapshot, Vec<DisplayPoint>) {
|
) -> (DisplaySnapshot, Vec<DisplayPoint>) {
|
||||||
let (unmarked_text, markers) = marked_text_offsets(text);
|
let (unmarked_text, markers) = marked_text_offsets(text);
|
||||||
|
|
||||||
let font = cx.text_style().font();
|
let font = Font {
|
||||||
|
family: "Courier".into(),
|
||||||
|
features: FontFeatures::default(),
|
||||||
|
weight: FontWeight::default(),
|
||||||
|
style: FontStyle::default(),
|
||||||
|
};
|
||||||
let font_size: Pixels = 14usize.into();
|
let font_size: Pixels = 14usize.into();
|
||||||
|
|
||||||
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
|
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
|
||||||
|
|
|
@ -6,10 +6,9 @@ use editor::{Editor, EditorElement, EditorStyle};
|
||||||
use extension::{ExtensionApiResponse, ExtensionManifest, ExtensionStatus, ExtensionStore};
|
use extension::{ExtensionApiResponse, ExtensionManifest, ExtensionStatus, ExtensionStore};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, uniform_list, AnyElement, AppContext, AvailableSpace, EventEmitter,
|
actions, canvas, uniform_list, AnyElement, AppContext, EventEmitter, FocusableView, FontStyle,
|
||||||
FocusableView, FontStyle, FontWeight, InteractiveElement, KeyContext, ParentElement, Render,
|
FontWeight, InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
|
||||||
Styled, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, WhiteSpace,
|
UniformListScrollHandle, View, ViewContext, VisualContext, WhiteSpace, WindowContext,
|
||||||
WindowContext,
|
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
@ -729,12 +728,12 @@ impl Render for ExtensionsPage {
|
||||||
return this.py_4().child(self.render_empty_state(cx));
|
return this.py_4().child(self.render_empty_state(cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.child(
|
|
||||||
canvas({
|
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
let scroll_handle = self.list.clone();
|
let scroll_handle = self.list.clone();
|
||||||
|
this.child(
|
||||||
|
canvas(
|
||||||
move |bounds, cx| {
|
move |bounds, cx| {
|
||||||
uniform_list::<_, ExtensionCard, _>(
|
let mut list = uniform_list::<_, ExtensionCard, _>(
|
||||||
view,
|
view,
|
||||||
"entries",
|
"entries",
|
||||||
count,
|
count,
|
||||||
|
@ -743,14 +742,12 @@ impl Render for ExtensionsPage {
|
||||||
.size_full()
|
.size_full()
|
||||||
.pb_4()
|
.pb_4()
|
||||||
.track_scroll(scroll_handle)
|
.track_scroll(scroll_handle)
|
||||||
.into_any_element()
|
.into_any_element();
|
||||||
.draw(
|
list.layout(bounds.origin, bounds.size.into(), cx);
|
||||||
bounds.origin,
|
list
|
||||||
bounds.size.map(AvailableSpace::Definite),
|
},
|
||||||
cx,
|
|_bounds, mut list, cx| list.paint(cx),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
.size_full(),
|
.size_full(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -20,7 +20,6 @@ pub use async_context::*;
|
||||||
use collections::{FxHashMap, FxHashSet, VecDeque};
|
use collections::{FxHashMap, FxHashSet, VecDeque};
|
||||||
pub use entity_map::*;
|
pub use entity_map::*;
|
||||||
pub use model_context::*;
|
pub use model_context::*;
|
||||||
use refineable::Refineable;
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub use test_context::*;
|
pub use test_context::*;
|
||||||
use util::{
|
use util::{
|
||||||
|
@ -34,8 +33,8 @@ use crate::{
|
||||||
DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke,
|
DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke,
|
||||||
LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder,
|
LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder,
|
||||||
PromptHandle, PromptLevel, Render, RenderablePromptHandle, SharedString, SubscriberSet,
|
PromptHandle, PromptLevel, Render, RenderablePromptHandle, SharedString, SubscriberSet,
|
||||||
Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext,
|
Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance,
|
||||||
Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
|
WindowContext, WindowHandle, WindowId,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod async_context;
|
mod async_context;
|
||||||
|
@ -216,7 +215,6 @@ pub struct AppContext {
|
||||||
pub(crate) svg_renderer: SvgRenderer,
|
pub(crate) svg_renderer: SvgRenderer,
|
||||||
asset_source: Arc<dyn AssetSource>,
|
asset_source: Arc<dyn AssetSource>,
|
||||||
pub(crate) image_cache: ImageCache,
|
pub(crate) image_cache: ImageCache,
|
||||||
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
|
|
||||||
pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>,
|
pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>,
|
||||||
pub(crate) entities: EntityMap,
|
pub(crate) entities: EntityMap,
|
||||||
pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
|
pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
|
||||||
|
@ -278,7 +276,6 @@ impl AppContext {
|
||||||
svg_renderer: SvgRenderer::new(asset_source.clone()),
|
svg_renderer: SvgRenderer::new(asset_source.clone()),
|
||||||
asset_source,
|
asset_source,
|
||||||
image_cache: ImageCache::new(http_client),
|
image_cache: ImageCache::new(http_client),
|
||||||
text_style_stack: Vec::new(),
|
|
||||||
globals_by_type: FxHashMap::default(),
|
globals_by_type: FxHashMap::default(),
|
||||||
entities,
|
entities,
|
||||||
new_view_observers: SubscriberSet::new(),
|
new_view_observers: SubscriberSet::new(),
|
||||||
|
@ -829,15 +826,6 @@ impl AppContext {
|
||||||
&self.text_system
|
&self.text_system
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The current text style. Which is composed of all the style refinements provided to `with_text_style`.
|
|
||||||
pub fn text_style(&self) -> TextStyle {
|
|
||||||
let mut style = TextStyle::default();
|
|
||||||
for refinement in &self.text_style_stack {
|
|
||||||
style.refine(refinement);
|
|
||||||
}
|
|
||||||
style
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether a global of the given type has been assigned.
|
/// Check whether a global of the given type has been assigned.
|
||||||
pub fn has_global<G: Global>(&self) -> bool {
|
pub fn has_global<G: Global>(&self) -> bool {
|
||||||
self.globals_by_type.contains_key(&TypeId::of::<G>())
|
self.globals_by_type.contains_key(&TypeId::of::<G>())
|
||||||
|
@ -1021,14 +1009,6 @@ impl AppContext {
|
||||||
inner(&mut self.keystroke_observers, Box::new(f))
|
inner(&mut self.keystroke_observers, Box::new(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
|
|
||||||
self.text_style_stack.push(text_style);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn pop_text_style(&mut self) {
|
|
||||||
self.text_style_stack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register key bindings.
|
/// Register key bindings.
|
||||||
pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
|
pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
|
||||||
self.keymap.borrow_mut().add_bindings(bindings);
|
self.keymap.borrow_mut().add_bindings(bindings);
|
||||||
|
@ -1127,15 +1107,18 @@ impl AppContext {
|
||||||
/// Checks if the given action is bound in the current context, as defined by the app's current focus,
|
/// Checks if the given action is bound in the current context, as defined by the app's current focus,
|
||||||
/// the bindings in the element tree, and any global action listeners.
|
/// the bindings in the element tree, and any global action listeners.
|
||||||
pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
|
pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
|
||||||
|
let mut action_available = false;
|
||||||
if let Some(window) = self.active_window() {
|
if let Some(window) = self.active_window() {
|
||||||
if let Ok(window_action_available) =
|
if let Ok(window_action_available) =
|
||||||
window.update(self, |_, cx| cx.is_action_available(action))
|
window.update(self, |_, cx| cx.is_action_available(action))
|
||||||
{
|
{
|
||||||
return window_action_available;
|
action_available = window_action_available;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.global_action_listeners
|
action_available
|
||||||
|
|| self
|
||||||
|
.global_action_listeners
|
||||||
.contains_key(&action.as_any().type_id())
|
.contains_key(&action.as_any().type_id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1152,6 +1135,11 @@ impl AppContext {
|
||||||
.update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
|
.update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
|
||||||
.log_err();
|
.log_err();
|
||||||
} else {
|
} else {
|
||||||
|
self.dispatch_global_action(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dispatch_global_action(&mut self, action: &dyn Action) {
|
||||||
self.propagate_event = true;
|
self.propagate_event = true;
|
||||||
|
|
||||||
if let Some(mut global_listeners) = self
|
if let Some(mut global_listeners) = self
|
||||||
|
@ -1198,7 +1186,6 @@ impl AppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Is there currently something being dragged?
|
/// Is there currently something being dragged?
|
||||||
pub fn has_active_drag(&self) -> bool {
|
pub fn has_active_drag(&self) -> bool {
|
||||||
|
|
|
@ -674,17 +674,10 @@ impl VisualTestContext {
|
||||||
f: impl FnOnce(&mut WindowContext) -> AnyElement,
|
f: impl FnOnce(&mut WindowContext) -> AnyElement,
|
||||||
) {
|
) {
|
||||||
self.update(|cx| {
|
self.update(|cx| {
|
||||||
let entity_id = cx
|
|
||||||
.window
|
|
||||||
.root_view
|
|
||||||
.as_ref()
|
|
||||||
.expect("Can't draw to this window without a root view")
|
|
||||||
.entity_id();
|
|
||||||
|
|
||||||
cx.with_element_context(|cx| {
|
cx.with_element_context(|cx| {
|
||||||
cx.with_view_id(entity_id, |cx| {
|
let mut element = f(cx);
|
||||||
f(cx).draw(origin, space, cx);
|
element.layout(origin, space, cx);
|
||||||
})
|
element.paint(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
|
|
292
crates/gpui/src/bounds_tree.rs
Normal file
292
crates/gpui/src/bounds_tree.rs
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
use crate::{Bounds, Half};
|
||||||
|
use std::{
|
||||||
|
cmp,
|
||||||
|
fmt::Debug,
|
||||||
|
ops::{Add, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct BoundsTree<U>
|
||||||
|
where
|
||||||
|
U: Default + Clone + Debug,
|
||||||
|
{
|
||||||
|
root: Option<usize>,
|
||||||
|
nodes: Vec<Node<U>>,
|
||||||
|
stack: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U> BoundsTree<U>
|
||||||
|
where
|
||||||
|
U: Clone + Debug + PartialOrd + Add<U, Output = U> + Sub<Output = U> + Half + Default,
|
||||||
|
{
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.root = None;
|
||||||
|
self.nodes.clear();
|
||||||
|
self.stack.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, new_bounds: Bounds<U>) -> u32 {
|
||||||
|
// If the tree is empty, make the root the new leaf.
|
||||||
|
if self.root.is_none() {
|
||||||
|
let new_node = self.push_leaf(new_bounds, 1);
|
||||||
|
self.root = Some(new_node);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the best place to add the new leaf based on heuristics.
|
||||||
|
let mut max_intersecting_ordering = 0;
|
||||||
|
let mut index = self.root.unwrap();
|
||||||
|
while let Node::Internal {
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
bounds: node_bounds,
|
||||||
|
..
|
||||||
|
} = &mut self.nodes[index]
|
||||||
|
{
|
||||||
|
let left = *left;
|
||||||
|
let right = *right;
|
||||||
|
*node_bounds = node_bounds.union(&new_bounds);
|
||||||
|
self.stack.push(index);
|
||||||
|
|
||||||
|
// Descend to the best-fit child, based on which one would increase
|
||||||
|
// the surface area the least. This attempts to keep the tree balanced
|
||||||
|
// in terms of surface area. If there is an intersection with the other child,
|
||||||
|
// add its keys to the intersections vector.
|
||||||
|
let left_cost = new_bounds
|
||||||
|
.union(&self.nodes[left].bounds())
|
||||||
|
.half_perimeter();
|
||||||
|
let right_cost = new_bounds
|
||||||
|
.union(&self.nodes[right].bounds())
|
||||||
|
.half_perimeter();
|
||||||
|
if left_cost < right_cost {
|
||||||
|
max_intersecting_ordering =
|
||||||
|
self.find_max_ordering(right, &new_bounds, max_intersecting_ordering);
|
||||||
|
index = left;
|
||||||
|
} else {
|
||||||
|
max_intersecting_ordering =
|
||||||
|
self.find_max_ordering(left, &new_bounds, max_intersecting_ordering);
|
||||||
|
index = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've found a leaf ('index' now refers to a leaf node).
|
||||||
|
// We'll insert a new parent node above the leaf and attach our new leaf to it.
|
||||||
|
let sibling = index;
|
||||||
|
|
||||||
|
// Check for collision with the located leaf node
|
||||||
|
let Node::Leaf {
|
||||||
|
bounds: sibling_bounds,
|
||||||
|
order: sibling_ordering,
|
||||||
|
..
|
||||||
|
} = &self.nodes[index]
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
if sibling_bounds.intersects(&new_bounds) {
|
||||||
|
max_intersecting_ordering = cmp::max(max_intersecting_ordering, *sibling_ordering);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ordering = max_intersecting_ordering + 1;
|
||||||
|
let new_node = self.push_leaf(new_bounds, ordering);
|
||||||
|
let new_parent = self.push_internal(sibling, new_node);
|
||||||
|
|
||||||
|
// If there was an old parent, we need to update its children indices.
|
||||||
|
if let Some(old_parent) = self.stack.last().copied() {
|
||||||
|
let Node::Internal { left, right, .. } = &mut self.nodes[old_parent] else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
if *left == sibling {
|
||||||
|
*left = new_parent;
|
||||||
|
} else {
|
||||||
|
*right = new_parent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the old parent was the root, the new parent is the new root.
|
||||||
|
self.root = Some(new_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
for node_index in self.stack.drain(..) {
|
||||||
|
let Node::Internal {
|
||||||
|
max_order: max_ordering,
|
||||||
|
..
|
||||||
|
} = &mut self.nodes[node_index]
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
*max_ordering = cmp::max(*max_ordering, ordering);
|
||||||
|
}
|
||||||
|
|
||||||
|
ordering
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_max_ordering(&self, index: usize, bounds: &Bounds<U>, mut max_ordering: u32) -> u32 {
|
||||||
|
match &self.nodes[index] {
|
||||||
|
Node::Leaf {
|
||||||
|
bounds: node_bounds,
|
||||||
|
order: ordering,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if bounds.intersects(node_bounds) {
|
||||||
|
max_ordering = cmp::max(*ordering, max_ordering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Node::Internal {
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
bounds: node_bounds,
|
||||||
|
max_order: node_max_ordering,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if bounds.intersects(node_bounds) && max_ordering < *node_max_ordering {
|
||||||
|
let left_max_ordering = self.nodes[*left].max_ordering();
|
||||||
|
let right_max_ordering = self.nodes[*right].max_ordering();
|
||||||
|
if left_max_ordering > right_max_ordering {
|
||||||
|
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
|
||||||
|
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
|
||||||
|
} else {
|
||||||
|
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
|
||||||
|
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max_ordering
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_leaf(&mut self, bounds: Bounds<U>, order: u32) -> usize {
|
||||||
|
self.nodes.push(Node::Leaf { bounds, order });
|
||||||
|
self.nodes.len() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_internal(&mut self, left: usize, right: usize) -> usize {
|
||||||
|
let left_node = &self.nodes[left];
|
||||||
|
let right_node = &self.nodes[right];
|
||||||
|
let new_bounds = left_node.bounds().union(right_node.bounds());
|
||||||
|
let max_ordering = cmp::max(left_node.max_ordering(), right_node.max_ordering());
|
||||||
|
self.nodes.push(Node::Internal {
|
||||||
|
bounds: new_bounds,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
max_order: max_ordering,
|
||||||
|
});
|
||||||
|
self.nodes.len() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U> Default for BoundsTree<U>
|
||||||
|
where
|
||||||
|
U: Default + Clone + Debug,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
BoundsTree {
|
||||||
|
root: None,
|
||||||
|
nodes: Vec::new(),
|
||||||
|
stack: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Node<U>
|
||||||
|
where
|
||||||
|
U: Clone + Default + Debug,
|
||||||
|
{
|
||||||
|
Leaf {
|
||||||
|
bounds: Bounds<U>,
|
||||||
|
order: u32,
|
||||||
|
},
|
||||||
|
Internal {
|
||||||
|
left: usize,
|
||||||
|
right: usize,
|
||||||
|
bounds: Bounds<U>,
|
||||||
|
max_order: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U> Node<U>
|
||||||
|
where
|
||||||
|
U: Clone + Default + Debug,
|
||||||
|
{
|
||||||
|
fn bounds(&self) -> &Bounds<U> {
|
||||||
|
match self {
|
||||||
|
Node::Leaf { bounds, .. } => bounds,
|
||||||
|
Node::Internal { bounds, .. } => bounds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_ordering(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Node::Leaf {
|
||||||
|
order: ordering, ..
|
||||||
|
} => *ordering,
|
||||||
|
Node::Internal {
|
||||||
|
max_order: max_ordering,
|
||||||
|
..
|
||||||
|
} => *max_ordering,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{Bounds, Point, Size};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_insert() {
|
||||||
|
let mut tree = BoundsTree::<f32>::default();
|
||||||
|
let bounds1 = Bounds {
|
||||||
|
origin: Point { x: 0.0, y: 0.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 10.0,
|
||||||
|
height: 10.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let bounds2 = Bounds {
|
||||||
|
origin: Point { x: 5.0, y: 5.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 10.0,
|
||||||
|
height: 10.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let bounds3 = Bounds {
|
||||||
|
origin: Point { x: 10.0, y: 10.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 10.0,
|
||||||
|
height: 10.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert the bounds into the tree and verify the order is correct
|
||||||
|
assert_eq!(tree.insert(bounds1), 1);
|
||||||
|
assert_eq!(tree.insert(bounds2), 2);
|
||||||
|
assert_eq!(tree.insert(bounds3), 3);
|
||||||
|
|
||||||
|
// Insert non-overlapping bounds and verify they can reuse orders
|
||||||
|
let bounds4 = Bounds {
|
||||||
|
origin: Point { x: 20.0, y: 20.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 10.0,
|
||||||
|
height: 10.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let bounds5 = Bounds {
|
||||||
|
origin: Point { x: 40.0, y: 40.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 10.0,
|
||||||
|
height: 10.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let bounds6 = Bounds {
|
||||||
|
origin: Point { x: 25.0, y: 25.0 },
|
||||||
|
size: Size {
|
||||||
|
width: 10.0,
|
||||||
|
height: 10.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert_eq!(tree.insert(bounds4), 1); // bounds4 does not overlap with bounds1, bounds2, or bounds3
|
||||||
|
assert_eq!(tree.insert(bounds5), 1); // bounds5 does not overlap with any other bounds
|
||||||
|
assert_eq!(tree.insert(bounds6), 2); // bounds6 overlaps with bounds4, so it should have a different order
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,9 +15,6 @@
|
||||||
//!
|
//!
|
||||||
//! But some state is too simple and voluminous to store in every view that needs it, e.g.
|
//! But some state is too simple and voluminous to store in every view that needs it, e.g.
|
||||||
//! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], associated type.
|
//! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], associated type.
|
||||||
//! If an element returns an [`ElementId`] from [`IntoElement::element_id()`], and that element id
|
|
||||||
//! appears in the same place relative to other views and ElementIds in the frame, then the previous
|
|
||||||
//! frame's state will be passed to the element's layout and paint methods.
|
|
||||||
//!
|
//!
|
||||||
//! # Implementing your own elements
|
//! # Implementing your own elements
|
||||||
//!
|
//!
|
||||||
|
@ -35,33 +32,48 @@
|
||||||
//! your own custom layout algorithm or rendering a code editor.
|
//! your own custom layout algorithm or rendering a code editor.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId,
|
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext,
|
||||||
Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
|
ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
pub(crate) use smallvec::SmallVec;
|
pub(crate) use smallvec::SmallVec;
|
||||||
use std::{any::Any, fmt::Debug, ops::DerefMut};
|
use std::{any::Any, fmt::Debug, mem, ops::DerefMut};
|
||||||
|
|
||||||
/// Implemented by types that participate in laying out and painting the contents of a window.
|
/// Implemented by types that participate in laying out and painting the contents of a window.
|
||||||
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
|
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
|
||||||
/// You can create custom elements by implementing this trait, see the module-level documentation
|
/// You can create custom elements by implementing this trait, see the module-level documentation
|
||||||
/// for more details.
|
/// for more details.
|
||||||
pub trait Element: 'static + IntoElement {
|
pub trait Element: 'static + IntoElement {
|
||||||
/// The type of state to store for this element between frames. See the module-level documentation
|
/// The type of state returned from [`Element::before_layout`]. A mutable reference to this state is subsequently
|
||||||
/// for details.
|
/// provided to [`Element::after_layout`] and [`Element::paint`].
|
||||||
type State: 'static;
|
type BeforeLayout: 'static;
|
||||||
|
|
||||||
|
/// The type of state returned from [`Element::after_layout`]. A mutable reference to this state is subsequently
|
||||||
|
/// provided to [`Element::paint`].
|
||||||
|
type AfterLayout: 'static;
|
||||||
|
|
||||||
/// Before an element can be painted, we need to know where it's going to be and how big it is.
|
/// Before an element can be painted, we need to know where it's going to be and how big it is.
|
||||||
/// Use this method to request a layout from Taffy and initialize the element's state.
|
/// Use this method to request a layout from Taffy and initialize the element's state.
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout);
|
||||||
|
|
||||||
|
/// After laying out an element, we need to commit its bounds to the current frame for hitbox
|
||||||
|
/// purposes. The state argument is the same state that was returned from [`Element::before_layout()`].
|
||||||
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: Option<Self::State>,
|
bounds: Bounds<Pixels>,
|
||||||
|
before_layout: &mut Self::BeforeLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State);
|
) -> Self::AfterLayout;
|
||||||
|
|
||||||
/// Once layout has been completed, this method will be called to paint the element to the screen.
|
/// Once layout has been completed, this method will be called to paint the element to the screen.
|
||||||
/// The state argument is the same state that was returned from [`Element::request_layout()`].
|
/// The state argument is the same state that was returned from [`Element::before_layout()`].
|
||||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext);
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
before_layout: &mut Self::BeforeLayout,
|
||||||
|
after_layout: &mut Self::AfterLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
);
|
||||||
|
|
||||||
/// Convert this element into a dynamically-typed [`AnyElement`].
|
/// Convert this element into a dynamically-typed [`AnyElement`].
|
||||||
fn into_any(self) -> AnyElement {
|
fn into_any(self) -> AnyElement {
|
||||||
|
@ -75,10 +87,6 @@ pub trait IntoElement: Sized {
|
||||||
/// Useful for converting other types into elements automatically, like Strings
|
/// Useful for converting other types into elements automatically, like Strings
|
||||||
type Element: Element;
|
type Element: Element;
|
||||||
|
|
||||||
/// The [`ElementId`] of self once converted into an [`Element`].
|
|
||||||
/// If present, the resulting element's state will be carried across frames.
|
|
||||||
fn element_id(&self) -> Option<ElementId>;
|
|
||||||
|
|
||||||
/// Convert self into a type that implements [`Element`].
|
/// Convert self into a type that implements [`Element`].
|
||||||
fn into_element(self) -> Self::Element;
|
fn into_element(self) -> Self::Element;
|
||||||
|
|
||||||
|
@ -86,41 +94,6 @@ pub trait IntoElement: Sized {
|
||||||
fn into_any_element(self) -> AnyElement {
|
fn into_any_element(self) -> AnyElement {
|
||||||
self.into_element().into_any()
|
self.into_element().into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert into an element, then draw in the current window at the given origin.
|
|
||||||
/// The available space argument is provided to the layout engine to determine the size of the
|
|
||||||
// root element. Once the element is drawn, its associated element state is yielded to the
|
|
||||||
// given callback.
|
|
||||||
fn draw_and_update_state<T, R>(
|
|
||||||
self,
|
|
||||||
origin: Point<Pixels>,
|
|
||||||
available_space: Size<T>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
f: impl FnOnce(&mut <Self::Element as Element>::State, &mut ElementContext) -> R,
|
|
||||||
) -> R
|
|
||||||
where
|
|
||||||
T: Clone + Default + Debug + Into<AvailableSpace>,
|
|
||||||
{
|
|
||||||
let element = self.into_element();
|
|
||||||
let element_id = element.element_id();
|
|
||||||
let element = DrawableElement {
|
|
||||||
element: Some(element),
|
|
||||||
phase: ElementDrawPhase::Start,
|
|
||||||
};
|
|
||||||
|
|
||||||
let frame_state =
|
|
||||||
DrawableElement::draw(element, origin, available_space.map(Into::into), cx);
|
|
||||||
|
|
||||||
if let Some(mut frame_state) = frame_state {
|
|
||||||
f(&mut frame_state, cx)
|
|
||||||
} else {
|
|
||||||
cx.with_element_state(element_id.unwrap(), |element_state, cx| {
|
|
||||||
let mut element_state = element_state.unwrap();
|
|
||||||
let result = f(&mut element_state, cx);
|
|
||||||
(result, element_state)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: IntoElement> FluentBuilder for T {}
|
impl<T: IntoElement> FluentBuilder for T {}
|
||||||
|
@ -188,24 +161,36 @@ impl<C: RenderOnce> Component<C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: RenderOnce> Element for Component<C> {
|
impl<C: RenderOnce> Element for Component<C> {
|
||||||
type State = AnyElement;
|
type BeforeLayout = AnyElement;
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
_: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
let mut element = self
|
let mut element = self
|
||||||
.0
|
.0
|
||||||
.take()
|
.take()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.render(cx.deref_mut())
|
.render(cx.deref_mut())
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
let layout_id = element.request_layout(cx);
|
let layout_id = element.before_layout(cx);
|
||||||
(layout_id, element)
|
(layout_id, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
_: Bounds<Pixels>,
|
||||||
|
element: &mut AnyElement,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
element.after_layout(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
_: Bounds<Pixels>,
|
||||||
|
element: &mut Self::BeforeLayout,
|
||||||
|
_: &mut Self::AfterLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
element.paint(cx)
|
element.paint(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,10 +198,6 @@ impl<C: RenderOnce> Element for Component<C> {
|
||||||
impl<C: RenderOnce> IntoElement for Component<C> {
|
impl<C: RenderOnce> IntoElement for Component<C> {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -227,9 +208,11 @@ impl<C: RenderOnce> IntoElement for Component<C> {
|
||||||
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
|
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
|
||||||
|
|
||||||
trait ElementObject {
|
trait ElementObject {
|
||||||
fn element_id(&self) -> Option<ElementId>;
|
fn inner_element(&mut self) -> &mut dyn Any;
|
||||||
|
|
||||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
|
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
|
||||||
|
|
||||||
|
fn after_layout(&mut self, cx: &mut ElementContext);
|
||||||
|
|
||||||
fn paint(&mut self, cx: &mut ElementContext);
|
fn paint(&mut self, cx: &mut ElementContext);
|
||||||
|
|
||||||
|
@ -238,110 +221,102 @@ trait ElementObject {
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> Size<Pixels>;
|
) -> Size<Pixels>;
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
origin: Point<Pixels>,
|
|
||||||
available_space: Size<AvailableSpace>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
|
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
|
||||||
pub(crate) struct DrawableElement<E: Element> {
|
pub struct Drawable<E: Element> {
|
||||||
element: Option<E>,
|
/// The drawn element.
|
||||||
phase: ElementDrawPhase<E::State>,
|
pub element: E,
|
||||||
|
phase: ElementDrawPhase<E::BeforeLayout, E::AfterLayout>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
enum ElementDrawPhase<S> {
|
enum ElementDrawPhase<BeforeLayout, AfterLayout> {
|
||||||
#[default]
|
#[default]
|
||||||
Start,
|
Start,
|
||||||
LayoutRequested {
|
BeforeLayout {
|
||||||
layout_id: LayoutId,
|
layout_id: LayoutId,
|
||||||
frame_state: Option<S>,
|
before_layout: BeforeLayout,
|
||||||
},
|
},
|
||||||
LayoutComputed {
|
LayoutComputed {
|
||||||
layout_id: LayoutId,
|
layout_id: LayoutId,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
frame_state: Option<S>,
|
before_layout: BeforeLayout,
|
||||||
},
|
},
|
||||||
|
AfterLayout {
|
||||||
|
node_id: DispatchNodeId,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
before_layout: BeforeLayout,
|
||||||
|
after_layout: AfterLayout,
|
||||||
|
},
|
||||||
|
Painted,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
|
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
|
||||||
impl<E: Element> DrawableElement<E> {
|
impl<E: Element> Drawable<E> {
|
||||||
fn new(element: E) -> Self {
|
fn new(element: E) -> Self {
|
||||||
DrawableElement {
|
Drawable {
|
||||||
element: Some(element),
|
element,
|
||||||
phase: ElementDrawPhase::Start,
|
phase: ElementDrawPhase::Start,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||||
self.element.as_ref()?.element_id()
|
match mem::take(&mut self.phase) {
|
||||||
}
|
ElementDrawPhase::Start => {
|
||||||
|
let (layout_id, before_layout) = self.element.before_layout(cx);
|
||||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
self.phase = ElementDrawPhase::BeforeLayout {
|
||||||
let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
|
|
||||||
{
|
|
||||||
let layout_id = cx.with_element_state(id, |element_state, cx| {
|
|
||||||
self.element
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.request_layout(element_state, cx)
|
|
||||||
});
|
|
||||||
(layout_id, None)
|
|
||||||
} else {
|
|
||||||
let (layout_id, frame_state) = self.element.as_mut().unwrap().request_layout(None, cx);
|
|
||||||
(layout_id, Some(frame_state))
|
|
||||||
};
|
|
||||||
|
|
||||||
self.phase = ElementDrawPhase::LayoutRequested {
|
|
||||||
layout_id,
|
layout_id,
|
||||||
frame_state,
|
before_layout,
|
||||||
};
|
};
|
||||||
layout_id
|
layout_id
|
||||||
}
|
}
|
||||||
|
_ => panic!("must call before_layout only once"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn paint(mut self, cx: &mut ElementContext) -> Option<E::State> {
|
fn after_layout(&mut self, cx: &mut ElementContext) {
|
||||||
match self.phase {
|
match mem::take(&mut self.phase) {
|
||||||
ElementDrawPhase::LayoutRequested {
|
ElementDrawPhase::BeforeLayout {
|
||||||
layout_id,
|
layout_id,
|
||||||
frame_state,
|
mut before_layout,
|
||||||
}
|
}
|
||||||
| ElementDrawPhase::LayoutComputed {
|
| ElementDrawPhase::LayoutComputed {
|
||||||
layout_id,
|
layout_id,
|
||||||
frame_state,
|
mut before_layout,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let bounds = cx.layout_bounds(layout_id);
|
let bounds = cx.layout_bounds(layout_id);
|
||||||
|
let node_id = cx.window.next_frame.dispatch_tree.push_node();
|
||||||
if let Some(mut frame_state) = frame_state {
|
let after_layout = self.element.after_layout(bounds, &mut before_layout, cx);
|
||||||
self.element
|
self.phase = ElementDrawPhase::AfterLayout {
|
||||||
.take()
|
node_id,
|
||||||
.unwrap()
|
bounds,
|
||||||
.paint(bounds, &mut frame_state, cx);
|
before_layout,
|
||||||
Some(frame_state)
|
after_layout,
|
||||||
} else {
|
};
|
||||||
let element_id = self
|
cx.window.next_frame.dispatch_tree.pop_node();
|
||||||
.element
|
}
|
||||||
.as_ref()
|
_ => panic!("must call before_layout before after_layout"),
|
||||||
.unwrap()
|
|
||||||
.element_id()
|
|
||||||
.expect("if we don't have frame state, we should have element state");
|
|
||||||
cx.with_element_state(element_id, |element_state, cx| {
|
|
||||||
let mut element_state = element_state.unwrap();
|
|
||||||
self.element
|
|
||||||
.take()
|
|
||||||
.unwrap()
|
|
||||||
.paint(bounds, &mut element_state, cx);
|
|
||||||
((), element_state)
|
|
||||||
});
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => panic!("must call layout before paint"),
|
fn paint(&mut self, cx: &mut ElementContext) -> E::BeforeLayout {
|
||||||
|
match mem::take(&mut self.phase) {
|
||||||
|
ElementDrawPhase::AfterLayout {
|
||||||
|
node_id,
|
||||||
|
bounds,
|
||||||
|
mut before_layout,
|
||||||
|
mut after_layout,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
cx.window.next_frame.dispatch_tree.set_active_node(node_id);
|
||||||
|
self.element
|
||||||
|
.paint(bounds, &mut before_layout, &mut after_layout, cx);
|
||||||
|
self.phase = ElementDrawPhase::Painted;
|
||||||
|
before_layout
|
||||||
|
}
|
||||||
|
_ => panic!("must call after_layout before paint"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,66 +326,63 @@ impl<E: Element> DrawableElement<E> {
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> Size<Pixels> {
|
) -> Size<Pixels> {
|
||||||
if matches!(&self.phase, ElementDrawPhase::Start) {
|
if matches!(&self.phase, ElementDrawPhase::Start) {
|
||||||
self.request_layout(cx);
|
self.before_layout(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout_id = match &mut self.phase {
|
let layout_id = match mem::take(&mut self.phase) {
|
||||||
ElementDrawPhase::LayoutRequested {
|
ElementDrawPhase::BeforeLayout {
|
||||||
layout_id,
|
layout_id,
|
||||||
frame_state,
|
before_layout,
|
||||||
} => {
|
} => {
|
||||||
cx.compute_layout(*layout_id, available_space);
|
cx.compute_layout(layout_id, available_space);
|
||||||
let layout_id = *layout_id;
|
|
||||||
self.phase = ElementDrawPhase::LayoutComputed {
|
self.phase = ElementDrawPhase::LayoutComputed {
|
||||||
layout_id,
|
layout_id,
|
||||||
available_space,
|
available_space,
|
||||||
frame_state: frame_state.take(),
|
before_layout,
|
||||||
};
|
};
|
||||||
layout_id
|
layout_id
|
||||||
}
|
}
|
||||||
ElementDrawPhase::LayoutComputed {
|
ElementDrawPhase::LayoutComputed {
|
||||||
layout_id,
|
layout_id,
|
||||||
available_space: prev_available_space,
|
available_space: prev_available_space,
|
||||||
..
|
before_layout,
|
||||||
} => {
|
} => {
|
||||||
if available_space != *prev_available_space {
|
if available_space != prev_available_space {
|
||||||
cx.compute_layout(*layout_id, available_space);
|
cx.compute_layout(layout_id, available_space);
|
||||||
*prev_available_space = available_space;
|
|
||||||
}
|
}
|
||||||
*layout_id
|
self.phase = ElementDrawPhase::LayoutComputed {
|
||||||
|
layout_id,
|
||||||
|
available_space,
|
||||||
|
before_layout,
|
||||||
|
};
|
||||||
|
layout_id
|
||||||
}
|
}
|
||||||
_ => panic!("cannot measure after painting"),
|
_ => panic!("cannot measure after painting"),
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.layout_bounds(layout_id).size
|
cx.layout_bounds(layout_id).size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
|
||||||
mut self,
|
|
||||||
origin: Point<Pixels>,
|
|
||||||
available_space: Size<AvailableSpace>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> Option<E::State> {
|
|
||||||
self.measure(available_space, cx);
|
|
||||||
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> ElementObject for Option<DrawableElement<E>>
|
impl<E> ElementObject for Drawable<E>
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
E::State: 'static,
|
E::BeforeLayout: 'static,
|
||||||
{
|
{
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
fn inner_element(&mut self) -> &mut dyn Any {
|
||||||
self.as_ref().unwrap().element_id()
|
&mut self.element
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||||
DrawableElement::request_layout(self.as_mut().unwrap(), cx)
|
Drawable::before_layout(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(&mut self, cx: &mut ElementContext) {
|
||||||
|
Drawable::after_layout(self, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, cx: &mut ElementContext) {
|
fn paint(&mut self, cx: &mut ElementContext) {
|
||||||
DrawableElement::paint(self.take().unwrap(), cx);
|
Drawable::paint(self, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn measure(
|
fn measure(
|
||||||
|
@ -418,16 +390,7 @@ where
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> Size<Pixels> {
|
) -> Size<Pixels> {
|
||||||
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
|
Drawable::measure(self, available_space, cx)
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
origin: Point<Pixels>,
|
|
||||||
available_space: Size<AvailableSpace>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) {
|
|
||||||
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,18 +401,28 @@ impl AnyElement {
|
||||||
pub(crate) fn new<E>(element: E) -> Self
|
pub(crate) fn new<E>(element: E) -> Self
|
||||||
where
|
where
|
||||||
E: 'static + Element,
|
E: 'static + Element,
|
||||||
E::State: Any,
|
E::BeforeLayout: Any,
|
||||||
{
|
{
|
||||||
let element = ELEMENT_ARENA
|
let element = ELEMENT_ARENA
|
||||||
.with_borrow_mut(|arena| arena.alloc(|| Some(DrawableElement::new(element))))
|
.with_borrow_mut(|arena| arena.alloc(|| Drawable::new(element)))
|
||||||
.map(|element| element as &mut dyn ElementObject);
|
.map(|element| element as &mut dyn ElementObject);
|
||||||
AnyElement(element)
|
AnyElement(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to downcast a reference to the boxed element to a specific type.
|
||||||
|
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||||
|
self.0.inner_element().downcast_mut::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
/// Request the layout ID of the element stored in this `AnyElement`.
|
/// Request the layout ID of the element stored in this `AnyElement`.
|
||||||
/// Used for laying out child elements in a parent element.
|
/// Used for laying out child elements in a parent element.
|
||||||
pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
pub fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||||
self.0.request_layout(cx)
|
self.0.before_layout(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commits the element bounds of this [AnyElement] for hitbox purposes.
|
||||||
|
pub fn after_layout(&mut self, cx: &mut ElementContext) {
|
||||||
|
self.0.after_layout(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paints the element stored in this `AnyElement`.
|
/// Paints the element stored in this `AnyElement`.
|
||||||
|
@ -466,35 +439,44 @@ impl AnyElement {
|
||||||
self.0.measure(available_space, cx)
|
self.0.measure(available_space, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes this element and performs layout in the available space, then paints it at the given origin.
|
/// Initializes this element, performs layout if needed and commits its bounds for hitbox purposes.
|
||||||
pub fn draw(
|
pub fn layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
origin: Point<Pixels>,
|
absolute_offset: Point<Pixels>,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) -> Size<Pixels> {
|
||||||
self.0.draw(origin, available_space, cx)
|
let size = self.measure(available_space, cx);
|
||||||
}
|
cx.with_absolute_element_offset(absolute_offset, |cx| self.after_layout(cx));
|
||||||
|
size
|
||||||
/// Returns the element ID of the element stored in this `AnyElement`, if any.
|
|
||||||
pub fn inner_id(&self) -> Option<ElementId> {
|
|
||||||
self.0.element_id()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for AnyElement {
|
impl Element for AnyElement {
|
||||||
type State = ();
|
type BeforeLayout = ();
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
let layout_id = self.before_layout(cx);
|
||||||
_: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
let layout_id = self.request_layout(cx);
|
|
||||||
(layout_id, ())
|
(layout_id, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut ElementContext) {
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
_: Bounds<Pixels>,
|
||||||
|
_: &mut Self::BeforeLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
self.after_layout(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
_: Bounds<Pixels>,
|
||||||
|
_: &mut Self::BeforeLayout,
|
||||||
|
_: &mut Self::AfterLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
self.paint(cx)
|
self.paint(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -502,10 +484,6 @@ impl Element for AnyElement {
|
||||||
impl IntoElement for AnyElement {
|
impl IntoElement for AnyElement {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -521,30 +499,32 @@ pub struct Empty;
|
||||||
impl IntoElement for Empty {
|
impl IntoElement for Empty {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Empty {
|
impl Element for Empty {
|
||||||
type State = ();
|
type BeforeLayout = ();
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
_state: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
(cx.request_layout(&crate::Style::default(), None), ())
|
(cx.request_layout(&crate::Style::default(), None), ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
_bounds: Bounds<Pixels>,
|
||||||
|
_state: &mut Self::BeforeLayout,
|
||||||
|
_cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
_bounds: Bounds<Pixels>,
|
_bounds: Bounds<Pixels>,
|
||||||
_state: &mut Self::State,
|
_before_layout: &mut Self::BeforeLayout,
|
||||||
|
_after_layout: &mut Self::AfterLayout,
|
||||||
_cx: &mut ElementContext,
|
_cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,54 +4,68 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe
|
||||||
|
|
||||||
/// Construct a canvas element with the given paint callback.
|
/// Construct a canvas element with the given paint callback.
|
||||||
/// Useful for adding short term custom drawing to a view.
|
/// Useful for adding short term custom drawing to a view.
|
||||||
pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut ElementContext)) -> Canvas {
|
pub fn canvas<T>(
|
||||||
|
after_layout: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
|
||||||
|
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut ElementContext),
|
||||||
|
) -> Canvas<T> {
|
||||||
Canvas {
|
Canvas {
|
||||||
paint_callback: Some(Box::new(callback)),
|
after_layout: Some(Box::new(after_layout)),
|
||||||
|
paint: Some(Box::new(paint)),
|
||||||
style: StyleRefinement::default(),
|
style: StyleRefinement::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A canvas element, meant for accessing the low level paint API without defining a whole
|
/// A canvas element, meant for accessing the low level paint API without defining a whole
|
||||||
/// custom element
|
/// custom element
|
||||||
pub struct Canvas {
|
pub struct Canvas<T> {
|
||||||
paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut ElementContext)>>,
|
after_layout: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
|
||||||
|
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for Canvas {
|
impl<T: 'static> IntoElement for Canvas<T> {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<crate::ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Canvas {
|
impl<T: 'static> Element for Canvas<T> {
|
||||||
type State = Style;
|
type BeforeLayout = Style;
|
||||||
|
type AfterLayout = Option<T>;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
_: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (crate::LayoutId, Self::State) {
|
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.refine(&self.style);
|
style.refine(&self.style);
|
||||||
let layout_id = cx.request_layout(&style, []);
|
let layout_id = cx.request_layout(&style, []);
|
||||||
(layout_id, style)
|
(layout_id, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut ElementContext) {
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
_before_layout: &mut Style,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) -> Option<T> {
|
||||||
|
Some(self.after_layout.take().unwrap()(bounds, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
style: &mut Style,
|
||||||
|
after_layout: &mut Self::AfterLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
let after_layout = after_layout.take().unwrap();
|
||||||
style.paint(bounds, cx, |cx| {
|
style.paint(bounds, cx, |cx| {
|
||||||
(self.paint_callback.take().unwrap())(&bounds, cx)
|
(self.paint.take().unwrap())(bounds, after_layout, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Styled for Canvas {
|
impl<T> Styled for Canvas<T> {
|
||||||
fn style(&mut self) -> &mut crate::StyleRefinement {
|
fn style(&mut self) -> &mut crate::StyleRefinement {
|
||||||
&mut self.style
|
&mut self.style
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,8 +2,8 @@ use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
point, size, Bounds, DevicePixels, Element, ElementContext, ImageData, InteractiveElement,
|
point, size, Bounds, DevicePixels, Element, ElementContext, Hitbox, ImageData,
|
||||||
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size,
|
InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size,
|
||||||
StyleRefinement, Styled, UriOrPath,
|
StyleRefinement, Styled, UriOrPath,
|
||||||
};
|
};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
@ -88,32 +88,37 @@ impl Img {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Img {
|
impl Element for Img {
|
||||||
type State = InteractiveElementState;
|
type BeforeLayout = ();
|
||||||
|
type AfterLayout = Option<Hitbox>;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
|
let layout_id = self
|
||||||
|
.interactivity
|
||||||
|
.before_layout(cx, |style, cx| cx.request_layout(&style, []));
|
||||||
|
(layout_id, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
element_state: Option<Self::State>,
|
bounds: Bounds<Pixels>,
|
||||||
|
_before_layout: &mut Self::BeforeLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> Option<Hitbox> {
|
||||||
self.interactivity
|
self.interactivity
|
||||||
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
|
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
element_state: &mut Self::State,
|
_: &mut Self::BeforeLayout,
|
||||||
|
hitbox: &mut Self::AfterLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let source = self.source.clone();
|
let source = self.source.clone();
|
||||||
self.interactivity.paint(
|
self.interactivity
|
||||||
bounds,
|
.paint(bounds, hitbox.as_ref(), cx, |style, cx| {
|
||||||
bounds.size,
|
|
||||||
element_state,
|
|
||||||
cx,
|
|
||||||
|style, _scroll_offset, cx| {
|
|
||||||
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
|
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||||
cx.with_z_index(1, |cx| {
|
|
||||||
match source {
|
match source {
|
||||||
ImageSource::Uri(_) | ImageSource::File(_) => {
|
ImageSource::Uri(_) | ImageSource::File(_) => {
|
||||||
let uri_or_path: UriOrPath = match source {
|
let uri_or_path: UriOrPath = match source {
|
||||||
|
@ -154,20 +159,14 @@ impl Element for Img {
|
||||||
// TODO: Add support for corner_radii and grayscale.
|
// TODO: Add support for corner_radii and grayscale.
|
||||||
cx.paint_surface(new_bounds, surface);
|
cx.paint_surface(new_bounds, surface);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for Img {
|
impl IntoElement for Img {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<crate::ElementId> {
|
|
||||||
self.interactivity.element_id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,12 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
|
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
|
||||||
Element, ElementContext, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
Element, ElementContext, HitboxId, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
||||||
StyleRefinement, Styled, WindowContext,
|
StyleRefinement, Styled, WindowContext,
|
||||||
};
|
};
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
use refineable::Refineable as _;
|
use refineable::Refineable as _;
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||||
use sum_tree::{Bias, SumTree};
|
use sum_tree::{Bias, SumTree};
|
||||||
use taffy::style::Overflow;
|
use taffy::style::Overflow;
|
||||||
|
@ -96,6 +97,13 @@ struct LayoutItemsResponse {
|
||||||
item_elements: VecDeque<AnyElement>,
|
item_elements: VecDeque<AnyElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Frame state used by the [List] element.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ListFrameState {
|
||||||
|
scroll_top: ListOffset,
|
||||||
|
items: SmallVec<[AnyElement; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum ListItem {
|
enum ListItem {
|
||||||
Unrendered,
|
Unrendered,
|
||||||
|
@ -302,7 +310,6 @@ impl StateInner {
|
||||||
height: Pixels,
|
height: Pixels,
|
||||||
delta: Point<Pixels>,
|
delta: Point<Pixels>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
padding: Edges<Pixels>,
|
|
||||||
) {
|
) {
|
||||||
// Drop scroll events after a reset, since we can't calculate
|
// Drop scroll events after a reset, since we can't calculate
|
||||||
// the new logical scroll top without the item heights
|
// the new logical scroll top without the item heights
|
||||||
|
@ -310,6 +317,7 @@ impl StateInner {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let padding = self.last_padding.unwrap_or_default();
|
||||||
let scroll_max =
|
let scroll_max =
|
||||||
(self.items.summary().height + padding.top + padding.bottom - height).max(px(0.));
|
(self.items.summary().height + padding.top + padding.bottom - height).max(px(0.));
|
||||||
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
|
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
|
||||||
|
@ -516,13 +524,13 @@ pub struct ListOffset {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for List {
|
impl Element for List {
|
||||||
type State = ();
|
type BeforeLayout = ListFrameState;
|
||||||
|
type AfterLayout = HitboxId;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state: Option<Self::State>,
|
|
||||||
cx: &mut crate::ElementContext,
|
cx: &mut crate::ElementContext,
|
||||||
) -> (crate::LayoutId, Self::State) {
|
) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||||
let layout_id = match self.sizing_behavior {
|
let layout_id = match self.sizing_behavior {
|
||||||
ListSizingBehavior::Infer => {
|
ListSizingBehavior::Infer => {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
|
@ -580,15 +588,15 @@ impl Element for List {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(layout_id, ())
|
(layout_id, ListFrameState::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<crate::Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
_state: &mut Self::State,
|
before_layout: &mut Self::BeforeLayout,
|
||||||
cx: &mut crate::ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) -> HitboxId {
|
||||||
let state = &mut *self.state.0.borrow_mut();
|
let state = &mut *self.state.0.borrow_mut();
|
||||||
state.reset = false;
|
state.reset = false;
|
||||||
|
|
||||||
|
@ -615,12 +623,11 @@ impl Element for List {
|
||||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
|
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
|
||||||
item_origin.y -= layout_response.scroll_top.offset_in_item;
|
item_origin.y -= layout_response.scroll_top.offset_in_item;
|
||||||
for item_element in &mut layout_response.item_elements {
|
for mut item_element in layout_response.item_elements {
|
||||||
let item_height = item_element
|
let item_size = item_element.measure(layout_response.available_item_space, cx);
|
||||||
.measure(layout_response.available_item_space, cx)
|
item_element.layout(item_origin, layout_response.available_item_space, cx);
|
||||||
.height;
|
before_layout.items.push(item_element);
|
||||||
item_element.draw(item_origin, layout_response.available_item_space, cx);
|
item_origin.y += item_size.height;
|
||||||
item_origin.y += item_height;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -628,20 +635,33 @@ impl Element for List {
|
||||||
state.last_layout_bounds = Some(bounds);
|
state.last_layout_bounds = Some(bounds);
|
||||||
state.last_padding = Some(padding);
|
state.last_padding = Some(padding);
|
||||||
|
|
||||||
|
cx.insert_hitbox(bounds, false).id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<crate::Pixels>,
|
||||||
|
before_layout: &mut Self::BeforeLayout,
|
||||||
|
hitbox_id: &mut HitboxId,
|
||||||
|
cx: &mut crate::ElementContext,
|
||||||
|
) {
|
||||||
|
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
|
for item in &mut before_layout.items {
|
||||||
|
item.paint(cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let list_state = self.state.clone();
|
let list_state = self.state.clone();
|
||||||
let height = bounds.size.height;
|
let height = bounds.size.height;
|
||||||
|
let scroll_top = before_layout.scroll_top;
|
||||||
|
let hitbox_id = *hitbox_id;
|
||||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble
|
if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(cx) {
|
||||||
&& bounds.contains(&event.position)
|
|
||||||
&& cx.was_top_layer(&event.position, cx.stacking_order())
|
|
||||||
{
|
|
||||||
list_state.0.borrow_mut().scroll(
|
list_state.0.borrow_mut().scroll(
|
||||||
&layout_response.scroll_top,
|
&scroll_top,
|
||||||
height,
|
height,
|
||||||
event.delta.pixel_delta(px(20.)),
|
event.delta.pixel_delta(px(20.)),
|
||||||
cx,
|
cx,
|
||||||
padding,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -651,10 +671,6 @@ impl Element for List {
|
||||||
impl IntoElement for List {
|
impl IntoElement for List {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<crate::ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -761,7 +777,7 @@ mod test {
|
||||||
cx.draw(
|
cx.draw(
|
||||||
point(px(0.), px(0.)),
|
point(px(0.), px(0.)),
|
||||||
size(px(100.), px(20.)).into(),
|
size(px(100.), px(20.)).into(),
|
||||||
|_| list(state.clone()).w_full().h_full().z_index(10).into_any(),
|
|_| list(state.clone()).w_full().h_full().into_any(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::{
|
||||||
/// The state that the overlay element uses to track its children.
|
/// The state that the overlay element uses to track its children.
|
||||||
pub struct OverlayState {
|
pub struct OverlayState {
|
||||||
child_layout_ids: SmallVec<[LayoutId; 4]>,
|
child_layout_ids: SmallVec<[LayoutId; 4]>,
|
||||||
|
offset: Point<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An overlay element that can be used to display UI that
|
/// An overlay element that can be used to display UI that
|
||||||
|
@ -69,17 +70,14 @@ impl ParentElement for Overlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Overlay {
|
impl Element for Overlay {
|
||||||
type State = OverlayState;
|
type BeforeLayout = OverlayState;
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
_: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (crate::LayoutId, Self::State) {
|
|
||||||
let child_layout_ids = self
|
let child_layout_ids = self
|
||||||
.children
|
.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|child| child.request_layout(cx))
|
.map(|child| child.before_layout(cx))
|
||||||
.collect::<SmallVec<_>>();
|
.collect::<SmallVec<_>>();
|
||||||
|
|
||||||
let overlay_style = Style {
|
let overlay_style = Style {
|
||||||
|
@ -90,22 +88,28 @@ impl Element for Overlay {
|
||||||
|
|
||||||
let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
|
let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
|
||||||
|
|
||||||
(layout_id, OverlayState { child_layout_ids })
|
(
|
||||||
|
layout_id,
|
||||||
|
OverlayState {
|
||||||
|
child_layout_ids,
|
||||||
|
offset: Point::default(),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: crate::Bounds<crate::Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
element_state: &mut Self::State,
|
before_layout: &mut Self::BeforeLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
if element_state.child_layout_ids.is_empty() {
|
if before_layout.child_layout_ids.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut child_min = point(Pixels::MAX, Pixels::MAX);
|
let mut child_min = point(Pixels::MAX, Pixels::MAX);
|
||||||
let mut child_max = Point::default();
|
let mut child_max = Point::default();
|
||||||
for child_layout_id in &element_state.child_layout_ids {
|
for child_layout_id in &before_layout.child_layout_ids {
|
||||||
let child_bounds = cx.layout_bounds(*child_layout_id);
|
let child_bounds = cx.layout_bounds(*child_layout_id);
|
||||||
child_min = child_min.min(&child_bounds.origin);
|
child_min = child_min.min(&child_bounds.origin);
|
||||||
child_max = child_max.max(&child_bounds.lower_right());
|
child_max = child_max.max(&child_bounds.lower_right());
|
||||||
|
@ -165,25 +169,30 @@ impl Element for Overlay {
|
||||||
desired.origin.y = limits.origin.y;
|
desired.origin.y = limits.origin.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut offset = cx.element_offset() + desired.origin - bounds.origin;
|
before_layout.offset = cx.element_offset() + desired.origin - bounds.origin;
|
||||||
offset = point(offset.x.round(), offset.y.round());
|
before_layout.offset = point(
|
||||||
cx.with_absolute_element_offset(offset, |cx| {
|
before_layout.offset.x.round(),
|
||||||
cx.break_content_mask(|cx| {
|
before_layout.offset.y.round(),
|
||||||
for child in &mut self.children {
|
);
|
||||||
child.paint(cx);
|
|
||||||
|
for child in self.children.drain(..) {
|
||||||
|
cx.defer_draw(child, before_layout.offset, 1);
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
_bounds: crate::Bounds<crate::Pixels>,
|
||||||
|
_before_layout: &mut Self::BeforeLayout,
|
||||||
|
_after_layout: &mut Self::AfterLayout,
|
||||||
|
_cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for Overlay {
|
impl IntoElement for Overlay {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<crate::ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState,
|
Bounds, Element, ElementContext, Hitbox, InteractiveElement, Interactivity, IntoElement,
|
||||||
Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled,
|
LayoutId, Pixels, SharedString, StyleRefinement, Styled,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
@ -27,28 +27,37 @@ impl Svg {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Svg {
|
impl Element for Svg {
|
||||||
type State = InteractiveElementState;
|
type BeforeLayout = ();
|
||||||
|
type AfterLayout = Option<Hitbox>;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
|
let layout_id = self
|
||||||
|
.interactivity
|
||||||
|
.before_layout(cx, |style, cx| cx.request_layout(&style, None));
|
||||||
|
(layout_id, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
element_state: Option<Self::State>,
|
bounds: Bounds<Pixels>,
|
||||||
|
_before_layout: &mut Self::BeforeLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> Option<Hitbox> {
|
||||||
self.interactivity.layout(element_state, cx, |style, cx| {
|
self.interactivity
|
||||||
cx.request_layout(&style, None)
|
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
element_state: &mut Self::State,
|
_before_layout: &mut Self::BeforeLayout,
|
||||||
|
hitbox: &mut Option<Hitbox>,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) where
|
) where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.interactivity
|
self.interactivity
|
||||||
.paint(bounds, bounds.size, element_state, cx, |style, _, cx| {
|
.paint(bounds, hitbox.as_ref(), cx, |style, cx| {
|
||||||
if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
|
if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
|
||||||
cx.paint_svg(bounds, path.clone(), color).log_err();
|
cx.paint_svg(bounds, path.clone(), color).log_err();
|
||||||
}
|
}
|
||||||
|
@ -59,10 +68,6 @@ impl Element for Svg {
|
||||||
impl IntoElement for Svg {
|
impl IntoElement for Svg {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
self.interactivity.element_id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
|
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
|
||||||
HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
|
Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
|
||||||
TOOLTIP_DELAY,
|
TOOLTIP_DELAY,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
@ -17,30 +17,37 @@ use std::{
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
impl Element for &'static str {
|
impl Element for &'static str {
|
||||||
type State = TextState;
|
type BeforeLayout = TextState;
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
_: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
let mut state = TextState::default();
|
let mut state = TextState::default();
|
||||||
let layout_id = state.layout(SharedString::from(*self), None, cx);
|
let layout_id = state.layout(SharedString::from(*self), None, cx);
|
||||||
(layout_id, state)
|
(layout_id, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
|
fn after_layout(
|
||||||
state.paint(bounds, self, cx)
|
&mut self,
|
||||||
|
_bounds: Bounds<Pixels>,
|
||||||
|
_text_state: &mut Self::BeforeLayout,
|
||||||
|
_cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
text_state: &mut TextState,
|
||||||
|
_: &mut (),
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
text_state.paint(bounds, self, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for &'static str {
|
impl IntoElement for &'static str {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -49,41 +56,44 @@ impl IntoElement for &'static str {
|
||||||
impl IntoElement for String {
|
impl IntoElement for String {
|
||||||
type Element = SharedString;
|
type Element = SharedString;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for SharedString {
|
impl Element for SharedString {
|
||||||
type State = TextState;
|
type BeforeLayout = TextState;
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
_: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
let mut state = TextState::default();
|
let mut state = TextState::default();
|
||||||
let layout_id = state.layout(self.clone(), None, cx);
|
let layout_id = state.layout(self.clone(), None, cx);
|
||||||
(layout_id, state)
|
(layout_id, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
_bounds: Bounds<Pixels>,
|
||||||
|
_text_state: &mut Self::BeforeLayout,
|
||||||
|
_cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
text_state: &mut Self::BeforeLayout,
|
||||||
|
_: &mut Self::AfterLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
let text_str: &str = self.as_ref();
|
let text_str: &str = self.as_ref();
|
||||||
state.paint(bounds, text_str, cx)
|
text_state.paint(bounds, text_str, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for SharedString {
|
impl IntoElement for SharedString {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -138,30 +148,37 @@ impl StyledText {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for StyledText {
|
impl Element for StyledText {
|
||||||
type State = TextState;
|
type BeforeLayout = TextState;
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
_: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
let mut state = TextState::default();
|
let mut state = TextState::default();
|
||||||
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
|
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
|
||||||
(layout_id, state)
|
(layout_id, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
fn after_layout(
|
||||||
state.paint(bounds, &self.text, cx)
|
&mut self,
|
||||||
|
_bounds: Bounds<Pixels>,
|
||||||
|
_state: &mut Self::BeforeLayout,
|
||||||
|
_cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
text_state: &mut Self::BeforeLayout,
|
||||||
|
_: &mut Self::AfterLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
text_state.paint(bounds, &self.text, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for StyledText {
|
impl IntoElement for StyledText {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<crate::ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -324,8 +341,8 @@ struct InteractiveTextClickEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct InteractiveTextState {
|
pub struct InteractiveTextState {
|
||||||
text_state: TextState,
|
|
||||||
mouse_down_index: Rc<Cell<Option<usize>>>,
|
mouse_down_index: Rc<Cell<Option<usize>>>,
|
||||||
hovered_index: Rc<Cell<Option<usize>>>,
|
hovered_index: Rc<Cell<Option<usize>>>,
|
||||||
active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
|
active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
|
||||||
|
@ -385,60 +402,53 @@ impl InteractiveText {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for InteractiveText {
|
impl Element for InteractiveText {
|
||||||
type State = InteractiveTextState;
|
type BeforeLayout = TextState;
|
||||||
|
type AfterLayout = Hitbox;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
|
self.text.before_layout(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: Option<Self::State>,
|
bounds: Bounds<Pixels>,
|
||||||
|
state: &mut Self::BeforeLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> Hitbox {
|
||||||
if let Some(InteractiveTextState {
|
self.text.after_layout(bounds, state, cx);
|
||||||
mouse_down_index,
|
cx.insert_hitbox(bounds, false)
|
||||||
hovered_index,
|
|
||||||
active_tooltip,
|
|
||||||
..
|
|
||||||
}) = state
|
|
||||||
{
|
|
||||||
let (layout_id, text_state) = self.text.request_layout(None, cx);
|
|
||||||
let element_state = InteractiveTextState {
|
|
||||||
text_state,
|
|
||||||
mouse_down_index,
|
|
||||||
hovered_index,
|
|
||||||
active_tooltip,
|
|
||||||
};
|
|
||||||
(layout_id, element_state)
|
|
||||||
} else {
|
|
||||||
let (layout_id, text_state) = self.text.request_layout(None, cx);
|
|
||||||
let element_state = InteractiveTextState {
|
|
||||||
text_state,
|
|
||||||
mouse_down_index: Rc::default(),
|
|
||||||
hovered_index: Rc::default(),
|
|
||||||
active_tooltip: Rc::default(),
|
|
||||||
};
|
|
||||||
(layout_id, element_state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
text_state: &mut Self::BeforeLayout,
|
||||||
|
hitbox: &mut Hitbox,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
cx.with_element_state::<InteractiveTextState, _>(
|
||||||
|
Some(self.element_id.clone()),
|
||||||
|
|interactive_state, cx| {
|
||||||
|
let mut interactive_state = interactive_state.unwrap().unwrap_or_default();
|
||||||
if let Some(click_listener) = self.click_listener.take() {
|
if let Some(click_listener) = self.click_listener.take() {
|
||||||
let mouse_position = cx.mouse_position();
|
let mouse_position = cx.mouse_position();
|
||||||
if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
|
if let Some(ix) = text_state.index_for_position(bounds, mouse_position) {
|
||||||
if self
|
if self
|
||||||
.clickable_ranges
|
.clickable_ranges
|
||||||
.iter()
|
.iter()
|
||||||
.any(|range| range.contains(&ix))
|
.any(|range| range.contains(&ix))
|
||||||
{
|
{
|
||||||
let stacking_order = cx.stacking_order().clone();
|
cx.set_cursor_style(crate::CursorStyle::PointingHand, hitbox)
|
||||||
cx.set_cursor_style(crate::CursorStyle::PointingHand, stacking_order);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_state = state.text_state.clone();
|
let text_state = text_state.clone();
|
||||||
let mouse_down = state.mouse_down_index.clone();
|
let mouse_down = interactive_state.mouse_down_index.clone();
|
||||||
if let Some(mouse_down_index) = mouse_down.get() {
|
if let Some(mouse_down_index) = mouse_down.get() {
|
||||||
|
let hitbox = hitbox.clone();
|
||||||
let clickable_ranges = mem::take(&mut self.clickable_ranges);
|
let clickable_ranges = mem::take(&mut self.clickable_ranges);
|
||||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
|
||||||
if let Some(mouse_up_index) =
|
if let Some(mouse_up_index) =
|
||||||
text_state.index_for_position(bounds, event.position)
|
text_state.index_for_position(bounds, event.position)
|
||||||
{
|
{
|
||||||
|
@ -457,8 +467,9 @@ impl Element for InteractiveText {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
let hitbox = hitbox.clone();
|
||||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
|
||||||
if let Some(mouse_down_index) =
|
if let Some(mouse_down_index) =
|
||||||
text_state.index_for_position(bounds, event.position)
|
text_state.index_for_position(bounds, event.position)
|
||||||
{
|
{
|
||||||
|
@ -469,29 +480,38 @@ impl Element for InteractiveText {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(hover_listener) = self.hover_listener.take() {
|
|
||||||
let text_state = state.text_state.clone();
|
cx.on_mouse_event({
|
||||||
let hovered_index = state.hovered_index.clone();
|
let mut hover_listener = self.hover_listener.take();
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
let hitbox = hitbox.clone();
|
||||||
if phase == DispatchPhase::Bubble {
|
let text_state = text_state.clone();
|
||||||
|
let hovered_index = interactive_state.hovered_index.clone();
|
||||||
|
move |event: &MouseMoveEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
|
||||||
let current = hovered_index.get();
|
let current = hovered_index.get();
|
||||||
let updated = text_state.index_for_position(bounds, event.position);
|
let updated = text_state.index_for_position(bounds, event.position);
|
||||||
if current != updated {
|
if current != updated {
|
||||||
hovered_index.set(updated);
|
hovered_index.set(updated);
|
||||||
|
if let Some(hover_listener) = hover_listener.as_ref() {
|
||||||
hover_listener(updated, event.clone(), cx);
|
hover_listener(updated, event.clone(), cx);
|
||||||
|
}
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
|
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
|
||||||
let active_tooltip = state.active_tooltip.clone();
|
let hitbox = hitbox.clone();
|
||||||
let pending_mouse_down = state.mouse_down_index.clone();
|
let active_tooltip = interactive_state.active_tooltip.clone();
|
||||||
let text_state = state.text_state.clone();
|
let pending_mouse_down = interactive_state.mouse_down_index.clone();
|
||||||
|
let text_state = text_state.clone();
|
||||||
|
|
||||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||||
let position = text_state.index_for_position(bounds, event.position);
|
let position = text_state.index_for_position(bounds, event.position);
|
||||||
let is_hovered = position.is_some() && pending_mouse_down.get().is_none();
|
let is_hovered = position.is_some()
|
||||||
|
&& hitbox.is_hovered(cx)
|
||||||
|
&& pending_mouse_down.get().is_none();
|
||||||
if !is_hovered {
|
if !is_hovered {
|
||||||
active_tooltip.take();
|
active_tooltip.take();
|
||||||
return;
|
return;
|
||||||
|
@ -511,12 +531,14 @@ impl Element for InteractiveText {
|
||||||
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let new_tooltip =
|
let new_tooltip =
|
||||||
tooltip_builder(position, cx).map(|tooltip| ActiveTooltip {
|
tooltip_builder(position, cx).map(|tooltip| {
|
||||||
|
ActiveTooltip {
|
||||||
tooltip: Some(AnyTooltip {
|
tooltip: Some(AnyTooltip {
|
||||||
view: tooltip,
|
view: tooltip,
|
||||||
cursor_offset: cx.mouse_position(),
|
cursor_offset: cx.mouse_position(),
|
||||||
}),
|
}),
|
||||||
_task: None,
|
_task: None,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
*active_tooltip.borrow_mut() = new_tooltip;
|
*active_tooltip.borrow_mut() = new_tooltip;
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
|
@ -531,12 +553,12 @@ impl Element for InteractiveText {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let active_tooltip = state.active_tooltip.clone();
|
let active_tooltip = interactive_state.active_tooltip.clone();
|
||||||
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
|
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
|
||||||
active_tooltip.take();
|
active_tooltip.take();
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(tooltip) = state
|
if let Some(tooltip) = interactive_state
|
||||||
.active_tooltip
|
.active_tooltip
|
||||||
.clone()
|
.clone()
|
||||||
.borrow()
|
.borrow()
|
||||||
|
@ -547,17 +569,17 @@ impl Element for InteractiveText {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.text.paint(bounds, &mut state.text_state, cx)
|
self.text.paint(bounds, text_state, &mut (), cx);
|
||||||
|
|
||||||
|
((), Some(interactive_state))
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for InteractiveText {
|
impl IntoElement for InteractiveText {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
Some(self.element_id.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
|
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
|
||||||
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
|
ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render,
|
||||||
Pixels, Render, ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
|
ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
||||||
|
@ -42,13 +42,13 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
UniformList {
|
UniformList {
|
||||||
id: id.clone(),
|
|
||||||
item_count,
|
item_count,
|
||||||
item_to_measure_index: 0,
|
item_to_measure_index: 0,
|
||||||
render_items: Box::new(render_range),
|
render_items: Box::new(render_range),
|
||||||
interactivity: Interactivity {
|
interactivity: Interactivity {
|
||||||
element_id: Some(id),
|
element_id: Some(id),
|
||||||
base_style: Box::new(base_style),
|
base_style: Box::new(base_style),
|
||||||
|
occlude_mouse: true,
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
location: Some(*core::panic::Location::caller()),
|
location: Some(*core::panic::Location::caller()),
|
||||||
|
@ -61,7 +61,6 @@ where
|
||||||
|
|
||||||
/// A list element for efficiently laying out and displaying a list of uniform-height elements.
|
/// A list element for efficiently laying out and displaying a list of uniform-height elements.
|
||||||
pub struct UniformList {
|
pub struct UniformList {
|
||||||
id: ElementId,
|
|
||||||
item_count: usize,
|
item_count: usize,
|
||||||
item_to_measure_index: usize,
|
item_to_measure_index: usize,
|
||||||
render_items:
|
render_items:
|
||||||
|
@ -70,6 +69,12 @@ pub struct UniformList {
|
||||||
scroll_handle: Option<UniformListScrollHandle>,
|
scroll_handle: Option<UniformListScrollHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Frame state used by the [UniformList].
|
||||||
|
pub struct UniformListFrameState {
|
||||||
|
item_size: Size<Pixels>,
|
||||||
|
items: SmallVec<[AnyElement; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A handle for controlling the scroll position of a uniform list.
|
/// A handle for controlling the scroll position of a uniform list.
|
||||||
/// This should be stored in your view and passed to the uniform_list on each frame.
|
/// This should be stored in your view and passed to the uniform_list on each frame.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
@ -99,72 +104,47 @@ impl Styled for UniformList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct UniformListState {
|
|
||||||
interactive: InteractiveElementState,
|
|
||||||
item_size: Size<Pixels>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element for UniformList {
|
impl Element for UniformList {
|
||||||
type State = UniformListState;
|
type BeforeLayout = UniformListFrameState;
|
||||||
|
type AfterLayout = Option<Hitbox>;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
state: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
let max_items = self.item_count;
|
let max_items = self.item_count;
|
||||||
let item_size = state
|
let item_size = self.measure_item(None, cx);
|
||||||
.as_ref()
|
let layout_id = self.interactivity.before_layout(cx, |style, cx| {
|
||||||
.map(|s| s.item_size)
|
cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| {
|
||||||
.unwrap_or_else(|| self.measure_item(None, cx));
|
|
||||||
|
|
||||||
let (layout_id, interactive) =
|
|
||||||
self.interactivity
|
|
||||||
.layout(state.map(|s| s.interactive), cx, |style, cx| {
|
|
||||||
cx.request_measured_layout(
|
|
||||||
style,
|
|
||||||
move |known_dimensions, available_space, _cx| {
|
|
||||||
let desired_height = item_size.height * max_items;
|
let desired_height = item_size.height * max_items;
|
||||||
let width =
|
let width = known_dimensions
|
||||||
known_dimensions
|
|
||||||
.width
|
.width
|
||||||
.unwrap_or(match available_space.width {
|
.unwrap_or(match available_space.width {
|
||||||
AvailableSpace::Definite(x) => x,
|
AvailableSpace::Definite(x) => x,
|
||||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
|
||||||
item_size.width
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let height = match available_space.height {
|
let height = match available_space.height {
|
||||||
AvailableSpace::Definite(height) => desired_height.min(height),
|
AvailableSpace::Definite(height) => desired_height.min(height),
|
||||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
|
||||||
desired_height
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
size(width, height)
|
size(width, height)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let element_state = UniformListState {
|
(
|
||||||
interactive,
|
layout_id,
|
||||||
|
UniformListFrameState {
|
||||||
item_size,
|
item_size,
|
||||||
};
|
items: SmallVec::new(),
|
||||||
|
},
|
||||||
(layout_id, element_state)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<crate::Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
element_state: &mut Self::State,
|
before_layout: &mut Self::BeforeLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) -> Option<Hitbox> {
|
||||||
let style =
|
let style = self.interactivity.compute_style(None, cx);
|
||||||
self.interactivity
|
|
||||||
.compute_style(Some(bounds), &mut element_state.interactive, cx);
|
|
||||||
let border = style.border_widths.to_pixels(cx.rem_size());
|
let border = style.border_widths.to_pixels(cx.rem_size());
|
||||||
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
||||||
|
|
||||||
|
@ -174,17 +154,12 @@ impl Element for UniformList {
|
||||||
- point(border.right + padding.right, border.bottom + padding.bottom),
|
- point(border.right + padding.right, border.bottom + padding.bottom),
|
||||||
);
|
);
|
||||||
|
|
||||||
let item_size = element_state.item_size;
|
|
||||||
let content_size = Size {
|
let content_size = Size {
|
||||||
width: padded_bounds.size.width,
|
width: padded_bounds.size.width,
|
||||||
height: item_size.height * self.item_count + padding.top + padding.bottom,
|
height: before_layout.item_size.height * self.item_count + padding.top + padding.bottom,
|
||||||
};
|
};
|
||||||
|
|
||||||
let shared_scroll_offset = element_state
|
let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap();
|
||||||
.interactive
|
|
||||||
.scroll_offset
|
|
||||||
.get_or_insert_with(Rc::default)
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
|
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
|
||||||
let shared_scroll_to_item = self
|
let shared_scroll_to_item = self
|
||||||
|
@ -192,12 +167,11 @@ impl Element for UniformList {
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.and_then(|handle| handle.deferred_scroll_to_item.take());
|
.and_then(|handle| handle.deferred_scroll_to_item.take());
|
||||||
|
|
||||||
self.interactivity.paint(
|
self.interactivity.after_layout(
|
||||||
bounds,
|
bounds,
|
||||||
content_size,
|
content_size,
|
||||||
&mut element_state.interactive,
|
|
||||||
cx,
|
cx,
|
||||||
|style, mut scroll_offset, cx| {
|
|style, mut scroll_offset, hitbox, cx| {
|
||||||
let border = style.border_widths.to_pixels(cx.rem_size());
|
let border = style.border_widths.to_pixels(cx.rem_size());
|
||||||
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
||||||
|
|
||||||
|
@ -240,36 +214,45 @@ impl Element for UniformList {
|
||||||
..cmp::min(last_visible_element_ix, self.item_count);
|
..cmp::min(last_visible_element_ix, self.item_count);
|
||||||
|
|
||||||
let mut items = (self.render_items)(visible_range.clone(), cx);
|
let mut items = (self.render_items)(visible_range.clone(), cx);
|
||||||
cx.with_z_index(1, |cx| {
|
|
||||||
let content_mask = ContentMask { bounds };
|
let content_mask = ContentMask { bounds };
|
||||||
cx.with_content_mask(Some(content_mask), |cx| {
|
cx.with_content_mask(Some(content_mask), |cx| {
|
||||||
for (item, ix) in items.iter_mut().zip(visible_range) {
|
for (mut item, ix) in items.into_iter().zip(visible_range) {
|
||||||
let item_origin = padded_bounds.origin
|
let item_origin = padded_bounds.origin
|
||||||
+ point(
|
+ point(px(0.), item_height * ix + scroll_offset.y + padding.top);
|
||||||
px(0.),
|
|
||||||
item_height * ix + scroll_offset.y + padding.top,
|
|
||||||
);
|
|
||||||
let available_space = size(
|
let available_space = size(
|
||||||
AvailableSpace::Definite(padded_bounds.size.width),
|
AvailableSpace::Definite(padded_bounds.size.width),
|
||||||
AvailableSpace::Definite(item_height),
|
AvailableSpace::Definite(item_height),
|
||||||
);
|
);
|
||||||
item.draw(item_origin, available_space, cx);
|
item.layout(item_origin, available_space, cx);
|
||||||
|
before_layout.items.push(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hitbox
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<crate::Pixels>,
|
||||||
|
before_layout: &mut Self::BeforeLayout,
|
||||||
|
hitbox: &mut Option<Hitbox>,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
self.interactivity
|
||||||
|
.paint(bounds, hitbox.as_ref(), cx, |_, cx| {
|
||||||
|
for item in &mut before_layout.items {
|
||||||
|
item.paint(cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for UniformList {
|
impl IntoElement for UniformList {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<crate::ElementId> {
|
|
||||||
Some(self.id.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -301,7 +284,7 @@ impl UniformList {
|
||||||
|
|
||||||
/// Track and render scroll state of this list with reference to the given scroll handle.
|
/// Track and render scroll state of this list with reference to the given scroll handle.
|
||||||
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
|
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
|
||||||
self.interactivity.scroll_handle = Some(handle.base_handle.clone());
|
self.interactivity.tracked_scroll_handle = Some(handle.base_handle.clone());
|
||||||
self.scroll_handle = Some(handle);
|
self.scroll_handle = Some(handle);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -828,6 +828,28 @@ where
|
||||||
y: self.origin.y.clone() + self.size.height.clone().half(),
|
y: self.origin.y.clone() + self.size.height.clone().half(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates the half perimeter of a rectangle defined by the bounds.
|
||||||
|
///
|
||||||
|
/// The half perimeter is calculated as the sum of the width and the height of the rectangle.
|
||||||
|
/// This method is generic over the type `T` which must implement the `Sub` trait to allow
|
||||||
|
/// calculation of the width and height from the bounds' origin and size, as well as the `Add` trait
|
||||||
|
/// to sum the width and height for the half perimeter.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use zed::{Bounds, Point, Size};
|
||||||
|
/// let bounds = Bounds {
|
||||||
|
/// origin: Point { x: 0, y: 0 },
|
||||||
|
/// size: Size { width: 10, height: 20 },
|
||||||
|
/// };
|
||||||
|
/// let half_perimeter = bounds.half_perimeter();
|
||||||
|
/// assert_eq!(half_perimeter, 30);
|
||||||
|
/// ```
|
||||||
|
pub fn half_perimeter(&self) -> T {
|
||||||
|
self.size.width.clone() + self.size.height.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
|
impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
|
||||||
|
@ -1145,6 +1167,22 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the bounds represent an empty area.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns `true` if either the width or the height of the bounds is less than or equal to zero, indicating an empty area.
|
||||||
|
impl<T: PartialOrd + Default + Debug + Clone> Bounds<T> {
|
||||||
|
/// Checks if the bounds represent an empty area.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns `true` if either the width or the height of the bounds is less than or equal to zero, indicating an empty area.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.size.width <= T::default() || self.size.height <= T::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Bounds<Pixels> {
|
impl Bounds<Pixels> {
|
||||||
/// Scales the bounds by a given factor, typically used to adjust for display scaling.
|
/// Scales the bounds by a given factor, typically used to adjust for display scaling.
|
||||||
///
|
///
|
||||||
|
@ -2617,6 +2655,12 @@ pub trait Half {
|
||||||
fn half(&self) -> Self;
|
fn half(&self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Half for i32 {
|
||||||
|
fn half(&self) -> Self {
|
||||||
|
self / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Half for f32 {
|
impl Half for f32 {
|
||||||
fn half(&self) -> Self {
|
fn half(&self) -> Self {
|
||||||
self / 2.
|
self / 2.
|
||||||
|
|
|
@ -70,6 +70,7 @@ mod app;
|
||||||
|
|
||||||
mod arena;
|
mod arena;
|
||||||
mod assets;
|
mod assets;
|
||||||
|
mod bounds_tree;
|
||||||
mod color;
|
mod color;
|
||||||
mod element;
|
mod element;
|
||||||
mod elements;
|
mod elements;
|
||||||
|
|
|
@ -54,11 +54,12 @@ use crate::{
|
||||||
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext,
|
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext,
|
||||||
};
|
};
|
||||||
use collections::FxHashMap;
|
use collections::FxHashMap;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
mem,
|
mem,
|
||||||
|
ops::Range,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ pub(crate) struct DispatchNodeId(usize);
|
||||||
pub(crate) struct DispatchTree {
|
pub(crate) struct DispatchTree {
|
||||||
node_stack: Vec<DispatchNodeId>,
|
node_stack: Vec<DispatchNodeId>,
|
||||||
pub(crate) context_stack: Vec<KeyContext>,
|
pub(crate) context_stack: Vec<KeyContext>,
|
||||||
|
view_stack: Vec<EntityId>,
|
||||||
nodes: Vec<DispatchNode>,
|
nodes: Vec<DispatchNode>,
|
||||||
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
|
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
|
||||||
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
|
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
|
||||||
|
@ -81,11 +83,28 @@ pub(crate) struct DispatchNode {
|
||||||
pub key_listeners: Vec<KeyListener>,
|
pub key_listeners: Vec<KeyListener>,
|
||||||
pub action_listeners: Vec<DispatchActionListener>,
|
pub action_listeners: Vec<DispatchActionListener>,
|
||||||
pub context: Option<KeyContext>,
|
pub context: Option<KeyContext>,
|
||||||
focus_id: Option<FocusId>,
|
pub focus_id: Option<FocusId>,
|
||||||
view_id: Option<EntityId>,
|
view_id: Option<EntityId>,
|
||||||
parent: Option<DispatchNodeId>,
|
parent: Option<DispatchNodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ReusedSubtree {
|
||||||
|
old_range: Range<usize>,
|
||||||
|
new_range: Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReusedSubtree {
|
||||||
|
pub fn refresh_node_id(&self, node_id: DispatchNodeId) -> DispatchNodeId {
|
||||||
|
debug_assert!(
|
||||||
|
self.old_range.contains(&node_id.0),
|
||||||
|
"node {} was not part of the reused subtree {:?}",
|
||||||
|
node_id.0,
|
||||||
|
self.old_range
|
||||||
|
);
|
||||||
|
DispatchNodeId((node_id.0 - self.old_range.start) + self.new_range.start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut ElementContext)>;
|
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut ElementContext)>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -99,6 +118,7 @@ impl DispatchTree {
|
||||||
Self {
|
Self {
|
||||||
node_stack: Vec::new(),
|
node_stack: Vec::new(),
|
||||||
context_stack: Vec::new(),
|
context_stack: Vec::new(),
|
||||||
|
view_stack: Vec::new(),
|
||||||
nodes: Vec::new(),
|
nodes: Vec::new(),
|
||||||
focusable_node_ids: FxHashMap::default(),
|
focusable_node_ids: FxHashMap::default(),
|
||||||
view_node_ids: FxHashMap::default(),
|
view_node_ids: FxHashMap::default(),
|
||||||
|
@ -111,72 +131,124 @@ impl DispatchTree {
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.node_stack.clear();
|
self.node_stack.clear();
|
||||||
self.context_stack.clear();
|
self.context_stack.clear();
|
||||||
|
self.view_stack.clear();
|
||||||
self.nodes.clear();
|
self.nodes.clear();
|
||||||
self.focusable_node_ids.clear();
|
self.focusable_node_ids.clear();
|
||||||
self.view_node_ids.clear();
|
self.view_node_ids.clear();
|
||||||
self.keystroke_matchers.clear();
|
self.keystroke_matchers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_node(
|
pub fn len(&self) -> usize {
|
||||||
&mut self,
|
self.nodes.len()
|
||||||
context: Option<KeyContext>,
|
}
|
||||||
focus_id: Option<FocusId>,
|
|
||||||
view_id: Option<EntityId>,
|
pub fn push_node(&mut self) -> DispatchNodeId {
|
||||||
) {
|
|
||||||
let parent = self.node_stack.last().copied();
|
let parent = self.node_stack.last().copied();
|
||||||
let node_id = DispatchNodeId(self.nodes.len());
|
let node_id = DispatchNodeId(self.nodes.len());
|
||||||
|
|
||||||
self.nodes.push(DispatchNode {
|
self.nodes.push(DispatchNode {
|
||||||
parent,
|
parent,
|
||||||
focus_id,
|
|
||||||
view_id,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
self.node_stack.push(node_id);
|
self.node_stack.push(node_id);
|
||||||
|
node_id
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(context) = context {
|
pub fn set_active_node(&mut self, node_id: DispatchNodeId) {
|
||||||
|
let next_node_parent = self.nodes[node_id.0].parent;
|
||||||
|
while self.node_stack.last().copied() != next_node_parent && !self.node_stack.is_empty() {
|
||||||
|
self.pop_node();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.node_stack.last().copied() == next_node_parent {
|
||||||
|
self.node_stack.push(node_id);
|
||||||
|
let active_node = &self.nodes[node_id.0];
|
||||||
|
if let Some(view_id) = active_node.view_id {
|
||||||
|
self.view_stack.push(view_id)
|
||||||
|
}
|
||||||
|
if let Some(context) = active_node.context.clone() {
|
||||||
|
self.context_stack.push(context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(self.node_stack.len(), 0);
|
||||||
|
|
||||||
|
let mut current_node_id = Some(node_id);
|
||||||
|
while let Some(node_id) = current_node_id {
|
||||||
|
let node = &self.nodes[node_id.0];
|
||||||
|
if let Some(context) = node.context.clone() {
|
||||||
|
self.context_stack.push(context);
|
||||||
|
}
|
||||||
|
if node.view_id.is_some() {
|
||||||
|
self.view_stack.push(node.view_id.unwrap());
|
||||||
|
}
|
||||||
|
self.node_stack.push(node_id);
|
||||||
|
current_node_id = node.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context_stack.reverse();
|
||||||
|
self.view_stack.reverse();
|
||||||
|
self.node_stack.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_key_context(&mut self, context: KeyContext) {
|
||||||
self.active_node().context = Some(context.clone());
|
self.active_node().context = Some(context.clone());
|
||||||
self.context_stack.push(context);
|
self.context_stack.push(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(focus_id) = focus_id {
|
pub fn set_focus_id(&mut self, focus_id: FocusId) {
|
||||||
|
let node_id = *self.node_stack.last().unwrap();
|
||||||
|
self.nodes[node_id.0].focus_id = Some(focus_id);
|
||||||
self.focusable_node_ids.insert(focus_id, node_id);
|
self.focusable_node_ids.insert(focus_id, node_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(view_id) = view_id {
|
pub fn set_view_id(&mut self, view_id: EntityId) {
|
||||||
|
if self.view_stack.last().copied() != Some(view_id) {
|
||||||
|
let node_id = *self.node_stack.last().unwrap();
|
||||||
|
self.nodes[node_id.0].view_id = Some(view_id);
|
||||||
self.view_node_ids.insert(view_id, node_id);
|
self.view_node_ids.insert(view_id, node_id);
|
||||||
|
self.view_stack.push(view_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_node(&mut self) {
|
pub fn pop_node(&mut self) {
|
||||||
let node = &self.nodes[self.active_node_id().0];
|
let node = &self.nodes[self.active_node_id().unwrap().0];
|
||||||
if node.context.is_some() {
|
if node.context.is_some() {
|
||||||
self.context_stack.pop();
|
self.context_stack.pop();
|
||||||
}
|
}
|
||||||
|
if node.view_id.is_some() {
|
||||||
|
self.view_stack.pop();
|
||||||
|
}
|
||||||
self.node_stack.pop();
|
self.node_stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_node(&mut self, source: &mut DispatchNode) {
|
fn move_node(&mut self, source: &mut DispatchNode) {
|
||||||
self.push_node(source.context.take(), source.focus_id, source.view_id);
|
self.push_node();
|
||||||
|
if let Some(context) = source.context.clone() {
|
||||||
|
self.set_key_context(context);
|
||||||
|
}
|
||||||
|
if let Some(focus_id) = source.focus_id {
|
||||||
|
self.set_focus_id(focus_id);
|
||||||
|
}
|
||||||
|
if let Some(view_id) = source.view_id {
|
||||||
|
self.set_view_id(view_id);
|
||||||
|
}
|
||||||
|
|
||||||
let target = self.active_node();
|
let target = self.active_node();
|
||||||
target.key_listeners = mem::take(&mut source.key_listeners);
|
target.key_listeners = mem::take(&mut source.key_listeners);
|
||||||
target.action_listeners = mem::take(&mut source.action_listeners);
|
target.action_listeners = mem::take(&mut source.action_listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reuse_view(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> {
|
pub fn reuse_subtree(&mut self, old_range: Range<usize>, source: &mut Self) -> ReusedSubtree {
|
||||||
let view_source_node_id = source
|
let new_range = self.nodes.len()..self.nodes.len() + old_range.len();
|
||||||
.view_node_ids
|
|
||||||
.get(&view_id)
|
|
||||||
.expect("view should exist in previous dispatch tree");
|
|
||||||
let view_source_node = &mut source.nodes[view_source_node_id.0];
|
|
||||||
self.move_node(view_source_node);
|
|
||||||
|
|
||||||
let mut grafted_view_ids = smallvec![view_id];
|
let mut source_stack = vec![];
|
||||||
let mut source_stack = vec![*view_source_node_id];
|
|
||||||
for (source_node_id, source_node) in source
|
for (source_node_id, source_node) in source
|
||||||
.nodes
|
.nodes
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.skip(view_source_node_id.0 + 1)
|
.skip(old_range.start)
|
||||||
|
.take(old_range.len())
|
||||||
{
|
{
|
||||||
let source_node_id = DispatchNodeId(source_node_id);
|
let source_node_id = DispatchNodeId(source_node_id);
|
||||||
while let Some(source_ancestor) = source_stack.last() {
|
while let Some(source_ancestor) = source_stack.last() {
|
||||||
|
@ -188,15 +260,8 @@ impl DispatchTree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if source_stack.is_empty() {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
source_stack.push(source_node_id);
|
source_stack.push(source_node_id);
|
||||||
self.move_node(source_node);
|
self.move_node(source_node);
|
||||||
if let Some(view_id) = source_node.view_id {
|
|
||||||
grafted_view_ids.push(view_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while !source_stack.is_empty() {
|
while !source_stack.is_empty() {
|
||||||
|
@ -204,7 +269,10 @@ impl DispatchTree {
|
||||||
self.pop_node();
|
self.pop_node();
|
||||||
}
|
}
|
||||||
|
|
||||||
grafted_view_ids
|
ReusedSubtree {
|
||||||
|
old_range,
|
||||||
|
new_range,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_pending_keystrokes(&mut self) {
|
pub fn clear_pending_keystrokes(&mut self) {
|
||||||
|
@ -424,7 +492,7 @@ impl DispatchTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_node(&mut self) -> &mut DispatchNode {
|
fn active_node(&mut self) -> &mut DispatchNode {
|
||||||
let active_node_id = self.active_node_id();
|
let active_node_id = self.active_node_id().unwrap();
|
||||||
&mut self.nodes[active_node_id.0]
|
&mut self.nodes[active_node_id.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,8 +505,8 @@ impl DispatchTree {
|
||||||
DispatchNodeId(0)
|
DispatchNodeId(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_node_id(&self) -> DispatchNodeId {
|
pub fn active_node_id(&self) -> Option<DispatchNodeId> {
|
||||||
*self.node_stack.last().unwrap()
|
self.node_stack.last().copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// todo(linux): remove
|
// todo(linux): remove
|
||||||
#![cfg_attr(target_os = "linux", allow(dead_code))]
|
#![cfg_attr(target_os = "linux", allow(dead_code))]
|
||||||
// todo(windows): remove
|
// todo("windows"): remove
|
||||||
#![cfg_attr(windows, allow(dead_code))]
|
#![cfg_attr(windows, allow(dead_code))]
|
||||||
|
|
||||||
mod app_menu;
|
mod app_menu;
|
||||||
|
@ -68,7 +68,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||||
Rc::new(LinuxPlatform::new())
|
Rc::new(LinuxPlatform::new())
|
||||||
}
|
}
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||||
Rc::new(WindowsPlatform::new())
|
Rc::new(WindowsPlatform::new())
|
||||||
|
|
|
@ -292,6 +292,7 @@ impl MetalRenderer {
|
||||||
znear: 0.0,
|
znear: 0.0,
|
||||||
zfar: 1.0,
|
zfar: 1.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
for batch in scene.batches() {
|
for batch in scene.batches() {
|
||||||
let ok = match batch {
|
let ok = match batch {
|
||||||
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
|
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
|
||||||
|
|
|
@ -126,7 +126,7 @@ impl Platform for TestPlatform {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
return Arc::new(crate::platform::mac::MacTextSystem::new());
|
return Arc::new(crate::platform::mac::MacTextSystem::new());
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,22 @@
|
||||||
// todo(windows): remove
|
// todo("windows"): remove
|
||||||
#![cfg_attr(windows, allow(dead_code))]
|
#![cfg_attr(windows, allow(dead_code))]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels,
|
bounds_tree::BoundsTree, point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges,
|
||||||
Point, ScaledPixels, StackingOrder,
|
Hsla, Pixels, Point, ScaledPixels,
|
||||||
};
|
};
|
||||||
use collections::{BTreeMap, FxHashSet};
|
use std::{fmt::Debug, iter::Peekable, ops::Range, slice};
|
||||||
use std::{fmt::Debug, iter::Peekable, slice};
|
|
||||||
|
|
||||||
#[allow(non_camel_case_types, unused)]
|
#[allow(non_camel_case_types, unused)]
|
||||||
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
||||||
|
|
||||||
pub(crate) type LayerId = u32;
|
|
||||||
pub(crate) type DrawOrder = u32;
|
pub(crate) type DrawOrder = u32;
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub(crate) struct ViewId {
|
|
||||||
low_bits: u32,
|
|
||||||
high_bits: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EntityId> for ViewId {
|
|
||||||
fn from(value: EntityId) -> Self {
|
|
||||||
let value = value.as_u64();
|
|
||||||
Self {
|
|
||||||
low_bits: value as u32,
|
|
||||||
high_bits: (value >> 32) as u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ViewId> for EntityId {
|
|
||||||
fn from(value: ViewId) -> Self {
|
|
||||||
let value = (value.low_bits as u64) | ((value.high_bits as u64) << 32);
|
|
||||||
value.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct Scene {
|
pub(crate) struct Scene {
|
||||||
last_layer: Option<(StackingOrder, LayerId)>,
|
pub(crate) paint_operations: Vec<PaintOperation>,
|
||||||
layers_by_order: BTreeMap<StackingOrder, LayerId>,
|
primitive_bounds: BoundsTree<ScaledPixels>,
|
||||||
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
|
layer_stack: Vec<DrawOrder>,
|
||||||
pub(crate) shadows: Vec<Shadow>,
|
pub(crate) shadows: Vec<Shadow>,
|
||||||
pub(crate) quads: Vec<Quad>,
|
pub(crate) quads: Vec<Quad>,
|
||||||
pub(crate) paths: Vec<Path<ScaledPixels>>,
|
pub(crate) paths: Vec<Path<ScaledPixels>>,
|
||||||
|
@ -54,12 +28,12 @@ pub(crate) struct Scene {
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.last_layer = None;
|
self.paint_operations.clear();
|
||||||
self.layers_by_order.clear();
|
self.primitive_bounds.clear();
|
||||||
self.orders_by_layer.clear();
|
self.layer_stack.clear();
|
||||||
|
self.paths.clear();
|
||||||
self.shadows.clear();
|
self.shadows.clear();
|
||||||
self.quads.clear();
|
self.quads.clear();
|
||||||
self.paths.clear();
|
|
||||||
self.underlines.clear();
|
self.underlines.clear();
|
||||||
self.monochrome_sprites.clear();
|
self.monochrome_sprites.clear();
|
||||||
self.polychrome_sprites.clear();
|
self.polychrome_sprites.clear();
|
||||||
|
@ -70,6 +44,92 @@ impl Scene {
|
||||||
&self.paths
|
&self.paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.paint_operations.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
|
||||||
|
let order = self.primitive_bounds.insert(bounds);
|
||||||
|
self.layer_stack.push(order);
|
||||||
|
self.paint_operations
|
||||||
|
.push(PaintOperation::StartLayer(bounds));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_layer(&mut self) {
|
||||||
|
self.layer_stack.pop();
|
||||||
|
self.paint_operations.push(PaintOperation::EndLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
|
||||||
|
let mut primitive = primitive.into();
|
||||||
|
let clipped_bounds = primitive
|
||||||
|
.bounds()
|
||||||
|
.intersect(&primitive.content_mask().bounds);
|
||||||
|
|
||||||
|
if clipped_bounds.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let order = self
|
||||||
|
.layer_stack
|
||||||
|
.last()
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds));
|
||||||
|
match &mut primitive {
|
||||||
|
Primitive::Shadow(shadow) => {
|
||||||
|
shadow.order = order;
|
||||||
|
self.shadows.push(shadow.clone());
|
||||||
|
}
|
||||||
|
Primitive::Quad(quad) => {
|
||||||
|
quad.order = order;
|
||||||
|
self.quads.push(quad.clone());
|
||||||
|
}
|
||||||
|
Primitive::Path(path) => {
|
||||||
|
path.order = order;
|
||||||
|
path.id = PathId(self.paths.len());
|
||||||
|
self.paths.push(path.clone());
|
||||||
|
}
|
||||||
|
Primitive::Underline(underline) => {
|
||||||
|
underline.order = order;
|
||||||
|
self.underlines.push(underline.clone());
|
||||||
|
}
|
||||||
|
Primitive::MonochromeSprite(sprite) => {
|
||||||
|
sprite.order = order;
|
||||||
|
self.monochrome_sprites.push(sprite.clone());
|
||||||
|
}
|
||||||
|
Primitive::PolychromeSprite(sprite) => {
|
||||||
|
sprite.order = order;
|
||||||
|
self.polychrome_sprites.push(sprite.clone());
|
||||||
|
}
|
||||||
|
Primitive::Surface(surface) => {
|
||||||
|
surface.order = order;
|
||||||
|
self.surfaces.push(surface.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.paint_operations
|
||||||
|
.push(PaintOperation::Primitive(primitive));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
|
||||||
|
for operation in &prev_scene.paint_operations[range] {
|
||||||
|
match operation {
|
||||||
|
PaintOperation::Primitive(primitive) => self.insert_primitive(primitive.clone()),
|
||||||
|
PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
|
||||||
|
PaintOperation::EndLayer => self.pop_layer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(&mut self) {
|
||||||
|
self.shadows.sort();
|
||||||
|
self.quads.sort();
|
||||||
|
self.paths.sort();
|
||||||
|
self.underlines.sort();
|
||||||
|
self.monochrome_sprites.sort();
|
||||||
|
self.polychrome_sprites.sort();
|
||||||
|
self.surfaces.sort();
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
|
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||||
BatchIterator {
|
BatchIterator {
|
||||||
shadows: &self.shadows,
|
shadows: &self.shadows,
|
||||||
|
@ -95,163 +155,61 @@ impl Scene {
|
||||||
surfaces_iter: self.surfaces.iter().peekable(),
|
surfaces_iter: self.surfaces.iter().peekable(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||||
let primitive = primitive.into();
|
pub(crate) enum PrimitiveKind {
|
||||||
let clipped_bounds = primitive
|
Shadow,
|
||||||
.bounds()
|
#[default]
|
||||||
.intersect(&primitive.content_mask().bounds);
|
Quad,
|
||||||
if clipped_bounds.size.width <= ScaledPixels(0.)
|
Path,
|
||||||
|| clipped_bounds.size.height <= ScaledPixels(0.)
|
Underline,
|
||||||
{
|
MonochromeSprite,
|
||||||
return;
|
PolychromeSprite,
|
||||||
}
|
Surface,
|
||||||
|
}
|
||||||
|
|
||||||
let layer_id = self.layer_id_for_order(order);
|
pub(crate) enum PaintOperation {
|
||||||
match primitive {
|
Primitive(Primitive),
|
||||||
Primitive::Shadow(mut shadow) => {
|
StartLayer(Bounds<ScaledPixels>),
|
||||||
shadow.layer_id = layer_id;
|
EndLayer,
|
||||||
self.shadows.push(shadow);
|
}
|
||||||
}
|
|
||||||
Primitive::Quad(mut quad) => {
|
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
quad.layer_id = layer_id;
|
pub(crate) enum Primitive {
|
||||||
self.quads.push(quad);
|
Shadow(Shadow),
|
||||||
}
|
Quad(Quad),
|
||||||
Primitive::Path(mut path) => {
|
Path(Path<ScaledPixels>),
|
||||||
path.layer_id = layer_id;
|
Underline(Underline),
|
||||||
path.id = PathId(self.paths.len());
|
MonochromeSprite(MonochromeSprite),
|
||||||
self.paths.push(path);
|
PolychromeSprite(PolychromeSprite),
|
||||||
}
|
Surface(Surface),
|
||||||
Primitive::Underline(mut underline) => {
|
}
|
||||||
underline.layer_id = layer_id;
|
|
||||||
self.underlines.push(underline);
|
impl Primitive {
|
||||||
}
|
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
|
||||||
Primitive::MonochromeSprite(mut sprite) => {
|
match self {
|
||||||
sprite.layer_id = layer_id;
|
Primitive::Shadow(shadow) => &shadow.bounds,
|
||||||
self.monochrome_sprites.push(sprite);
|
Primitive::Quad(quad) => &quad.bounds,
|
||||||
}
|
Primitive::Path(path) => &path.bounds,
|
||||||
Primitive::PolychromeSprite(mut sprite) => {
|
Primitive::Underline(underline) => &underline.bounds,
|
||||||
sprite.layer_id = layer_id;
|
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
||||||
self.polychrome_sprites.push(sprite);
|
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
||||||
}
|
Primitive::Surface(surface) => &surface.bounds,
|
||||||
Primitive::Surface(mut surface) => {
|
|
||||||
surface.layer_id = layer_id;
|
|
||||||
self.surfaces.push(surface);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layer_id_for_order(&mut self, order: &StackingOrder) -> LayerId {
|
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
|
||||||
if let Some((last_order, last_layer_id)) = self.last_layer.as_ref() {
|
match self {
|
||||||
if order == last_order {
|
Primitive::Shadow(shadow) => &shadow.content_mask,
|
||||||
return *last_layer_id;
|
Primitive::Quad(quad) => &quad.content_mask,
|
||||||
|
Primitive::Path(path) => &path.content_mask,
|
||||||
|
Primitive::Underline(underline) => &underline.content_mask,
|
||||||
|
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
||||||
|
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
||||||
|
Primitive::Surface(surface) => &surface.content_mask,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
|
|
||||||
*layer_id
|
|
||||||
} else {
|
|
||||||
let next_id = self.layers_by_order.len() as LayerId;
|
|
||||||
self.layers_by_order.insert(order.clone(), next_id);
|
|
||||||
self.orders_by_layer.insert(next_id, order.clone());
|
|
||||||
next_id
|
|
||||||
};
|
|
||||||
self.last_layer = Some((order.clone(), layer_id));
|
|
||||||
layer_id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reuse_views(&mut self, views: &FxHashSet<EntityId>, prev_scene: &mut Self) {
|
|
||||||
for shadow in prev_scene.shadows.drain(..) {
|
|
||||||
if views.contains(&shadow.view_id.into()) {
|
|
||||||
let order = &prev_scene.orders_by_layer[&shadow.layer_id];
|
|
||||||
self.insert(order, shadow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for quad in prev_scene.quads.drain(..) {
|
|
||||||
if views.contains(&quad.view_id.into()) {
|
|
||||||
let order = &prev_scene.orders_by_layer[&quad.layer_id];
|
|
||||||
self.insert(order, quad);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for path in prev_scene.paths.drain(..) {
|
|
||||||
if views.contains(&path.view_id.into()) {
|
|
||||||
let order = &prev_scene.orders_by_layer[&path.layer_id];
|
|
||||||
self.insert(order, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for underline in prev_scene.underlines.drain(..) {
|
|
||||||
if views.contains(&underline.view_id.into()) {
|
|
||||||
let order = &prev_scene.orders_by_layer[&underline.layer_id];
|
|
||||||
self.insert(order, underline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for sprite in prev_scene.monochrome_sprites.drain(..) {
|
|
||||||
if views.contains(&sprite.view_id.into()) {
|
|
||||||
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
|
|
||||||
self.insert(order, sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for sprite in prev_scene.polychrome_sprites.drain(..) {
|
|
||||||
if views.contains(&sprite.view_id.into()) {
|
|
||||||
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
|
|
||||||
self.insert(order, sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for surface in prev_scene.surfaces.drain(..) {
|
|
||||||
if views.contains(&surface.view_id.into()) {
|
|
||||||
let order = &prev_scene.orders_by_layer[&surface.layer_id];
|
|
||||||
self.insert(order, surface);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(&mut self) {
|
|
||||||
let mut orders = vec![0; self.layers_by_order.len()];
|
|
||||||
for (ix, layer_id) in self.layers_by_order.values().enumerate() {
|
|
||||||
orders[*layer_id as usize] = ix as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
for shadow in &mut self.shadows {
|
|
||||||
shadow.order = orders[shadow.layer_id as usize];
|
|
||||||
}
|
|
||||||
self.shadows.sort_by_key(|shadow| shadow.order);
|
|
||||||
|
|
||||||
for quad in &mut self.quads {
|
|
||||||
quad.order = orders[quad.layer_id as usize];
|
|
||||||
}
|
|
||||||
self.quads.sort_by_key(|quad| quad.order);
|
|
||||||
|
|
||||||
for path in &mut self.paths {
|
|
||||||
path.order = orders[path.layer_id as usize];
|
|
||||||
}
|
|
||||||
self.paths.sort_by_key(|path| path.order);
|
|
||||||
|
|
||||||
for underline in &mut self.underlines {
|
|
||||||
underline.order = orders[underline.layer_id as usize];
|
|
||||||
}
|
|
||||||
self.underlines.sort_by_key(|underline| underline.order);
|
|
||||||
|
|
||||||
for monochrome_sprite in &mut self.monochrome_sprites {
|
|
||||||
monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize];
|
|
||||||
}
|
|
||||||
self.monochrome_sprites.sort_by_key(|sprite| sprite.order);
|
|
||||||
|
|
||||||
for polychrome_sprite in &mut self.polychrome_sprites {
|
|
||||||
polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize];
|
|
||||||
}
|
|
||||||
self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
|
|
||||||
|
|
||||||
for surface in &mut self.surfaces {
|
|
||||||
surface.order = orders[surface.layer_id as usize];
|
|
||||||
}
|
|
||||||
self.surfaces.sort_by_key(|surface| surface.order);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BatchIterator<'a> {
|
struct BatchIterator<'a> {
|
||||||
|
@ -439,54 +397,6 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
|
||||||
pub(crate) enum PrimitiveKind {
|
|
||||||
Shadow,
|
|
||||||
#[default]
|
|
||||||
Quad,
|
|
||||||
Path,
|
|
||||||
Underline,
|
|
||||||
MonochromeSprite,
|
|
||||||
PolychromeSprite,
|
|
||||||
Surface,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum Primitive {
|
|
||||||
Shadow(Shadow),
|
|
||||||
Quad(Quad),
|
|
||||||
Path(Path<ScaledPixels>),
|
|
||||||
Underline(Underline),
|
|
||||||
MonochromeSprite(MonochromeSprite),
|
|
||||||
PolychromeSprite(PolychromeSprite),
|
|
||||||
Surface(Surface),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Primitive {
|
|
||||||
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
|
|
||||||
match self {
|
|
||||||
Primitive::Shadow(shadow) => &shadow.bounds,
|
|
||||||
Primitive::Quad(quad) => &quad.bounds,
|
|
||||||
Primitive::Path(path) => &path.bounds,
|
|
||||||
Primitive::Underline(underline) => &underline.bounds,
|
|
||||||
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
|
||||||
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
|
||||||
Primitive::Surface(surface) => &surface.bounds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
|
|
||||||
match self {
|
|
||||||
Primitive::Shadow(shadow) => &shadow.content_mask,
|
|
||||||
Primitive::Quad(quad) => &quad.content_mask,
|
|
||||||
Primitive::Path(path) => &path.content_mask,
|
|
||||||
Primitive::Underline(underline) => &underline.content_mask,
|
|
||||||
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
|
||||||
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
|
||||||
Primitive::Surface(surface) => &surface.content_mask,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum PrimitiveBatch<'a> {
|
pub(crate) enum PrimitiveBatch<'a> {
|
||||||
Shadows(&'a [Shadow]),
|
Shadows(&'a [Shadow]),
|
||||||
|
@ -507,8 +417,6 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct Quad {
|
pub(crate) struct Quad {
|
||||||
pub view_id: ViewId,
|
|
||||||
pub layer_id: LayerId,
|
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
pub bounds: Bounds<ScaledPixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub content_mask: ContentMask<ScaledPixels>,
|
pub content_mask: ContentMask<ScaledPixels>,
|
||||||
|
@ -539,8 +447,6 @@ impl From<Quad> for Primitive {
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct Underline {
|
pub(crate) struct Underline {
|
||||||
pub view_id: ViewId,
|
|
||||||
pub layer_id: LayerId,
|
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
pub bounds: Bounds<ScaledPixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub content_mask: ContentMask<ScaledPixels>,
|
pub content_mask: ContentMask<ScaledPixels>,
|
||||||
|
@ -570,8 +476,6 @@ impl From<Underline> for Primitive {
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct Shadow {
|
pub(crate) struct Shadow {
|
||||||
pub view_id: ViewId,
|
|
||||||
pub layer_id: LayerId,
|
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
pub bounds: Bounds<ScaledPixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub corner_radii: Corners<ScaledPixels>,
|
pub corner_radii: Corners<ScaledPixels>,
|
||||||
|
@ -602,8 +506,6 @@ impl From<Shadow> for Primitive {
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct MonochromeSprite {
|
pub(crate) struct MonochromeSprite {
|
||||||
pub view_id: ViewId,
|
|
||||||
pub layer_id: LayerId,
|
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
pub bounds: Bounds<ScaledPixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub content_mask: ContentMask<ScaledPixels>,
|
pub content_mask: ContentMask<ScaledPixels>,
|
||||||
|
@ -635,8 +537,6 @@ impl From<MonochromeSprite> for Primitive {
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct PolychromeSprite {
|
pub(crate) struct PolychromeSprite {
|
||||||
pub view_id: ViewId,
|
|
||||||
pub layer_id: LayerId,
|
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
pub bounds: Bounds<ScaledPixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub content_mask: ContentMask<ScaledPixels>,
|
pub content_mask: ContentMask<ScaledPixels>,
|
||||||
|
@ -669,8 +569,6 @@ impl From<PolychromeSprite> for Primitive {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub(crate) struct Surface {
|
pub(crate) struct Surface {
|
||||||
pub view_id: ViewId,
|
|
||||||
pub layer_id: LayerId,
|
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
pub bounds: Bounds<ScaledPixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub content_mask: ContentMask<ScaledPixels>,
|
pub content_mask: ContentMask<ScaledPixels>,
|
||||||
|
@ -700,11 +598,9 @@ impl From<Surface> for Primitive {
|
||||||
pub(crate) struct PathId(pub(crate) usize);
|
pub(crate) struct PathId(pub(crate) usize);
|
||||||
|
|
||||||
/// A line made up of a series of vertices and control points.
|
/// A line made up of a series of vertices and control points.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Path<P: Clone + Default + Debug> {
|
pub struct Path<P: Clone + Default + Debug> {
|
||||||
pub(crate) id: PathId,
|
pub(crate) id: PathId,
|
||||||
pub(crate) view_id: ViewId,
|
|
||||||
layer_id: LayerId,
|
|
||||||
order: DrawOrder,
|
order: DrawOrder,
|
||||||
pub(crate) bounds: Bounds<P>,
|
pub(crate) bounds: Bounds<P>,
|
||||||
pub(crate) content_mask: ContentMask<P>,
|
pub(crate) content_mask: ContentMask<P>,
|
||||||
|
@ -720,8 +616,6 @@ impl Path<Pixels> {
|
||||||
pub fn new(start: Point<Pixels>) -> Self {
|
pub fn new(start: Point<Pixels>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: PathId(0),
|
id: PathId(0),
|
||||||
view_id: ViewId::default(),
|
|
||||||
layer_id: LayerId::default(),
|
|
||||||
order: DrawOrder::default(),
|
order: DrawOrder::default(),
|
||||||
vertices: Vec::new(),
|
vertices: Vec::new(),
|
||||||
start,
|
start,
|
||||||
|
@ -740,8 +634,6 @@ impl Path<Pixels> {
|
||||||
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
|
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
|
||||||
Path {
|
Path {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
view_id: self.view_id,
|
|
||||||
layer_id: self.layer_id,
|
|
||||||
order: self.order,
|
order: self.order,
|
||||||
bounds: self.bounds.scale(factor),
|
bounds: self.bounds.scale(factor),
|
||||||
content_mask: self.content_mask.scale(factor),
|
content_mask: self.content_mask.scale(factor),
|
||||||
|
|
|
@ -115,9 +115,6 @@ pub struct Style {
|
||||||
/// The mouse cursor style shown when the mouse pointer is over an element.
|
/// The mouse cursor style shown when the mouse pointer is over an element.
|
||||||
pub mouse_cursor: Option<CursorStyle>,
|
pub mouse_cursor: Option<CursorStyle>,
|
||||||
|
|
||||||
/// The z-index to set for this element
|
|
||||||
pub z_index: Option<u16>,
|
|
||||||
|
|
||||||
/// Whether to draw a red debugging outline around this element
|
/// Whether to draw a red debugging outline around this element
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
|
@ -323,6 +320,13 @@ pub struct HighlightStyle {
|
||||||
impl Eq for HighlightStyle {}
|
impl Eq for HighlightStyle {}
|
||||||
|
|
||||||
impl Style {
|
impl Style {
|
||||||
|
/// Returns true if the style is visible and the background is opaque.
|
||||||
|
pub fn has_opaque_background(&self) -> bool {
|
||||||
|
self.background
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the text style in this element style.
|
/// Get the text style in this element style.
|
||||||
pub fn text_style(&self) -> Option<&TextStyleRefinement> {
|
pub fn text_style(&self) -> Option<&TextStyleRefinement> {
|
||||||
if self.text.is_some() {
|
if self.text.is_some() {
|
||||||
|
@ -402,17 +406,14 @@ impl Style {
|
||||||
|
|
||||||
let rem_size = cx.rem_size();
|
let rem_size = cx.rem_size();
|
||||||
|
|
||||||
cx.with_z_index(0, |cx| {
|
|
||||||
cx.paint_shadows(
|
cx.paint_shadows(
|
||||||
bounds,
|
bounds,
|
||||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||||
&self.box_shadow,
|
&self.box_shadow,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
let background_color = self.background.as_ref().and_then(Fill::color);
|
let background_color = self.background.as_ref().and_then(Fill::color);
|
||||||
if background_color.map_or(false, |color| !color.is_transparent()) {
|
if background_color.map_or(false, |color| !color.is_transparent()) {
|
||||||
cx.with_z_index(1, |cx| {
|
|
||||||
let mut border_color = background_color.unwrap_or_default();
|
let mut border_color = background_color.unwrap_or_default();
|
||||||
border_color.a = 0.;
|
border_color.a = 0.;
|
||||||
cx.paint_quad(quad(
|
cx.paint_quad(quad(
|
||||||
|
@ -422,15 +423,11 @@ impl Style {
|
||||||
Edges::default(),
|
Edges::default(),
|
||||||
border_color,
|
border_color,
|
||||||
));
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.with_z_index(2, |cx| {
|
|
||||||
continuation(cx);
|
continuation(cx);
|
||||||
});
|
|
||||||
|
|
||||||
if self.is_border_visible() {
|
if self.is_border_visible() {
|
||||||
cx.with_z_index(3, |cx| {
|
|
||||||
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
|
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
|
||||||
let border_widths = self.border_widths.to_pixels(rem_size);
|
let border_widths = self.border_widths.to_pixels(rem_size);
|
||||||
let max_border_width = border_widths.max();
|
let max_border_width = border_widths.max();
|
||||||
|
@ -438,12 +435,10 @@ impl Style {
|
||||||
|
|
||||||
let top_bounds = Bounds::from_corners(
|
let top_bounds = Bounds::from_corners(
|
||||||
bounds.origin,
|
bounds.origin,
|
||||||
bounds.upper_right()
|
bounds.upper_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||||
+ point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
|
||||||
);
|
);
|
||||||
let bottom_bounds = Bounds::from_corners(
|
let bottom_bounds = Bounds::from_corners(
|
||||||
bounds.lower_left()
|
bounds.lower_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||||
- point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
|
||||||
bounds.lower_right(),
|
bounds.lower_right(),
|
||||||
);
|
);
|
||||||
let left_bounds = Bounds::from_corners(
|
let left_bounds = Bounds::from_corners(
|
||||||
|
@ -492,7 +487,6 @@ impl Style {
|
||||||
cx.paint_quad(quad);
|
cx.paint_quad(quad);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -545,7 +539,6 @@ impl Default for Style {
|
||||||
box_shadow: Default::default(),
|
box_shadow: Default::default(),
|
||||||
text: TextStyleRefinement::default(),
|
text: TextStyleRefinement::default(),
|
||||||
mouse_cursor: None,
|
mouse_cursor: None,
|
||||||
z_index: None,
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
debug: false,
|
debug: false,
|
||||||
|
|
|
@ -15,12 +15,6 @@ pub trait Styled: Sized {
|
||||||
|
|
||||||
gpui_macros::style_helpers!();
|
gpui_macros::style_helpers!();
|
||||||
|
|
||||||
/// Set the z-index of this element.
|
|
||||||
fn z_index(mut self, z_index: u16) -> Self {
|
|
||||||
self.style().z_index = Some(z_index);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the position of the element to `relative`.
|
/// Sets the position of the element to `relative`.
|
||||||
/// [Docs](https://tailwindcss.com/docs/position)
|
/// [Docs](https://tailwindcss.com/docs/position)
|
||||||
fn relative(mut self) -> Self {
|
fn relative(mut self) -> Self {
|
||||||
|
|
|
@ -47,11 +47,7 @@ impl TaffyLayoutEngine {
|
||||||
self.styles.clear();
|
self.styles.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requested_style(&self, layout_id: LayoutId) -> Option<&Style> {
|
pub fn before_layout(
|
||||||
self.styles.get(&layout_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_layout(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
style: &Style,
|
style: &Style,
|
||||||
rem_size: Pixels,
|
rem_size: Pixels,
|
||||||
|
@ -447,6 +443,27 @@ pub enum AvailableSpace {
|
||||||
MaxContent,
|
MaxContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AvailableSpace {
|
||||||
|
/// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
|
||||||
|
///
|
||||||
|
/// This function is useful when you want to create a `Size` with the minimum content constraints
|
||||||
|
/// for both dimensions.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let min_content_size = AvailableSpace::min_size();
|
||||||
|
/// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
|
||||||
|
/// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
|
||||||
|
/// ```
|
||||||
|
pub const fn min_size() -> Size<Self> {
|
||||||
|
Size {
|
||||||
|
width: Self::MinContent,
|
||||||
|
height: Self::MinContent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<AvailableSpace> for TaffyAvailableSpace {
|
impl From<AvailableSpace> for TaffyAvailableSpace {
|
||||||
fn from(space: AvailableSpace) -> TaffyAvailableSpace {
|
fn from(space: AvailableSpace) -> TaffyAvailableSpace {
|
||||||
match space {
|
match space {
|
||||||
|
|
|
@ -9,11 +9,11 @@ pub use line_layout::*;
|
||||||
pub use line_wrapper::*;
|
pub use line_wrapper::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
px, Bounds, DevicePixels, EntityId, Hsla, Pixels, PlatformTextSystem, Point, Result,
|
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
|
||||||
SharedString, Size, StrikethroughStyle, UnderlineStyle,
|
StrikethroughStyle, UnderlineStyle,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use collections::{BTreeSet, FxHashMap, FxHashSet};
|
use collections::{BTreeSet, FxHashMap};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use derive_more::Deref;
|
use derive_more::Deref;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -24,7 +24,7 @@ use std::{
|
||||||
cmp,
|
cmp,
|
||||||
fmt::{Debug, Display, Formatter},
|
fmt::{Debug, Display, Formatter},
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut, Range},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ impl TextSystem {
|
||||||
/// The GPUI text layout subsystem.
|
/// The GPUI text layout subsystem.
|
||||||
#[derive(Deref)]
|
#[derive(Deref)]
|
||||||
pub struct WindowTextSystem {
|
pub struct WindowTextSystem {
|
||||||
line_layout_cache: Arc<LineLayoutCache>,
|
line_layout_cache: LineLayoutCache,
|
||||||
#[deref]
|
#[deref]
|
||||||
text_system: Arc<TextSystem>,
|
text_system: Arc<TextSystem>,
|
||||||
}
|
}
|
||||||
|
@ -287,15 +287,17 @@ pub struct WindowTextSystem {
|
||||||
impl WindowTextSystem {
|
impl WindowTextSystem {
|
||||||
pub(crate) fn new(text_system: Arc<TextSystem>) -> Self {
|
pub(crate) fn new(text_system: Arc<TextSystem>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
line_layout_cache: Arc::new(LineLayoutCache::new(
|
line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),
|
||||||
text_system.platform_text_system.clone(),
|
|
||||||
)),
|
|
||||||
text_system,
|
text_system,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
|
pub(crate) fn layout_index(&self) -> LineLayoutIndex {
|
||||||
self.line_layout_cache.with_view(view_id, f)
|
self.line_layout_cache.layout_index()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
|
||||||
|
self.line_layout_cache.reuse_layouts(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shape the given line, at the given font_size, for painting to the screen.
|
/// Shape the given line, at the given font_size, for painting to the screen.
|
||||||
|
@ -455,8 +457,8 @@ impl WindowTextSystem {
|
||||||
Ok(lines)
|
Ok(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
|
pub(crate) fn finish_frame(&self) {
|
||||||
self.line_layout_cache.finish_frame(reused_views)
|
self.line_layout_cache.finish_frame()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given line of text, at the given font_size.
|
/// Layout the given line of text, at the given font_size.
|
||||||
|
|
|
@ -107,8 +107,10 @@ fn paint_line(
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
decoration_runs: &[DecorationRun],
|
decoration_runs: &[DecorationRun],
|
||||||
wrap_boundaries: &[WrapBoundary],
|
wrap_boundaries: &[WrapBoundary],
|
||||||
cx: &mut ElementContext<'_>,
|
cx: &mut ElementContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let line_bounds = Bounds::new(origin, size(layout.width, line_height));
|
||||||
|
cx.paint_layer(line_bounds, |cx| {
|
||||||
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
|
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
|
||||||
let baseline_offset = point(px(0.), padding_top + layout.ascent);
|
let baseline_offset = point(px(0.), padding_top + layout.ascent);
|
||||||
let mut decoration_runs = decoration_runs.iter();
|
let mut decoration_runs = decoration_runs.iter();
|
||||||
|
@ -129,7 +131,8 @@ fn paint_line(
|
||||||
|
|
||||||
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
|
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
|
||||||
wraps.next();
|
wraps.next();
|
||||||
if let Some((background_origin, background_color)) = current_background.as_mut() {
|
if let Some((background_origin, background_color)) = current_background.as_mut()
|
||||||
|
{
|
||||||
cx.paint_quad(fill(
|
cx.paint_quad(fill(
|
||||||
Bounds {
|
Bounds {
|
||||||
origin: *background_origin,
|
origin: *background_origin,
|
||||||
|
@ -177,8 +180,10 @@ fn paint_line(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(run_background) = style_run.background_color {
|
if let Some(run_background) = style_run.background_color {
|
||||||
current_background
|
current_background.get_or_insert((
|
||||||
.get_or_insert((point(glyph_origin.x, glyph_origin.y), run_background));
|
point(glyph_origin.x, glyph_origin.y),
|
||||||
|
run_background,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_, underline_style)) = &mut current_underline {
|
if let Some((_, underline_style)) = &mut current_underline {
|
||||||
|
@ -315,4 +320,5 @@ fn paint_line(
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::{px, EntityId, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
|
use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
|
||||||
use collections::{FxHashMap, FxHashSet};
|
use collections::FxHashMap;
|
||||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
|
ops::Range,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -277,63 +278,71 @@ impl WrappedLineLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct LineLayoutCache {
|
pub(crate) struct LineLayoutCache {
|
||||||
view_stack: Mutex<Vec<EntityId>>,
|
previous_frame: Mutex<FrameCache>,
|
||||||
previous_frame: Mutex<FxHashMap<CacheKey, Arc<LineLayout>>>,
|
current_frame: RwLock<FrameCache>,
|
||||||
current_frame: RwLock<FxHashMap<CacheKey, Arc<LineLayout>>>,
|
|
||||||
previous_frame_wrapped: Mutex<FxHashMap<CacheKey, Arc<WrappedLineLayout>>>,
|
|
||||||
current_frame_wrapped: RwLock<FxHashMap<CacheKey, Arc<WrappedLineLayout>>>,
|
|
||||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct FrameCache {
|
||||||
|
lines: FxHashMap<Arc<CacheKey>, Arc<LineLayout>>,
|
||||||
|
wrapped_lines: FxHashMap<Arc<CacheKey>, Arc<WrappedLineLayout>>,
|
||||||
|
used_lines: Vec<Arc<CacheKey>>,
|
||||||
|
used_wrapped_lines: Vec<Arc<CacheKey>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub(crate) struct LineLayoutIndex {
|
||||||
|
lines_index: usize,
|
||||||
|
wrapped_lines_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl LineLayoutCache {
|
impl LineLayoutCache {
|
||||||
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_stack: Mutex::default(),
|
|
||||||
previous_frame: Mutex::default(),
|
previous_frame: Mutex::default(),
|
||||||
current_frame: RwLock::default(),
|
current_frame: RwLock::default(),
|
||||||
previous_frame_wrapped: Mutex::default(),
|
|
||||||
current_frame_wrapped: RwLock::default(),
|
|
||||||
platform_text_system,
|
platform_text_system,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
|
pub fn layout_index(&self) -> LineLayoutIndex {
|
||||||
debug_assert_eq!(self.view_stack.lock().len(), 0);
|
let frame = self.current_frame.read();
|
||||||
|
LineLayoutIndex {
|
||||||
|
lines_index: frame.used_lines.len(),
|
||||||
|
wrapped_lines_index: frame.used_wrapped_lines.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
|
||||||
|
let mut previous_frame = &mut *self.previous_frame.lock();
|
||||||
|
let mut current_frame = &mut *self.current_frame.write();
|
||||||
|
|
||||||
|
for key in &previous_frame.used_lines[range.start.lines_index..range.end.lines_index] {
|
||||||
|
if let Some((key, line)) = previous_frame.lines.remove_entry(key) {
|
||||||
|
current_frame.lines.insert(key, line);
|
||||||
|
}
|
||||||
|
current_frame.used_lines.push(key.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in &previous_frame.used_wrapped_lines
|
||||||
|
[range.start.wrapped_lines_index..range.end.wrapped_lines_index]
|
||||||
|
{
|
||||||
|
if let Some((key, line)) = previous_frame.wrapped_lines.remove_entry(key) {
|
||||||
|
current_frame.wrapped_lines.insert(key, line);
|
||||||
|
}
|
||||||
|
current_frame.used_wrapped_lines.push(key.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish_frame(&self) {
|
||||||
let mut prev_frame = self.previous_frame.lock();
|
let mut prev_frame = self.previous_frame.lock();
|
||||||
let mut curr_frame = self.current_frame.write();
|
let mut curr_frame = self.current_frame.write();
|
||||||
for (key, layout) in prev_frame.drain() {
|
|
||||||
if key
|
|
||||||
.parent_view_id
|
|
||||||
.map_or(false, |view_id| reused_views.contains(&view_id))
|
|
||||||
{
|
|
||||||
curr_frame.insert(key, layout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
|
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
|
||||||
|
curr_frame.lines.clear();
|
||||||
let mut prev_frame_wrapped = self.previous_frame_wrapped.lock();
|
curr_frame.wrapped_lines.clear();
|
||||||
let mut curr_frame_wrapped = self.current_frame_wrapped.write();
|
curr_frame.used_lines.clear();
|
||||||
for (key, layout) in prev_frame_wrapped.drain() {
|
curr_frame.used_wrapped_lines.clear();
|
||||||
if key
|
|
||||||
.parent_view_id
|
|
||||||
.map_or(false, |view_id| reused_views.contains(&view_id))
|
|
||||||
{
|
|
||||||
curr_frame_wrapped.insert(key, layout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::mem::swap(&mut *prev_frame_wrapped, &mut *curr_frame_wrapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
|
|
||||||
self.view_stack.lock().push(view_id);
|
|
||||||
let result = f();
|
|
||||||
self.view_stack.lock().pop();
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parent_view_id(&self) -> Option<EntityId> {
|
|
||||||
self.view_stack.lock().last().copied()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout_wrapped_line(
|
pub fn layout_wrapped_line(
|
||||||
|
@ -348,19 +357,24 @@ impl LineLayoutCache {
|
||||||
font_size,
|
font_size,
|
||||||
runs,
|
runs,
|
||||||
wrap_width,
|
wrap_width,
|
||||||
parent_view_id: self.parent_view_id(),
|
|
||||||
} as &dyn AsCacheKeyRef;
|
} as &dyn AsCacheKeyRef;
|
||||||
|
|
||||||
let current_frame = self.current_frame_wrapped.upgradable_read();
|
let current_frame = self.current_frame.upgradable_read();
|
||||||
if let Some(layout) = current_frame.get(key) {
|
if let Some(layout) = current_frame.wrapped_lines.get(key) {
|
||||||
return layout.clone();
|
return layout.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previous_frame_entry = self.previous_frame.lock().wrapped_lines.remove_entry(key);
|
||||||
|
if let Some((key, layout)) = previous_frame_entry {
|
||||||
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
|
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
|
||||||
if let Some((key, layout)) = self.previous_frame_wrapped.lock().remove_entry(key) {
|
current_frame
|
||||||
current_frame.insert(key, layout.clone());
|
.wrapped_lines
|
||||||
|
.insert(key.clone(), layout.clone());
|
||||||
|
current_frame.used_wrapped_lines.push(key);
|
||||||
layout
|
layout
|
||||||
} else {
|
} else {
|
||||||
|
drop(current_frame);
|
||||||
|
|
||||||
let unwrapped_layout = self.layout_line(text, font_size, runs);
|
let unwrapped_layout = self.layout_line(text, font_size, runs);
|
||||||
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
|
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
|
||||||
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
|
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
|
||||||
|
@ -372,14 +386,19 @@ impl LineLayoutCache {
|
||||||
wrap_boundaries,
|
wrap_boundaries,
|
||||||
wrap_width,
|
wrap_width,
|
||||||
});
|
});
|
||||||
let key = CacheKey {
|
let key = Arc::new(CacheKey {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
font_size,
|
font_size,
|
||||||
runs: SmallVec::from(runs),
|
runs: SmallVec::from(runs),
|
||||||
wrap_width,
|
wrap_width,
|
||||||
parent_view_id: self.parent_view_id(),
|
});
|
||||||
};
|
|
||||||
current_frame.insert(key, layout.clone());
|
let mut current_frame = self.current_frame.write();
|
||||||
|
current_frame
|
||||||
|
.wrapped_lines
|
||||||
|
.insert(key.clone(), layout.clone());
|
||||||
|
current_frame.used_wrapped_lines.push(key);
|
||||||
|
|
||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,28 +409,28 @@ impl LineLayoutCache {
|
||||||
font_size,
|
font_size,
|
||||||
runs,
|
runs,
|
||||||
wrap_width: None,
|
wrap_width: None,
|
||||||
parent_view_id: self.parent_view_id(),
|
|
||||||
} as &dyn AsCacheKeyRef;
|
} as &dyn AsCacheKeyRef;
|
||||||
|
|
||||||
let current_frame = self.current_frame.upgradable_read();
|
let current_frame = self.current_frame.upgradable_read();
|
||||||
if let Some(layout) = current_frame.get(key) {
|
if let Some(layout) = current_frame.lines.get(key) {
|
||||||
return layout.clone();
|
return layout.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
|
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
|
||||||
if let Some((key, layout)) = self.previous_frame.lock().remove_entry(key) {
|
if let Some((key, layout)) = self.previous_frame.lock().lines.remove_entry(key) {
|
||||||
current_frame.insert(key, layout.clone());
|
current_frame.lines.insert(key.clone(), layout.clone());
|
||||||
|
current_frame.used_lines.push(key);
|
||||||
layout
|
layout
|
||||||
} else {
|
} else {
|
||||||
let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
|
let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
|
||||||
let key = CacheKey {
|
let key = Arc::new(CacheKey {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
font_size,
|
font_size,
|
||||||
runs: SmallVec::from(runs),
|
runs: SmallVec::from(runs),
|
||||||
wrap_width: None,
|
wrap_width: None,
|
||||||
parent_view_id: self.parent_view_id(),
|
});
|
||||||
};
|
current_frame.lines.insert(key.clone(), layout.clone());
|
||||||
current_frame.insert(key, layout.clone());
|
current_frame.used_lines.push(key);
|
||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -428,13 +447,12 @@ trait AsCacheKeyRef {
|
||||||
fn as_cache_key_ref(&self) -> CacheKeyRef;
|
fn as_cache_key_ref(&self) -> CacheKeyRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq)]
|
#[derive(Clone, Debug, Eq)]
|
||||||
struct CacheKey {
|
struct CacheKey {
|
||||||
text: String,
|
text: String,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
runs: SmallVec<[FontRun; 1]>,
|
runs: SmallVec<[FontRun; 1]>,
|
||||||
wrap_width: Option<Pixels>,
|
wrap_width: Option<Pixels>,
|
||||||
parent_view_id: Option<EntityId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
@ -443,7 +461,6 @@ struct CacheKeyRef<'a> {
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
runs: &'a [FontRun],
|
runs: &'a [FontRun],
|
||||||
wrap_width: Option<Pixels>,
|
wrap_width: Option<Pixels>,
|
||||||
parent_view_id: Option<EntityId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
|
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
|
||||||
|
@ -467,7 +484,6 @@ impl AsCacheKeyRef for CacheKey {
|
||||||
font_size: self.font_size,
|
font_size: self.font_size,
|
||||||
runs: self.runs.as_slice(),
|
runs: self.runs.as_slice(),
|
||||||
wrap_width: self.wrap_width,
|
wrap_width: self.wrap_width,
|
||||||
parent_view_id: self.parent_view_id,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -484,9 +500,9 @@ impl Hash for CacheKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for CacheKey {
|
impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
|
||||||
fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
|
fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
|
||||||
self as &dyn AsCacheKeyRef
|
self.as_ref() as &dyn AsCacheKeyRef
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, Bounds,
|
seal::Sealed, AfterLayoutIndex, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds,
|
||||||
ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
|
ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
|
||||||
FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style,
|
FocusableView, IntoElement, LayoutId, Model, PaintIndex, Pixels, Render, Style,
|
||||||
TextStyle, ViewContext, VisualContext, WeakModel,
|
StyleRefinement, TextStyle, ViewContext, VisualContext, WeakModel,
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use refineable::Refineable;
|
||||||
use std::{
|
use std::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
|
ops::Range,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A view is a piece of state that can be presented on screen by implementing the [Render] trait.
|
/// A view is a piece of state that can be presented on screen by implementing the [Render] trait.
|
||||||
|
@ -20,17 +22,15 @@ pub struct View<V> {
|
||||||
|
|
||||||
impl<V> Sealed for View<V> {}
|
impl<V> Sealed for View<V> {}
|
||||||
|
|
||||||
#[doc(hidden)]
|
struct AnyViewState {
|
||||||
pub struct AnyViewState {
|
after_layout_range: Range<AfterLayoutIndex>,
|
||||||
root_style: Style,
|
paint_range: Range<PaintIndex>,
|
||||||
next_stacking_order_id: u16,
|
cache_key: ViewCacheKey,
|
||||||
cache_key: Option<ViewCacheKey>,
|
|
||||||
element: Option<AnyElement>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct ViewCacheKey {
|
struct ViewCacheKey {
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
stacking_order: StackingOrder,
|
|
||||||
content_mask: ContentMask<Pixels>,
|
content_mask: ContentMask<Pixels>,
|
||||||
text_style: TextStyle,
|
text_style: TextStyle,
|
||||||
}
|
}
|
||||||
|
@ -90,22 +90,39 @@ impl<V: 'static> View<V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Render> Element for View<V> {
|
impl<V: Render> Element for View<V> {
|
||||||
type State = Option<AnyElement>;
|
type BeforeLayout = AnyElement;
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||||
_state: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
cx.with_view_id(self.entity_id(), |cx| {
|
|
||||||
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
|
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
|
||||||
let layout_id = element.request_layout(cx);
|
let layout_id = element.before_layout(cx);
|
||||||
(layout_id, Some(element))
|
(layout_id, element)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
|
fn after_layout(
|
||||||
cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx));
|
&mut self,
|
||||||
|
_: Bounds<Pixels>,
|
||||||
|
element: &mut Self::BeforeLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
cx.set_view_id(self.entity_id());
|
||||||
|
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||||
|
element.after_layout(cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
_: Bounds<Pixels>,
|
||||||
|
element: &mut Self::BeforeLayout,
|
||||||
|
_: &mut Self::AfterLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||||
|
element.paint(cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,16 +220,16 @@ impl<V> Eq for WeakView<V> {}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AnyView {
|
pub struct AnyView {
|
||||||
model: AnyModel,
|
model: AnyModel,
|
||||||
pub(crate) request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
|
||||||
cache: bool,
|
cached_style: Option<StyleRefinement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnyView {
|
impl AnyView {
|
||||||
/// Indicate that this view should be cached when using it as an element.
|
/// Indicate that this view should be cached when using it as an element.
|
||||||
/// When using this method, the view's previous layout and paint will be recycled from the previous frame if [ViewContext::notify] has not been called since it was rendered.
|
/// When using this method, the view's previous layout and paint will be recycled from the previous frame if [ViewContext::notify] has not been called since it was rendered.
|
||||||
/// The one exception is when [WindowContext::refresh] is called, in which case caching is ignored.
|
/// The one exception is when [WindowContext::refresh] is called, in which case caching is ignored.
|
||||||
pub fn cached(mut self) -> Self {
|
pub fn cached(mut self, style: StyleRefinement) -> Self {
|
||||||
self.cache = true;
|
self.cached_style = Some(style);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +237,7 @@ impl AnyView {
|
||||||
pub fn downgrade(&self) -> AnyWeakView {
|
pub fn downgrade(&self) -> AnyWeakView {
|
||||||
AnyWeakView {
|
AnyWeakView {
|
||||||
model: self.model.downgrade(),
|
model: self.model.downgrade(),
|
||||||
layout: self.request_layout,
|
render: self.render,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,8 +248,8 @@ impl AnyView {
|
||||||
Ok(model) => Ok(View { model }),
|
Ok(model) => Ok(View { model }),
|
||||||
Err(model) => Err(Self {
|
Err(model) => Err(Self {
|
||||||
model,
|
model,
|
||||||
request_layout: self.request_layout,
|
render: self.render,
|
||||||
cache: self.cache,
|
cached_style: self.cached_style,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,113 +263,134 @@ impl AnyView {
|
||||||
pub fn entity_id(&self) -> EntityId {
|
pub fn entity_id(&self) -> EntityId {
|
||||||
self.model.entity_id()
|
self.model.entity_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn draw(
|
|
||||||
&self,
|
|
||||||
origin: Point<Pixels>,
|
|
||||||
available_space: Size<AvailableSpace>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) {
|
|
||||||
cx.paint_view(self.entity_id(), |cx| {
|
|
||||||
cx.with_absolute_element_offset(origin, |cx| {
|
|
||||||
let (layout_id, mut rendered_element) = (self.request_layout)(self, cx);
|
|
||||||
cx.compute_layout(layout_id, available_space);
|
|
||||||
rendered_element.paint(cx)
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Render> From<View<V>> for AnyView {
|
impl<V: Render> From<View<V>> for AnyView {
|
||||||
fn from(value: View<V>) -> Self {
|
fn from(value: View<V>) -> Self {
|
||||||
AnyView {
|
AnyView {
|
||||||
model: value.model.into_any(),
|
model: value.model.into_any(),
|
||||||
request_layout: any_view::request_layout::<V>,
|
render: any_view::render::<V>,
|
||||||
cache: false,
|
cached_style: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for AnyView {
|
impl Element for AnyView {
|
||||||
type State = AnyViewState;
|
type BeforeLayout = Option<AnyElement>;
|
||||||
|
type AfterLayout = Option<AnyElement>;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
|
if let Some(style) = self.cached_style.as_ref() {
|
||||||
|
let mut root_style = Style::default();
|
||||||
|
root_style.refine(style);
|
||||||
|
let layout_id = cx.request_layout(&root_style, None);
|
||||||
|
(layout_id, None)
|
||||||
|
} else {
|
||||||
|
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||||
|
let mut element = (self.render)(self, cx);
|
||||||
|
let layout_id = element.before_layout(cx);
|
||||||
|
(layout_id, Some(element))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: Option<Self::State>,
|
bounds: Bounds<Pixels>,
|
||||||
|
element: &mut Self::BeforeLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> Option<AnyElement> {
|
||||||
cx.with_view_id(self.entity_id(), |cx| {
|
cx.set_view_id(self.entity_id());
|
||||||
if self.cache
|
if self.cached_style.is_some() {
|
||||||
|
cx.with_element_state::<AnyViewState, _>(
|
||||||
|
Some(ElementId::View(self.entity_id())),
|
||||||
|
|element_state, cx| {
|
||||||
|
let mut element_state = element_state.unwrap();
|
||||||
|
|
||||||
|
let content_mask = cx.content_mask();
|
||||||
|
let text_style = cx.text_style();
|
||||||
|
|
||||||
|
if let Some(mut element_state) = element_state {
|
||||||
|
if element_state.cache_key.bounds == bounds
|
||||||
|
&& element_state.cache_key.content_mask == content_mask
|
||||||
|
&& element_state.cache_key.text_style == text_style
|
||||||
&& !cx.window.dirty_views.contains(&self.entity_id())
|
&& !cx.window.dirty_views.contains(&self.entity_id())
|
||||||
&& !cx.window.refreshing
|
&& !cx.window.refreshing
|
||||||
{
|
{
|
||||||
if let Some(state) = state {
|
let after_layout_start = cx.after_layout_index();
|
||||||
let layout_id = cx.request_layout(&state.root_style, None);
|
cx.reuse_after_layout(element_state.after_layout_range.clone());
|
||||||
return (layout_id, state);
|
let after_layout_end = cx.after_layout_index();
|
||||||
|
element_state.after_layout_range = after_layout_start..after_layout_end;
|
||||||
|
return (None, Some(element_state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (layout_id, element) = (self.request_layout)(self, cx);
|
let after_layout_start = cx.after_layout_index();
|
||||||
let root_style = cx.layout_style(layout_id).unwrap().clone();
|
let mut element = (self.render)(self, cx);
|
||||||
let state = AnyViewState {
|
element.layout(bounds.origin, bounds.size.into(), cx);
|
||||||
root_style,
|
let after_layout_end = cx.after_layout_index();
|
||||||
next_stacking_order_id: 0,
|
|
||||||
cache_key: None,
|
(
|
||||||
element: Some(element),
|
Some(element),
|
||||||
};
|
Some(AnyViewState {
|
||||||
(layout_id, state)
|
after_layout_range: after_layout_start..after_layout_end,
|
||||||
|
paint_range: PaintIndex::default()..PaintIndex::default(),
|
||||||
|
cache_key: ViewCacheKey {
|
||||||
|
bounds,
|
||||||
|
content_mask,
|
||||||
|
text_style,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||||
|
let mut element = element.take().unwrap();
|
||||||
|
element.after_layout(cx);
|
||||||
|
Some(element)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
|
||||||
cx.paint_view(self.entity_id(), |cx| {
|
|
||||||
if !self.cache {
|
|
||||||
state.element.take().unwrap().paint(cx);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(cache_key) = state.cache_key.as_mut() {
|
fn paint(
|
||||||
if cache_key.bounds == bounds
|
&mut self,
|
||||||
&& cache_key.content_mask == cx.content_mask()
|
_bounds: Bounds<Pixels>,
|
||||||
&& cache_key.stacking_order == *cx.stacking_order()
|
_: &mut Self::BeforeLayout,
|
||||||
&& cache_key.text_style == cx.text_style()
|
element: &mut Self::AfterLayout,
|
||||||
{
|
cx: &mut ElementContext,
|
||||||
cx.reuse_view(state.next_stacking_order_id);
|
) {
|
||||||
return;
|
if self.cached_style.is_some() {
|
||||||
}
|
cx.with_element_state::<AnyViewState, _>(
|
||||||
}
|
Some(ElementId::View(self.entity_id())),
|
||||||
|
|element_state, cx| {
|
||||||
|
let mut element_state = element_state.unwrap().unwrap();
|
||||||
|
|
||||||
if let Some(mut element) = state.element.take() {
|
let paint_start = cx.paint_index();
|
||||||
|
|
||||||
|
if let Some(element) = element {
|
||||||
element.paint(cx);
|
element.paint(cx);
|
||||||
} else {
|
} else {
|
||||||
let mut element = (self.request_layout)(self, cx).1;
|
cx.reuse_paint(element_state.paint_range.clone());
|
||||||
element.draw(bounds.origin, bounds.size.into(), cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.next_stacking_order_id = cx
|
let paint_end = cx.paint_index();
|
||||||
.window
|
element_state.paint_range = paint_start..paint_end;
|
||||||
.next_frame
|
|
||||||
.next_stacking_order_ids
|
((), Some(element_state))
|
||||||
.last()
|
},
|
||||||
.copied()
|
)
|
||||||
.unwrap();
|
} else {
|
||||||
state.cache_key = Some(ViewCacheKey {
|
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||||
bounds,
|
element.as_mut().unwrap().paint(cx);
|
||||||
stacking_order: cx.stacking_order().clone(),
|
|
||||||
content_mask: cx.content_mask(),
|
|
||||||
text_style: cx.text_style(),
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static + Render> IntoElement for View<V> {
|
impl<V: 'static + Render> IntoElement for View<V> {
|
||||||
type Element = View<V>;
|
type Element = View<V>;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
Some(ElementId::from_entity_id(self.model.entity_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -361,10 +399,6 @@ impl<V: 'static + Render> IntoElement for View<V> {
|
||||||
impl IntoElement for AnyView {
|
impl IntoElement for AnyView {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
Some(ElementId::from_entity_id(self.model.entity_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -373,7 +407,7 @@ impl IntoElement for AnyView {
|
||||||
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
|
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
|
||||||
pub struct AnyWeakView {
|
pub struct AnyWeakView {
|
||||||
model: AnyWeakModel,
|
model: AnyWeakModel,
|
||||||
layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnyWeakView {
|
impl AnyWeakView {
|
||||||
|
@ -382,8 +416,8 @@ impl AnyWeakView {
|
||||||
let model = self.model.upgrade()?;
|
let model = self.model.upgrade()?;
|
||||||
Some(AnyView {
|
Some(AnyView {
|
||||||
model,
|
model,
|
||||||
request_layout: self.layout,
|
render: self.render,
|
||||||
cache: false,
|
cached_style: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,7 +426,7 @@ impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
|
||||||
fn from(view: WeakView<V>) -> Self {
|
fn from(view: WeakView<V>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
model: view.model.into(),
|
model: view.model.into(),
|
||||||
layout: any_view::request_layout::<V>,
|
render: any_view::render::<V>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,15 +446,13 @@ impl std::fmt::Debug for AnyWeakView {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod any_view {
|
mod any_view {
|
||||||
use crate::{AnyElement, AnyView, ElementContext, IntoElement, LayoutId, Render};
|
use crate::{AnyElement, AnyView, ElementContext, IntoElement, Render};
|
||||||
|
|
||||||
pub(crate) fn request_layout<V: 'static + Render>(
|
pub(crate) fn render<V: 'static + Render>(
|
||||||
view: &AnyView,
|
view: &AnyView,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, AnyElement) {
|
) -> AnyElement {
|
||||||
let view = view.clone().downcast::<V>().unwrap();
|
let view = view.clone().downcast::<V>().unwrap();
|
||||||
let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element());
|
view.update(cx, |view, cx| view.render(cx).into_any_element())
|
||||||
let layout_id = element.request_layout(cx);
|
|
||||||
(layout_id, element)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext,
|
px, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, Bounds,
|
||||||
AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId,
|
Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId,
|
||||||
DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
|
Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId,
|
||||||
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
|
Hsla, KeyBinding, KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model,
|
||||||
Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
|
ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
|
||||||
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
|
PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels,
|
||||||
PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
|
SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle,
|
||||||
TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
|
TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
|
||||||
WindowOptions, WindowTextSystem,
|
WindowOptions, WindowTextSystem,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
|
@ -14,6 +14,7 @@ use collections::FxHashSet;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use refineable::Refineable;
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -40,26 +41,6 @@ mod prompts;
|
||||||
pub use element_cx::*;
|
pub use element_cx::*;
|
||||||
pub use prompts::*;
|
pub use prompts::*;
|
||||||
|
|
||||||
const ACTIVE_DRAG_Z_INDEX: u16 = 1;
|
|
||||||
|
|
||||||
/// A global stacking order, which is created by stacking successive z-index values.
|
|
||||||
/// Each z-index will always be interpreted in the context of its parent z-index.
|
|
||||||
#[derive(Debug, Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
|
|
||||||
pub struct StackingOrder(SmallVec<[StackingContext; 64]>);
|
|
||||||
|
|
||||||
/// A single entry in a primitive's z-index stacking order
|
|
||||||
#[derive(Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
|
|
||||||
pub struct StackingContext {
|
|
||||||
pub(crate) z_index: u16,
|
|
||||||
pub(crate) id: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for StackingContext {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{{{}.{}}} ", self.z_index, self.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the two different phases when dispatching events.
|
/// Represents the two different phases when dispatching events.
|
||||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum DispatchPhase {
|
pub enum DispatchPhase {
|
||||||
|
@ -258,8 +239,10 @@ pub struct Window {
|
||||||
layout_engine: Option<TaffyLayoutEngine>,
|
layout_engine: Option<TaffyLayoutEngine>,
|
||||||
pub(crate) root_view: Option<AnyView>,
|
pub(crate) root_view: Option<AnyView>,
|
||||||
pub(crate) element_id_stack: GlobalElementId,
|
pub(crate) element_id_stack: GlobalElementId,
|
||||||
|
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
|
||||||
pub(crate) rendered_frame: Frame,
|
pub(crate) rendered_frame: Frame,
|
||||||
pub(crate) next_frame: Frame,
|
pub(crate) next_frame: Frame,
|
||||||
|
pub(crate) next_hitbox_id: HitboxId,
|
||||||
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||||
|
@ -267,6 +250,7 @@ pub struct Window {
|
||||||
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
|
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
|
||||||
default_prevented: bool,
|
default_prevented: bool,
|
||||||
mouse_position: Point<Pixels>,
|
mouse_position: Point<Pixels>,
|
||||||
|
mouse_hit_test: HitTest,
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
bounds: WindowBounds,
|
bounds: WindowBounds,
|
||||||
|
@ -278,7 +262,7 @@ pub struct Window {
|
||||||
pub(crate) needs_present: Rc<Cell<bool>>,
|
pub(crate) needs_present: Rc<Cell<bool>>,
|
||||||
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
||||||
pub(crate) refreshing: bool,
|
pub(crate) refreshing: bool,
|
||||||
pub(crate) drawing: bool,
|
pub(crate) draw_phase: DrawPhase,
|
||||||
activation_observers: SubscriberSet<(), AnyObserver>,
|
activation_observers: SubscriberSet<(), AnyObserver>,
|
||||||
pub(crate) focus: Option<FocusId>,
|
pub(crate) focus: Option<FocusId>,
|
||||||
focus_enabled: bool,
|
focus_enabled: bool,
|
||||||
|
@ -286,6 +270,14 @@ pub struct Window {
|
||||||
prompt: Option<RenderablePromptHandle>,
|
prompt: Option<RenderablePromptHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum DrawPhase {
|
||||||
|
None,
|
||||||
|
Layout,
|
||||||
|
Paint,
|
||||||
|
Focus,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
struct PendingInput {
|
struct PendingInput {
|
||||||
keystrokes: SmallVec<[Keystroke; 1]>,
|
keystrokes: SmallVec<[Keystroke; 1]>,
|
||||||
|
@ -319,7 +311,6 @@ impl PendingInput {
|
||||||
|
|
||||||
pub(crate) struct ElementStateBox {
|
pub(crate) struct ElementStateBox {
|
||||||
pub(crate) inner: Box<dyn Any>,
|
pub(crate) inner: Box<dyn Any>,
|
||||||
pub(crate) parent_view_id: EntityId,
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub(crate) type_name: &'static str,
|
pub(crate) type_name: &'static str,
|
||||||
}
|
}
|
||||||
|
@ -452,15 +443,18 @@ impl Window {
|
||||||
layout_engine: Some(TaffyLayoutEngine::new()),
|
layout_engine: Some(TaffyLayoutEngine::new()),
|
||||||
root_view: None,
|
root_view: None,
|
||||||
element_id_stack: GlobalElementId::default(),
|
element_id_stack: GlobalElementId::default(),
|
||||||
|
text_style_stack: Vec::new(),
|
||||||
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||||
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||||
next_frame_callbacks,
|
next_frame_callbacks,
|
||||||
|
next_hitbox_id: HitboxId::default(),
|
||||||
dirty_views: FxHashSet::default(),
|
dirty_views: FxHashSet::default(),
|
||||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||||
focus_listeners: SubscriberSet::new(),
|
focus_listeners: SubscriberSet::new(),
|
||||||
focus_lost_listeners: SubscriberSet::new(),
|
focus_lost_listeners: SubscriberSet::new(),
|
||||||
default_prevented: true,
|
default_prevented: true,
|
||||||
mouse_position,
|
mouse_position,
|
||||||
|
mouse_hit_test: HitTest::default(),
|
||||||
modifiers,
|
modifiers,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
bounds,
|
bounds,
|
||||||
|
@ -472,7 +466,7 @@ impl Window {
|
||||||
needs_present,
|
needs_present,
|
||||||
last_input_timestamp,
|
last_input_timestamp,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
drawing: false,
|
draw_phase: DrawPhase::None,
|
||||||
activation_observers: SubscriberSet::new(),
|
activation_observers: SubscriberSet::new(),
|
||||||
focus: None,
|
focus: None,
|
||||||
focus_enabled: true,
|
focus_enabled: true,
|
||||||
|
@ -533,7 +527,7 @@ impl<'a> WindowContext<'a> {
|
||||||
|
|
||||||
/// Mark the window as dirty, scheduling it to be redrawn on the next frame.
|
/// Mark the window as dirty, scheduling it to be redrawn on the next frame.
|
||||||
pub fn refresh(&mut self) {
|
pub fn refresh(&mut self) {
|
||||||
if !self.window.drawing {
|
if self.window.draw_phase == DrawPhase::None {
|
||||||
self.window.refreshing = true;
|
self.window.refreshing = true;
|
||||||
self.window.dirty.set(true);
|
self.window.dirty.set(true);
|
||||||
}
|
}
|
||||||
|
@ -592,11 +586,24 @@ impl<'a> WindowContext<'a> {
|
||||||
&self.window.text_system
|
&self.window.text_system
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The current text style. Which is composed of all the style refinements provided to `with_text_style`.
|
||||||
|
pub fn text_style(&self) -> TextStyle {
|
||||||
|
let mut style = TextStyle::default();
|
||||||
|
for refinement in &self.window.text_style_stack {
|
||||||
|
style.refine(refinement);
|
||||||
|
}
|
||||||
|
style
|
||||||
|
}
|
||||||
|
|
||||||
/// Dispatch the given action on the currently focused element.
|
/// Dispatch the given action on the currently focused element.
|
||||||
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
||||||
let focus_handle = self.focused();
|
let focus_handle = self.focused();
|
||||||
|
|
||||||
self.defer(move |cx| {
|
let window = self.window.handle;
|
||||||
|
self.app.defer(move |cx| {
|
||||||
|
cx.propagate_event = true;
|
||||||
|
window
|
||||||
|
.update(cx, |_, cx| {
|
||||||
let node_id = focus_handle
|
let node_id = focus_handle
|
||||||
.and_then(|handle| {
|
.and_then(|handle| {
|
||||||
cx.window
|
cx.window
|
||||||
|
@ -606,8 +613,12 @@ impl<'a> WindowContext<'a> {
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| cx.window.rendered_frame.dispatch_tree.root_node_id());
|
.unwrap_or_else(|| cx.window.rendered_frame.dispatch_tree.root_node_id());
|
||||||
|
|
||||||
cx.propagate_event = true;
|
cx.dispatch_action_on_node(node_id, action.as_ref());
|
||||||
cx.dispatch_action_on_node(node_id, action);
|
})
|
||||||
|
.log_err();
|
||||||
|
if cx.propagate_event {
|
||||||
|
cx.dispatch_global_action(action.as_ref());
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,171 +873,21 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window.modifiers
|
self.window.modifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if there is no opaque layer containing the given point
|
|
||||||
/// on top of the given level. Layers who are extensions of the queried layer
|
|
||||||
/// are not considered to be on top of queried layer.
|
|
||||||
pub fn was_top_layer(&self, point: &Point<Pixels>, layer: &StackingOrder) -> bool {
|
|
||||||
// Precondition: the depth map is ordered from topmost to bottomost.
|
|
||||||
|
|
||||||
for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() {
|
|
||||||
if layer >= opaque_layer {
|
|
||||||
// The queried layer is either above or is the same as the this opaque layer.
|
|
||||||
// Anything after this point is guaranteed to be below the queried layer.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bounds.contains(point) {
|
|
||||||
// This opaque layer is above the queried layer but it doesn't contain
|
|
||||||
// the given position, so we can ignore it even if it's above.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, we've established that this opaque layer is on top of the queried layer
|
|
||||||
// and contains the position:
|
|
||||||
// If neither the opaque layer or the queried layer is an extension of the other then
|
|
||||||
// we know they are on different stacking orders, and return false.
|
|
||||||
let is_on_same_layer = opaque_layer
|
|
||||||
.iter()
|
|
||||||
.zip(layer.iter())
|
|
||||||
.all(|(a, b)| a.z_index == b.z_index);
|
|
||||||
|
|
||||||
if !is_on_same_layer {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn was_top_layer_under_active_drag(
|
|
||||||
&self,
|
|
||||||
point: &Point<Pixels>,
|
|
||||||
layer: &StackingOrder,
|
|
||||||
) -> bool {
|
|
||||||
// Precondition: the depth map is ordered from topmost to bottomost.
|
|
||||||
|
|
||||||
for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() {
|
|
||||||
if layer >= opaque_layer {
|
|
||||||
// The queried layer is either above or is the same as the this opaque layer.
|
|
||||||
// Anything after this point is guaranteed to be below the queried layer.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bounds.contains(point) {
|
|
||||||
// This opaque layer is above the queried layer but it doesn't contain
|
|
||||||
// the given position, so we can ignore it even if it's above.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All normal content is rendered with a base z-index of 0, we know that if the root of this opaque layer
|
|
||||||
// equals `ACTIVE_DRAG_Z_INDEX` then it must be the drag layer and we can ignore it as we are
|
|
||||||
// looking to see if the queried layer was the topmost underneath the drag layer.
|
|
||||||
if opaque_layer
|
|
||||||
.first()
|
|
||||||
.map(|c| c.z_index == ACTIVE_DRAG_Z_INDEX)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, we've established that this opaque layer is on top of the queried layer
|
|
||||||
// and contains the position:
|
|
||||||
// If neither the opaque layer or the queried layer is an extension of the other then
|
|
||||||
// we know they are on different stacking orders, and return false.
|
|
||||||
let is_on_same_layer = opaque_layer
|
|
||||||
.iter()
|
|
||||||
.zip(layer.iter())
|
|
||||||
.all(|(a, b)| a.z_index == b.z_index);
|
|
||||||
|
|
||||||
if !is_on_same_layer {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called during painting to get the current stacking order.
|
|
||||||
pub fn stacking_order(&self) -> &StackingOrder {
|
|
||||||
&self.window.next_frame.z_index_stack
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
|
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
|
||||||
/// the contents of the new [Scene], use [present].
|
/// the contents of the new [Scene], use [present].
|
||||||
#[profiling::function]
|
#[profiling::function]
|
||||||
pub fn draw(&mut self) {
|
pub fn draw(&mut self) {
|
||||||
self.window.dirty.set(false);
|
self.window.dirty.set(false);
|
||||||
self.window.drawing = true;
|
|
||||||
|
|
||||||
if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut()
|
// Restore the previously-used input handler.
|
||||||
{
|
if let Some(input_handler) = self.window.platform_window.take_input_handler() {
|
||||||
let input_handler = self.window.platform_window.take_input_handler();
|
self.window
|
||||||
requested_handler.handler = input_handler;
|
.rendered_frame
|
||||||
|
.input_handlers
|
||||||
|
.push(Some(input_handler));
|
||||||
}
|
}
|
||||||
|
|
||||||
let root_view = self.window.root_view.take().unwrap();
|
self.with_element_context(|cx| cx.draw_roots());
|
||||||
let mut prompt = self.window.prompt.take();
|
|
||||||
self.with_element_context(|cx| {
|
|
||||||
cx.with_z_index(0, |cx| {
|
|
||||||
cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
|
|
||||||
// We need to use cx.cx here so we can utilize borrow splitting
|
|
||||||
for (action_type, action_listeners) in &cx.cx.app.global_action_listeners {
|
|
||||||
for action_listener in action_listeners.iter().cloned() {
|
|
||||||
cx.cx.window.next_frame.dispatch_tree.on_action(
|
|
||||||
*action_type,
|
|
||||||
Rc::new(
|
|
||||||
move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| {
|
|
||||||
action_listener(action, phase, cx)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let available_space = cx.window.viewport_size.map(Into::into);
|
|
||||||
|
|
||||||
let origin = Point::default();
|
|
||||||
cx.paint_view(root_view.entity_id(), |cx| {
|
|
||||||
cx.with_absolute_element_offset(origin, |cx| {
|
|
||||||
let (layout_id, mut rendered_element) =
|
|
||||||
(root_view.request_layout)(&root_view, cx);
|
|
||||||
cx.compute_layout(layout_id, available_space);
|
|
||||||
rendered_element.paint(cx);
|
|
||||||
|
|
||||||
if let Some(prompt) = &mut prompt {
|
|
||||||
prompt.paint(cx).draw(origin, available_space, cx)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
self.window.prompt = prompt;
|
|
||||||
|
|
||||||
if let Some(active_drag) = self.app.active_drag.take() {
|
|
||||||
self.with_element_context(|cx| {
|
|
||||||
cx.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| {
|
|
||||||
let offset = cx.mouse_position() - active_drag.cursor_offset;
|
|
||||||
let available_space =
|
|
||||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
|
||||||
active_drag.view.draw(offset, available_space, cx);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
self.active_drag = Some(active_drag);
|
|
||||||
} else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() {
|
|
||||||
self.with_element_context(|cx| {
|
|
||||||
cx.with_z_index(1, |cx| {
|
|
||||||
let available_space =
|
|
||||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
|
||||||
tooltip_request.tooltip.view.draw(
|
|
||||||
tooltip_request.tooltip.cursor_offset,
|
|
||||||
available_space,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
self.window.next_frame.tooltip_request = Some(tooltip_request);
|
|
||||||
}
|
|
||||||
self.window.dirty_views.clear();
|
self.window.dirty_views.clear();
|
||||||
|
|
||||||
self.window
|
self.window
|
||||||
|
@ -1038,26 +899,22 @@ impl<'a> WindowContext<'a> {
|
||||||
);
|
);
|
||||||
self.window.next_frame.focus = self.window.focus;
|
self.window.next_frame.focus = self.window.focus;
|
||||||
self.window.next_frame.window_active = self.window.active.get();
|
self.window.next_frame.window_active = self.window.active.get();
|
||||||
self.window.root_view = Some(root_view);
|
|
||||||
|
|
||||||
// Set the cursor only if we're the active window.
|
// Set the cursor only if we're the active window.
|
||||||
let cursor_style_request = self.window.next_frame.requested_cursor_style.take();
|
|
||||||
if self.is_window_active() {
|
if self.is_window_active() {
|
||||||
let cursor_style =
|
let cursor_style = self.compute_cursor_style().unwrap_or(CursorStyle::Arrow);
|
||||||
cursor_style_request.map_or(CursorStyle::Arrow, |request| request.style);
|
|
||||||
self.platform.set_cursor_style(cursor_style);
|
self.platform.set_cursor_style(cursor_style);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register requested input handler with the platform window.
|
// Register requested input handler with the platform window.
|
||||||
if let Some(requested_input) = self.window.next_frame.requested_input_handler.as_mut() {
|
if let Some(input_handler) = self.window.next_frame.input_handlers.pop() {
|
||||||
if let Some(handler) = requested_input.handler.take() {
|
self.window
|
||||||
self.window.platform_window.set_input_handler(handler);
|
.platform_window
|
||||||
}
|
.set_input_handler(input_handler.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window.layout_engine.as_mut().unwrap().clear();
|
self.window.layout_engine.as_mut().unwrap().clear();
|
||||||
self.text_system()
|
self.text_system().finish_frame();
|
||||||
.finish_frame(&self.window.next_frame.reused_views);
|
|
||||||
self.window
|
self.window
|
||||||
.next_frame
|
.next_frame
|
||||||
.finish(&mut self.window.rendered_frame);
|
.finish(&mut self.window.rendered_frame);
|
||||||
|
@ -1069,6 +926,7 @@ impl<'a> WindowContext<'a> {
|
||||||
element_arena.clear();
|
element_arena.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.window.draw_phase = DrawPhase::Focus;
|
||||||
let previous_focus_path = self.window.rendered_frame.focus_path();
|
let previous_focus_path = self.window.rendered_frame.focus_path();
|
||||||
let previous_window_active = self.window.rendered_frame.window_active;
|
let previous_window_active = self.window.rendered_frame.window_active;
|
||||||
mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame);
|
mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame);
|
||||||
|
@ -1104,7 +962,7 @@ impl<'a> WindowContext<'a> {
|
||||||
.retain(&(), |listener| listener(&event, self));
|
.retain(&(), |listener| listener(&event, self));
|
||||||
}
|
}
|
||||||
self.window.refreshing = false;
|
self.window.refreshing = false;
|
||||||
self.window.drawing = false;
|
self.window.draw_phase = DrawPhase::None;
|
||||||
self.window.needs_present.set(true);
|
self.window.needs_present.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1117,6 +975,18 @@ impl<'a> WindowContext<'a> {
|
||||||
profiling::finish_frame!();
|
profiling::finish_frame!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compute_cursor_style(&mut self) -> Option<CursorStyle> {
|
||||||
|
// TODO: maybe we should have a HashMap keyed by HitboxId.
|
||||||
|
let request = self
|
||||||
|
.window
|
||||||
|
.next_frame
|
||||||
|
.cursor_styles
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|request| request.hitbox_id.is_hovered(self))?;
|
||||||
|
Some(request.style)
|
||||||
|
}
|
||||||
|
|
||||||
/// Dispatch a given keystroke as though the user had typed it.
|
/// Dispatch a given keystroke as though the user had typed it.
|
||||||
/// You can create a keystroke with Keystroke::parse("").
|
/// You can create a keystroke with Keystroke::parse("").
|
||||||
pub fn dispatch_keystroke(&mut self, keystroke: Keystroke) -> bool {
|
pub fn dispatch_keystroke(&mut self, keystroke: Keystroke) -> bool {
|
||||||
|
@ -1251,43 +1121,32 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
|
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
|
||||||
if let Some(mut handlers) = self
|
self.window.mouse_hit_test = self.window.rendered_frame.hit_test(self.mouse_position());
|
||||||
.window
|
|
||||||
.rendered_frame
|
|
||||||
.mouse_listeners
|
|
||||||
.remove(&event.type_id())
|
|
||||||
{
|
|
||||||
// Because handlers may add other handlers, we sort every time.
|
|
||||||
handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
|
|
||||||
|
|
||||||
|
let mut mouse_listeners = mem::take(&mut self.window.rendered_frame.mouse_listeners);
|
||||||
|
self.with_element_context(|cx| {
|
||||||
// Capture phase, events bubble from back to front. Handlers for this phase are used for
|
// Capture phase, events bubble from back to front. Handlers for this phase are used for
|
||||||
// special purposes, such as detecting events outside of a given Bounds.
|
// special purposes, such as detecting events outside of a given Bounds.
|
||||||
for (_, _, handler) in &mut handlers {
|
for listener in &mut mouse_listeners {
|
||||||
self.with_element_context(|cx| {
|
let listener = listener.as_mut().unwrap();
|
||||||
handler(event, DispatchPhase::Capture, cx);
|
listener(event, DispatchPhase::Capture, cx);
|
||||||
});
|
if !cx.app.propagate_event {
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bubble phase, where most normal handlers do their work.
|
// Bubble phase, where most normal handlers do their work.
|
||||||
if self.app.propagate_event {
|
if cx.app.propagate_event {
|
||||||
for (_, _, handler) in handlers.iter_mut().rev() {
|
for listener in mouse_listeners.iter_mut().rev() {
|
||||||
self.with_element_context(|cx| {
|
let listener = listener.as_mut().unwrap();
|
||||||
handler(event, DispatchPhase::Bubble, cx);
|
listener(event, DispatchPhase::Bubble, cx);
|
||||||
});
|
if !cx.app.propagate_event {
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
self.window
|
self.window.rendered_frame.mouse_listeners = mouse_listeners;
|
||||||
.rendered_frame
|
|
||||||
.mouse_listeners
|
|
||||||
.insert(event.type_id(), handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.app.propagate_event && self.has_active_drag() {
|
if self.app.propagate_event && self.has_active_drag() {
|
||||||
if event.is::<MouseMoveEvent>() {
|
if event.is::<MouseMoveEvent>() {
|
||||||
|
@ -1357,6 +1216,7 @@ impl<'a> WindowContext<'a> {
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.window.pending_input = Some(currently_pending);
|
self.window.pending_input = Some(currently_pending);
|
||||||
|
|
||||||
self.propagate_event = false;
|
self.propagate_event = false;
|
||||||
|
@ -1376,7 +1236,7 @@ impl<'a> WindowContext<'a> {
|
||||||
|
|
||||||
self.propagate_event = true;
|
self.propagate_event = true;
|
||||||
for binding in bindings {
|
for binding in bindings {
|
||||||
self.dispatch_action_on_node(node_id, binding.action.boxed_clone());
|
self.dispatch_action_on_node(node_id, binding.action.as_ref());
|
||||||
if !self.propagate_event {
|
if !self.propagate_event {
|
||||||
self.dispatch_keystroke_observers(event, Some(binding.action));
|
self.dispatch_keystroke_observers(event, Some(binding.action));
|
||||||
return;
|
return;
|
||||||
|
@ -1454,7 +1314,7 @@ impl<'a> WindowContext<'a> {
|
||||||
|
|
||||||
self.propagate_event = true;
|
self.propagate_event = true;
|
||||||
for binding in currently_pending.bindings {
|
for binding in currently_pending.bindings {
|
||||||
self.dispatch_action_on_node(node_id, binding.action.boxed_clone());
|
self.dispatch_action_on_node(node_id, binding.action.as_ref());
|
||||||
if !self.propagate_event {
|
if !self.propagate_event {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1486,7 +1346,7 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
|
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: &dyn Action) {
|
||||||
let dispatch_path = self
|
let dispatch_path = self
|
||||||
.window
|
.window
|
||||||
.rendered_frame
|
.rendered_frame
|
||||||
|
@ -1628,10 +1488,20 @@ impl<'a> WindowContext<'a> {
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
|
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
|
||||||
|
|
||||||
self.window
|
let mut actions = self
|
||||||
|
.window
|
||||||
.rendered_frame
|
.rendered_frame
|
||||||
.dispatch_tree
|
.dispatch_tree
|
||||||
.available_actions(node_id)
|
.available_actions(node_id);
|
||||||
|
for action_type in self.global_action_listeners.keys() {
|
||||||
|
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.as_any().type_id()) {
|
||||||
|
let action = self.actions.build_action_type(action_type).ok();
|
||||||
|
if let Some(action) = action {
|
||||||
|
actions.insert(ix, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns key bindings that invoke the given action on the currently focused element.
|
/// Returns key bindings that invoke the given action on the currently focused element.
|
||||||
|
@ -1697,15 +1567,6 @@ impl<'a> WindowContext<'a> {
|
||||||
.on_should_close(Box::new(move || this.update(|cx| f(cx)).unwrap_or(true)))
|
.on_should_close(Box::new(move || this.update(|cx| f(cx)).unwrap_or(true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parent_view_id(&self) -> EntityId {
|
|
||||||
*self
|
|
||||||
.window
|
|
||||||
.next_frame
|
|
||||||
.view_stack
|
|
||||||
.last()
|
|
||||||
.expect("a view should always be on the stack while drawing")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register an action listener on the window for the next frame. The type of action
|
/// Register an action listener on the window for the next frame. The type of action
|
||||||
/// is determined by the first parameter of the given listener. When the next frame is rendered
|
/// is determined by the first parameter of the given listener. When the next frame is rendered
|
||||||
/// the listener will be cleared.
|
/// the listener will be cleared.
|
||||||
|
@ -2141,7 +2002,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.window.drawing {
|
if self.window.draw_phase == DrawPhase::None {
|
||||||
self.window_cx.window.dirty.set(true);
|
self.window_cx.window.dirty.set(true);
|
||||||
self.window_cx.app.push_effect(Effect::Notify {
|
self.window_cx.app.push_effect(Effect::Notify {
|
||||||
emitter: self.view.model.entity_id,
|
emitter: self.view.model.entity_id,
|
||||||
|
@ -2734,12 +2595,6 @@ impl Display for ElementId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementId {
|
|
||||||
pub(crate) fn from_entity_id(entity_id: EntityId) -> Self {
|
|
||||||
ElementId::View(entity_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<SharedString> for ElementId {
|
impl TryInto<SharedString> for ElementId {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,9 +3,9 @@ use std::ops::Deref;
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
div, opaque_grey, white, AnyElement, AnyView, ElementContext, EventEmitter, FocusHandle,
|
div, opaque_grey, white, AnyView, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
||||||
FocusableView, InteractiveElement, IntoElement, ParentElement, PromptLevel, Render,
|
IntoElement, ParentElement, PromptLevel, Render, StatefulInteractiveElement, Styled, View,
|
||||||
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext,
|
ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The event emitted when a prompt's option is selected.
|
/// The event emitted when a prompt's option is selected.
|
||||||
|
@ -57,13 +57,7 @@ impl PromptHandle {
|
||||||
|
|
||||||
/// A prompt handle capable of being rendered in a window.
|
/// A prompt handle capable of being rendered in a window.
|
||||||
pub struct RenderablePromptHandle {
|
pub struct RenderablePromptHandle {
|
||||||
view: Box<dyn PromptViewHandle>,
|
pub(crate) view: Box<dyn PromptViewHandle>,
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderablePromptHandle {
|
|
||||||
pub(crate) fn paint(&mut self, _: &mut ElementContext) -> AnyElement {
|
|
||||||
self.view.any_view().into_any_element()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use this function in conjunction with [AppContext::set_prompt_renderer] to force
|
/// Use this function in conjunction with [AppContext::set_prompt_renderer] to force
|
||||||
|
@ -146,7 +140,6 @@ impl Render for FallbackPromptRenderer {
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.z_index(u16::MAX)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
@ -184,7 +177,7 @@ impl FocusableView for FallbackPromptRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait PromptViewHandle {
|
pub(crate) trait PromptViewHandle {
|
||||||
fn any_view(&self) -> AnyView;
|
fn any_view(&self) -> AnyView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,6 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
|
||||||
{
|
{
|
||||||
type Element = gpui::Component<Self>;
|
type Element = gpui::Component<Self>;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<gpui::ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
gpui::Component::new(self)
|
gpui::Component::new(self)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<PathBuf> {
|
||||||
// If the symlink is not there or is outdated, first try replacing it
|
// If the symlink is not there or is outdated, first try replacing it
|
||||||
// without escalating.
|
// without escalating.
|
||||||
smol::fs::remove_file(link_path).await.log_err();
|
smol::fs::remove_file(link_path).await.log_err();
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
if smol::fs::unix::symlink(&cli_path, link_path)
|
if smol::fs::unix::symlink(&cli_path, link_path)
|
||||||
|
|
|
@ -807,7 +807,7 @@ impl Render for LspLogToolbarItemView {
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(Label::new(RPC_MESSAGES))
|
.child(Label::new(RPC_MESSAGES))
|
||||||
.child(
|
.child(
|
||||||
div().z_index(120).child(
|
div().child(
|
||||||
Checkbox::new(
|
Checkbox::new(
|
||||||
ix,
|
ix,
|
||||||
if row.rpc_trace_enabled {
|
if row.rpc_trace_enabled {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId};
|
use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div,
|
actions, canvas, div, rems, uniform_list, AnyElement, AppContext, Div, EventEmitter,
|
||||||
EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model,
|
FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, MouseButton,
|
||||||
MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled,
|
MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, UniformListScrollHandle, View,
|
||||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
|
ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use language::{Buffer, OwnedSyntaxLayer};
|
use language::{Buffer, OwnedSyntaxLayer};
|
||||||
use std::{mem, ops::Range};
|
use std::{mem, ops::Range};
|
||||||
|
@ -281,7 +281,7 @@ impl Render for SyntaxTreeView {
|
||||||
.and_then(|buffer| buffer.active_layer.as_ref())
|
.and_then(|buffer| buffer.active_layer.as_ref())
|
||||||
{
|
{
|
||||||
let layer = layer.clone();
|
let layer = layer.clone();
|
||||||
let list = uniform_list(
|
let mut list = uniform_list(
|
||||||
cx.view().clone(),
|
cx.view().clone(),
|
||||||
"SyntaxTreeView",
|
"SyntaxTreeView",
|
||||||
layer.node().descendant_count(),
|
layer.node().descendant_count(),
|
||||||
|
@ -360,16 +360,16 @@ impl Render for SyntaxTreeView {
|
||||||
)
|
)
|
||||||
.size_full()
|
.size_full()
|
||||||
.track_scroll(self.list_scroll_handle.clone())
|
.track_scroll(self.list_scroll_handle.clone())
|
||||||
.text_bg(cx.theme().colors().background);
|
.text_bg(cx.theme().colors().background).into_any_element();
|
||||||
|
|
||||||
rendered = rendered.child(
|
rendered = rendered.child(
|
||||||
canvas(move |bounds, cx| {
|
canvas(
|
||||||
list.into_any_element().draw(
|
move |bounds, cx| {
|
||||||
bounds.origin,
|
list.layout(bounds.origin, bounds.size.into(), cx);
|
||||||
bounds.size.map(AvailableSpace::Definite),
|
list
|
||||||
cx,
|
},
|
||||||
|
|_, mut list, cx| list.paint(cx),
|
||||||
)
|
)
|
||||||
})
|
|
||||||
.size_full(),
|
.size_full(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl super::LspAdapter for OmniSharpAdapter {
|
||||||
archive.unpack(container_dir).await?;
|
archive.unpack(container_dir).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
fs::set_permissions(
|
fs::set_permissions(
|
||||||
|
|
|
@ -350,7 +350,7 @@ impl LspAdapter for NextLspAdapter {
|
||||||
}
|
}
|
||||||
futures::io::copy(response.body_mut(), &mut file).await?;
|
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
fs::set_permissions(
|
fs::set_permissions(
|
||||||
|
|
|
@ -79,7 +79,7 @@ impl super::LspAdapter for LuaLspAdapter {
|
||||||
archive.unpack(container_dir).await?;
|
archive.unpack(container_dir).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
fs::set_permissions(
|
fs::set_permissions(
|
||||||
|
|
|
@ -70,7 +70,7 @@ impl LspAdapter for RustLspAdapter {
|
||||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||||
let mut file = File::create(&destination_path).await?;
|
let mut file = File::create(&destination_path).await?;
|
||||||
futures::io::copy(decompressed_bytes, &mut file).await?;
|
futures::io::copy(decompressed_bytes, &mut file).await?;
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
fs::set_permissions(
|
fs::set_permissions(
|
||||||
|
|
|
@ -68,7 +68,7 @@ impl LspAdapter for TaploLspAdapter {
|
||||||
|
|
||||||
futures::io::copy(decompressed_bytes, &mut file).await?;
|
futures::io::copy(decompressed_bytes, &mut file).await?;
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
fs::set_permissions(
|
fs::set_permissions(
|
||||||
|
|
|
@ -73,7 +73,7 @@ impl LspAdapter for ZlsAdapter {
|
||||||
archive.unpack(container_dir).await?;
|
archive.unpack(container_dir).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
fs::set_permissions(
|
fs::set_permissions(
|
||||||
|
|
|
@ -1378,7 +1378,7 @@ impl ProjectPanel {
|
||||||
let is_selected = self
|
let is_selected = self
|
||||||
.selection
|
.selection
|
||||||
.map_or(false, |selection| selection.entry_id == entry_id);
|
.map_or(false, |selection| selection.entry_id == entry_id);
|
||||||
let width = self.width.unwrap_or(px(0.));
|
let width = self.size(cx);
|
||||||
|
|
||||||
let filename_text_color = details
|
let filename_text_color = details
|
||||||
.git_status
|
.git_status
|
||||||
|
|
|
@ -7,7 +7,6 @@ mod picker;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
mod text;
|
mod text;
|
||||||
mod viewport_units;
|
mod viewport_units;
|
||||||
mod z_index;
|
|
||||||
|
|
||||||
pub use auto_height_editor::*;
|
pub use auto_height_editor::*;
|
||||||
pub use cursor::*;
|
pub use cursor::*;
|
||||||
|
@ -18,4 +17,3 @@ pub use picker::*;
|
||||||
pub use scroll::*;
|
pub use scroll::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
pub use viewport_units::*;
|
pub use viewport_units::*;
|
||||||
pub use z_index::*;
|
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
use gpui::{px, rgb, Div, IntoElement, Render, RenderOnce};
|
|
||||||
use story::Story;
|
|
||||||
use ui::prelude::*;
|
|
||||||
|
|
||||||
/// A reimplementation of the MDN `z-index` example, found here:
|
|
||||||
/// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
|
|
||||||
pub struct ZIndexStory;
|
|
||||||
|
|
||||||
impl Render for ZIndexStory {
|
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
Story::container().child(Story::title("z-index")).child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w(px(250.))
|
|
||||||
.child(Story::label("z-index: auto"))
|
|
||||||
.child(ZIndexExample::new(0)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w(px(250.))
|
|
||||||
.child(Story::label("z-index: 1"))
|
|
||||||
.child(ZIndexExample::new(1)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w(px(250.))
|
|
||||||
.child(Story::label("z-index: 3"))
|
|
||||||
.child(ZIndexExample::new(3)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w(px(250.))
|
|
||||||
.child(Story::label("z-index: 5"))
|
|
||||||
.child(ZIndexExample::new(5)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w(px(250.))
|
|
||||||
.child(Story::label("z-index: 7"))
|
|
||||||
.child(ZIndexExample::new(7)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Styles: Styled + Sized {
|
|
||||||
// Trailing `_` is so we don't collide with `block` style `StyleHelpers`.
|
|
||||||
fn block_(self) -> Self {
|
|
||||||
self.absolute()
|
|
||||||
.w(px(150.))
|
|
||||||
.h(px(50.))
|
|
||||||
.text_color(rgb(0x000000))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blue(self) -> Self {
|
|
||||||
self.bg(rgb(0xe5e8fc))
|
|
||||||
.border_5()
|
|
||||||
.border_color(rgb(0x112382))
|
|
||||||
.line_height(px(55.))
|
|
||||||
// HACK: Simulate `text-align: center`.
|
|
||||||
.pl(px(24.))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn red(self) -> Self {
|
|
||||||
self.bg(rgb(0xfce5e7))
|
|
||||||
.border_5()
|
|
||||||
.border_color(rgb(0xe3a1a7))
|
|
||||||
// HACK: Simulate `text-align: center`.
|
|
||||||
.pl(px(8.))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Styles for Div {}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
struct ZIndexExample {
|
|
||||||
z_index: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for ZIndexExample {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
div()
|
|
||||||
.relative()
|
|
||||||
.size_full()
|
|
||||||
// Example element.
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.top(px(15.))
|
|
||||||
.left(px(15.))
|
|
||||||
.w(px(180.))
|
|
||||||
.h(px(230.))
|
|
||||||
.bg(rgb(0xfcfbe5))
|
|
||||||
.text_color(rgb(0x000000))
|
|
||||||
.border_5()
|
|
||||||
.border_color(rgb(0xe3e0a1))
|
|
||||||
.line_height(px(215.))
|
|
||||||
// HACK: Simulate `text-align: center`.
|
|
||||||
.pl(px(24.))
|
|
||||||
.z_index(self.z_index)
|
|
||||||
.child(SharedString::from(format!(
|
|
||||||
"z-index: {}",
|
|
||||||
if self.z_index == 0 {
|
|
||||||
"auto".to_string()
|
|
||||||
} else {
|
|
||||||
self.z_index.to_string()
|
|
||||||
}
|
|
||||||
))),
|
|
||||||
)
|
|
||||||
// Blue blocks.
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.blue()
|
|
||||||
.block_()
|
|
||||||
.top(px(0.))
|
|
||||||
.left(px(0.))
|
|
||||||
.z_index(6)
|
|
||||||
.child("z-index: 6"),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.blue()
|
|
||||||
.block_()
|
|
||||||
.top(px(30.))
|
|
||||||
.left(px(30.))
|
|
||||||
.z_index(4)
|
|
||||||
.child("z-index: 4"),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.blue()
|
|
||||||
.block_()
|
|
||||||
.top(px(60.))
|
|
||||||
.left(px(60.))
|
|
||||||
.z_index(2)
|
|
||||||
.child("z-index: 2"),
|
|
||||||
)
|
|
||||||
// Red blocks.
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.red()
|
|
||||||
.block_()
|
|
||||||
.top(px(150.))
|
|
||||||
.left(px(0.))
|
|
||||||
.child("z-index: auto"),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.red()
|
|
||||||
.block_()
|
|
||||||
.top(px(180.))
|
|
||||||
.left(px(30.))
|
|
||||||
.child("z-index: auto"),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.red()
|
|
||||||
.block_()
|
|
||||||
.top(px(210.))
|
|
||||||
.left(px(60.))
|
|
||||||
.child("z-index: auto"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ZIndexExample {
|
|
||||||
pub fn new(z_index: u16) -> Self {
|
|
||||||
Self { z_index }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,7 +35,6 @@ pub enum ComponentStory {
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
Text,
|
Text,
|
||||||
ViewportUnits,
|
ViewportUnits,
|
||||||
ZIndex,
|
|
||||||
Picker,
|
Picker,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +66,6 @@ impl ComponentStory {
|
||||||
Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(),
|
Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(),
|
||||||
Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(),
|
Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(),
|
||||||
Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(),
|
Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(),
|
||||||
Self::ZIndex => cx.new_view(|_| ZIndexStory).into(),
|
|
||||||
Self::Picker => PickerStory::new(cx).into(),
|
Self::Picker => PickerStory::new(cx).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -404,7 +404,7 @@ impl TerminalBuilder {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let (fd, shell_pid) = (pty.file().as_raw_fd(), pty.child().id());
|
let (fd, shell_pid) = (pty.file().as_raw_fd(), pty.child().id());
|
||||||
|
|
||||||
// todo(windows)
|
// todo("windows")
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let (fd, shell_pid) = {
|
let (fd, shell_pid) = {
|
||||||
let child = pty.child_watcher();
|
let child = pty.child_watcher();
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
|
use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, fill, point, px, relative, AnyElement, AvailableSpace, Bounds, DispatchPhase, Element,
|
div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, ElementContext,
|
||||||
ElementContext, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla,
|
FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler,
|
||||||
InputHandler, InteractiveBounds, InteractiveElement, InteractiveElementState, Interactivity,
|
InteractiveElement, Interactivity, IntoElement, LayoutId, Model, ModelContext,
|
||||||
IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent,
|
ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine,
|
||||||
Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun,
|
StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UnderlineStyle,
|
||||||
TextStyle, UnderlineStyle, WeakView, WhiteSpace, WindowContext, WindowTextSystem,
|
WeakView, WhiteSpace, WindowContext, WindowTextSystem,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::CursorShape;
|
use language::CursorShape;
|
||||||
|
@ -15,10 +15,13 @@ use terminal::{
|
||||||
grid::Dimensions,
|
grid::Dimensions,
|
||||||
index::Point as AlacPoint,
|
index::Point as AlacPoint,
|
||||||
term::{cell::Flags, TermMode},
|
term::{cell::Flags, TermMode},
|
||||||
vte::ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
|
vte::ansi::{
|
||||||
|
Color::{self as AnsiColor, Named},
|
||||||
|
CursorShape as AlacCursorShape, NamedColor,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
terminal_settings::TerminalSettings,
|
terminal_settings::TerminalSettings,
|
||||||
IndexedCell, Terminal, TerminalContent, TerminalSize,
|
HoveredWord, IndexedCell, Terminal, TerminalContent, TerminalSize,
|
||||||
};
|
};
|
||||||
use theme::{ActiveTheme, Theme, ThemeSettings};
|
use theme::{ActiveTheme, Theme, ThemeSettings};
|
||||||
use ui::Tooltip;
|
use ui::Tooltip;
|
||||||
|
@ -29,16 +32,18 @@ use std::{fmt::Debug, ops::RangeInclusive};
|
||||||
|
|
||||||
/// The information generated during layout that is necessary for painting.
|
/// The information generated during layout that is necessary for painting.
|
||||||
pub struct LayoutState {
|
pub struct LayoutState {
|
||||||
|
hitbox: Hitbox,
|
||||||
cells: Vec<LayoutCell>,
|
cells: Vec<LayoutCell>,
|
||||||
rects: Vec<LayoutRect>,
|
rects: Vec<LayoutRect>,
|
||||||
relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,
|
relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,
|
||||||
cursor: Option<Cursor>,
|
cursor: Option<CursorLayout>,
|
||||||
background_color: Hsla,
|
background_color: Hsla,
|
||||||
dimensions: TerminalSize,
|
dimensions: TerminalSize,
|
||||||
mode: TermMode,
|
mode: TermMode,
|
||||||
display_offset: usize,
|
display_offset: usize,
|
||||||
hyperlink_tooltip: Option<AnyElement>,
|
hyperlink_tooltip: Option<AnyElement>,
|
||||||
gutter: Pixels,
|
gutter: Pixels,
|
||||||
|
last_hovered_word: Option<HoveredWord>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.
|
/// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.
|
||||||
|
@ -392,7 +397,174 @@ impl TerminalElement {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_layout(&self, bounds: Bounds<gpui::Pixels>, cx: &mut ElementContext) -> LayoutState {
|
fn generic_button_handler<E>(
|
||||||
|
connection: Model<Terminal>,
|
||||||
|
origin: Point<Pixels>,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
f: impl Fn(&mut Terminal, Point<Pixels>, &E, &mut ModelContext<Terminal>),
|
||||||
|
) -> impl Fn(&E, &mut WindowContext) {
|
||||||
|
move |event, cx| {
|
||||||
|
cx.focus(&focus_handle);
|
||||||
|
connection.update(cx, |terminal, cx| {
|
||||||
|
f(terminal, origin, event, cx);
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_mouse_listeners(
|
||||||
|
&mut self,
|
||||||
|
origin: Point<Pixels>,
|
||||||
|
mode: TermMode,
|
||||||
|
hitbox: &Hitbox,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
let focus = self.focus.clone();
|
||||||
|
let terminal = self.terminal.clone();
|
||||||
|
|
||||||
|
self.interactivity.on_mouse_down(MouseButton::Left, {
|
||||||
|
let terminal = terminal.clone();
|
||||||
|
let focus = focus.clone();
|
||||||
|
move |e, cx| {
|
||||||
|
cx.focus(&focus);
|
||||||
|
terminal.update(cx, |terminal, cx| {
|
||||||
|
terminal.mouse_down(&e, origin);
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.on_mouse_event({
|
||||||
|
let focus = self.focus.clone();
|
||||||
|
let terminal = self.terminal.clone();
|
||||||
|
let hitbox = hitbox.clone();
|
||||||
|
move |e: &MouseMoveEvent, phase, cx| {
|
||||||
|
if phase != DispatchPhase::Bubble || !focus.is_focused(cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.pressed_button.is_some() && !cx.has_active_drag() {
|
||||||
|
let hovered = hitbox.is_hovered(cx);
|
||||||
|
terminal.update(cx, |terminal, cx| {
|
||||||
|
if !terminal.selection_started() {
|
||||||
|
if hovered {
|
||||||
|
terminal.mouse_drag(e, origin, hitbox.bounds);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
terminal.mouse_drag(e, origin, hitbox.bounds);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if hitbox.is_hovered(cx) {
|
||||||
|
terminal.update(cx, |terminal, cx| {
|
||||||
|
terminal.mouse_move(&e, origin);
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.interactivity.on_mouse_up(
|
||||||
|
MouseButton::Left,
|
||||||
|
TerminalElement::generic_button_handler(
|
||||||
|
terminal.clone(),
|
||||||
|
origin,
|
||||||
|
focus.clone(),
|
||||||
|
move |terminal, origin, e, cx| {
|
||||||
|
terminal.mouse_up(&e, origin, cx);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.interactivity.on_scroll_wheel({
|
||||||
|
let terminal = terminal.clone();
|
||||||
|
move |e, cx| {
|
||||||
|
terminal.update(cx, |terminal, cx| {
|
||||||
|
terminal.scroll_wheel(e, origin);
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mouse mode handlers:
|
||||||
|
// All mouse modes need the extra click handlers
|
||||||
|
if mode.intersects(TermMode::MOUSE_MODE) {
|
||||||
|
self.interactivity.on_mouse_down(
|
||||||
|
MouseButton::Right,
|
||||||
|
TerminalElement::generic_button_handler(
|
||||||
|
terminal.clone(),
|
||||||
|
origin,
|
||||||
|
focus.clone(),
|
||||||
|
move |terminal, origin, e, _cx| {
|
||||||
|
terminal.mouse_down(&e, origin);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.interactivity.on_mouse_down(
|
||||||
|
MouseButton::Middle,
|
||||||
|
TerminalElement::generic_button_handler(
|
||||||
|
terminal.clone(),
|
||||||
|
origin,
|
||||||
|
focus.clone(),
|
||||||
|
move |terminal, origin, e, _cx| {
|
||||||
|
terminal.mouse_down(&e, origin);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.interactivity.on_mouse_up(
|
||||||
|
MouseButton::Right,
|
||||||
|
TerminalElement::generic_button_handler(
|
||||||
|
terminal.clone(),
|
||||||
|
origin,
|
||||||
|
focus.clone(),
|
||||||
|
move |terminal, origin, e, cx| {
|
||||||
|
terminal.mouse_up(&e, origin, cx);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.interactivity.on_mouse_up(
|
||||||
|
MouseButton::Middle,
|
||||||
|
TerminalElement::generic_button_handler(
|
||||||
|
terminal,
|
||||||
|
origin,
|
||||||
|
focus,
|
||||||
|
move |terminal, origin, e, cx| {
|
||||||
|
terminal.mouse_up(&e, origin, cx);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for TerminalElement {
|
||||||
|
type BeforeLayout = ();
|
||||||
|
type AfterLayout = LayoutState;
|
||||||
|
|
||||||
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
|
self.interactivity.occlude_mouse();
|
||||||
|
let layout_id = self.interactivity.before_layout(cx, |mut style, cx| {
|
||||||
|
style.size.width = relative(1.).into();
|
||||||
|
style.size.height = relative(1.).into();
|
||||||
|
let layout_id = cx.request_layout(&style, None);
|
||||||
|
|
||||||
|
layout_id
|
||||||
|
});
|
||||||
|
(layout_id, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
_: &mut Self::BeforeLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) -> Self::AfterLayout {
|
||||||
|
self.interactivity
|
||||||
|
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, cx| {
|
||||||
|
let hitbox = hitbox.unwrap();
|
||||||
let settings = ThemeSettings::get_global(cx).clone();
|
let settings = ThemeSettings::get_global(cx).clone();
|
||||||
|
|
||||||
let buffer_font_size = settings.buffer_font_size(cx);
|
let buffer_font_size = settings.buffer_font_size(cx);
|
||||||
|
@ -481,28 +653,24 @@ impl TerminalElement {
|
||||||
let last_hovered_word = self.terminal.update(cx, |terminal, cx| {
|
let last_hovered_word = self.terminal.update(cx, |terminal, cx| {
|
||||||
terminal.set_size(dimensions);
|
terminal.set_size(dimensions);
|
||||||
terminal.sync(cx);
|
terminal.sync(cx);
|
||||||
if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() {
|
if self.can_navigate_to_selected_word
|
||||||
|
&& terminal.can_navigate_to_selected_word()
|
||||||
|
{
|
||||||
terminal.last_content.last_hovered_word.clone()
|
terminal.last_content.last_hovered_word.clone()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if bounds.contains(&cx.mouse_position()) {
|
|
||||||
let stacking_order = cx.stacking_order().clone();
|
|
||||||
if self.can_navigate_to_selected_word && last_hovered_word.is_some() {
|
|
||||||
cx.set_cursor_style(gpui::CursorStyle::PointingHand, stacking_order);
|
|
||||||
} else {
|
|
||||||
cx.set_cursor_style(gpui::CursorStyle::IBeam, stacking_order);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
|
let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
|
||||||
div()
|
let offset = bounds.origin + Point::new(gutter, px(0.));
|
||||||
|
let mut element = div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.id("terminal-element")
|
.id("terminal-element")
|
||||||
.tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
|
.tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
|
||||||
.into_any_element()
|
.into_any_element();
|
||||||
|
element.layout(offset, bounds.size.into(), cx);
|
||||||
|
element
|
||||||
});
|
});
|
||||||
|
|
||||||
let TerminalContent {
|
let TerminalContent {
|
||||||
|
@ -575,20 +743,20 @@ impl TerminalElement {
|
||||||
AlacCursorShape::Hidden => unreachable!(),
|
AlacCursorShape::Hidden => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Cursor::new(
|
CursorLayout::new(
|
||||||
cursor_position,
|
cursor_position,
|
||||||
block_width,
|
block_width,
|
||||||
dimensions.line_height,
|
dimensions.line_height,
|
||||||
theme.players().local().cursor,
|
theme.players().local().cursor,
|
||||||
shape,
|
shape,
|
||||||
text,
|
text,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
LayoutState {
|
LayoutState {
|
||||||
|
hitbox,
|
||||||
cells,
|
cells,
|
||||||
cursor,
|
cursor,
|
||||||
background_color,
|
background_color,
|
||||||
|
@ -599,184 +767,18 @@ impl TerminalElement {
|
||||||
display_offset: *display_offset,
|
display_offset: *display_offset,
|
||||||
hyperlink_tooltip,
|
hyperlink_tooltip,
|
||||||
gutter,
|
gutter,
|
||||||
}
|
last_hovered_word,
|
||||||
}
|
|
||||||
|
|
||||||
fn generic_button_handler<E>(
|
|
||||||
connection: Model<Terminal>,
|
|
||||||
origin: Point<Pixels>,
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
f: impl Fn(&mut Terminal, Point<Pixels>, &E, &mut ModelContext<Terminal>),
|
|
||||||
) -> impl Fn(&E, &mut WindowContext) {
|
|
||||||
move |event, cx| {
|
|
||||||
cx.focus(&focus_handle);
|
|
||||||
connection.update(cx, |terminal, cx| {
|
|
||||||
f(terminal, origin, event, cx);
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_mouse_listeners(
|
|
||||||
&mut self,
|
|
||||||
origin: Point<Pixels>,
|
|
||||||
mode: TermMode,
|
|
||||||
bounds: Bounds<Pixels>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) {
|
|
||||||
let focus = self.focus.clone();
|
|
||||||
let terminal = self.terminal.clone();
|
|
||||||
let interactive_bounds = InteractiveBounds {
|
|
||||||
bounds: bounds.intersect(&cx.content_mask().bounds),
|
|
||||||
stacking_order: cx.stacking_order().clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.interactivity.on_mouse_down(MouseButton::Left, {
|
|
||||||
let terminal = terminal.clone();
|
|
||||||
let focus = focus.clone();
|
|
||||||
move |e, cx| {
|
|
||||||
cx.focus(&focus);
|
|
||||||
terminal.update(cx, |terminal, cx| {
|
|
||||||
terminal.mouse_down(&e, origin);
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.on_mouse_event({
|
|
||||||
let focus = self.focus.clone();
|
|
||||||
let terminal = self.terminal.clone();
|
|
||||||
move |e: &MouseMoveEvent, phase, cx| {
|
|
||||||
if phase != DispatchPhase::Bubble || !focus.is_focused(cx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.pressed_button.is_some() && !cx.has_active_drag() {
|
|
||||||
let visibly_contains = interactive_bounds.visibly_contains(&e.position, cx);
|
|
||||||
terminal.update(cx, |terminal, cx| {
|
|
||||||
if !terminal.selection_started() {
|
|
||||||
if visibly_contains {
|
|
||||||
terminal.mouse_drag(e, origin, bounds);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
terminal.mouse_drag(e, origin, bounds);
|
|
||||||
cx.notify();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if interactive_bounds.visibly_contains(&e.position, cx) {
|
|
||||||
terminal.update(cx, |terminal, cx| {
|
|
||||||
terminal.mouse_move(&e, origin);
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.interactivity.on_mouse_up(
|
|
||||||
MouseButton::Left,
|
|
||||||
TerminalElement::generic_button_handler(
|
|
||||||
terminal.clone(),
|
|
||||||
origin,
|
|
||||||
focus.clone(),
|
|
||||||
move |terminal, origin, e, cx| {
|
|
||||||
terminal.mouse_up(&e, origin, cx);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
self.interactivity.on_scroll_wheel({
|
|
||||||
let terminal = terminal.clone();
|
|
||||||
move |e, cx| {
|
|
||||||
terminal.update(cx, |terminal, cx| {
|
|
||||||
terminal.scroll_wheel(e, origin);
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mouse mode handlers:
|
|
||||||
// All mouse modes need the extra click handlers
|
|
||||||
if mode.intersects(TermMode::MOUSE_MODE) {
|
|
||||||
self.interactivity.on_mouse_down(
|
|
||||||
MouseButton::Right,
|
|
||||||
TerminalElement::generic_button_handler(
|
|
||||||
terminal.clone(),
|
|
||||||
origin,
|
|
||||||
focus.clone(),
|
|
||||||
move |terminal, origin, e, _cx| {
|
|
||||||
terminal.mouse_down(&e, origin);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
self.interactivity.on_mouse_down(
|
|
||||||
MouseButton::Middle,
|
|
||||||
TerminalElement::generic_button_handler(
|
|
||||||
terminal.clone(),
|
|
||||||
origin,
|
|
||||||
focus.clone(),
|
|
||||||
move |terminal, origin, e, _cx| {
|
|
||||||
terminal.mouse_down(&e, origin);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
self.interactivity.on_mouse_up(
|
|
||||||
MouseButton::Right,
|
|
||||||
TerminalElement::generic_button_handler(
|
|
||||||
terminal.clone(),
|
|
||||||
origin,
|
|
||||||
focus.clone(),
|
|
||||||
move |terminal, origin, e, cx| {
|
|
||||||
terminal.mouse_up(&e, origin, cx);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
self.interactivity.on_mouse_up(
|
|
||||||
MouseButton::Middle,
|
|
||||||
TerminalElement::generic_button_handler(
|
|
||||||
terminal,
|
|
||||||
origin,
|
|
||||||
focus,
|
|
||||||
move |terminal, origin, e, cx| {
|
|
||||||
terminal.mouse_up(&e, origin, cx);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element for TerminalElement {
|
|
||||||
type State = InteractiveElementState;
|
|
||||||
|
|
||||||
fn request_layout(
|
|
||||||
&mut self,
|
|
||||||
element_state: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext<'_>,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
let (layout_id, interactive_state) =
|
|
||||||
self.interactivity
|
|
||||||
.layout(element_state, cx, |mut style, cx| {
|
|
||||||
style.size.width = relative(1.).into();
|
|
||||||
style.size.height = relative(1.).into();
|
|
||||||
let layout_id = cx.request_layout(&style, None);
|
|
||||||
|
|
||||||
layout_id
|
|
||||||
});
|
|
||||||
|
|
||||||
(layout_id, interactive_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
state: &mut Self::State,
|
_: &mut Self::BeforeLayout,
|
||||||
|
layout: &mut Self::AfterLayout,
|
||||||
cx: &mut ElementContext<'_>,
|
cx: &mut ElementContext<'_>,
|
||||||
) {
|
) {
|
||||||
let mut layout = self.compute_layout(bounds, cx);
|
|
||||||
|
|
||||||
cx.paint_quad(fill(bounds, layout.background_color));
|
cx.paint_quad(fill(bounds, layout.background_color));
|
||||||
let origin = bounds.origin + Point::new(layout.gutter, px(0.));
|
let origin = bounds.origin + Point::new(layout.gutter, px(0.));
|
||||||
|
|
||||||
|
@ -789,10 +791,17 @@ impl Element for TerminalElement {
|
||||||
workspace: self.workspace.clone(),
|
workspace: self.workspace.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.register_mouse_listeners(origin, layout.mode, bounds, cx);
|
self.register_mouse_listeners(origin, layout.mode, &layout.hitbox, cx);
|
||||||
|
if self.can_navigate_to_selected_word && layout.last_hovered_word.is_some() {
|
||||||
|
cx.set_cursor_style(gpui::CursorStyle::PointingHand, &layout.hitbox);
|
||||||
|
} else {
|
||||||
|
cx.set_cursor_style(gpui::CursorStyle::IBeam, &layout.hitbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cursor = layout.cursor.take();
|
||||||
|
let hyperlink_tooltip = layout.hyperlink_tooltip.take();
|
||||||
self.interactivity
|
self.interactivity
|
||||||
.paint(bounds, bounds.size, state, cx, |_, _, cx| {
|
.paint(bounds, Some(&layout.hitbox), cx, |_, cx| {
|
||||||
cx.handle_input(&self.focus, terminal_input_handler);
|
cx.handle_input(&self.focus, terminal_input_handler);
|
||||||
|
|
||||||
cx.on_key_event({
|
cx.on_key_event({
|
||||||
|
@ -815,9 +824,7 @@ impl Element for TerminalElement {
|
||||||
rect.paint(origin, &layout, cx);
|
rect.paint(origin, &layout, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.with_z_index(1, |cx| {
|
for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
|
||||||
for (relative_highlighted_range, color) in
|
|
||||||
layout.relative_highlighted_ranges.iter()
|
|
||||||
{
|
{
|
||||||
if let Some((start_y, highlighted_range_lines)) =
|
if let Some((start_y, highlighted_range_lines)) =
|
||||||
to_highlighted_range_lines(relative_highlighted_range, &layout, origin)
|
to_highlighted_range_lines(relative_highlighted_range, &layout, origin)
|
||||||
|
@ -833,24 +840,19 @@ impl Element for TerminalElement {
|
||||||
hr.paint(bounds, cx);
|
hr.paint(bounds, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
cx.with_z_index(2, |cx| {
|
|
||||||
for cell in &layout.cells {
|
for cell in &layout.cells {
|
||||||
cell.paint(origin, &layout, bounds, cx);
|
cell.paint(origin, &layout, bounds, cx);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if self.cursor_visible {
|
if self.cursor_visible {
|
||||||
cx.with_z_index(3, |cx| {
|
if let Some(mut cursor) = cursor {
|
||||||
if let Some(cursor) = &layout.cursor {
|
|
||||||
cursor.paint(origin, cx);
|
cursor.paint(origin, cx);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut element) = layout.hyperlink_tooltip.take() {
|
if let Some(mut element) = hyperlink_tooltip {
|
||||||
element.draw(origin, bounds.size.map(AvailableSpace::Definite), cx)
|
element.paint(cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -859,10 +861,6 @@ impl Element for TerminalElement {
|
||||||
impl IntoElement for TerminalElement {
|
impl IntoElement for TerminalElement {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
|
||||||
Some("terminal".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,6 @@ impl Render for PlayerStory {
|
||||||
.relative()
|
.relative()
|
||||||
.neg_mx_1()
|
.neg_mx_1()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.z_index(3)
|
|
||||||
.border_2()
|
.border_2()
|
||||||
.border_color(player.background)
|
.border_color(player.background)
|
||||||
.size(px(28.))
|
.size(px(28.))
|
||||||
|
@ -93,7 +92,6 @@ impl Render for PlayerStory {
|
||||||
.relative()
|
.relative()
|
||||||
.neg_mx_1()
|
.neg_mx_1()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.z_index(2)
|
|
||||||
.border_2()
|
.border_2()
|
||||||
.border_color(player.background)
|
.border_color(player.background)
|
||||||
.size(px(28.))
|
.size(px(28.))
|
||||||
|
@ -109,7 +107,6 @@ impl Render for PlayerStory {
|
||||||
.relative()
|
.relative()
|
||||||
.neg_mx_1()
|
.neg_mx_1()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.z_index(1)
|
|
||||||
.border_2()
|
.border_2()
|
||||||
.border_color(player.background)
|
.border_color(player.background)
|
||||||
.size(px(28.))
|
.size(px(28.))
|
||||||
|
|
|
@ -122,9 +122,6 @@ impl RenderOnce for Avatar {
|
||||||
.size(image_size)
|
.size(image_size)
|
||||||
.bg(cx.theme().colors().ghost_element_background),
|
.bg(cx.theme().colors().ghost_element_background),
|
||||||
)
|
)
|
||||||
.children(
|
.children(self.indicator.map(|indicator| div().child(indicator)))
|
||||||
self.indicator
|
|
||||||
.map(|indicator| div().z_index(1).child(indicator)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,7 +243,7 @@ impl ContextMenuItem {
|
||||||
|
|
||||||
impl Render for ContextMenu {
|
impl Render for ContextMenu {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
div().elevation_2(cx).flex().flex_row().child(
|
div().occlude().elevation_2(cx).flex().flex_row().child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.min_w(px(200.))
|
.min_w(px(200.))
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
|
|
|
@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
overlay, point, prelude::FluentBuilder, px, rems, AnchorCorner, AnyElement, Bounds,
|
overlay, point, prelude::FluentBuilder, px, rems, AnchorCorner, AnyElement, Bounds,
|
||||||
DismissEvent, DispatchPhase, Element, ElementContext, ElementId, InteractiveBounds,
|
DismissEvent, DispatchPhase, Element, ElementContext, ElementId, HitboxId, IntoElement,
|
||||||
IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View,
|
LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
|
||||||
VisualContext, WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Clickable, Selectable};
|
use crate::{Clickable, Selectable};
|
||||||
|
@ -109,6 +109,21 @@ impl<M: ManagedView> PopoverMenu<M> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_element_state<R>(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
f: impl FnOnce(&mut Self, &mut PopoverMenuElementState<M>, &mut ElementContext) -> R,
|
||||||
|
) -> R {
|
||||||
|
cx.with_element_state::<PopoverMenuElementState<M>, _>(
|
||||||
|
Some(self.id.clone()),
|
||||||
|
|element_state, cx| {
|
||||||
|
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||||
|
let result = f(self, &mut element_state, cx);
|
||||||
|
(result, Some(element_state))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [`PopoverMenu`]
|
/// Creates a [`PopoverMenu`]
|
||||||
|
@ -123,52 +138,64 @@ pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PopoverMenuState<M> {
|
pub struct PopoverMenuElementState<M> {
|
||||||
|
menu: Rc<RefCell<Option<View<M>>>>,
|
||||||
|
child_bounds: Option<Bounds<Pixels>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Clone for PopoverMenuElementState<M> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
menu: Rc::clone(&self.menu),
|
||||||
|
child_bounds: self.child_bounds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for PopoverMenuElementState<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
menu: Rc::default(),
|
||||||
|
child_bounds: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PopoverMenuFrameState {
|
||||||
child_layout_id: Option<LayoutId>,
|
child_layout_id: Option<LayoutId>,
|
||||||
child_element: Option<AnyElement>,
|
child_element: Option<AnyElement>,
|
||||||
child_bounds: Option<Bounds<Pixels>>,
|
|
||||||
menu_element: Option<AnyElement>,
|
menu_element: Option<AnyElement>,
|
||||||
menu: Rc<RefCell<Option<View<M>>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: ManagedView> Element for PopoverMenu<M> {
|
impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||||
type State = PopoverMenuState<M>;
|
type BeforeLayout = PopoverMenuFrameState;
|
||||||
|
type AfterLayout = Option<HitboxId>;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
self.with_element_state(cx, |this, element_state, cx| {
|
||||||
element_state: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (gpui::LayoutId, Self::State) {
|
|
||||||
let mut menu_layout_id = None;
|
let mut menu_layout_id = None;
|
||||||
|
|
||||||
let (menu, child_bounds) = if let Some(element_state) = element_state {
|
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
|
||||||
(element_state.menu, element_state.child_bounds)
|
let mut overlay = overlay().snap_to_window().anchor(this.anchor);
|
||||||
} else {
|
|
||||||
(Rc::default(), None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let menu_element = menu.borrow_mut().as_mut().map(|menu| {
|
if let Some(child_bounds) = element_state.child_bounds {
|
||||||
let mut overlay = overlay().snap_to_window().anchor(self.anchor);
|
|
||||||
|
|
||||||
if let Some(child_bounds) = child_bounds {
|
|
||||||
overlay = overlay.position(
|
overlay = overlay.position(
|
||||||
self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx),
|
this.resolved_attach().corner(child_bounds) + this.resolved_offset(cx),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut element = overlay.child(menu.clone()).into_any();
|
let mut element = overlay.child(menu.clone()).into_any();
|
||||||
menu_layout_id = Some(element.request_layout(cx));
|
menu_layout_id = Some(element.before_layout(cx));
|
||||||
element
|
element
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut child_element = self
|
let mut child_element = this.child_builder.take().map(|child_builder| {
|
||||||
.child_builder
|
(child_builder)(element_state.menu.clone(), this.menu_builder.clone())
|
||||||
.take()
|
});
|
||||||
.map(|child_builder| (child_builder)(menu.clone(), self.menu_builder.clone()));
|
|
||||||
|
|
||||||
let child_layout_id = child_element
|
let child_layout_id = child_element
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|child_element| child_element.request_layout(cx));
|
.map(|child_element| child_element.before_layout(cx));
|
||||||
|
|
||||||
let layout_id = cx.request_layout(
|
let layout_id = cx.request_layout(
|
||||||
&gpui::Style::default(),
|
&gpui::Style::default(),
|
||||||
|
@ -177,60 +204,70 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||||
|
|
||||||
(
|
(
|
||||||
layout_id,
|
layout_id,
|
||||||
PopoverMenuState {
|
PopoverMenuFrameState {
|
||||||
menu,
|
|
||||||
child_element,
|
child_element,
|
||||||
child_layout_id,
|
child_layout_id,
|
||||||
menu_element,
|
menu_element,
|
||||||
child_bounds,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
_bounds: Bounds<Pixels>,
|
||||||
|
before_layout: &mut Self::BeforeLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) -> Option<HitboxId> {
|
||||||
|
self.with_element_state(cx, |_this, element_state, cx| {
|
||||||
|
if let Some(child) = before_layout.child_element.as_mut() {
|
||||||
|
child.after_layout(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(menu) = before_layout.menu_element.as_mut() {
|
||||||
|
menu.after_layout(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
before_layout.child_layout_id.map(|layout_id| {
|
||||||
|
let bounds = cx.layout_bounds(layout_id);
|
||||||
|
element_state.child_bounds = Some(bounds);
|
||||||
|
cx.insert_hitbox(bounds, false).id
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Bounds<gpui::Pixels>,
|
_: Bounds<gpui::Pixels>,
|
||||||
element_state: &mut Self::State,
|
before_layout: &mut Self::BeforeLayout,
|
||||||
|
child_hitbox: &mut Option<HitboxId>,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
if let Some(mut child) = element_state.child_element.take() {
|
self.with_element_state(cx, |_this, _element_state, cx| {
|
||||||
|
if let Some(mut child) = before_layout.child_element.take() {
|
||||||
child.paint(cx);
|
child.paint(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(child_layout_id) = element_state.child_layout_id.take() {
|
if let Some(mut menu) = before_layout.menu_element.take() {
|
||||||
element_state.child_bounds = Some(cx.layout_bounds(child_layout_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mut menu) = element_state.menu_element.take() {
|
|
||||||
menu.paint(cx);
|
menu.paint(cx);
|
||||||
|
|
||||||
if let Some(child_bounds) = element_state.child_bounds {
|
if let Some(child_hitbox) = *child_hitbox {
|
||||||
let interactive_bounds = InteractiveBounds {
|
|
||||||
bounds: child_bounds,
|
|
||||||
stacking_order: cx.stacking_order().clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mouse-downing outside the menu dismisses it, so we don't
|
// Mouse-downing outside the menu dismisses it, so we don't
|
||||||
// want a click on the toggle to re-open it.
|
// want a click on the toggle to re-open it.
|
||||||
cx.on_mouse_event(move |e: &MouseDownEvent, phase, cx| {
|
cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble
|
if phase == DispatchPhase::Bubble && child_hitbox.is_hovered(cx) {
|
||||||
&& interactive_bounds.visibly_contains(&e.position, cx)
|
|
||||||
{
|
|
||||||
cx.stop_propagation()
|
cx.stop_propagation()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: ManagedView> IntoElement for PopoverMenu<M> {
|
impl<M: ManagedView> IntoElement for PopoverMenu<M> {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<gpui::ElementId> {
|
|
||||||
Some(self.id.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element,
|
overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element,
|
||||||
ElementContext, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseButton,
|
ElementContext, ElementId, Hitbox, IntoElement, LayoutId, ManagedView, MouseButton,
|
||||||
MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext,
|
MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,6 +37,21 @@ impl<M: ManagedView> RightClickMenu<M> {
|
||||||
self.attach = Some(attach);
|
self.attach = Some(attach);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_element_state<R>(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
f: impl FnOnce(&mut Self, &mut MenuHandleElementState<M>, &mut ElementContext) -> R,
|
||||||
|
) -> R {
|
||||||
|
cx.with_element_state::<MenuHandleElementState<M>, _>(
|
||||||
|
Some(self.id.clone()),
|
||||||
|
|element_state, cx| {
|
||||||
|
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||||
|
let result = f(self, &mut element_state, cx);
|
||||||
|
(result, Some(element_state))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [`RightClickMenu`]
|
/// Creates a [`RightClickMenu`]
|
||||||
|
@ -50,50 +65,63 @@ pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MenuHandleState<M> {
|
pub struct MenuHandleElementState<M> {
|
||||||
menu: Rc<RefCell<Option<View<M>>>>,
|
menu: Rc<RefCell<Option<View<M>>>>,
|
||||||
position: Rc<RefCell<Point<Pixels>>>,
|
position: Rc<RefCell<Point<Pixels>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Clone for MenuHandleElementState<M> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
menu: Rc::clone(&self.menu),
|
||||||
|
position: Rc::clone(&self.position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for MenuHandleElementState<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
menu: Rc::default(),
|
||||||
|
position: Rc::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MenuHandleFrameState {
|
||||||
child_layout_id: Option<LayoutId>,
|
child_layout_id: Option<LayoutId>,
|
||||||
child_element: Option<AnyElement>,
|
child_element: Option<AnyElement>,
|
||||||
menu_element: Option<AnyElement>,
|
menu_element: Option<AnyElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: ManagedView> Element for RightClickMenu<M> {
|
impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||||
type State = MenuHandleState<M>;
|
type BeforeLayout = MenuHandleFrameState;
|
||||||
|
type AfterLayout = Hitbox;
|
||||||
fn request_layout(
|
|
||||||
&mut self,
|
|
||||||
element_state: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (gpui::LayoutId, Self::State) {
|
|
||||||
let (menu, position) = if let Some(element_state) = element_state {
|
|
||||||
(element_state.menu, element_state.position)
|
|
||||||
} else {
|
|
||||||
(Rc::default(), Rc::default())
|
|
||||||
};
|
|
||||||
|
|
||||||
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) {
|
||||||
|
self.with_element_state(cx, |this, element_state, cx| {
|
||||||
let mut menu_layout_id = None;
|
let mut menu_layout_id = None;
|
||||||
|
|
||||||
let menu_element = menu.borrow_mut().as_mut().map(|menu| {
|
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
|
||||||
let mut overlay = overlay().snap_to_window();
|
let mut overlay = overlay().snap_to_window();
|
||||||
if let Some(anchor) = self.anchor {
|
if let Some(anchor) = this.anchor {
|
||||||
overlay = overlay.anchor(anchor);
|
overlay = overlay.anchor(anchor);
|
||||||
}
|
}
|
||||||
overlay = overlay.position(*position.borrow());
|
overlay = overlay.position(*element_state.position.borrow());
|
||||||
|
|
||||||
let mut element = overlay.child(menu.clone()).into_any();
|
let mut element = overlay.child(menu.clone()).into_any();
|
||||||
menu_layout_id = Some(element.request_layout(cx));
|
menu_layout_id = Some(element.before_layout(cx));
|
||||||
element
|
element
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut child_element = self
|
let mut child_element = this
|
||||||
.child_builder
|
.child_builder
|
||||||
.take()
|
.take()
|
||||||
.map(|child_builder| (child_builder)(menu.borrow().is_some()));
|
.map(|child_builder| (child_builder)(element_state.menu.borrow().is_some()));
|
||||||
|
|
||||||
let child_layout_id = child_element
|
let child_layout_id = child_element
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|child_element| child_element.request_layout(cx));
|
.map(|child_element| child_element.before_layout(cx));
|
||||||
|
|
||||||
let layout_id = cx.request_layout(
|
let layout_id = cx.request_layout(
|
||||||
&gpui::Style::default(),
|
&gpui::Style::default(),
|
||||||
|
@ -102,48 +130,68 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||||
|
|
||||||
(
|
(
|
||||||
layout_id,
|
layout_id,
|
||||||
MenuHandleState {
|
MenuHandleFrameState {
|
||||||
menu,
|
|
||||||
position,
|
|
||||||
child_element,
|
child_element,
|
||||||
child_layout_id,
|
child_layout_id,
|
||||||
menu_element,
|
menu_element,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
before_layout: &mut Self::BeforeLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) -> Hitbox {
|
||||||
|
cx.with_element_id(Some(self.id.clone()), |cx| {
|
||||||
|
let hitbox = cx.insert_hitbox(bounds, false);
|
||||||
|
|
||||||
|
if let Some(child) = before_layout.child_element.as_mut() {
|
||||||
|
child.after_layout(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(menu) = before_layout.menu_element.as_mut() {
|
||||||
|
menu.after_layout(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
hitbox
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<gpui::Pixels>,
|
_bounds: Bounds<gpui::Pixels>,
|
||||||
element_state: &mut Self::State,
|
before_layout: &mut Self::BeforeLayout,
|
||||||
|
hitbox: &mut Self::AfterLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
if let Some(mut child) = element_state.child_element.take() {
|
self.with_element_state(cx, |this, element_state, cx| {
|
||||||
|
if let Some(mut child) = before_layout.child_element.take() {
|
||||||
child.paint(cx);
|
child.paint(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut menu) = element_state.menu_element.take() {
|
if let Some(mut menu) = before_layout.menu_element.take() {
|
||||||
menu.paint(cx);
|
menu.paint(cx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(builder) = self.menu_builder.take() else {
|
let Some(builder) = this.menu_builder.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let attach = this.attach;
|
||||||
let menu = element_state.menu.clone();
|
let menu = element_state.menu.clone();
|
||||||
let position = element_state.position.clone();
|
let position = element_state.position.clone();
|
||||||
let attach = self.attach;
|
let child_layout_id = before_layout.child_layout_id;
|
||||||
let child_layout_id = element_state.child_layout_id;
|
|
||||||
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
|
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
|
||||||
|
|
||||||
let interactive_bounds = InteractiveBounds {
|
let hitbox_id = hitbox.id;
|
||||||
bounds: bounds.intersect(&cx.content_mask().bounds),
|
|
||||||
stacking_order: cx.stacking_order().clone(),
|
|
||||||
};
|
|
||||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble
|
if phase == DispatchPhase::Bubble
|
||||||
&& event.button == MouseButton::Right
|
&& event.button == MouseButton::Right
|
||||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
&& hitbox_id.is_hovered(cx)
|
||||||
{
|
{
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
cx.prevent_default();
|
cx.prevent_default();
|
||||||
|
@ -164,26 +212,25 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||||
.detach();
|
.detach();
|
||||||
cx.focus_view(&new_menu);
|
cx.focus_view(&new_menu);
|
||||||
*menu.borrow_mut() = Some(new_menu);
|
*menu.borrow_mut() = Some(new_menu);
|
||||||
|
*position.borrow_mut() = if child_layout_id.is_some() {
|
||||||
*position.borrow_mut() =
|
if let Some(attach) = attach {
|
||||||
if let Some(attach) = attach.filter(|_| child_layout_id.is_some()) {
|
|
||||||
attach.corner(child_bounds)
|
attach.corner(child_bounds)
|
||||||
} else {
|
} else {
|
||||||
cx.mouse_position()
|
cx.mouse_position()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cx.mouse_position()
|
||||||
};
|
};
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: ManagedView> IntoElement for RightClickMenu<M> {
|
impl<M: ManagedView> IntoElement for RightClickMenu<M> {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<gpui::ElementId> {
|
|
||||||
Some(self.id.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,6 @@ impl RenderOnce for TabBar {
|
||||||
.absolute()
|
.absolute()
|
||||||
.top_0()
|
.top_0()
|
||||||
.left_0()
|
.left_0()
|
||||||
.z_index(1)
|
|
||||||
.size_full()
|
.size_full()
|
||||||
.border_b()
|
.border_b()
|
||||||
.border_color(cx.theme().colors().border),
|
.border_color(cx.theme().colors().border),
|
||||||
|
@ -131,7 +130,6 @@ impl RenderOnce for TabBar {
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("tabs")
|
.id("tabs")
|
||||||
.z_index(2)
|
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.overflow_x_scroll()
|
.overflow_x_scroll()
|
||||||
.when_some(self.scroll_handle, |cx, scroll_handle| {
|
.when_some(self.scroll_handle, |cx, scroll_handle| {
|
||||||
|
|
|
@ -7,7 +7,6 @@ use crate::{ElevationIndex, UiTextSize};
|
||||||
|
|
||||||
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
|
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
|
||||||
this.bg(cx.theme().colors().elevated_surface_background)
|
this.bg(cx.theme().colors().elevated_surface_background)
|
||||||
.z_index(index.z_index())
|
|
||||||
.rounded(px(8.))
|
.rounded(px(8.))
|
||||||
.border()
|
.border()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
|
|
@ -20,17 +20,6 @@ pub enum ElevationIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElevationIndex {
|
impl ElevationIndex {
|
||||||
pub fn z_index(self) -> u16 {
|
|
||||||
match self {
|
|
||||||
ElevationIndex::Background => 0,
|
|
||||||
ElevationIndex::Surface => 42,
|
|
||||||
ElevationIndex::ElevatedSurface => 84,
|
|
||||||
ElevationIndex::Wash => 126,
|
|
||||||
ElevationIndex::ModalSurface => 168,
|
|
||||||
ElevationIndex::DraggedElement => 210,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
|
pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
|
||||||
match self {
|
match self {
|
||||||
ElevationIndex::Surface => smallvec![],
|
ElevationIndex::Surface => smallvec![],
|
||||||
|
@ -75,16 +64,6 @@ pub enum LayerIndex {
|
||||||
ElevatedElement,
|
ElevatedElement,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerIndex {
|
|
||||||
pub fn usize(&self) -> usize {
|
|
||||||
match *self {
|
|
||||||
LayerIndex::BehindElement => 0,
|
|
||||||
LayerIndex::Element => 100,
|
|
||||||
LayerIndex::ElevatedElement => 200,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An appropriate z-index for the given layer based on its intended usage.
|
/// An appropriate z-index for the given layer based on its intended usage.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ElementIndex {
|
pub enum ElementIndex {
|
||||||
|
@ -95,16 +74,3 @@ pub enum ElementIndex {
|
||||||
Content,
|
Content,
|
||||||
Overlay,
|
Overlay,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementIndex {
|
|
||||||
pub fn usize(&self) -> usize {
|
|
||||||
match *self {
|
|
||||||
ElementIndex::Effect => 0,
|
|
||||||
ElementIndex::Background => 100,
|
|
||||||
ElementIndex::Tint => 200,
|
|
||||||
ElementIndex::Highlight => 300,
|
|
||||||
ElementIndex::Content => 400,
|
|
||||||
ElementIndex::Overlay => 500,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ use crate::{status_bar::StatusItemView, Workspace};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
|
div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
|
||||||
EventEmitter, FocusHandle, FocusableView, IntoElement, KeyContext, MouseButton, ParentElement,
|
EventEmitter, FocusHandle, FocusableView, IntoElement, KeyContext, MouseButton, ParentElement,
|
||||||
Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
|
Render, SharedString, StyleRefinement, Styled, Subscription, View, ViewContext, VisualContext,
|
||||||
WindowContext,
|
WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -563,8 +563,7 @@ impl Render for Dock {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.z_index(1)
|
.occlude();
|
||||||
.block_mouse();
|
|
||||||
|
|
||||||
match self.position() {
|
match self.position() {
|
||||||
DockPosition::Left => {
|
DockPosition::Left => {
|
||||||
|
@ -618,7 +617,12 @@ impl Render for Dock {
|
||||||
Axis::Horizontal => this.min_w(size).h_full(),
|
Axis::Horizontal => this.min_w(size).h_full(),
|
||||||
Axis::Vertical => this.min_h(size).w_full(),
|
Axis::Vertical => this.min_h(size).w_full(),
|
||||||
})
|
})
|
||||||
.child(entry.panel.to_any().cached()),
|
.child(
|
||||||
|
entry
|
||||||
|
.panel
|
||||||
|
.to_any()
|
||||||
|
.cached(StyleRefinement::default().v_flex().size_full()),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(handle)
|
.child(handle)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -139,13 +139,7 @@ impl Render for ModalLayer {
|
||||||
return div();
|
return div();
|
||||||
};
|
};
|
||||||
|
|
||||||
div()
|
div().absolute().size_full().top_0().left_0().child(
|
||||||
.absolute()
|
|
||||||
.size_full()
|
|
||||||
.top_0()
|
|
||||||
.left_0()
|
|
||||||
.z_index(169)
|
|
||||||
.child(
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.h(px(0.0))
|
.h(px(0.0))
|
||||||
.top_20()
|
.top_20()
|
||||||
|
|
|
@ -1561,7 +1561,6 @@ impl Pane {
|
||||||
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
|
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.z_index(1)
|
|
||||||
.bottom_0()
|
.bottom_0()
|
||||||
.right_0()
|
.right_0()
|
||||||
.size_0()
|
.size_0()
|
||||||
|
@ -1886,7 +1885,6 @@ impl Render for Pane {
|
||||||
.child(
|
.child(
|
||||||
// drag target
|
// drag target
|
||||||
div()
|
div()
|
||||||
.z_index(1)
|
|
||||||
.invisible()
|
.invisible()
|
||||||
.absolute()
|
.absolute()
|
||||||
.bg(theme::color_alpha(
|
.bg(theme::color_alpha(
|
||||||
|
|
|
@ -1,239 +0,0 @@
|
||||||
use super::DraggedItem;
|
|
||||||
use crate::{Pane, SplitDirection, Workspace};
|
|
||||||
use gpui::{
|
|
||||||
color::Color,
|
|
||||||
elements::{Canvas, MouseEventHandler, ParentComponent, Stack},
|
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
|
||||||
platform::MouseButton,
|
|
||||||
scene::MouseUp,
|
|
||||||
AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
|
|
||||||
};
|
|
||||||
use project2::ProjectEntryId;
|
|
||||||
|
|
||||||
pub fn dragged_item_receiver<Tag, D, F>(
|
|
||||||
pane: &Pane,
|
|
||||||
region_id: usize,
|
|
||||||
drop_index: usize,
|
|
||||||
allow_same_pane: bool,
|
|
||||||
split_margin: Option<f32>,
|
|
||||||
cx: &mut ViewContext<Pane>,
|
|
||||||
render_child: F,
|
|
||||||
) -> MouseEventHandler<Pane>
|
|
||||||
where
|
|
||||||
Tag: 'static,
|
|
||||||
D: Element<Pane>,
|
|
||||||
F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
|
|
||||||
{
|
|
||||||
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
|
||||||
let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
|
|
||||||
drag_and_drop
|
|
||||||
.currently_dragged::<DraggedItem>(cx.window())
|
|
||||||
.map(|(drag_position, _)| drag_position)
|
|
||||||
.or_else(|| {
|
|
||||||
drag_and_drop
|
|
||||||
.currently_dragged::<ProjectEntryId>(cx.window())
|
|
||||||
.map(|(drag_position, _)| drag_position)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
|
|
||||||
// Observing hovered will cause a render when the mouse enters regardless
|
|
||||||
// of if mouse position was accessed before
|
|
||||||
let drag_position = if state.dragging() {
|
|
||||||
drag_position
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Stack::new()
|
|
||||||
.with_child(render_child(state, cx))
|
|
||||||
.with_children(drag_position.map(|drag_position| {
|
|
||||||
Canvas::new(move |bounds, _, _, cx| {
|
|
||||||
if bounds.contains_point(drag_position) {
|
|
||||||
let overlay_region = split_margin
|
|
||||||
.and_then(|split_margin| {
|
|
||||||
drop_split_direction(drag_position, bounds, split_margin)
|
|
||||||
.map(|dir| (dir, split_margin))
|
|
||||||
})
|
|
||||||
.map(|(dir, margin)| dir.along_edge(bounds, margin))
|
|
||||||
.unwrap_or(bounds);
|
|
||||||
|
|
||||||
cx.scene().push_stacking_context(None, None);
|
|
||||||
let background = overlay_color(cx);
|
|
||||||
cx.scene().push_quad(Quad {
|
|
||||||
bounds: overlay_region,
|
|
||||||
background: Some(background),
|
|
||||||
border: Default::default(),
|
|
||||||
corner_radii: Default::default(),
|
|
||||||
});
|
|
||||||
cx.scene().pop_stacking_context();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
if drag_position.is_some() {
|
|
||||||
handler = handler
|
|
||||||
.on_up(MouseButton::Left, {
|
|
||||||
move |event, pane, cx| {
|
|
||||||
let workspace = pane.workspace.clone();
|
|
||||||
let pane = cx.weak_handle();
|
|
||||||
handle_dropped_item(
|
|
||||||
event,
|
|
||||||
workspace,
|
|
||||||
&pane,
|
|
||||||
drop_index,
|
|
||||||
allow_same_pane,
|
|
||||||
split_margin,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_move(|_, _, cx| {
|
|
||||||
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
|
||||||
|
|
||||||
if drag_and_drop
|
|
||||||
.currently_dragged::<DraggedItem>(cx.window())
|
|
||||||
.is_some()
|
|
||||||
|| drag_and_drop
|
|
||||||
.currently_dragged::<ProjectEntryId>(cx.window())
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
cx.notify();
|
|
||||||
} else {
|
|
||||||
cx.propagate_event();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handler
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_dropped_item<V: 'static>(
|
|
||||||
event: MouseUp,
|
|
||||||
workspace: WeakViewHandle<Workspace>,
|
|
||||||
pane: &WeakViewHandle<Pane>,
|
|
||||||
index: usize,
|
|
||||||
allow_same_pane: bool,
|
|
||||||
split_margin: Option<f32>,
|
|
||||||
cx: &mut EventContext<V>,
|
|
||||||
) {
|
|
||||||
enum Action {
|
|
||||||
Move(WeakViewHandle<Pane>, usize),
|
|
||||||
Open(ProjectEntryId),
|
|
||||||
}
|
|
||||||
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
|
||||||
let action = if let Some((_, dragged_item)) =
|
|
||||||
drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
|
|
||||||
{
|
|
||||||
Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
|
|
||||||
} else if let Some((_, project_entry)) =
|
|
||||||
drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
|
|
||||||
{
|
|
||||||
Action::Open(*project_entry)
|
|
||||||
} else {
|
|
||||||
cx.propagate_event();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(split_direction) =
|
|
||||||
split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
|
|
||||||
{
|
|
||||||
let pane_to_split = pane.clone();
|
|
||||||
match action {
|
|
||||||
Action::Move(from, item_id_to_move) => {
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(workspace) = workspace.upgrade(cx) {
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
workspace.split_pane_with_item(
|
|
||||||
pane_to_split,
|
|
||||||
split_direction,
|
|
||||||
from,
|
|
||||||
item_id_to_move,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Action::Open(project_entry) => {
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(workspace) = workspace.upgrade(cx) {
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
if let Some(task) = workspace.split_pane_with_project_entry(
|
|
||||||
pane_to_split,
|
|
||||||
split_direction,
|
|
||||||
project_entry,
|
|
||||||
cx,
|
|
||||||
) {
|
|
||||||
task.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
match action {
|
|
||||||
Action::Move(from, item_id) => {
|
|
||||||
if pane != &from || allow_same_pane {
|
|
||||||
let pane = pane.clone();
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(((workspace, from), to)) = workspace
|
|
||||||
.upgrade(cx)
|
|
||||||
.zip(from.upgrade(cx))
|
|
||||||
.zip(pane.upgrade(cx))
|
|
||||||
{
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
workspace.move_item(from, to, item_id, index, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cx.propagate_event();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::Open(project_entry) => {
|
|
||||||
let pane = pane.clone();
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(workspace) = workspace.upgrade(cx) {
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
if let Some(path) =
|
|
||||||
workspace.project.read(cx).path_for_entry(project_entry, cx)
|
|
||||||
{
|
|
||||||
workspace
|
|
||||||
.open_path(path, Some(pane), true, cx)
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drop_split_direction(
|
|
||||||
position: Vector2F,
|
|
||||||
region: RectF,
|
|
||||||
split_margin: f32,
|
|
||||||
) -> Option<SplitDirection> {
|
|
||||||
let mut min_direction = None;
|
|
||||||
let mut min_distance = split_margin;
|
|
||||||
for direction in SplitDirection::all() {
|
|
||||||
let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
|
|
||||||
|
|
||||||
if edge_distance < min_distance {
|
|
||||||
min_direction = Some(direction);
|
|
||||||
min_distance = edge_distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
min_direction
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overlay_color(cx: &AppContext) -> Color {
|
|
||||||
theme2::current(cx).workspace.drop_target_overlay_color
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ use call::{ActiveCall, ParticipantLocation};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels,
|
point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels,
|
||||||
Point, View, ViewContext,
|
Point, StyleRefinement, View, ViewContext,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
@ -239,7 +239,10 @@ impl Member {
|
||||||
.relative()
|
.relative()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(AnyView::from(pane.clone()).cached())
|
.child(
|
||||||
|
AnyView::from(pane.clone())
|
||||||
|
.cached(StyleRefinement::default().v_flex().size_full()),
|
||||||
|
)
|
||||||
.when_some(leader_border, |this, color| {
|
.when_some(leader_border, |this, color| {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
|
@ -260,7 +263,6 @@ impl Member {
|
||||||
.right_3()
|
.right_3()
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
.p_1()
|
.p_1()
|
||||||
.z_index(1)
|
|
||||||
.child(status_box)
|
.child(status_box)
|
||||||
.when_some(
|
.when_some(
|
||||||
leader_join_data,
|
leader_join_data,
|
||||||
|
@ -588,13 +590,15 @@ impl SplitDirection {
|
||||||
|
|
||||||
mod element {
|
mod element {
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
|
px, relative, Along, AnyElement, Axis, Bounds, Element, IntoElement, MouseDownEvent,
|
||||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
|
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, WeakView,
|
||||||
WeakView, WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
use gpui::{CursorStyle, Hitbox};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -637,6 +641,22 @@ mod element {
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PaneAxisLayout {
|
||||||
|
dragged_handle: Rc<RefCell<Option<usize>>>,
|
||||||
|
children: Vec<PaneAxisChildLayout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PaneAxisChildLayout {
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
element: AnyElement,
|
||||||
|
handle: Option<PaneAxisHandleLayout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PaneAxisHandleLayout {
|
||||||
|
hitbox: Hitbox,
|
||||||
|
divider_bounds: Bounds<Pixels>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PaneAxisElement {
|
impl PaneAxisElement {
|
||||||
pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
|
pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
|
||||||
self.active_pane_ix = active_pane_ix;
|
self.active_pane_ix = active_pane_ix;
|
||||||
|
@ -733,16 +753,11 @@ mod element {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn push_handle(
|
fn layout_handle(
|
||||||
flexes: Arc<Mutex<Vec<f32>>>,
|
|
||||||
dragged_handle: Rc<RefCell<Option<usize>>>,
|
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
ix: usize,
|
|
||||||
pane_bounds: Bounds<Pixels>,
|
pane_bounds: Bounds<Pixels>,
|
||||||
axis_bounds: Bounds<Pixels>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) -> PaneAxisHandleLayout {
|
||||||
let handle_bounds = Bounds {
|
let handle_bounds = Bounds {
|
||||||
origin: pane_bounds.origin.apply_along(axis, |origin| {
|
origin: pane_bounds.origin.apply_along(axis, |origin| {
|
||||||
origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
|
origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
|
||||||
|
@ -758,99 +773,53 @@ mod element {
|
||||||
size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
|
size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.with_z_index(3, |cx| {
|
PaneAxisHandleLayout {
|
||||||
if handle_bounds.contains(&cx.mouse_position()) {
|
hitbox: cx.insert_hitbox(handle_bounds, true),
|
||||||
let stacking_order = cx.stacking_order().clone();
|
divider_bounds,
|
||||||
let cursor_style = match axis {
|
|
||||||
Axis::Vertical => CursorStyle::ResizeUpDown,
|
|
||||||
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
|
||||||
};
|
|
||||||
cx.set_cursor_style(cursor_style, stacking_order);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.add_opaque_layer(handle_bounds);
|
|
||||||
cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
|
|
||||||
|
|
||||||
cx.on_mouse_event({
|
|
||||||
let dragged_handle = dragged_handle.clone();
|
|
||||||
let flexes = flexes.clone();
|
|
||||||
let workspace = workspace.clone();
|
|
||||||
move |e: &MouseDownEvent, phase, cx| {
|
|
||||||
if phase.bubble() && handle_bounds.contains(&e.position) {
|
|
||||||
dragged_handle.replace(Some(ix));
|
|
||||||
if e.click_count >= 2 {
|
|
||||||
let mut borrow = flexes.lock();
|
|
||||||
*borrow = vec![1.; borrow.len()];
|
|
||||||
workspace
|
|
||||||
.update(cx, |this, cx| this.schedule_serialize(cx))
|
|
||||||
.log_err();
|
|
||||||
|
|
||||||
cx.refresh();
|
|
||||||
}
|
|
||||||
cx.stop_propagation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cx.on_mouse_event({
|
|
||||||
let workspace = workspace.clone();
|
|
||||||
move |e: &MouseMoveEvent, phase, cx| {
|
|
||||||
let dragged_handle = dragged_handle.borrow();
|
|
||||||
|
|
||||||
if phase.bubble() && *dragged_handle == Some(ix) {
|
|
||||||
Self::compute_resize(
|
|
||||||
&flexes,
|
|
||||||
e,
|
|
||||||
ix,
|
|
||||||
axis,
|
|
||||||
pane_bounds.origin,
|
|
||||||
axis_bounds.size,
|
|
||||||
workspace.clone(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for PaneAxisElement {
|
impl IntoElement for PaneAxisElement {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ui::prelude::ElementId> {
|
|
||||||
Some(self.basis.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for PaneAxisElement {
|
impl Element for PaneAxisElement {
|
||||||
type State = Rc<RefCell<Option<usize>>>;
|
type BeforeLayout = ();
|
||||||
|
type AfterLayout = PaneAxisLayout;
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: Option<Self::State>,
|
|
||||||
cx: &mut ui::prelude::ElementContext,
|
cx: &mut ui::prelude::ElementContext,
|
||||||
) -> (gpui::LayoutId, Self::State) {
|
) -> (gpui::LayoutId, Self::BeforeLayout) {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.flex_grow = 1.;
|
style.flex_grow = 1.;
|
||||||
style.flex_shrink = 1.;
|
style.flex_shrink = 1.;
|
||||||
style.flex_basis = relative(0.).into();
|
style.flex_basis = relative(0.).into();
|
||||||
style.size.width = relative(1.).into();
|
style.size.width = relative(1.).into();
|
||||||
style.size.height = relative(1.).into();
|
style.size.height = relative(1.).into();
|
||||||
let layout_id = cx.request_layout(&style, None);
|
(cx.request_layout(&style, None), ())
|
||||||
let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
|
|
||||||
(layout_id, dragged_pane)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn after_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: gpui::Bounds<ui::prelude::Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
state: &mut Self::State,
|
_state: &mut Self::BeforeLayout,
|
||||||
cx: &mut ui::prelude::ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) -> PaneAxisLayout {
|
||||||
|
let dragged_handle = cx.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
|
||||||
|
Some(self.basis.into()),
|
||||||
|
|state, _cx| {
|
||||||
|
let state = state
|
||||||
|
.unwrap()
|
||||||
|
.unwrap_or_else(|| Rc::new(RefCell::new(None)));
|
||||||
|
(state.clone(), Some(state))
|
||||||
|
},
|
||||||
|
);
|
||||||
let flexes = self.flexes.lock().clone();
|
let flexes = self.flexes.lock().clone();
|
||||||
let len = self.children.len();
|
let len = self.children.len();
|
||||||
debug_assert!(flexes.len() == len);
|
debug_assert!(flexes.len() == len);
|
||||||
|
@ -875,7 +844,11 @@ mod element {
|
||||||
let mut bounding_boxes = self.bounding_boxes.lock();
|
let mut bounding_boxes = self.bounding_boxes.lock();
|
||||||
bounding_boxes.clear();
|
bounding_boxes.clear();
|
||||||
|
|
||||||
for (ix, child) in self.children.iter_mut().enumerate() {
|
let mut layout = PaneAxisLayout {
|
||||||
|
dragged_handle: dragged_handle.clone(),
|
||||||
|
children: Vec::new(),
|
||||||
|
};
|
||||||
|
for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
|
||||||
let child_flex = active_pane_magnification
|
let child_flex = active_pane_magnification
|
||||||
.map(|magnification| {
|
.map(|magnification| {
|
||||||
if self.active_pane_ix == Some(ix) {
|
if self.active_pane_ix == Some(ix) {
|
||||||
|
@ -896,40 +869,105 @@ mod element {
|
||||||
size: child_size,
|
size: child_size,
|
||||||
};
|
};
|
||||||
bounding_boxes.push(Some(child_bounds));
|
bounding_boxes.push(Some(child_bounds));
|
||||||
cx.with_z_index(0, |cx| {
|
child.layout(origin, child_size.into(), cx);
|
||||||
child.draw(origin, child_size.into(), cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
if active_pane_magnification.is_none() {
|
|
||||||
cx.with_z_index(1, |cx| {
|
|
||||||
if ix < len - 1 {
|
|
||||||
Self::push_handle(
|
|
||||||
self.flexes.clone(),
|
|
||||||
state.clone(),
|
|
||||||
self.axis,
|
|
||||||
ix,
|
|
||||||
child_bounds,
|
|
||||||
bounds,
|
|
||||||
self.workspace.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
|
origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
|
||||||
|
layout.children.push(PaneAxisChildLayout {
|
||||||
|
bounds: child_bounds,
|
||||||
|
element: child,
|
||||||
|
handle: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.with_z_index(1, |cx| {
|
for (ix, child_layout) in layout.children.iter_mut().enumerate() {
|
||||||
|
if active_pane_magnification.is_none() {
|
||||||
|
if ix < len - 1 {
|
||||||
|
child_layout.handle =
|
||||||
|
Some(Self::layout_handle(self.axis, child_layout.bounds, cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: gpui::Bounds<ui::prelude::Pixels>,
|
||||||
|
_: &mut Self::BeforeLayout,
|
||||||
|
layout: &mut Self::AfterLayout,
|
||||||
|
cx: &mut ui::prelude::ElementContext,
|
||||||
|
) {
|
||||||
|
for child in &mut layout.children {
|
||||||
|
child.element.paint(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ix, child) in &mut layout.children.iter_mut().enumerate() {
|
||||||
|
if let Some(handle) = child.handle.as_mut() {
|
||||||
|
let cursor_style = match self.axis {
|
||||||
|
Axis::Vertical => CursorStyle::ResizeUpDown,
|
||||||
|
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
||||||
|
};
|
||||||
|
cx.set_cursor_style(cursor_style, &handle.hitbox);
|
||||||
|
cx.paint_quad(gpui::fill(
|
||||||
|
handle.divider_bounds,
|
||||||
|
cx.theme().colors().border,
|
||||||
|
));
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let state = state.clone();
|
let dragged_handle = layout.dragged_handle.clone();
|
||||||
move |_: &MouseUpEvent, phase, _cx| {
|
let flexes = self.flexes.clone();
|
||||||
if phase.bubble() {
|
let workspace = self.workspace.clone();
|
||||||
state.replace(None);
|
let handle_hitbox = handle.hitbox.clone();
|
||||||
|
move |e: &MouseDownEvent, phase, cx| {
|
||||||
|
if phase.bubble() && handle_hitbox.is_hovered(cx) {
|
||||||
|
dragged_handle.replace(Some(ix));
|
||||||
|
if e.click_count >= 2 {
|
||||||
|
let mut borrow = flexes.lock();
|
||||||
|
*borrow = vec![1.; borrow.len()];
|
||||||
|
workspace
|
||||||
|
.update(cx, |this, cx| this.schedule_serialize(cx))
|
||||||
|
.log_err();
|
||||||
|
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
cx.stop_propagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cx.on_mouse_event({
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let dragged_handle = layout.dragged_handle.clone();
|
||||||
|
let flexes = self.flexes.clone();
|
||||||
|
let child_bounds = child.bounds;
|
||||||
|
let axis = self.axis;
|
||||||
|
move |e: &MouseMoveEvent, phase, cx| {
|
||||||
|
let dragged_handle = dragged_handle.borrow();
|
||||||
|
if phase.bubble() && *dragged_handle == Some(ix) {
|
||||||
|
Self::compute_resize(
|
||||||
|
&flexes,
|
||||||
|
e,
|
||||||
|
ix,
|
||||||
|
axis,
|
||||||
|
child_bounds.origin,
|
||||||
|
bounds.size,
|
||||||
|
workspace.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.on_mouse_event({
|
||||||
|
let dragged_handle = layout.dragged_handle.clone();
|
||||||
|
move |_: &MouseUpEvent, phase, _cx| {
|
||||||
|
if phase.bubble() {
|
||||||
|
dragged_handle.replace(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2756,7 +2756,6 @@ impl Workspace {
|
||||||
Some(
|
Some(
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.z_index(100)
|
|
||||||
.right_3()
|
.right_3()
|
||||||
.bottom_3()
|
.bottom_3()
|
||||||
.w_112()
|
.w_112()
|
||||||
|
@ -3832,18 +3831,15 @@ impl Render for Workspace {
|
||||||
.border_t()
|
.border_t()
|
||||||
.border_b()
|
.border_b()
|
||||||
.border_color(colors.border)
|
.border_color(colors.border)
|
||||||
.child(
|
.child({
|
||||||
canvas({
|
|
||||||
let this = cx.view().clone();
|
let this = cx.view().clone();
|
||||||
move |bounds, cx| {
|
canvas(
|
||||||
this.update(cx, |this, _cx| {
|
move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
|
||||||
this.bounds = *bounds;
|
|_, _, _| {},
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.absolute()
|
|
||||||
.size_full(),
|
|
||||||
)
|
)
|
||||||
|
.absolute()
|
||||||
|
.size_full()
|
||||||
|
})
|
||||||
.on_drag_move(
|
.on_drag_move(
|
||||||
cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
|
cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
|
||||||
match e.drag(cx).0 {
|
match e.drag(cx).0 {
|
||||||
|
@ -3868,7 +3864,6 @@ impl Render for Workspace {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(self.modal_layer.clone())
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -3917,11 +3912,11 @@ impl Render for Workspace {
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.children(self.render_notifications(cx))
|
.child(self.modal_layer.clone())
|
||||||
.children(self.zoomed.as_ref().and_then(|view| {
|
.children(self.zoomed.as_ref().and_then(|view| {
|
||||||
let zoomed_view = view.upgrade()?;
|
let zoomed_view = view.upgrade()?;
|
||||||
let div = div()
|
let div = div()
|
||||||
.z_index(1)
|
.occlude()
|
||||||
.absolute()
|
.absolute()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.border_color(colors.border)
|
.border_color(colors.border)
|
||||||
|
@ -3936,7 +3931,8 @@ impl Render for Workspace {
|
||||||
Some(DockPosition::Bottom) => div.top_2().border_t(),
|
Some(DockPosition::Bottom) => div.top_2().border_t(),
|
||||||
None => div.top_2().bottom_2().left_2().right_2().border(),
|
None => div.top_2().bottom_2().left_2().right_2().border(),
|
||||||
})
|
})
|
||||||
})),
|
}))
|
||||||
|
.children(self.render_notifications(cx)),
|
||||||
)
|
)
|
||||||
.child(self.status_bar.clone())
|
.child(self.status_bar.clone())
|
||||||
.children(if self.project.read(cx).is_disconnected() {
|
.children(if self.project.read(cx).is_disconnected() {
|
||||||
|
@ -4662,13 +4658,10 @@ pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
|
||||||
struct DisconnectedOverlay;
|
struct DisconnectedOverlay;
|
||||||
|
|
||||||
impl Element for DisconnectedOverlay {
|
impl Element for DisconnectedOverlay {
|
||||||
type State = AnyElement;
|
type BeforeLayout = AnyElement;
|
||||||
|
type AfterLayout = ();
|
||||||
|
|
||||||
fn request_layout(
|
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||||
&mut self,
|
|
||||||
_: Option<Self::State>,
|
|
||||||
cx: &mut ElementContext,
|
|
||||||
) -> (LayoutId, Self::State) {
|
|
||||||
let mut background = cx.theme().colors().elevated_surface_background;
|
let mut background = cx.theme().colors().elevated_surface_background;
|
||||||
background.fade_out(0.2);
|
background.fade_out(0.2);
|
||||||
let mut overlay = div()
|
let mut overlay = div()
|
||||||
|
@ -4686,29 +4679,33 @@ impl Element for DisconnectedOverlay {
|
||||||
"Your connection to the remote project has been lost.",
|
"Your connection to the remote project has been lost.",
|
||||||
))
|
))
|
||||||
.into_any();
|
.into_any();
|
||||||
(overlay.request_layout(cx), overlay)
|
(overlay.before_layout(cx), overlay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn after_layout(
|
||||||
|
&mut self,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
overlay: &mut Self::BeforeLayout,
|
||||||
|
cx: &mut ElementContext,
|
||||||
|
) {
|
||||||
|
cx.insert_hitbox(bounds, true);
|
||||||
|
overlay.after_layout(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
_: Bounds<Pixels>,
|
||||||
overlay: &mut Self::State,
|
overlay: &mut Self::BeforeLayout,
|
||||||
|
_: &mut Self::AfterLayout,
|
||||||
cx: &mut ElementContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
cx.with_z_index(u16::MAX, |cx| {
|
overlay.paint(cx)
|
||||||
cx.add_opaque_layer(bounds);
|
|
||||||
overlay.paint(cx);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for DisconnectedOverlay {
|
impl IntoElement for DisconnectedOverlay {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ui::prelude::ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue