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).


![before_after](https://github.com/zed-industries/zed/assets/482957/c8120b07-1dbd-4776-834a-d040e569a71e)

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:
Antonio Scandurra 2024-03-11 10:45:57 +01:00 committed by GitHub
parent 9afd78b35e
commit 4700d33728
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
74 changed files with 6434 additions and 6301 deletions

View File

@ -31,9 +31,9 @@ use fs::Fs;
use futures::StreamExt;
use gpui::{
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext,
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter,
FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement,
IntoElement, Model, ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context, EventEmitter, FocusHandle,
FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model,
ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
};
@ -1284,25 +1284,25 @@ impl Render for AssistantPanel {
let view = cx.view().clone();
let scroll_handle = self.saved_conversations_scroll_handle.clone();
let conversation_count = self.saved_conversations.len();
canvas(move |bounds, cx| {
uniform_list(
view,
"saved_conversations",
conversation_count,
|this, range, cx| {
range
.map(|ix| this.render_saved_conversation(ix, cx))
.collect()
},
)
.track_scroll(scroll_handle)
.into_any_element()
.draw(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
);
})
canvas(
move |bounds, cx| {
let mut list = uniform_list(
view,
"saved_conversations",
conversation_count,
|this, range, cx| {
range
.map(|ix| this.render_saved_conversation(ix, cx))
.collect()
},
)
.track_scroll(scroll_handle)
.into_any_element();
list.layout(bounds.origin, bounds.size.into(), cx);
list
},
|_bounds, mut list, cx| list.paint(cx),
)
.size_full()
.into_any_element()
}),

View File

@ -156,7 +156,7 @@ mod linux {
}
}
// todo(windows)
// todo("windows")
#[cfg(target_os = "windows")]
mod windows {
use std::path::Path;

View File

@ -132,7 +132,7 @@ async fn main() -> Result<()> {
.await
.map_err(|e| anyhow!(e))?;
// todo(windows)
// todo("windows")
#[cfg(windows)]
unimplemented!();
}

View File

@ -600,11 +600,9 @@ impl ChatPanel {
) -> Div {
div()
.absolute()
.z_index(1)
.child(
div()
.absolute()
.z_index(1)
.right_8()
.w_6()
.rounded_tl_md()
@ -638,7 +636,6 @@ impl ChatPanel {
.child(
div()
.absolute()
.z_index(1)
.right_2()
.w_6()
.rounded_tr_md()
@ -855,7 +852,7 @@ impl Render for ChatPanel {
.size_full()
.on_action(cx.listener(Self::send))
.child(
h_flex().z_index(1).child(
h_flex().child(
TabBar::new("chat_header").child(
h_flex()
.w_full()

View File

@ -989,7 +989,6 @@ impl CollabPanel {
.children(has_channel_buffer_changed.then(|| {
div()
.w_1p5()
.z_index(1)
.absolute()
.right(px(2.))
.top(px(2.))
@ -1022,7 +1021,6 @@ impl CollabPanel {
.children(has_messages_notification.then(|| {
div()
.w_1p5()
.z_index(1)
.absolute()
.right(px(2.))
.top(px(4.))
@ -2617,7 +2615,6 @@ impl CollabPanel {
.children(has_notes_notification.then(|| {
div()
.w_1p5()
.z_index(1)
.absolute()
.right(px(-1.))
.top(px(-1.))
@ -2632,49 +2629,44 @@ impl CollabPanel {
),
)
.child(
h_flex()
.absolute()
.right(rems(0.))
.z_index(1)
.h_full()
.child(
h_flex()
.h_full()
.gap_1()
.px_1()
.child(
IconButton::new("channel_chat", IconName::MessageBubbles)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(if has_messages_notification {
Color::Default
} else {
Color::Muted
})
.on_click(cx.listener(move |this, _, cx| {
this.join_channel_chat(channel_id, cx)
}))
.tooltip(|cx| Tooltip::text("Open channel chat", cx))
.visible_on_hover(""),
)
.child(
IconButton::new("channel_notes", IconName::File)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(if has_notes_notification {
Color::Default
} else {
Color::Muted
})
.on_click(cx.listener(move |this, _, cx| {
this.open_channel_notes(channel_id, cx)
}))
.tooltip(|cx| Tooltip::text("Open channel notes", cx))
.visible_on_hover(""),
),
),
h_flex().absolute().right(rems(0.)).h_full().child(
h_flex()
.h_full()
.gap_1()
.px_1()
.child(
IconButton::new("channel_chat", IconName::MessageBubbles)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(if has_messages_notification {
Color::Default
} else {
Color::Muted
})
.on_click(cx.listener(move |this, _, cx| {
this.join_channel_chat(channel_id, cx)
}))
.tooltip(|cx| Tooltip::text("Open channel chat", cx))
.visible_on_hover(""),
)
.child(
IconButton::new("channel_notes", IconName::File)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(if has_notes_notification {
Color::Default
} else {
Color::Muted
})
.on_click(cx.listener(move |this, _, cx| {
this.open_channel_notes(channel_id, cx)
}))
.tooltip(|cx| Tooltip::text("Open channel notes", cx))
.visible_on_hover(""),
),
),
)
.tooltip({
let channel_store = self.channel_store.clone();
@ -2720,31 +2712,34 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) ->
let thickness = px(1.);
let color = cx.theme().colors().text;
canvas(move |bounds, cx| {
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
let right = bounds.right();
let top = bounds.top();
canvas(
|_, _| {},
move |bounds, _, cx| {
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
let right = bounds.right();
let top = bounds.top();
cx.paint_quad(fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
cx.paint_quad(fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
),
),
),
color,
));
cx.paint_quad(fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
));
})
color,
));
cx.paint_quad(fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
));
},
)
.w(width)
.h(line_height)
}

View File

@ -329,24 +329,27 @@ impl Render for CollabTitlebarItem {
}
}
fn render_color_ribbon(color: Hsla) -> gpui::Canvas {
canvas(move |bounds, cx| {
let height = bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(height.0 / 2.0);
let mut path = Path::new(bounds.lower_left());
path.curve_to(
bounds.origin + point(horizontal_offset, vertical_offset),
bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
bounds.lower_right(),
bounds.upper_right() + point(px(0.0), vertical_offset),
);
path.line_to(bounds.lower_left());
cx.paint_path(path, color);
})
fn render_color_ribbon(color: Hsla) -> impl Element {
canvas(
move |_, _| {},
move |bounds, _, cx| {
let height = bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(height.0 / 2.0);
let mut path = Path::new(bounds.lower_left());
path.curve_to(
bounds.origin + point(horizontal_offset, vertical_offset),
bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
bounds.lower_right(),
bounds.upper_right() + point(px(0.0), vertical_offset),
);
path.line_to(bounds.lower_left());
cx.paint_path(path, color);
},
)
.h_1()
.w_full()
}

View File

@ -14,25 +14,25 @@ impl FacePile {
}
pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
Self {
base: h_flex(),
faces,
}
Self { base: div(), faces }
}
}
impl RenderOnce for FacePile {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let player_count = self.faces.len();
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
div()
.z_index((player_count - ix) as u16)
.when(isnt_last, |div| div.neg_mr_1())
.child(player)
});
self.base.children(player_list)
// Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
self.base
.flex()
.flex_row_reverse()
.items_center()
.justify_start()
.children(
self.faces
.into_iter()
.enumerate()
.rev()
.map(|(ix, player)| div().when(ix > 0, |div| div.neg_ml_1()).child(player)),
)
}
}

View File

@ -894,7 +894,7 @@ mod tests {
display_map::{BlockContext, TransformBlock},
DisplayPoint, GutterDimensions,
};
use gpui::{px, TestAppContext, VisualTestContext, WindowContext};
use gpui::{px, Stateful, TestAppContext, VisualTestContext, WindowContext};
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
use project::FakeFs;
use serde_json::json;
@ -1600,20 +1600,18 @@ mod tests {
let name: SharedString = match block {
TransformBlock::Custom(block) => cx.with_element_context({
|cx| -> Option<SharedString> {
block
.render(&mut BlockContext {
context: cx,
anchor_x: px(0.),
gutter_dimensions: &GutterDimensions::default(),
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id: ix,
editor_style: &editor::EditorStyle::default(),
})
.inner_id()?
.try_into()
.ok()
let mut element = block.render(&mut BlockContext {
context: cx,
anchor_x: px(0.),
gutter_dimensions: &GutterDimensions::default(),
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id: ix,
editor_style: &editor::EditorStyle::default(),
});
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
element.interactivity().element_id.clone()?.try_into().ok()
}
})?,

View File

@ -46,7 +46,7 @@ pub use block_map::{
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(crate) use inlay_map::Inlay;

View File

@ -51,7 +51,9 @@ pub use display_map::DisplayPoint;
use display_map::*;
pub use editor_settings::EditorSettings;
use element::LineWithInvisibles;
pub use element::{Cursor, EditorElement, HighlightedRange, HighlightedRangeLine};
pub use element::{
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
};
use futures::FutureExt;
use fuzzy::{StringMatch, StringMatchCandidate};
use git::diff_hunk_to_display;
@ -4202,14 +4204,14 @@ impl Editor {
}
pub fn render_fold_indicators(
&self,
&mut self,
fold_data: Vec<Option<(FoldStatus, u32, bool)>>,
_style: &EditorStyle,
gutter_hovered: bool,
_line_height: Pixels,
_gutter_margin: Pixels,
editor_view: View<Editor>,
) -> Vec<Option<IconButton>> {
cx: &mut ViewContext<Self>,
) -> Vec<Option<AnyElement>> {
fold_data
.iter()
.enumerate()
@ -4218,24 +4220,20 @@ impl Editor {
.map(|(fold_status, buffer_row, active)| {
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
IconButton::new(ix, ui::IconName::ChevronDown)
.on_click({
let view = editor_view.clone();
move |_e, cx| {
view.update(cx, |editor, cx| match fold_status {
FoldStatus::Folded => {
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
}
FoldStatus::Foldable => {
editor.fold_at(&FoldAt { buffer_row }, cx);
}
})
.on_click(cx.listener(move |this, _e, cx| match fold_status {
FoldStatus::Folded => {
this.unfold_at(&UnfoldAt { buffer_row }, cx);
}
})
FoldStatus::Foldable => {
this.fold_at(&FoldAt { buffer_row }, cx);
}
}))
.icon_color(ui::Color::Muted)
.icon_size(ui::IconSize::Small)
.selected(fold_status == FoldStatus::Folded)
.selected_icon(ui::IconName::ChevronRight)
.size(ui::ButtonSize::None)
.into_any_element()
})
})
.flatten()
@ -9215,7 +9213,7 @@ impl Editor {
&self,
search_range: Range<Anchor>,
display_snapshot: &DisplaySnapshot,
cx: &mut ViewContext<Self>,
cx: &WindowContext,
) -> Vec<Range<DisplayPoint>> {
display_snapshot
.buffer_snapshot
@ -9986,7 +9984,7 @@ impl EditorSnapshot {
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()
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
use crate::{
element::PointForPosition,
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 language::{Bias, ToOffset};

View File

@ -499,9 +499,10 @@ impl InfoPopover {
.overflow_y_scroll()
.max_w(max_size.width)
.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.
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.child(crate::render_parsed_markdown(
"content",
&self.parsed_content,
@ -563,6 +564,7 @@ impl DiagnosticPopover {
div()
.id("diagnostic")
.block()
.elevation_2(cx)
.overflow_y_scroll()
.px_2()
@ -602,11 +604,10 @@ mod tests {
use super::*;
use crate::{
editor_tests::init_test,
element::PointForPosition,
hover_links::update_inlay_link_and_hover_points,
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
test::editor_lsp_test_context::EditorLspTestContext,
InlayId,
InlayId, PointForPosition,
};
use collections::BTreeSet;
use gpui::{FontWeight, HighlightStyle, UnderlineStyle};

View File

@ -6,7 +6,7 @@ use crate::{
DisplayPoint, Editor, EditorMode, MultiBuffer,
};
use gpui::{Context, Model, Pixels, ViewContext};
use gpui::{Context, Font, FontFeatures, FontStyle, FontWeight, Model, Pixels, ViewContext};
use project::Project;
use util::test::{marked_text_offsets, marked_text_ranges};
@ -26,7 +26,12 @@ pub fn marked_display_snapshot(
) -> (DisplaySnapshot, Vec<DisplayPoint>) {
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 buffer = MultiBuffer::build_simple(&unmarked_text, cx);

View File

@ -6,10 +6,9 @@ use editor::{Editor, EditorElement, EditorStyle};
use extension::{ExtensionApiResponse, ExtensionManifest, ExtensionStatus, ExtensionStore};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, canvas, uniform_list, AnyElement, AppContext, AvailableSpace, EventEmitter,
FocusableView, FontStyle, FontWeight, InteractiveElement, KeyContext, ParentElement, Render,
Styled, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, WhiteSpace,
WindowContext,
actions, canvas, uniform_list, AnyElement, AppContext, EventEmitter, FocusableView, FontStyle,
FontWeight, InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
UniformListScrollHandle, View, ViewContext, VisualContext, WhiteSpace, WindowContext,
};
use settings::Settings;
use std::ops::DerefMut;
@ -729,12 +728,12 @@ impl Render for ExtensionsPage {
return this.py_4().child(self.render_empty_state(cx));
}
let view = cx.view().clone();
let scroll_handle = self.list.clone();
this.child(
canvas({
let view = cx.view().clone();
let scroll_handle = self.list.clone();
canvas(
move |bounds, cx| {
uniform_list::<_, ExtensionCard, _>(
let mut list = uniform_list::<_, ExtensionCard, _>(
view,
"entries",
count,
@ -743,14 +742,12 @@ impl Render for ExtensionsPage {
.size_full()
.pb_4()
.track_scroll(scroll_handle)
.into_any_element()
.draw(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
)
}
})
.into_any_element();
list.layout(bounds.origin, bounds.size.into(), cx);
list
},
|_bounds, mut list, cx| list.paint(cx),
)
.size_full(),
)
}))

View File

@ -20,7 +20,6 @@ pub use async_context::*;
use collections::{FxHashMap, FxHashSet, VecDeque};
pub use entity_map::*;
pub use model_context::*;
use refineable::Refineable;
#[cfg(any(test, feature = "test-support"))]
pub use test_context::*;
use util::{
@ -34,8 +33,8 @@ use crate::{
DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke,
LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder,
PromptHandle, PromptLevel, Render, RenderablePromptHandle, SharedString, SubscriberSet,
Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext,
Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance,
WindowContext, WindowHandle, WindowId,
};
mod async_context;
@ -216,7 +215,6 @@ pub struct AppContext {
pub(crate) svg_renderer: SvgRenderer,
asset_source: Arc<dyn AssetSource>,
pub(crate) image_cache: ImageCache,
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
pub(crate) globals_by_type: FxHashMap<TypeId, Box<dyn Any>>,
pub(crate) entities: EntityMap,
pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
@ -278,7 +276,6 @@ impl AppContext {
svg_renderer: SvgRenderer::new(asset_source.clone()),
asset_source,
image_cache: ImageCache::new(http_client),
text_style_stack: Vec::new(),
globals_by_type: FxHashMap::default(),
entities,
new_view_observers: SubscriberSet::new(),
@ -829,15 +826,6 @@ impl AppContext {
&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.
pub fn has_global<G: Global>(&self) -> bool {
self.globals_by_type.contains_key(&TypeId::of::<G>())
@ -1021,14 +1009,6 @@ impl AppContext {
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.
pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
self.keymap.borrow_mut().add_bindings(bindings);
@ -1127,16 +1107,19 @@ impl AppContext {
/// 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.
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 Ok(window_action_available) =
window.update(self, |_, cx| cx.is_action_available(action))
{
return window_action_available;
action_available = window_action_available;
}
}
self.global_action_listeners
.contains_key(&action.as_any().type_id())
action_available
|| self
.global_action_listeners
.contains_key(&action.as_any().type_id())
}
/// Sets the menu bar for this application. This will replace any existing menu bar.
@ -1152,14 +1135,41 @@ impl AppContext {
.update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
.log_err();
} else {
self.propagate_event = true;
self.dispatch_global_action(action);
}
}
pub(crate) fn dispatch_global_action(&mut self, action: &dyn Action) {
self.propagate_event = true;
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
{
for listener in &global_listeners {
listener(action.as_any(), DispatchPhase::Capture, self);
if !self.propagate_event {
break;
}
}
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
}
if self.propagate_event {
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
{
for listener in &global_listeners {
listener(action.as_any(), DispatchPhase::Capture, self);
for listener in global_listeners.iter().rev() {
listener(action.as_any(), DispatchPhase::Bubble, self);
if !self.propagate_event {
break;
}
@ -1174,29 +1184,6 @@ impl AppContext {
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
}
if self.propagate_event {
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
{
for listener in global_listeners.iter().rev() {
listener(action.as_any(), DispatchPhase::Bubble, self);
if !self.propagate_event {
break;
}
}
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
}
}
}
}

View File

@ -674,17 +674,10 @@ impl VisualTestContext {
f: impl FnOnce(&mut WindowContext) -> AnyElement,
) {
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_view_id(entity_id, |cx| {
f(cx).draw(origin, space, cx);
})
let mut element = f(cx);
element.layout(origin, space, cx);
element.paint(cx);
});
cx.refresh();

View 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
}
}

View File

@ -15,9 +15,6 @@
//!
//! 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.
//! 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
//!
@ -35,33 +32,48 @@
//! your own custom layout algorithm or rendering a code editor.
use crate::{
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId,
Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext,
ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
};
use derive_more::{Deref, DerefMut};
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.
/// 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
/// for more details.
pub trait Element: 'static + IntoElement {
/// The type of state to store for this element between frames. See the module-level documentation
/// for details.
type State: 'static;
/// The type of state returned from [`Element::before_layout`]. A mutable reference to this state is subsequently
/// provided to [`Element::after_layout`] and [`Element::paint`].
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.
/// 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,
state: Option<Self::State>,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
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.
/// The state argument is the same state that was returned from [`Element::request_layout()`].
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext);
/// The state argument is the same state that was returned from [`Element::before_layout()`].
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`].
fn into_any(self) -> AnyElement {
@ -75,10 +87,6 @@ pub trait IntoElement: Sized {
/// Useful for converting other types into elements automatically, like Strings
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`].
fn into_element(self) -> Self::Element;
@ -86,41 +94,6 @@ pub trait IntoElement: Sized {
fn into_any_element(self) -> AnyElement {
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 {}
@ -188,24 +161,36 @@ impl<C: RenderOnce> Component<C> {
}
impl<C: RenderOnce> Element for Component<C> {
type State = AnyElement;
type BeforeLayout = AnyElement;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut element = self
.0
.take()
.unwrap()
.render(cx.deref_mut())
.into_any_element();
let layout_id = element.request_layout(cx);
let layout_id = element.before_layout(cx);
(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)
}
}
@ -213,10 +198,6 @@ impl<C: RenderOnce> Element for Component<C> {
impl<C: RenderOnce> IntoElement for Component<C> {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@ -227,9 +208,11 @@ impl<C: RenderOnce> IntoElement for Component<C> {
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
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);
@ -238,110 +221,102 @@ trait ElementObject {
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> 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.
pub(crate) struct DrawableElement<E: Element> {
element: Option<E>,
phase: ElementDrawPhase<E::State>,
pub struct Drawable<E: Element> {
/// The drawn element.
pub element: E,
phase: ElementDrawPhase<E::BeforeLayout, E::AfterLayout>,
}
#[derive(Default)]
enum ElementDrawPhase<S> {
enum ElementDrawPhase<BeforeLayout, AfterLayout> {
#[default]
Start,
LayoutRequested {
BeforeLayout {
layout_id: LayoutId,
frame_state: Option<S>,
before_layout: BeforeLayout,
},
LayoutComputed {
layout_id: LayoutId,
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.
impl<E: Element> DrawableElement<E> {
impl<E: Element> Drawable<E> {
fn new(element: E) -> Self {
DrawableElement {
element: Some(element),
Drawable {
element,
phase: ElementDrawPhase::Start,
}
}
fn element_id(&self) -> Option<ElementId> {
self.element.as_ref()?.element_id()
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
match mem::take(&mut self.phase) {
ElementDrawPhase::Start => {
let (layout_id, before_layout) = self.element.before_layout(cx);
self.phase = ElementDrawPhase::BeforeLayout {
layout_id,
before_layout,
};
layout_id
}
_ => panic!("must call before_layout only once"),
}
}
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
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,
frame_state,
};
layout_id
}
fn paint(mut self, cx: &mut ElementContext) -> Option<E::State> {
match self.phase {
ElementDrawPhase::LayoutRequested {
fn after_layout(&mut self, cx: &mut ElementContext) {
match mem::take(&mut self.phase) {
ElementDrawPhase::BeforeLayout {
layout_id,
frame_state,
mut before_layout,
}
| ElementDrawPhase::LayoutComputed {
layout_id,
frame_state,
mut before_layout,
..
} => {
let bounds = cx.layout_bounds(layout_id);
if let Some(mut frame_state) = frame_state {
self.element
.take()
.unwrap()
.paint(bounds, &mut frame_state, cx);
Some(frame_state)
} else {
let element_id = self
.element
.as_ref()
.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
}
let node_id = cx.window.next_frame.dispatch_tree.push_node();
let after_layout = self.element.after_layout(bounds, &mut before_layout, cx);
self.phase = ElementDrawPhase::AfterLayout {
node_id,
bounds,
before_layout,
after_layout,
};
cx.window.next_frame.dispatch_tree.pop_node();
}
_ => panic!("must call before_layout before after_layout"),
}
}
_ => 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,
) -> Size<Pixels> {
if matches!(&self.phase, ElementDrawPhase::Start) {
self.request_layout(cx);
self.before_layout(cx);
}
let layout_id = match &mut self.phase {
ElementDrawPhase::LayoutRequested {
let layout_id = match mem::take(&mut self.phase) {
ElementDrawPhase::BeforeLayout {
layout_id,
frame_state,
before_layout,
} => {
cx.compute_layout(*layout_id, available_space);
let layout_id = *layout_id;
cx.compute_layout(layout_id, available_space);
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
available_space,
frame_state: frame_state.take(),
before_layout,
};
layout_id
}
ElementDrawPhase::LayoutComputed {
layout_id,
available_space: prev_available_space,
..
before_layout,
} => {
if available_space != *prev_available_space {
cx.compute_layout(*layout_id, available_space);
*prev_available_space = available_space;
if available_space != prev_available_space {
cx.compute_layout(layout_id, available_space);
}
*layout_id
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
available_space,
before_layout,
};
layout_id
}
_ => panic!("cannot measure after painting"),
};
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
E: Element,
E::State: 'static,
E::BeforeLayout: 'static,
{
fn element_id(&self) -> Option<ElementId> {
self.as_ref().unwrap().element_id()
fn inner_element(&mut self) -> &mut dyn Any {
&mut self.element
}
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
DrawableElement::request_layout(self.as_mut().unwrap(), cx)
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
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) {
DrawableElement::paint(self.take().unwrap(), cx);
Drawable::paint(self, cx);
}
fn measure(
@ -418,16 +390,7 @@ where
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> Size<Pixels> {
DrawableElement::measure(self.as_mut().unwrap(), 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);
Drawable::measure(self, available_space, cx)
}
}
@ -438,18 +401,28 @@ impl AnyElement {
pub(crate) fn new<E>(element: E) -> Self
where
E: 'static + Element,
E::State: Any,
E::BeforeLayout: Any,
{
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);
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`.
/// Used for laying out child elements in a parent element.
pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
self.0.request_layout(cx)
pub fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
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`.
@ -466,35 +439,44 @@ impl AnyElement {
self.0.measure(available_space, cx)
}
/// Initializes this element and performs layout in the available space, then paints it at the given origin.
pub fn draw(
/// Initializes this element, performs layout if needed and commits its bounds for hitbox purposes.
pub fn layout(
&mut self,
origin: Point<Pixels>,
absolute_offset: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) {
self.0.draw(origin, available_space, cx)
}
/// Returns the element ID of the element stored in this `AnyElement`, if any.
pub fn inner_id(&self) -> Option<ElementId> {
self.0.element_id()
) -> Size<Pixels> {
let size = self.measure(available_space, cx);
cx.with_absolute_element_offset(absolute_offset, |cx| self.after_layout(cx));
size
}
}
impl Element for AnyElement {
type State = ();
type BeforeLayout = ();
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let layout_id = self.request_layout(cx);
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let layout_id = self.before_layout(cx);
(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)
}
}
@ -502,10 +484,6 @@ impl Element for AnyElement {
impl IntoElement for AnyElement {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@ -521,30 +499,32 @@ pub struct Empty;
impl IntoElement for Empty {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
}
impl Element for Empty {
type State = ();
type BeforeLayout = ();
type AfterLayout = ();
fn request_layout(
&mut self,
_state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
(cx.request_layout(&crate::Style::default(), None), ())
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) {
}
fn paint(
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::State,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_cx: &mut ElementContext,
) {
}

View File

@ -4,54 +4,68 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe
/// Construct a canvas element with the given paint callback.
/// 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 {
paint_callback: Some(Box::new(callback)),
after_layout: Some(Box::new(after_layout)),
paint: Some(Box::new(paint)),
style: StyleRefinement::default(),
}
}
/// A canvas element, meant for accessing the low level paint API without defining a whole
/// custom element
pub struct Canvas {
paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut ElementContext)>>,
pub struct Canvas<T> {
after_layout: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
style: StyleRefinement,
}
impl IntoElement for Canvas {
impl<T: 'static> IntoElement for Canvas<T> {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
}
impl Element for Canvas {
type State = Style;
impl<T: 'static> Element for Canvas<T> {
type BeforeLayout = Style;
type AfterLayout = Option<T>;
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (crate::LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
let mut style = Style::default();
style.refine(&self.style);
let layout_id = cx.request_layout(&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| {
(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 {
&mut self.style
}

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,8 @@ use std::path::PathBuf;
use std::sync::Arc;
use crate::{
point, size, Bounds, DevicePixels, Element, ElementContext, ImageData, InteractiveElement,
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size,
point, size, Bounds, DevicePixels, Element, ElementContext, Hitbox, ImageData,
InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size,
StyleRefinement, Styled, UriOrPath,
};
use futures::FutureExt;
@ -88,86 +88,85 @@ impl 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,
element_state: Option<Self::State>,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
) -> Option<Hitbox> {
self.interactivity
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::State,
_: &mut Self::BeforeLayout,
hitbox: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
let source = self.source.clone();
self.interactivity.paint(
bounds,
bounds.size,
element_state,
cx,
|style, _scroll_offset, cx| {
self.interactivity
.paint(bounds, hitbox.as_ref(), cx, |style, cx| {
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
cx.with_z_index(1, |cx| {
match source {
ImageSource::Uri(_) | ImageSource::File(_) => {
let uri_or_path: UriOrPath = match source {
ImageSource::Uri(uri) => uri.into(),
ImageSource::File(path) => path.into(),
_ => unreachable!(),
};
match source {
ImageSource::Uri(_) | ImageSource::File(_) => {
let uri_or_path: UriOrPath = match source {
ImageSource::Uri(uri) => uri.into(),
ImageSource::File(path) => path.into(),
_ => unreachable!(),
};
let image_future = cx.image_cache.get(uri_or_path.clone(), cx);
if let Some(data) = image_future
.clone()
.now_or_never()
.and_then(|result| result.ok())
{
let new_bounds = preserve_aspect_ratio(bounds, data.size());
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
.log_err();
} else {
cx.spawn(|mut cx| async move {
if image_future.await.ok().is_some() {
cx.on_next_frame(|cx| cx.refresh());
}
})
.detach();
}
}
ImageSource::Data(data) => {
let image_future = cx.image_cache.get(uri_or_path.clone(), cx);
if let Some(data) = image_future
.clone()
.now_or_never()
.and_then(|result| result.ok())
{
let new_bounds = preserve_aspect_ratio(bounds, data.size());
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
.log_err();
} else {
cx.spawn(|mut cx| async move {
if image_future.await.ok().is_some() {
cx.on_next_frame(|cx| cx.refresh());
}
})
.detach();
}
}
#[cfg(target_os = "macos")]
ImageSource::Surface(surface) => {
let size = size(surface.width().into(), surface.height().into());
let new_bounds = preserve_aspect_ratio(bounds, size);
// TODO: Add support for corner_radii and grayscale.
cx.paint_surface(new_bounds, surface);
}
};
});
},
)
ImageSource::Data(data) => {
let new_bounds = preserve_aspect_ratio(bounds, data.size());
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
.log_err();
}
#[cfg(target_os = "macos")]
ImageSource::Surface(surface) => {
let size = size(surface.width().into(), surface.height().into());
let new_bounds = preserve_aspect_ratio(bounds, size);
// TODO: Add support for corner_radii and grayscale.
cx.paint_surface(new_bounds, surface);
}
}
})
}
}
impl IntoElement for Img {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
self.interactivity.element_id.clone()
}
fn into_element(self) -> Self::Element {
self
}

View File

@ -8,11 +8,12 @@
use crate::{
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,
};
use collections::VecDeque;
use refineable::Refineable as _;
use smallvec::SmallVec;
use std::{cell::RefCell, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
use taffy::style::Overflow;
@ -96,6 +97,13 @@ struct LayoutItemsResponse {
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)]
enum ListItem {
Unrendered,
@ -302,7 +310,6 @@ impl StateInner {
height: Pixels,
delta: Point<Pixels>,
cx: &mut WindowContext,
padding: Edges<Pixels>,
) {
// Drop scroll events after a reset, since we can't calculate
// the new logical scroll top without the item heights
@ -310,6 +317,7 @@ impl StateInner {
return;
}
let padding = self.last_padding.unwrap_or_default();
let scroll_max =
(self.items.summary().height + padding.top + padding.bottom - height).max(px(0.));
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
@ -516,13 +524,13 @@ pub struct ListOffset {
}
impl Element for List {
type State = ();
type BeforeLayout = ListFrameState;
type AfterLayout = HitboxId;
fn request_layout(
fn before_layout(
&mut self,
_state: Option<Self::State>,
cx: &mut crate::ElementContext,
) -> (crate::LayoutId, Self::State) {
) -> (crate::LayoutId, Self::BeforeLayout) {
let layout_id = match self.sizing_behavior {
ListSizingBehavior::Infer => {
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,
bounds: Bounds<crate::Pixels>,
_state: &mut Self::State,
cx: &mut crate::ElementContext,
) {
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> HitboxId {
let state = &mut *self.state.0.borrow_mut();
state.reset = false;
@ -615,12 +623,11 @@ impl Element for List {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
item_origin.y -= layout_response.scroll_top.offset_in_item;
for item_element in &mut layout_response.item_elements {
let item_height = item_element
.measure(layout_response.available_item_space, cx)
.height;
item_element.draw(item_origin, layout_response.available_item_space, cx);
item_origin.y += item_height;
for mut item_element in layout_response.item_elements {
let item_size = item_element.measure(layout_response.available_item_space, cx);
item_element.layout(item_origin, layout_response.available_item_space, cx);
before_layout.items.push(item_element);
item_origin.y += item_size.height;
}
});
}
@ -628,20 +635,33 @@ impl Element for List {
state.last_layout_bounds = Some(bounds);
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 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| {
if phase == DispatchPhase::Bubble
&& bounds.contains(&event.position)
&& cx.was_top_layer(&event.position, cx.stacking_order())
{
if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(cx) {
list_state.0.borrow_mut().scroll(
&layout_response.scroll_top,
&scroll_top,
height,
event.delta.pixel_delta(px(20.)),
cx,
padding,
)
}
});
@ -651,10 +671,6 @@ impl Element for List {
impl IntoElement for List {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@ -761,7 +777,7 @@ mod test {
cx.draw(
point(px(0.), px(0.)),
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

View File

@ -9,6 +9,7 @@ use crate::{
/// The state that the overlay element uses to track its children.
pub struct OverlayState {
child_layout_ids: SmallVec<[LayoutId; 4]>,
offset: Point<Pixels>,
}
/// An overlay element that can be used to display UI that
@ -69,17 +70,14 @@ impl ParentElement for Overlay {
}
impl Element for Overlay {
type State = OverlayState;
type BeforeLayout = OverlayState;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (crate::LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
let child_layout_ids = self
.children
.iter_mut()
.map(|child| child.request_layout(cx))
.map(|child| child.before_layout(cx))
.collect::<SmallVec<_>>();
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());
(layout_id, OverlayState { child_layout_ids })
(
layout_id,
OverlayState {
child_layout_ids,
offset: Point::default(),
},
)
}
fn paint(
fn after_layout(
&mut self,
bounds: crate::Bounds<crate::Pixels>,
element_state: &mut Self::State,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) {
if element_state.child_layout_ids.is_empty() {
if before_layout.child_layout_ids.is_empty() {
return;
}
let mut child_min = point(Pixels::MAX, Pixels::MAX);
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);
child_min = child_min.min(&child_bounds.origin);
child_max = child_max.max(&child_bounds.lower_right());
@ -165,25 +169,30 @@ impl Element for Overlay {
desired.origin.y = limits.origin.y;
}
let mut offset = cx.element_offset() + desired.origin - bounds.origin;
offset = point(offset.x.round(), offset.y.round());
cx.with_absolute_element_offset(offset, |cx| {
cx.break_content_mask(|cx| {
for child in &mut self.children {
child.paint(cx);
}
})
})
before_layout.offset = cx.element_offset() + desired.origin - bounds.origin;
before_layout.offset = point(
before_layout.offset.x.round(),
before_layout.offset.y.round(),
);
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 {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}

View File

@ -1,6 +1,6 @@
use crate::{
Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState,
Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled,
Bounds, Element, ElementContext, Hitbox, InteractiveElement, Interactivity, IntoElement,
LayoutId, Pixels, SharedString, StyleRefinement, Styled,
};
use util::ResultExt;
@ -27,28 +27,37 @@ impl 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,
element_state: Option<Self::State>,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
self.interactivity.layout(element_state, cx, |style, cx| {
cx.request_layout(&style, None)
})
) -> Option<Hitbox> {
self.interactivity
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::State,
_before_layout: &mut Self::BeforeLayout,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
) where
Self: Sized,
{
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) {
cx.paint_svg(bounds, path.clone(), color).log_err();
}
@ -59,10 +68,6 @@ impl Element for Svg {
impl IntoElement for Svg {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
self.interactivity.element_id.clone()
}
fn into_element(self) -> Self::Element {
self
}

View File

@ -1,7 +1,7 @@
use crate::{
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
TOOLTIP_DELAY,
};
use anyhow::anyhow;
@ -17,30 +17,37 @@ use std::{
use util::ResultExt;
impl Element for &'static str {
type State = TextState;
type BeforeLayout = TextState;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
let layout_id = state.layout(SharedString::from(*self), None, cx);
(layout_id, state)
}
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
state.paint(bounds, self, cx)
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 TextState,
_: &mut (),
cx: &mut ElementContext,
) {
text_state.paint(bounds, self, cx)
}
}
impl IntoElement for &'static str {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@ -49,41 +56,44 @@ impl IntoElement for &'static str {
impl IntoElement for String {
type Element = SharedString;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self.into()
}
}
impl Element for SharedString {
type State = TextState;
type BeforeLayout = TextState;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
let layout_id = state.layout(self.clone(), None, cx);
(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();
state.paint(bounds, text_str, cx)
text_state.paint(bounds, text_str, cx)
}
}
impl IntoElement for SharedString {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@ -138,30 +148,37 @@ impl StyledText {
}
impl Element for StyledText {
type State = TextState;
type BeforeLayout = TextState;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
(layout_id, state)
}
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
state.paint(bounds, &self.text, cx)
fn after_layout(
&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 {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@ -324,8 +341,8 @@ struct InteractiveTextClickEvent {
}
#[doc(hidden)]
#[derive(Default)]
pub struct InteractiveTextState {
text_state: TextState,
mouse_down_index: Rc<Cell<Option<usize>>>,
hovered_index: Rc<Cell<Option<usize>>>,
active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
@ -385,179 +402,184 @@ impl InteractiveText {
}
impl Element for InteractiveText {
type State = InteractiveTextState;
type BeforeLayout = TextState;
type AfterLayout = Hitbox;
fn request_layout(
&mut self,
state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
if let Some(InteractiveTextState {
mouse_down_index,
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 before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
self.text.before_layout(cx)
}
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
if let Some(click_listener) = self.click_listener.take() {
let mouse_position = cx.mouse_position();
if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
if self
.clickable_ranges
.iter()
.any(|range| range.contains(&ix))
{
let stacking_order = cx.stacking_order().clone();
cx.set_cursor_style(crate::CursorStyle::PointingHand, stacking_order);
}
}
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> Hitbox {
self.text.after_layout(bounds, state, cx);
cx.insert_hitbox(bounds, false)
}
let text_state = state.text_state.clone();
let mouse_down = state.mouse_down_index.clone();
if let Some(mouse_down_index) = mouse_down.get() {
let clickable_ranges = mem::take(&mut self.clickable_ranges);
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
if let Some(mouse_up_index) =
text_state.index_for_position(bounds, event.position)
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() {
let mouse_position = cx.mouse_position();
if let Some(ix) = text_state.index_for_position(bounds, mouse_position) {
if self
.clickable_ranges
.iter()
.any(|range| range.contains(&ix))
{
click_listener(
&clickable_ranges,
InteractiveTextClickEvent {
mouse_down_index,
mouse_up_index,
},
cx,
)
}
mouse_down.take();
cx.refresh();
}
});
} else {
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
if let Some(mouse_down_index) =
text_state.index_for_position(bounds, event.position)
{
mouse_down.set(Some(mouse_down_index));
cx.refresh();
cx.set_cursor_style(crate::CursorStyle::PointingHand, hitbox)
}
}
});
}
}
if let Some(hover_listener) = self.hover_listener.take() {
let text_state = state.text_state.clone();
let hovered_index = state.hovered_index.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
let current = hovered_index.get();
let updated = text_state.index_for_position(bounds, event.position);
if current != updated {
hovered_index.set(updated);
hover_listener(updated, event.clone(), cx);
cx.refresh();
}
}
});
}
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
let active_tooltip = state.active_tooltip.clone();
let pending_mouse_down = state.mouse_down_index.clone();
let text_state = state.text_state.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
let position = text_state.index_for_position(bounds, event.position);
let is_hovered = position.is_some() && pending_mouse_down.get().is_none();
if !is_hovered {
active_tooltip.take();
return;
}
let position = position.unwrap();
let text_state = text_state.clone();
let mouse_down = interactive_state.mouse_down_index.clone();
if let Some(mouse_down_index) = mouse_down.get() {
let hitbox = hitbox.clone();
let clickable_ranges = mem::take(&mut self.clickable_ranges);
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
if let Some(mouse_up_index) =
text_state.index_for_position(bounds, event.position)
{
click_listener(
&clickable_ranges,
InteractiveTextClickEvent {
mouse_down_index,
mouse_up_index,
},
cx,
)
}
if phase != DispatchPhase::Bubble {
return;
}
if active_tooltip.borrow().is_none() {
let task = cx.spawn({
let active_tooltip = active_tooltip.clone();
let tooltip_builder = tooltip_builder.clone();
move |mut cx| async move {
cx.background_executor().timer(TOOLTIP_DELAY).await;
cx.update(|cx| {
let new_tooltip =
tooltip_builder(position, cx).map(|tooltip| ActiveTooltip {
tooltip: Some(AnyTooltip {
view: tooltip,
cursor_offset: cx.mouse_position(),
}),
_task: None,
});
*active_tooltip.borrow_mut() = new_tooltip;
mouse_down.take();
cx.refresh();
})
.ok();
}
});
} else {
let hitbox = hitbox.clone();
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
if let Some(mouse_down_index) =
text_state.index_for_position(bounds, event.position)
{
mouse_down.set(Some(mouse_down_index));
cx.refresh();
}
}
});
}
}
cx.on_mouse_event({
let mut hover_listener = self.hover_listener.take();
let hitbox = hitbox.clone();
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 updated = text_state.index_for_position(bounds, event.position);
if current != updated {
hovered_index.set(updated);
if let Some(hover_listener) = hover_listener.as_ref() {
hover_listener(updated, event.clone(), cx);
}
cx.refresh();
}
}
}
});
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
let hitbox = hitbox.clone();
let active_tooltip = interactive_state.active_tooltip.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| {
let position = text_state.index_for_position(bounds, event.position);
let is_hovered = position.is_some()
&& hitbox.is_hovered(cx)
&& pending_mouse_down.get().is_none();
if !is_hovered {
active_tooltip.take();
return;
}
let position = position.unwrap();
if phase != DispatchPhase::Bubble {
return;
}
if active_tooltip.borrow().is_none() {
let task = cx.spawn({
let active_tooltip = active_tooltip.clone();
let tooltip_builder = tooltip_builder.clone();
move |mut cx| async move {
cx.background_executor().timer(TOOLTIP_DELAY).await;
cx.update(|cx| {
let new_tooltip =
tooltip_builder(position, cx).map(|tooltip| {
ActiveTooltip {
tooltip: Some(AnyTooltip {
view: tooltip,
cursor_offset: cx.mouse_position(),
}),
_task: None,
}
});
*active_tooltip.borrow_mut() = new_tooltip;
cx.refresh();
})
.ok();
}
});
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
tooltip: None,
_task: Some(task),
});
}
});
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
tooltip: None,
_task: Some(task),
let active_tooltip = interactive_state.active_tooltip.clone();
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
active_tooltip.take();
});
if let Some(tooltip) = interactive_state
.active_tooltip
.clone()
.borrow()
.as_ref()
.and_then(|at| at.tooltip.clone())
{
cx.set_tooltip(tooltip);
}
}
});
let active_tooltip = state.active_tooltip.clone();
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
active_tooltip.take();
});
self.text.paint(bounds, text_state, &mut (), cx);
if let Some(tooltip) = state
.active_tooltip
.clone()
.borrow()
.as_ref()
.and_then(|at| at.tooltip.clone())
{
cx.set_tooltip(tooltip);
}
}
self.text.paint(bounds, &mut state.text_state, cx)
((), Some(interactive_state))
},
);
}
}
impl IntoElement for InteractiveText {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
Some(self.element_id.clone())
}
fn into_element(self) -> Self::Element {
self
}

View File

@ -6,8 +6,8 @@
use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
Pixels, Render, ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render,
ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -42,13 +42,13 @@ where
};
UniformList {
id: id.clone(),
item_count,
item_to_measure_index: 0,
render_items: Box::new(render_range),
interactivity: Interactivity {
element_id: Some(id),
base_style: Box::new(base_style),
occlude_mouse: true,
#[cfg(debug_assertions)]
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.
pub struct UniformList {
id: ElementId,
item_count: usize,
item_to_measure_index: usize,
render_items:
@ -70,6 +69,12 @@ pub struct UniformList {
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.
/// This should be stored in your view and passed to the uniform_list on each frame.
#[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 {
type State = UniformListState;
type BeforeLayout = UniformListFrameState;
type AfterLayout = Option<Hitbox>;
fn request_layout(
&mut self,
state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let max_items = self.item_count;
let item_size = state
.as_ref()
.map(|s| s.item_size)
.unwrap_or_else(|| self.measure_item(None, cx));
let item_size = self.measure_item(None, cx);
let layout_id = self.interactivity.before_layout(cx, |style, cx| {
cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| {
let desired_height = item_size.height * max_items;
let width = known_dimensions
.width
.unwrap_or(match available_space.width {
AvailableSpace::Definite(x) => x,
AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
});
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 width =
known_dimensions
.width
.unwrap_or(match available_space.width {
AvailableSpace::Definite(x) => x,
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
item_size.width
}
});
let height = match available_space.height {
AvailableSpace::Definite(height) => desired_height.min(height),
AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
};
size(width, height)
})
});
let height = match available_space.height {
AvailableSpace::Definite(height) => desired_height.min(height),
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
desired_height
}
};
size(width, height)
},
)
});
let element_state = UniformListState {
interactive,
item_size,
};
(layout_id, element_state)
(
layout_id,
UniformListFrameState {
item_size,
items: SmallVec::new(),
},
)
}
fn paint(
fn after_layout(
&mut self,
bounds: Bounds<crate::Pixels>,
element_state: &mut Self::State,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) {
let style =
self.interactivity
.compute_style(Some(bounds), &mut element_state.interactive, cx);
) -> Option<Hitbox> {
let style = self.interactivity.compute_style(None, cx);
let border = style.border_widths.to_pixels(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),
);
let item_size = element_state.item_size;
let content_size = Size {
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
.interactive
.scroll_offset
.get_or_insert_with(Rc::default)
.clone();
let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap();
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
let shared_scroll_to_item = self
@ -192,12 +167,11 @@ impl Element for UniformList {
.as_mut()
.and_then(|handle| handle.deferred_scroll_to_item.take());
self.interactivity.paint(
self.interactivity.after_layout(
bounds,
content_size,
&mut element_state.interactive,
cx,
|style, mut scroll_offset, cx| {
|style, mut scroll_offset, hitbox, cx| {
let border = style.border_widths.to_pixels(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);
let mut items = (self.render_items)(visible_range.clone(), cx);
cx.with_z_index(1, |cx| {
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (item, ix) in items.iter_mut().zip(visible_range) {
let item_origin = padded_bounds.origin
+ point(
px(0.),
item_height * ix + scroll_offset.y + padding.top,
);
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.draw(item_origin, available_space, cx);
}
});
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (mut item, ix) in items.into_iter().zip(visible_range) {
let item_origin = padded_bounds.origin
+ point(px(0.), item_height * ix + scroll_offset.y + padding.top);
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
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 {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
Some(self.id.clone())
}
fn into_element(self) -> Self::Element {
self
}
@ -301,7 +284,7 @@ impl UniformList {
/// Track and render scroll state of this list with reference to the given scroll handle.
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
}

View File

@ -828,6 +828,28 @@ where
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> {
@ -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> {
/// 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;
}
impl Half for i32 {
fn half(&self) -> Self {
self / 2
}
}
impl Half for f32 {
fn half(&self) -> Self {
self / 2.

View File

@ -70,6 +70,7 @@ mod app;
mod arena;
mod assets;
mod bounds_tree;
mod color;
mod element;
mod elements;

View File

@ -54,11 +54,12 @@ use crate::{
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext,
};
use collections::FxHashMap;
use smallvec::{smallvec, SmallVec};
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
cell::RefCell,
mem,
ops::Range,
rc::Rc,
};
@ -68,6 +69,7 @@ pub(crate) struct DispatchNodeId(usize);
pub(crate) struct DispatchTree {
node_stack: Vec<DispatchNodeId>,
pub(crate) context_stack: Vec<KeyContext>,
view_stack: Vec<EntityId>,
nodes: Vec<DispatchNode>,
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
@ -81,11 +83,28 @@ pub(crate) struct DispatchNode {
pub key_listeners: Vec<KeyListener>,
pub action_listeners: Vec<DispatchActionListener>,
pub context: Option<KeyContext>,
focus_id: Option<FocusId>,
pub focus_id: Option<FocusId>,
view_id: Option<EntityId>,
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)>;
#[derive(Clone)]
@ -99,6 +118,7 @@ impl DispatchTree {
Self {
node_stack: Vec::new(),
context_stack: Vec::new(),
view_stack: Vec::new(),
nodes: Vec::new(),
focusable_node_ids: FxHashMap::default(),
view_node_ids: FxHashMap::default(),
@ -111,72 +131,124 @@ impl DispatchTree {
pub fn clear(&mut self) {
self.node_stack.clear();
self.context_stack.clear();
self.view_stack.clear();
self.nodes.clear();
self.focusable_node_ids.clear();
self.view_node_ids.clear();
self.keystroke_matchers.clear();
}
pub fn push_node(
&mut self,
context: Option<KeyContext>,
focus_id: Option<FocusId>,
view_id: Option<EntityId>,
) {
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn push_node(&mut self) -> DispatchNodeId {
let parent = self.node_stack.last().copied();
let node_id = DispatchNodeId(self.nodes.len());
self.nodes.push(DispatchNode {
parent,
focus_id,
view_id,
..Default::default()
});
self.node_stack.push(node_id);
node_id
}
if let Some(context) = context {
self.active_node().context = Some(context.clone());
self.context_stack.push(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 let Some(focus_id) = focus_id {
self.focusable_node_ids.insert(focus_id, node_id);
}
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);
if let Some(view_id) = view_id {
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.context_stack.push(context);
}
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);
}
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_stack.push(view_id);
}
}
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() {
self.context_stack.pop();
}
if node.view_id.is_some() {
self.view_stack.pop();
}
self.node_stack.pop();
}
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();
target.key_listeners = mem::take(&mut source.key_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]> {
let view_source_node_id = source
.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);
pub fn reuse_subtree(&mut self, old_range: Range<usize>, source: &mut Self) -> ReusedSubtree {
let new_range = self.nodes.len()..self.nodes.len() + old_range.len();
let mut grafted_view_ids = smallvec![view_id];
let mut source_stack = vec![*view_source_node_id];
let mut source_stack = vec![];
for (source_node_id, source_node) in source
.nodes
.iter_mut()
.enumerate()
.skip(view_source_node_id.0 + 1)
.skip(old_range.start)
.take(old_range.len())
{
let source_node_id = DispatchNodeId(source_node_id);
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);
self.move_node(source_node);
if let Some(view_id) = source_node.view_id {
grafted_view_ids.push(view_id);
}
}
source_stack.push(source_node_id);
self.move_node(source_node);
}
while !source_stack.is_empty() {
@ -204,7 +269,10 @@ impl DispatchTree {
self.pop_node();
}
grafted_view_ids
ReusedSubtree {
old_range,
new_range,
}
}
pub fn clear_pending_keystrokes(&mut self) {
@ -424,7 +492,7 @@ impl DispatchTree {
}
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]
}
@ -437,8 +505,8 @@ impl DispatchTree {
DispatchNodeId(0)
}
fn active_node_id(&self) -> DispatchNodeId {
*self.node_stack.last().unwrap()
pub fn active_node_id(&self) -> Option<DispatchNodeId> {
self.node_stack.last().copied()
}
}

View File

@ -1,6 +1,6 @@
// todo(linux): remove
#![cfg_attr(target_os = "linux", allow(dead_code))]
// todo(windows): remove
// todo("windows"): remove
#![cfg_attr(windows, allow(dead_code))]
mod app_menu;
@ -68,7 +68,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
pub(crate) fn current_platform() -> Rc<dyn Platform> {
Rc::new(LinuxPlatform::new())
}
// todo(windows)
// todo("windows")
#[cfg(target_os = "windows")]
pub(crate) fn current_platform() -> Rc<dyn Platform> {
Rc::new(WindowsPlatform::new())

View File

@ -292,6 +292,7 @@ impl MetalRenderer {
znear: 0.0,
zfar: 1.0,
});
for batch in scene.batches() {
let ok = match batch {
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(

View File

@ -126,7 +126,7 @@ impl Platform for TestPlatform {
#[cfg(target_os = "macos")]
return Arc::new(crate::platform::mac::MacTextSystem::new());
// todo(windows)
// todo("windows")
#[cfg(target_os = "windows")]
unimplemented!()
}

View File

@ -1,48 +1,22 @@
// todo(windows): remove
// todo("windows"): remove
#![cfg_attr(windows, allow(dead_code))]
use crate::{
point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels,
Point, ScaledPixels, StackingOrder,
bounds_tree::BoundsTree, point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges,
Hsla, Pixels, Point, ScaledPixels,
};
use collections::{BTreeMap, FxHashSet};
use std::{fmt::Debug, iter::Peekable, slice};
use std::{fmt::Debug, iter::Peekable, ops::Range, slice};
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
pub(crate) type LayerId = 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)]
pub(crate) struct Scene {
last_layer: Option<(StackingOrder, LayerId)>,
layers_by_order: BTreeMap<StackingOrder, LayerId>,
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
pub(crate) paint_operations: Vec<PaintOperation>,
primitive_bounds: BoundsTree<ScaledPixels>,
layer_stack: Vec<DrawOrder>,
pub(crate) shadows: Vec<Shadow>,
pub(crate) quads: Vec<Quad>,
pub(crate) paths: Vec<Path<ScaledPixels>>,
@ -54,12 +28,12 @@ pub(crate) struct Scene {
impl Scene {
pub fn clear(&mut self) {
self.last_layer = None;
self.layers_by_order.clear();
self.orders_by_layer.clear();
self.paint_operations.clear();
self.primitive_bounds.clear();
self.layer_stack.clear();
self.paths.clear();
self.shadows.clear();
self.quads.clear();
self.paths.clear();
self.underlines.clear();
self.monochrome_sprites.clear();
self.polychrome_sprites.clear();
@ -70,6 +44,92 @@ impl Scene {
&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> {
BatchIterator {
shadows: &self.shadows,
@ -95,162 +155,60 @@ impl Scene {
surfaces_iter: self.surfaces.iter().peekable(),
}
}
}
pub(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
let primitive = primitive.into();
let clipped_bounds = primitive
.bounds()
.intersect(&primitive.content_mask().bounds);
if clipped_bounds.size.width <= ScaledPixels(0.)
|| clipped_bounds.size.height <= ScaledPixels(0.)
{
return;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
pub(crate) enum PrimitiveKind {
Shadow,
#[default]
Quad,
Path,
Underline,
MonochromeSprite,
PolychromeSprite,
Surface,
}
let layer_id = self.layer_id_for_order(order);
match primitive {
Primitive::Shadow(mut shadow) => {
shadow.layer_id = layer_id;
self.shadows.push(shadow);
}
Primitive::Quad(mut quad) => {
quad.layer_id = layer_id;
self.quads.push(quad);
}
Primitive::Path(mut path) => {
path.layer_id = layer_id;
path.id = PathId(self.paths.len());
self.paths.push(path);
}
Primitive::Underline(mut underline) => {
underline.layer_id = layer_id;
self.underlines.push(underline);
}
Primitive::MonochromeSprite(mut sprite) => {
sprite.layer_id = layer_id;
self.monochrome_sprites.push(sprite);
}
Primitive::PolychromeSprite(mut sprite) => {
sprite.layer_id = layer_id;
self.polychrome_sprites.push(sprite);
}
Primitive::Surface(mut surface) => {
surface.layer_id = layer_id;
self.surfaces.push(surface);
}
pub(crate) enum PaintOperation {
Primitive(Primitive),
StartLayer(Bounds<ScaledPixels>),
EndLayer,
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
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,
}
}
fn layer_id_for_order(&mut self, order: &StackingOrder) -> LayerId {
if let Some((last_order, last_layer_id)) = self.last_layer.as_ref() {
if order == last_order {
return *last_layer_id;
}
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,
}
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);
}
}
@ -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)]
pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]),
@ -507,8 +417,6 @@ pub(crate) enum PrimitiveBatch<'a> {
#[derive(Default, Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Quad {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@ -539,8 +447,6 @@ impl From<Quad> for Primitive {
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Underline {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@ -570,8 +476,6 @@ impl From<Underline> for Primitive {
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Shadow {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
@ -602,8 +506,6 @@ impl From<Shadow> for Primitive {
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct MonochromeSprite {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@ -635,8 +537,6 @@ impl From<MonochromeSprite> for Primitive {
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct PolychromeSprite {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@ -669,8 +569,6 @@ impl From<PolychromeSprite> for Primitive {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Surface {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@ -700,11 +598,9 @@ impl From<Surface> for Primitive {
pub(crate) struct PathId(pub(crate) usize);
/// 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(crate) id: PathId,
pub(crate) view_id: ViewId,
layer_id: LayerId,
order: DrawOrder,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
@ -720,8 +616,6 @@ impl Path<Pixels> {
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
view_id: ViewId::default(),
layer_id: LayerId::default(),
order: DrawOrder::default(),
vertices: Vec::new(),
start,
@ -740,8 +634,6 @@ impl Path<Pixels> {
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
view_id: self.view_id,
layer_id: self.layer_id,
order: self.order,
bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),

View File

@ -115,9 +115,6 @@ pub struct Style {
/// The mouse cursor style shown when the mouse pointer is over an element.
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
#[cfg(debug_assertions)]
pub debug: bool,
@ -323,6 +320,13 @@ pub struct HighlightStyle {
impl Eq for HighlightStyle {}
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.
pub fn text_style(&self) -> Option<&TextStyleRefinement> {
if self.text.is_some() {
@ -402,97 +406,87 @@ impl Style {
let rem_size = cx.rem_size();
cx.with_z_index(0, |cx| {
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
&self.box_shadow,
);
});
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
&self.box_shadow,
);
let background_color = self.background.as_ref().and_then(Fill::color);
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();
border_color.a = 0.;
cx.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
Edges::default(),
border_color,
));
});
let mut border_color = background_color.unwrap_or_default();
border_color.a = 0.;
cx.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
Edges::default(),
border_color,
));
}
cx.with_z_index(2, |cx| {
continuation(cx);
});
continuation(cx);
if self.is_border_visible() {
cx.with_z_index(3, |cx| {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
let max_corner_radius = corner_radii.max();
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
let max_corner_radius = corner_radii.max();
let top_bounds = Bounds::from_corners(
bounds.origin,
bounds.upper_right()
+ point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
);
let bottom_bounds = Bounds::from_corners(
bounds.lower_left()
- point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
bounds.lower_right(),
);
let left_bounds = Bounds::from_corners(
top_bounds.lower_left(),
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
);
let right_bounds = Bounds::from_corners(
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
bottom_bounds.upper_right(),
);
let top_bounds = Bounds::from_corners(
bounds.origin,
bounds.upper_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
);
let bottom_bounds = Bounds::from_corners(
bounds.lower_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
bounds.lower_right(),
);
let left_bounds = Bounds::from_corners(
top_bounds.lower_left(),
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
);
let right_bounds = Bounds::from_corners(
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
bottom_bounds.upper_right(),
);
let mut background = self.border_color.unwrap_or_default();
background.a = 0.;
let quad = quad(
bounds,
corner_radii,
background,
border_widths,
self.border_color.unwrap_or_default(),
);
let mut background = self.border_color.unwrap_or_default();
background.a = 0.;
let quad = quad(
bounds,
corner_radii,
background,
border_widths,
self.border_color.unwrap_or_default(),
);
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(quad.clone());
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(quad);
},
);
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(quad.clone());
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(quad);
},
);
}
#[cfg(debug_assertions)]
@ -545,7 +539,6 @@ impl Default for Style {
box_shadow: Default::default(),
text: TextStyleRefinement::default(),
mouse_cursor: None,
z_index: None,
#[cfg(debug_assertions)]
debug: false,

View File

@ -15,12 +15,6 @@ pub trait Styled: Sized {
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`.
/// [Docs](https://tailwindcss.com/docs/position)
fn relative(mut self) -> Self {

View File

@ -47,11 +47,7 @@ impl TaffyLayoutEngine {
self.styles.clear();
}
pub fn requested_style(&self, layout_id: LayoutId) -> Option<&Style> {
self.styles.get(&layout_id)
}
pub fn request_layout(
pub fn before_layout(
&mut self,
style: &Style,
rem_size: Pixels,
@ -447,6 +443,27 @@ pub enum AvailableSpace {
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 {
fn from(space: AvailableSpace) -> TaffyAvailableSpace {
match space {

View File

@ -9,11 +9,11 @@ pub use line_layout::*;
pub use line_wrapper::*;
use crate::{
px, Bounds, DevicePixels, EntityId, Hsla, Pixels, PlatformTextSystem, Point, Result,
SharedString, Size, StrikethroughStyle, UnderlineStyle,
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
StrikethroughStyle, UnderlineStyle,
};
use anyhow::anyhow;
use collections::{BTreeSet, FxHashMap, FxHashSet};
use collections::{BTreeSet, FxHashMap};
use core::fmt;
use derive_more::Deref;
use itertools::Itertools;
@ -24,7 +24,7 @@ use std::{
cmp,
fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher},
ops::{Deref, DerefMut},
ops::{Deref, DerefMut, Range},
sync::Arc,
};
@ -279,7 +279,7 @@ impl TextSystem {
/// The GPUI text layout subsystem.
#[derive(Deref)]
pub struct WindowTextSystem {
line_layout_cache: Arc<LineLayoutCache>,
line_layout_cache: LineLayoutCache,
#[deref]
text_system: Arc<TextSystem>,
}
@ -287,15 +287,17 @@ pub struct WindowTextSystem {
impl WindowTextSystem {
pub(crate) fn new(text_system: Arc<TextSystem>) -> Self {
Self {
line_layout_cache: Arc::new(LineLayoutCache::new(
text_system.platform_text_system.clone(),
)),
line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),
text_system,
}
}
pub(crate) fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
self.line_layout_cache.with_view(view_id, f)
pub(crate) fn layout_index(&self) -> LineLayoutIndex {
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.
@ -455,8 +457,8 @@ impl WindowTextSystem {
Ok(lines)
}
pub(crate) fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
self.line_layout_cache.finish_frame(reused_views)
pub(crate) fn finish_frame(&self) {
self.line_layout_cache.finish_frame()
}
/// Layout the given line of text, at the given font_size.

View File

@ -107,212 +107,218 @@ fn paint_line(
line_height: Pixels,
decoration_runs: &[DecorationRun],
wrap_boundaries: &[WrapBoundary],
cx: &mut ElementContext<'_>,
cx: &mut ElementContext,
) -> Result<()> {
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + layout.ascent);
let mut decoration_runs = decoration_runs.iter();
let mut wraps = wrap_boundaries.iter().peekable();
let mut run_end = 0;
let mut color = black();
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
let text_system = cx.text_system().clone();
let mut glyph_origin = origin;
let mut prev_glyph_position = Point::default();
for (run_ix, run) in layout.runs.iter().enumerate() {
let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
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 baseline_offset = point(px(0.), padding_top + layout.ascent);
let mut decoration_runs = decoration_runs.iter();
let mut wraps = wrap_boundaries.iter().peekable();
let mut run_end = 0;
let mut color = black();
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
let text_system = cx.text_system().clone();
let mut glyph_origin = origin;
let mut prev_glyph_position = Point::default();
for (run_ix, run) in layout.runs.iter().enumerate() {
let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
wraps.next();
if let Some((background_origin, background_color)) = current_background.as_mut() {
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
wraps.next();
if let Some((background_origin, background_color)) = current_background.as_mut()
{
cx.paint_quad(fill(
Bounds {
origin: *background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
*background_color,
));
background_origin.x = origin.x;
background_origin.y += line_height;
}
if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
cx.paint_underline(
*underline_origin,
glyph_origin.x - underline_origin.x,
underline_style,
);
underline_origin.x = origin.x;
underline_origin.y += line_height;
}
if let Some((strikethrough_origin, strikethrough_style)) =
current_strikethrough.as_mut()
{
cx.paint_strikethrough(
*strikethrough_origin,
glyph_origin.x - strikethrough_origin.x,
strikethrough_style,
);
strikethrough_origin.x = origin.x;
strikethrough_origin.y += line_height;
}
glyph_origin.x = origin.x;
glyph_origin.y += line_height;
}
prev_glyph_position = glyph.position;
let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
if glyph.index >= run_end {
if let Some(style_run) = decoration_runs.next() {
if let Some((_, background_color)) = &mut current_background {
if style_run.background_color.as_ref() != Some(background_color) {
finished_background = current_background.take();
}
}
if let Some(run_background) = style_run.background_color {
current_background.get_or_insert((
point(glyph_origin.x, glyph_origin.y),
run_background,
));
}
if let Some((_, underline_style)) = &mut current_underline {
if style_run.underline.as_ref() != Some(underline_style) {
finished_underline = current_underline.take();
}
}
if let Some(run_underline) = style_run.underline.as_ref() {
current_underline.get_or_insert((
point(
glyph_origin.x,
glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
),
UnderlineStyle {
color: Some(run_underline.color.unwrap_or(style_run.color)),
thickness: run_underline.thickness,
wavy: run_underline.wavy,
},
));
}
if let Some((_, strikethrough_style)) = &mut current_strikethrough {
if style_run.strikethrough.as_ref() != Some(strikethrough_style) {
finished_strikethrough = current_strikethrough.take();
}
}
if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
current_strikethrough.get_or_insert((
point(
glyph_origin.x,
glyph_origin.y
+ (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
),
StrikethroughStyle {
color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
thickness: run_strikethrough.thickness,
},
));
}
run_end += style_run.len as usize;
color = style_run.color;
} else {
run_end = layout.len;
finished_background = current_background.take();
finished_underline = current_underline.take();
finished_strikethrough = current_strikethrough.take();
}
}
if let Some((background_origin, background_color)) = finished_background {
cx.paint_quad(fill(
Bounds {
origin: *background_origin,
origin: background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
*background_color,
background_color,
));
background_origin.x = origin.x;
background_origin.y += line_height;
}
if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline(
*underline_origin,
underline_origin,
glyph_origin.x - underline_origin.x,
underline_style,
&underline_style,
);
underline_origin.x = origin.x;
underline_origin.y += line_height;
}
if let Some((strikethrough_origin, strikethrough_style)) =
current_strikethrough.as_mut()
{
if let Some((strikethrough_origin, strikethrough_style)) = finished_strikethrough {
cx.paint_strikethrough(
*strikethrough_origin,
strikethrough_origin,
glyph_origin.x - strikethrough_origin.x,
strikethrough_style,
&strikethrough_style,
);
strikethrough_origin.x = origin.x;
strikethrough_origin.y += line_height;
}
glyph_origin.x = origin.x;
glyph_origin.y += line_height;
}
prev_glyph_position = glyph.position;
let max_glyph_bounds = Bounds {
origin: glyph_origin,
size: max_glyph_size,
};
let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
if glyph.index >= run_end {
if let Some(style_run) = decoration_runs.next() {
if let Some((_, background_color)) = &mut current_background {
if style_run.background_color.as_ref() != Some(background_color) {
finished_background = current_background.take();
}
let content_mask = cx.content_mask();
if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji {
cx.paint_emoji(
glyph_origin + baseline_offset,
run.font_id,
glyph.id,
layout.font_size,
)?;
} else {
cx.paint_glyph(
glyph_origin + baseline_offset,
run.font_id,
glyph.id,
layout.font_size,
color,
)?;
}
if let Some(run_background) = style_run.background_color {
current_background
.get_or_insert((point(glyph_origin.x, glyph_origin.y), run_background));
}
if let Some((_, underline_style)) = &mut current_underline {
if style_run.underline.as_ref() != Some(underline_style) {
finished_underline = current_underline.take();
}
}
if let Some(run_underline) = style_run.underline.as_ref() {
current_underline.get_or_insert((
point(
glyph_origin.x,
glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
),
UnderlineStyle {
color: Some(run_underline.color.unwrap_or(style_run.color)),
thickness: run_underline.thickness,
wavy: run_underline.wavy,
},
));
}
if let Some((_, strikethrough_style)) = &mut current_strikethrough {
if style_run.strikethrough.as_ref() != Some(strikethrough_style) {
finished_strikethrough = current_strikethrough.take();
}
}
if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
current_strikethrough.get_or_insert((
point(
glyph_origin.x,
glyph_origin.y
+ (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
),
StrikethroughStyle {
color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
thickness: run_strikethrough.thickness,
},
));
}
run_end += style_run.len as usize;
color = style_run.color;
} else {
run_end = layout.len;
finished_background = current_background.take();
finished_underline = current_underline.take();
finished_strikethrough = current_strikethrough.take();
}
}
if let Some((background_origin, background_color)) = finished_background {
cx.paint_quad(fill(
Bounds {
origin: background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
background_color,
));
}
if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
);
}
if let Some((strikethrough_origin, strikethrough_style)) = finished_strikethrough {
cx.paint_strikethrough(
strikethrough_origin,
glyph_origin.x - strikethrough_origin.x,
&strikethrough_style,
);
}
let max_glyph_bounds = Bounds {
origin: glyph_origin,
size: max_glyph_size,
};
let content_mask = cx.content_mask();
if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji {
cx.paint_emoji(
glyph_origin + baseline_offset,
run.font_id,
glyph.id,
layout.font_size,
)?;
} else {
cx.paint_glyph(
glyph_origin + baseline_offset,
run.font_id,
glyph.id,
layout.font_size,
color,
)?;
}
}
}
}
let mut last_line_end_x = origin.x + layout.width;
if let Some(boundary) = wrap_boundaries.last() {
let run = &layout.runs[boundary.run_ix];
let glyph = &run.glyphs[boundary.glyph_ix];
last_line_end_x -= glyph.position.x;
}
let mut last_line_end_x = origin.x + layout.width;
if let Some(boundary) = wrap_boundaries.last() {
let run = &layout.runs[boundary.run_ix];
let glyph = &run.glyphs[boundary.glyph_ix];
last_line_end_x -= glyph.position.x;
}
if let Some((background_origin, background_color)) = current_background.take() {
cx.paint_quad(fill(
Bounds {
origin: background_origin,
size: size(last_line_end_x - background_origin.x, line_height),
},
background_color,
));
}
if let Some((background_origin, background_color)) = current_background.take() {
cx.paint_quad(fill(
Bounds {
origin: background_origin,
size: size(last_line_end_x - background_origin.x, line_height),
},
background_color,
));
}
if let Some((underline_start, underline_style)) = current_underline.take() {
cx.paint_underline(
underline_start,
last_line_end_x - underline_start.x,
&underline_style,
);
}
if let Some((underline_start, underline_style)) = current_underline.take() {
cx.paint_underline(
underline_start,
last_line_end_x - underline_start.x,
&underline_style,
);
}
if let Some((strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
cx.paint_strikethrough(
strikethrough_start,
last_line_end_x - strikethrough_start.x,
&strikethrough_style,
);
}
if let Some((strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
cx.paint_strikethrough(
strikethrough_start,
last_line_end_x - strikethrough_start.x,
&strikethrough_style,
);
}
Ok(())
Ok(())
})
}

View File

@ -1,10 +1,11 @@
use crate::{px, EntityId, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
use collections::{FxHashMap, FxHashSet};
use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
use collections::FxHashMap;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use std::{
borrow::Borrow,
hash::{Hash, Hasher},
ops::Range,
sync::Arc,
};
@ -277,63 +278,71 @@ impl WrappedLineLayout {
}
pub(crate) struct LineLayoutCache {
view_stack: Mutex<Vec<EntityId>>,
previous_frame: Mutex<FxHashMap<CacheKey, Arc<LineLayout>>>,
current_frame: RwLock<FxHashMap<CacheKey, Arc<LineLayout>>>,
previous_frame_wrapped: Mutex<FxHashMap<CacheKey, Arc<WrappedLineLayout>>>,
current_frame_wrapped: RwLock<FxHashMap<CacheKey, Arc<WrappedLineLayout>>>,
previous_frame: Mutex<FrameCache>,
current_frame: RwLock<FrameCache>,
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 {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
Self {
view_stack: Mutex::default(),
previous_frame: Mutex::default(),
current_frame: RwLock::default(),
previous_frame_wrapped: Mutex::default(),
current_frame_wrapped: RwLock::default(),
platform_text_system,
}
}
pub fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
debug_assert_eq!(self.view_stack.lock().len(), 0);
pub fn layout_index(&self) -> LineLayoutIndex {
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 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);
let mut prev_frame_wrapped = self.previous_frame_wrapped.lock();
let mut curr_frame_wrapped = self.current_frame_wrapped.write();
for (key, layout) in prev_frame_wrapped.drain() {
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()
curr_frame.lines.clear();
curr_frame.wrapped_lines.clear();
curr_frame.used_lines.clear();
curr_frame.used_wrapped_lines.clear();
}
pub fn layout_wrapped_line(
@ -348,19 +357,24 @@ impl LineLayoutCache {
font_size,
runs,
wrap_width,
parent_view_id: self.parent_view_id(),
} as &dyn AsCacheKeyRef;
let current_frame = self.current_frame_wrapped.upgradable_read();
if let Some(layout) = current_frame.get(key) {
let current_frame = self.current_frame.upgradable_read();
if let Some(layout) = current_frame.wrapped_lines.get(key) {
return layout.clone();
}
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
if let Some((key, layout)) = self.previous_frame_wrapped.lock().remove_entry(key) {
current_frame.insert(key, 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);
current_frame
.wrapped_lines
.insert(key.clone(), layout.clone());
current_frame.used_wrapped_lines.push(key);
layout
} else {
drop(current_frame);
let unwrapped_layout = self.layout_line(text, font_size, runs);
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
@ -372,14 +386,19 @@ impl LineLayoutCache {
wrap_boundaries,
wrap_width,
});
let key = CacheKey {
let key = Arc::new(CacheKey {
text: text.into(),
font_size,
runs: SmallVec::from(runs),
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
}
}
@ -390,28 +409,28 @@ impl LineLayoutCache {
font_size,
runs,
wrap_width: None,
parent_view_id: self.parent_view_id(),
} as &dyn AsCacheKeyRef;
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();
}
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
if let Some((key, layout)) = self.previous_frame.lock().remove_entry(key) {
current_frame.insert(key, layout.clone());
if let Some((key, layout)) = self.previous_frame.lock().lines.remove_entry(key) {
current_frame.lines.insert(key.clone(), layout.clone());
current_frame.used_lines.push(key);
layout
} else {
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(),
font_size,
runs: SmallVec::from(runs),
wrap_width: None,
parent_view_id: self.parent_view_id(),
};
current_frame.insert(key, layout.clone());
});
current_frame.lines.insert(key.clone(), layout.clone());
current_frame.used_lines.push(key);
layout
}
}
@ -428,13 +447,12 @@ trait AsCacheKeyRef {
fn as_cache_key_ref(&self) -> CacheKeyRef;
}
#[derive(Debug, Eq)]
#[derive(Clone, Debug, Eq)]
struct CacheKey {
text: String,
font_size: Pixels,
runs: SmallVec<[FontRun; 1]>,
wrap_width: Option<Pixels>,
parent_view_id: Option<EntityId>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
@ -443,7 +461,6 @@ struct CacheKeyRef<'a> {
font_size: Pixels,
runs: &'a [FontRun],
wrap_width: Option<Pixels>,
parent_view_id: Option<EntityId>,
}
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
@ -467,7 +484,6 @@ impl AsCacheKeyRef for CacheKey {
font_size: self.font_size,
runs: self.runs.as_slice(),
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) {
self as &dyn AsCacheKeyRef
self.as_ref() as &dyn AsCacheKeyRef
}
}

View File

@ -1,14 +1,16 @@
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,
FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style,
TextStyle, ViewContext, VisualContext, WeakModel,
FocusableView, IntoElement, LayoutId, Model, PaintIndex, Pixels, Render, Style,
StyleRefinement, TextStyle, ViewContext, VisualContext, WeakModel,
};
use anyhow::{Context, Result};
use refineable::Refineable;
use std::{
any::{type_name, TypeId},
fmt,
hash::{Hash, Hasher},
ops::Range,
};
/// 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> {}
#[doc(hidden)]
pub struct AnyViewState {
root_style: Style,
next_stacking_order_id: u16,
cache_key: Option<ViewCacheKey>,
element: Option<AnyElement>,
struct AnyViewState {
after_layout_range: Range<AfterLayoutIndex>,
paint_range: Range<PaintIndex>,
cache_key: ViewCacheKey,
}
#[derive(Default)]
struct ViewCacheKey {
bounds: Bounds<Pixels>,
stacking_order: StackingOrder,
content_mask: ContentMask<Pixels>,
text_style: TextStyle,
}
@ -90,22 +90,39 @@ impl<V: 'static> View<V> {
}
impl<V: Render> Element for View<V> {
type State = Option<AnyElement>;
type BeforeLayout = AnyElement;
type AfterLayout = ();
fn request_layout(
&mut self,
_state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
cx.with_view_id(self.entity_id(), |cx| {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
let layout_id = element.request_layout(cx);
(layout_id, Some(element))
let layout_id = element.before_layout(cx);
(layout_id, element)
})
}
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx));
fn after_layout(
&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)]
pub struct AnyView {
model: AnyModel,
pub(crate) request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
cache: bool,
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
cached_style: Option<StyleRefinement>,
}
impl AnyView {
/// 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.
/// The one exception is when [WindowContext::refresh] is called, in which case caching is ignored.
pub fn cached(mut self) -> Self {
self.cache = true;
pub fn cached(mut self, style: StyleRefinement) -> Self {
self.cached_style = Some(style);
self
}
@ -220,7 +237,7 @@ impl AnyView {
pub fn downgrade(&self) -> AnyWeakView {
AnyWeakView {
model: self.model.downgrade(),
layout: self.request_layout,
render: self.render,
}
}
@ -231,8 +248,8 @@ impl AnyView {
Ok(model) => Ok(View { model }),
Err(model) => Err(Self {
model,
request_layout: self.request_layout,
cache: self.cache,
render: self.render,
cached_style: self.cached_style,
}),
}
}
@ -246,113 +263,134 @@ impl AnyView {
pub fn entity_id(&self) -> EntityId {
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 {
fn from(value: View<V>) -> Self {
AnyView {
model: value.model.into_any(),
request_layout: any_view::request_layout::<V>,
cache: false,
render: any_view::render::<V>,
cached_style: None,
}
}
}
impl Element for AnyView {
type State = AnyViewState;
type BeforeLayout = Option<AnyElement>;
type AfterLayout = Option<AnyElement>;
fn request_layout(
&mut self,
state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
cx.with_view_id(self.entity_id(), |cx| {
if self.cache
&& !cx.window.dirty_views.contains(&self.entity_id())
&& !cx.window.refreshing
{
if let Some(state) = state {
let layout_id = cx.request_layout(&state.root_style, None);
return (layout_id, state);
}
}
let (layout_id, element) = (self.request_layout)(self, cx);
let root_style = cx.layout_style(layout_id).unwrap().clone();
let state = AnyViewState {
root_style,
next_stacking_order_id: 0,
cache_key: None,
element: Some(element),
};
(layout_id, state)
})
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 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;
}
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> Option<AnyElement> {
cx.set_view_id(self.entity_id());
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();
if let Some(cache_key) = state.cache_key.as_mut() {
if cache_key.bounds == bounds
&& cache_key.content_mask == cx.content_mask()
&& cache_key.stacking_order == *cx.stacking_order()
&& cache_key.text_style == cx.text_style()
{
cx.reuse_view(state.next_stacking_order_id);
return;
}
}
let content_mask = cx.content_mask();
let text_style = cx.text_style();
if let Some(mut element) = state.element.take() {
element.paint(cx);
} else {
let mut element = (self.request_layout)(self, cx).1;
element.draw(bounds.origin, bounds.size.into(), cx);
}
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.refreshing
{
let after_layout_start = cx.after_layout_index();
cx.reuse_after_layout(element_state.after_layout_range.clone());
let after_layout_end = cx.after_layout_index();
element_state.after_layout_range = after_layout_start..after_layout_end;
return (None, Some(element_state));
}
}
state.next_stacking_order_id = cx
.window
.next_frame
.next_stacking_order_ids
.last()
.copied()
.unwrap();
state.cache_key = Some(ViewCacheKey {
bounds,
stacking_order: cx.stacking_order().clone(),
content_mask: cx.content_mask(),
text_style: cx.text_style(),
});
})
let after_layout_start = cx.after_layout_index();
let mut element = (self.render)(self, cx);
element.layout(bounds.origin, bounds.size.into(), cx);
let after_layout_end = cx.after_layout_index();
(
Some(element),
Some(AnyViewState {
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>,
_: &mut Self::BeforeLayout,
element: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
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();
let paint_start = cx.paint_index();
if let Some(element) = element {
element.paint(cx);
} else {
cx.reuse_paint(element_state.paint_range.clone());
}
let paint_end = cx.paint_index();
element_state.paint_range = paint_start..paint_end;
((), Some(element_state))
},
)
} else {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
element.as_mut().unwrap().paint(cx);
})
}
}
}
impl<V: 'static + Render> IntoElement for 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 {
self
}
@ -361,10 +399,6 @@ impl<V: 'static + Render> IntoElement for View<V> {
impl IntoElement for AnyView {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
Some(ElementId::from_entity_id(self.model.entity_id))
}
fn into_element(self) -> Self::Element {
self
}
@ -373,7 +407,7 @@ impl IntoElement for AnyView {
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
pub struct AnyWeakView {
model: AnyWeakModel,
layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
}
impl AnyWeakView {
@ -382,8 +416,8 @@ impl AnyWeakView {
let model = self.model.upgrade()?;
Some(AnyView {
model,
request_layout: self.layout,
cache: false,
render: self.render,
cached_style: None,
})
}
}
@ -392,7 +426,7 @@ impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
fn from(view: WeakView<V>) -> Self {
Self {
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 {
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,
cx: &mut ElementContext,
) -> (LayoutId, AnyElement) {
) -> AnyElement {
let view = view.clone().downcast::<V>().unwrap();
let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element());
let layout_id = element.request_layout(cx);
(layout_id, element)
view.update(cx, |view, cx| view.render(cx).into_any_element())
}
}

View File

@ -1,12 +1,12 @@
use crate::{
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext,
AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId,
DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
px, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, Bounds,
Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId,
Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId,
Hsla, KeyBinding, KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model,
ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels,
SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle,
TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
WindowOptions, WindowTextSystem,
};
use anyhow::{anyhow, Context as _, Result};
@ -14,6 +14,7 @@ use collections::FxHashSet;
use derive_more::{Deref, DerefMut};
use futures::channel::oneshot;
use parking_lot::RwLock;
use refineable::Refineable;
use slotmap::SlotMap;
use smallvec::SmallVec;
use std::{
@ -40,26 +41,6 @@ mod prompts;
pub use element_cx::*;
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.
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
pub enum DispatchPhase {
@ -258,8 +239,10 @@ pub struct Window {
layout_engine: Option<TaffyLayoutEngine>,
pub(crate) root_view: Option<AnyView>,
pub(crate) element_id_stack: GlobalElementId,
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
pub(crate) rendered_frame: Frame,
pub(crate) next_frame: Frame,
pub(crate) next_hitbox_id: HitboxId,
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
pub(crate) dirty_views: FxHashSet<EntityId>,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
@ -267,6 +250,7 @@ pub struct Window {
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
default_prevented: bool,
mouse_position: Point<Pixels>,
mouse_hit_test: HitTest,
modifiers: Modifiers,
scale_factor: f32,
bounds: WindowBounds,
@ -278,7 +262,7 @@ pub struct Window {
pub(crate) needs_present: Rc<Cell<bool>>,
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
pub(crate) refreshing: bool,
pub(crate) drawing: bool,
pub(crate) draw_phase: DrawPhase,
activation_observers: SubscriberSet<(), AnyObserver>,
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
@ -286,6 +270,14 @@ pub struct Window {
prompt: Option<RenderablePromptHandle>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum DrawPhase {
None,
Layout,
Paint,
Focus,
}
#[derive(Default, Debug)]
struct PendingInput {
keystrokes: SmallVec<[Keystroke; 1]>,
@ -319,7 +311,6 @@ impl PendingInput {
pub(crate) struct ElementStateBox {
pub(crate) inner: Box<dyn Any>,
pub(crate) parent_view_id: EntityId,
#[cfg(debug_assertions)]
pub(crate) type_name: &'static str,
}
@ -452,15 +443,18 @@ impl Window {
layout_engine: Some(TaffyLayoutEngine::new()),
root_view: None,
element_id_stack: GlobalElementId::default(),
text_style_stack: Vec::new(),
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_callbacks,
next_hitbox_id: HitboxId::default(),
dirty_views: FxHashSet::default(),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
focus_listeners: SubscriberSet::new(),
focus_lost_listeners: SubscriberSet::new(),
default_prevented: true,
mouse_position,
mouse_hit_test: HitTest::default(),
modifiers,
scale_factor,
bounds,
@ -472,7 +466,7 @@ impl Window {
needs_present,
last_input_timestamp,
refreshing: false,
drawing: false,
draw_phase: DrawPhase::None,
activation_observers: SubscriberSet::new(),
focus: None,
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.
pub fn refresh(&mut self) {
if !self.window.drawing {
if self.window.draw_phase == DrawPhase::None {
self.window.refreshing = true;
self.window.dirty.set(true);
}
@ -592,22 +586,39 @@ impl<'a> WindowContext<'a> {
&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.
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
let focus_handle = self.focused();
self.defer(move |cx| {
let node_id = focus_handle
.and_then(|handle| {
cx.window
.rendered_frame
.dispatch_tree
.focusable_node_id(handle.id)
})
.unwrap_or_else(|| cx.window.rendered_frame.dispatch_tree.root_node_id());
let window = self.window.handle;
self.app.defer(move |cx| {
cx.propagate_event = true;
cx.dispatch_action_on_node(node_id, action);
window
.update(cx, |_, cx| {
let node_id = focus_handle
.and_then(|handle| {
cx.window
.rendered_frame
.dispatch_tree
.focusable_node_id(handle.id)
})
.unwrap_or_else(|| cx.window.rendered_frame.dispatch_tree.root_node_id());
cx.dispatch_action_on_node(node_id, action.as_ref());
})
.log_err();
if cx.propagate_event {
cx.dispatch_global_action(action.as_ref());
}
})
}
@ -862,171 +873,21 @@ impl<'a> WindowContext<'a> {
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
/// the contents of the new [Scene], use [present].
#[profiling::function]
pub fn draw(&mut self) {
self.window.dirty.set(false);
self.window.drawing = true;
if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut()
{
let input_handler = self.window.platform_window.take_input_handler();
requested_handler.handler = input_handler;
// Restore the previously-used input handler.
if let Some(input_handler) = self.window.platform_window.take_input_handler() {
self.window
.rendered_frame
.input_handlers
.push(Some(input_handler));
}
let root_view = self.window.root_view.take().unwrap();
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.with_element_context(|cx| cx.draw_roots());
self.window.dirty_views.clear();
self.window
@ -1038,26 +899,22 @@ impl<'a> WindowContext<'a> {
);
self.window.next_frame.focus = self.window.focus;
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.
let cursor_style_request = self.window.next_frame.requested_cursor_style.take();
if self.is_window_active() {
let cursor_style =
cursor_style_request.map_or(CursorStyle::Arrow, |request| request.style);
let cursor_style = self.compute_cursor_style().unwrap_or(CursorStyle::Arrow);
self.platform.set_cursor_style(cursor_style);
}
// 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(handler) = requested_input.handler.take() {
self.window.platform_window.set_input_handler(handler);
}
if let Some(input_handler) = self.window.next_frame.input_handlers.pop() {
self.window
.platform_window
.set_input_handler(input_handler.unwrap());
}
self.window.layout_engine.as_mut().unwrap().clear();
self.text_system()
.finish_frame(&self.window.next_frame.reused_views);
self.text_system().finish_frame();
self.window
.next_frame
.finish(&mut self.window.rendered_frame);
@ -1069,6 +926,7 @@ impl<'a> WindowContext<'a> {
element_arena.clear();
});
self.window.draw_phase = DrawPhase::Focus;
let previous_focus_path = self.window.rendered_frame.focus_path();
let previous_window_active = self.window.rendered_frame.window_active;
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));
}
self.window.refreshing = false;
self.window.drawing = false;
self.window.draw_phase = DrawPhase::None;
self.window.needs_present.set(true);
}
@ -1117,6 +975,18 @@ impl<'a> WindowContext<'a> {
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.
/// You can create a keystroke with Keystroke::parse("").
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) {
if let Some(mut handlers) = self
.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));
self.window.mouse_hit_test = self.window.rendered_frame.hit_test(self.mouse_position());
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
// special purposes, such as detecting events outside of a given Bounds.
for (_, _, handler) in &mut handlers {
self.with_element_context(|cx| {
handler(event, DispatchPhase::Capture, cx);
});
if !self.app.propagate_event {
for listener in &mut mouse_listeners {
let listener = listener.as_mut().unwrap();
listener(event, DispatchPhase::Capture, cx);
if !cx.app.propagate_event {
break;
}
}
// Bubble phase, where most normal handlers do their work.
if self.app.propagate_event {
for (_, _, handler) in handlers.iter_mut().rev() {
self.with_element_context(|cx| {
handler(event, DispatchPhase::Bubble, cx);
});
if !self.app.propagate_event {
if cx.app.propagate_event {
for listener in mouse_listeners.iter_mut().rev() {
let listener = listener.as_mut().unwrap();
listener(event, DispatchPhase::Bubble, cx);
if !cx.app.propagate_event {
break;
}
}
}
self.window
.rendered_frame
.mouse_listeners
.insert(event.type_id(), handlers);
}
});
self.window.rendered_frame.mouse_listeners = mouse_listeners;
if self.app.propagate_event && self.has_active_drag() {
if event.is::<MouseMoveEvent>() {
@ -1357,6 +1216,7 @@ impl<'a> WindowContext<'a> {
})
.log_err();
}));
self.window.pending_input = Some(currently_pending);
self.propagate_event = false;
@ -1376,7 +1236,7 @@ impl<'a> WindowContext<'a> {
self.propagate_event = true;
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 {
self.dispatch_keystroke_observers(event, Some(binding.action));
return;
@ -1454,7 +1314,7 @@ impl<'a> WindowContext<'a> {
self.propagate_event = true;
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 {
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
.window
.rendered_frame
@ -1628,10 +1488,20 @@ impl<'a> WindowContext<'a> {
})
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
self.window
let mut actions = self
.window
.rendered_frame
.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.
@ -1697,15 +1567,6 @@ impl<'a> WindowContext<'a> {
.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
/// is determined by the first parameter of the given listener. When the next frame is rendered
/// 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.app.push_effect(Effect::Notify {
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 {
type Error = anyhow::Error;

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,9 @@ use std::ops::Deref;
use futures::channel::oneshot;
use crate::{
div, opaque_grey, white, AnyElement, AnyView, ElementContext, EventEmitter, FocusHandle,
FocusableView, InteractiveElement, IntoElement, ParentElement, PromptLevel, Render,
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext,
div, opaque_grey, white, AnyView, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
IntoElement, ParentElement, PromptLevel, Render, StatefulInteractiveElement, Styled, View,
ViewContext, VisualContext, WindowContext,
};
/// 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.
pub struct RenderablePromptHandle {
view: Box<dyn PromptViewHandle>,
}
impl RenderablePromptHandle {
pub(crate) fn paint(&mut self, _: &mut ElementContext) -> AnyElement {
self.view.any_view().into_any_element()
}
pub(crate) view: Box<dyn PromptViewHandle>,
}
/// Use this function in conjunction with [AppContext::set_prompt_renderer] to force
@ -146,7 +140,6 @@ impl Render for FallbackPromptRenderer {
div()
.size_full()
.z_index(u16::MAX)
.child(
div()
.size_full()
@ -184,7 +177,7 @@ impl FocusableView for FallbackPromptRenderer {
}
}
trait PromptViewHandle {
pub(crate) trait PromptViewHandle {
fn any_view(&self) -> AnyView;
}

View File

@ -13,10 +13,6 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
{
type Element = gpui::Component<Self>;
fn element_id(&self) -> Option<gpui::ElementId> {
None
}
fn into_element(self) -> Self::Element {
gpui::Component::new(self)
}

View File

@ -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
// without escalating.
smol::fs::remove_file(link_path).await.log_err();
// todo(windows)
// todo("windows")
#[cfg(not(windows))]
{
if smol::fs::unix::symlink(&cli_path, link_path)

View File

@ -807,7 +807,7 @@ impl Render for LspLogToolbarItemView {
.justify_between()
.child(Label::new(RPC_MESSAGES))
.child(
div().z_index(120).child(
div().child(
Checkbox::new(
ix,
if row.rpc_trace_enabled {

View File

@ -1,9 +1,9 @@
use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId};
use gpui::{
actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div,
EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model,
MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
actions, canvas, div, rems, uniform_list, AnyElement, AppContext, Div, EventEmitter,
FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, MouseButton,
MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
};
use language::{Buffer, OwnedSyntaxLayer};
use std::{mem, ops::Range};
@ -281,7 +281,7 @@ impl Render for SyntaxTreeView {
.and_then(|buffer| buffer.active_layer.as_ref())
{
let layer = layer.clone();
let list = uniform_list(
let mut list = uniform_list(
cx.view().clone(),
"SyntaxTreeView",
layer.node().descendant_count(),
@ -360,16 +360,16 @@ impl Render for SyntaxTreeView {
)
.size_full()
.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(
canvas(move |bounds, cx| {
list.into_any_element().draw(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
)
})
canvas(
move |bounds, cx| {
list.layout(bounds.origin, bounds.size.into(), cx);
list
},
|_, mut list, cx| list.paint(cx),
)
.size_full(),
);
}

View File

@ -77,7 +77,7 @@ impl super::LspAdapter for OmniSharpAdapter {
archive.unpack(container_dir).await?;
}
// todo(windows)
// todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(

View File

@ -350,7 +350,7 @@ impl LspAdapter for NextLspAdapter {
}
futures::io::copy(response.body_mut(), &mut file).await?;
// todo(windows)
// todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(

View File

@ -79,7 +79,7 @@ impl super::LspAdapter for LuaLspAdapter {
archive.unpack(container_dir).await?;
}
// todo(windows)
// todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(

View File

@ -70,7 +70,7 @@ impl LspAdapter for RustLspAdapter {
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
let mut file = File::create(&destination_path).await?;
futures::io::copy(decompressed_bytes, &mut file).await?;
// todo(windows)
// todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(

View File

@ -68,7 +68,7 @@ impl LspAdapter for TaploLspAdapter {
futures::io::copy(decompressed_bytes, &mut file).await?;
// todo(windows)
// todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(

View File

@ -73,7 +73,7 @@ impl LspAdapter for ZlsAdapter {
archive.unpack(container_dir).await?;
}
// todo(windows)
// todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(

View File

@ -1378,7 +1378,7 @@ impl ProjectPanel {
let is_selected = self
.selection
.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
.git_status

View File

@ -7,7 +7,6 @@ mod picker;
mod scroll;
mod text;
mod viewport_units;
mod z_index;
pub use auto_height_editor::*;
pub use cursor::*;
@ -18,4 +17,3 @@ pub use picker::*;
pub use scroll::*;
pub use text::*;
pub use viewport_units::*;
pub use z_index::*;

View File

@ -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 }
}
}

View File

@ -35,7 +35,6 @@ pub enum ComponentStory {
ToggleButton,
Text,
ViewportUnits,
ZIndex,
Picker,
}
@ -67,7 +66,6 @@ impl ComponentStory {
Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(),
Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(),
Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(),
Self::ZIndex => cx.new_view(|_| ZIndexStory).into(),
Self::Picker => PickerStory::new(cx).into(),
}
}

View File

@ -404,7 +404,7 @@ impl TerminalBuilder {
#[cfg(unix)]
let (fd, shell_pid) = (pty.file().as_raw_fd(), pty.child().id());
// todo(windows)
// todo("windows")
#[cfg(windows)]
let (fd, shell_pid) = {
let child = pty.child_watcher();

View File

@ -1,11 +1,11 @@
use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine};
use gpui::{
div, fill, point, px, relative, AnyElement, AvailableSpace, Bounds, DispatchPhase, Element,
ElementContext, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla,
InputHandler, InteractiveBounds, InteractiveElement, InteractiveElementState, Interactivity,
IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent,
Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun,
TextStyle, UnderlineStyle, WeakView, WhiteSpace, WindowContext, WindowTextSystem,
div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, ElementContext,
FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler,
InteractiveElement, Interactivity, IntoElement, LayoutId, Model, ModelContext,
ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine,
StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UnderlineStyle,
WeakView, WhiteSpace, WindowContext, WindowTextSystem,
};
use itertools::Itertools;
use language::CursorShape;
@ -15,10 +15,13 @@ use terminal::{
grid::Dimensions,
index::Point as AlacPoint,
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,
IndexedCell, Terminal, TerminalContent, TerminalSize,
HoveredWord, IndexedCell, Terminal, TerminalContent, TerminalSize,
};
use theme::{ActiveTheme, Theme, ThemeSettings};
use ui::Tooltip;
@ -29,16 +32,18 @@ use std::{fmt::Debug, ops::RangeInclusive};
/// The information generated during layout that is necessary for painting.
pub struct LayoutState {
hitbox: Hitbox,
cells: Vec<LayoutCell>,
rects: Vec<LayoutRect>,
relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,
cursor: Option<Cursor>,
cursor: Option<CursorLayout>,
background_color: Hsla,
dimensions: TerminalSize,
mode: TermMode,
display_offset: usize,
hyperlink_tooltip: Option<AnyElement>,
gutter: Pixels,
last_hovered_word: Option<HoveredWord>,
}
/// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.
@ -392,216 +397,6 @@ impl TerminalElement {
result
}
fn compute_layout(&self, bounds: Bounds<gpui::Pixels>, cx: &mut ElementContext) -> LayoutState {
let settings = ThemeSettings::get_global(cx).clone();
let buffer_font_size = settings.buffer_font_size(cx);
let terminal_settings = TerminalSettings::get_global(cx);
let font_family = terminal_settings
.font_family
.as_ref()
.map(|string| string.clone().into())
.unwrap_or(settings.buffer_font.family);
let font_features = terminal_settings
.font_features
.unwrap_or(settings.buffer_font.features);
let line_height = terminal_settings.line_height.value();
let font_size = terminal_settings.font_size;
let font_size =
font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx));
let theme = cx.theme().clone();
let link_style = HighlightStyle {
color: Some(theme.colors().link_text_hover),
font_weight: None,
font_style: None,
background_color: None,
underline: Some(UnderlineStyle {
thickness: px(1.0),
color: Some(theme.colors().link_text_hover),
wavy: false,
}),
strikethrough: None,
fade_out: None,
};
let text_style = TextStyle {
font_family,
font_features,
font_size: font_size.into(),
font_style: FontStyle::Normal,
line_height: line_height.into(),
background_color: None,
white_space: WhiteSpace::Normal,
// These are going to be overridden per-cell
underline: None,
strikethrough: None,
color: theme.colors().text,
font_weight: FontWeight::NORMAL,
};
let text_system = cx.text_system();
let player_color = theme.players().local();
let match_color = theme.colors().search_match_background;
let gutter;
let dimensions = {
let rem_size = cx.rem_size();
let font_pixels = text_style.font_size.to_pixels(rem_size);
let line_height = font_pixels * line_height.to_pixels(rem_size);
let font_id = cx.text_system().resolve_font(&text_style.font());
let cell_width = text_system
.advance(font_id, font_pixels, 'm')
.unwrap()
.width;
gutter = cell_width;
let mut size = bounds.size;
size.width -= gutter;
// https://github.com/zed-industries/zed/issues/2750
// if the terminal is one column wide, rendering 🦀
// causes alacritty to misbehave.
if size.width < cell_width * 2.0 {
size.width = cell_width * 2.0;
}
TerminalSize::new(line_height, cell_width, size)
};
let search_matches = self.terminal.read(cx).matches.clone();
let background_color = theme.colors().terminal_background;
let last_hovered_word = self.terminal.update(cx, |terminal, cx| {
terminal.set_size(dimensions);
terminal.sync(cx);
if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() {
terminal.last_content.last_hovered_word.clone()
} else {
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| {
div()
.size_full()
.id("terminal-element")
.tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
.into_any_element()
});
let TerminalContent {
cells,
mode,
display_offset,
cursor_char,
selection,
cursor,
..
} = &self.terminal.read(cx).last_content;
// searches, highlights to a single range representations
let mut relative_highlighted_ranges = Vec::new();
for search_match in search_matches {
relative_highlighted_ranges.push((search_match, match_color))
}
if let Some(selection) = selection {
relative_highlighted_ranges
.push((selection.start..=selection.end, player_color.selection));
}
// then have that representation be converted to the appropriate highlight data structure
let (cells, rects) = TerminalElement::layout_grid(
cells,
&text_style,
&cx.text_system(),
last_hovered_word
.as_ref()
.map(|last_hovered_word| (link_style, &last_hovered_word.word_match)),
cx,
);
// Layout cursor. Rectangle is used for IME, so we should lay it out even
// if we don't end up showing it.
let cursor = if let AlacCursorShape::Hidden = cursor.shape {
None
} else {
let cursor_point = DisplayCursor::from(cursor.point, *display_offset);
let cursor_text = {
let str_trxt = cursor_char.to_string();
let len = str_trxt.len();
cx.text_system()
.shape_line(
str_trxt.into(),
text_style.font_size.to_pixels(cx.rem_size()),
&[TextRun {
len,
font: text_style.font(),
color: theme.colors().terminal_background,
background_color: None,
underline: Default::default(),
strikethrough: None,
}],
)
.unwrap()
};
let focused = self.focused;
TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map(
move |(cursor_position, block_width)| {
let (shape, text) = match cursor.shape {
AlacCursorShape::Block if !focused => (CursorShape::Hollow, None),
AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)),
AlacCursorShape::Underline => (CursorShape::Underscore, None),
AlacCursorShape::Beam => (CursorShape::Bar, None),
AlacCursorShape::HollowBlock => (CursorShape::Hollow, None),
//This case is handled in the if wrapping the whole cursor layout
AlacCursorShape::Hidden => unreachable!(),
};
Cursor::new(
cursor_position,
block_width,
dimensions.line_height,
theme.players().local().cursor,
shape,
text,
None,
)
},
)
};
LayoutState {
cells,
cursor,
background_color,
dimensions,
rects,
relative_highlighted_ranges,
mode: *mode,
display_offset: *display_offset,
hyperlink_tooltip,
gutter,
}
}
fn generic_button_handler<E>(
connection: Model<Terminal>,
origin: Point<Pixels>,
@ -622,15 +417,11 @@ impl TerminalElement {
&mut self,
origin: Point<Pixels>,
mode: TermMode,
bounds: Bounds<Pixels>,
hitbox: &Hitbox,
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();
@ -647,27 +438,28 @@ impl TerminalElement {
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 visibly_contains = interactive_bounds.visibly_contains(&e.position, cx);
let hovered = hitbox.is_hovered(cx);
terminal.update(cx, |terminal, cx| {
if !terminal.selection_started() {
if visibly_contains {
terminal.mouse_drag(e, origin, bounds);
if hovered {
terminal.mouse_drag(e, origin, hitbox.bounds);
cx.notify();
}
} else {
terminal.mouse_drag(e, origin, bounds);
terminal.mouse_drag(e, origin, hitbox.bounds);
cx.notify();
}
})
}
if interactive_bounds.visibly_contains(&e.position, cx) {
if hitbox.is_hovered(cx) {
terminal.update(cx, |terminal, cx| {
terminal.mouse_move(&e, origin);
cx.notify();
@ -749,34 +541,244 @@ impl TerminalElement {
}
impl Element for TerminalElement {
type State = InteractiveElementState;
type BeforeLayout = ();
type AfterLayout = LayoutState;
fn request_layout(
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,
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);
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();
layout_id
let buffer_font_size = settings.buffer_font_size(cx);
let terminal_settings = TerminalSettings::get_global(cx);
let font_family = terminal_settings
.font_family
.as_ref()
.map(|string| string.clone().into())
.unwrap_or(settings.buffer_font.family);
let font_features = terminal_settings
.font_features
.unwrap_or(settings.buffer_font.features);
let line_height = terminal_settings.line_height.value();
let font_size = terminal_settings.font_size;
let font_size =
font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx));
let theme = cx.theme().clone();
let link_style = HighlightStyle {
color: Some(theme.colors().link_text_hover),
font_weight: None,
font_style: None,
background_color: None,
underline: Some(UnderlineStyle {
thickness: px(1.0),
color: Some(theme.colors().link_text_hover),
wavy: false,
}),
strikethrough: None,
fade_out: None,
};
let text_style = TextStyle {
font_family,
font_features,
font_size: font_size.into(),
font_style: FontStyle::Normal,
line_height: line_height.into(),
background_color: None,
white_space: WhiteSpace::Normal,
// These are going to be overridden per-cell
underline: None,
strikethrough: None,
color: theme.colors().text,
font_weight: FontWeight::NORMAL,
};
let text_system = cx.text_system();
let player_color = theme.players().local();
let match_color = theme.colors().search_match_background;
let gutter;
let dimensions = {
let rem_size = cx.rem_size();
let font_pixels = text_style.font_size.to_pixels(rem_size);
let line_height = font_pixels * line_height.to_pixels(rem_size);
let font_id = cx.text_system().resolve_font(&text_style.font());
let cell_width = text_system
.advance(font_id, font_pixels, 'm')
.unwrap()
.width;
gutter = cell_width;
let mut size = bounds.size;
size.width -= gutter;
// https://github.com/zed-industries/zed/issues/2750
// if the terminal is one column wide, rendering 🦀
// causes alacritty to misbehave.
if size.width < cell_width * 2.0 {
size.width = cell_width * 2.0;
}
TerminalSize::new(line_height, cell_width, size)
};
let search_matches = self.terminal.read(cx).matches.clone();
let background_color = theme.colors().terminal_background;
let last_hovered_word = self.terminal.update(cx, |terminal, cx| {
terminal.set_size(dimensions);
terminal.sync(cx);
if self.can_navigate_to_selected_word
&& terminal.can_navigate_to_selected_word()
{
terminal.last_content.last_hovered_word.clone()
} else {
None
}
});
(layout_id, interactive_state)
let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| {
let offset = bounds.origin + Point::new(gutter, px(0.));
let mut element = div()
.size_full()
.id("terminal-element")
.tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
.into_any_element();
element.layout(offset, bounds.size.into(), cx);
element
});
let TerminalContent {
cells,
mode,
display_offset,
cursor_char,
selection,
cursor,
..
} = &self.terminal.read(cx).last_content;
// searches, highlights to a single range representations
let mut relative_highlighted_ranges = Vec::new();
for search_match in search_matches {
relative_highlighted_ranges.push((search_match, match_color))
}
if let Some(selection) = selection {
relative_highlighted_ranges
.push((selection.start..=selection.end, player_color.selection));
}
// then have that representation be converted to the appropriate highlight data structure
let (cells, rects) = TerminalElement::layout_grid(
cells,
&text_style,
&cx.text_system(),
last_hovered_word
.as_ref()
.map(|last_hovered_word| (link_style, &last_hovered_word.word_match)),
cx,
);
// Layout cursor. Rectangle is used for IME, so we should lay it out even
// if we don't end up showing it.
let cursor = if let AlacCursorShape::Hidden = cursor.shape {
None
} else {
let cursor_point = DisplayCursor::from(cursor.point, *display_offset);
let cursor_text = {
let str_trxt = cursor_char.to_string();
let len = str_trxt.len();
cx.text_system()
.shape_line(
str_trxt.into(),
text_style.font_size.to_pixels(cx.rem_size()),
&[TextRun {
len,
font: text_style.font(),
color: theme.colors().terminal_background,
background_color: None,
underline: Default::default(),
strikethrough: None,
}],
)
.unwrap()
};
let focused = self.focused;
TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map(
move |(cursor_position, block_width)| {
let (shape, text) = match cursor.shape {
AlacCursorShape::Block if !focused => (CursorShape::Hollow, None),
AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)),
AlacCursorShape::Underline => (CursorShape::Underscore, None),
AlacCursorShape::Beam => (CursorShape::Bar, None),
AlacCursorShape::HollowBlock => (CursorShape::Hollow, None),
//This case is handled in the if wrapping the whole cursor layout
AlacCursorShape::Hidden => unreachable!(),
};
CursorLayout::new(
cursor_position,
block_width,
dimensions.line_height,
theme.players().local().cursor,
shape,
text,
)
},
)
};
LayoutState {
hitbox,
cells,
cursor,
background_color,
dimensions,
rects,
relative_highlighted_ranges,
mode: *mode,
display_offset: *display_offset,
hyperlink_tooltip,
gutter,
last_hovered_word,
}
})
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::State,
_: &mut Self::BeforeLayout,
layout: &mut Self::AfterLayout,
cx: &mut ElementContext<'_>,
) {
let mut layout = self.compute_layout(bounds, cx);
cx.paint_quad(fill(bounds, layout.background_color));
let origin = bounds.origin + Point::new(layout.gutter, px(0.));
@ -789,10 +791,17 @@ impl Element for TerminalElement {
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
.paint(bounds, bounds.size, state, cx, |_, _, cx| {
.paint(bounds, Some(&layout.hitbox), cx, |_, cx| {
cx.handle_input(&self.focus, terminal_input_handler);
cx.on_key_event({
@ -815,42 +824,35 @@ impl Element for TerminalElement {
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)) =
to_highlighted_range_lines(relative_highlighted_range, &layout, origin)
{
if let Some((start_y, highlighted_range_lines)) =
to_highlighted_range_lines(relative_highlighted_range, &layout, origin)
{
let hr = HighlightedRange {
start_y, //Need to change this
line_height: layout.dimensions.line_height,
lines: highlighted_range_lines,
color: *color,
//Copied from editor. TODO: move to theme or something
corner_radius: 0.15 * layout.dimensions.line_height,
};
hr.paint(bounds, cx);
}
let hr = HighlightedRange {
start_y, //Need to change this
line_height: layout.dimensions.line_height,
lines: highlighted_range_lines,
color: *color,
//Copied from editor. TODO: move to theme or something
corner_radius: 0.15 * layout.dimensions.line_height,
};
hr.paint(bounds, cx);
}
});
cx.with_z_index(2, |cx| {
for cell in &layout.cells {
cell.paint(origin, &layout, bounds, cx);
}
});
if self.cursor_visible {
cx.with_z_index(3, |cx| {
if let Some(cursor) = &layout.cursor {
cursor.paint(origin, cx);
}
});
}
if let Some(mut element) = layout.hyperlink_tooltip.take() {
element.draw(origin, bounds.size.map(AvailableSpace::Definite), cx)
for cell in &layout.cells {
cell.paint(origin, &layout, bounds, cx);
}
if self.cursor_visible {
if let Some(mut cursor) = cursor {
cursor.paint(origin, cx);
}
}
if let Some(mut element) = hyperlink_tooltip {
element.paint(cx);
}
});
}
@ -859,10 +861,6 @@ impl Element for TerminalElement {
impl IntoElement for TerminalElement {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
Some("terminal".into())
}
fn into_element(self) -> Self::Element {
self
}

View File

@ -77,7 +77,6 @@ impl Render for PlayerStory {
.relative()
.neg_mx_1()
.rounded_full()
.z_index(3)
.border_2()
.border_color(player.background)
.size(px(28.))
@ -93,7 +92,6 @@ impl Render for PlayerStory {
.relative()
.neg_mx_1()
.rounded_full()
.z_index(2)
.border_2()
.border_color(player.background)
.size(px(28.))
@ -109,7 +107,6 @@ impl Render for PlayerStory {
.relative()
.neg_mx_1()
.rounded_full()
.z_index(1)
.border_2()
.border_color(player.background)
.size(px(28.))

View File

@ -122,9 +122,6 @@ impl RenderOnce for Avatar {
.size(image_size)
.bg(cx.theme().colors().ghost_element_background),
)
.children(
self.indicator
.map(|indicator| div().z_index(1).child(indicator)),
)
.children(self.indicator.map(|indicator| div().child(indicator)))
}
}

View File

@ -243,7 +243,7 @@ impl ContextMenuItem {
impl Render for ContextMenu {
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()
.min_w(px(200.))
.track_focus(&self.focus_handle)

View File

@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc};
use gpui::{
overlay, point, prelude::FluentBuilder, px, rems, AnchorCorner, AnyElement, Bounds,
DismissEvent, DispatchPhase, Element, ElementContext, ElementId, InteractiveBounds,
IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View,
VisualContext, WindowContext,
DismissEvent, DispatchPhase, Element, ElementContext, ElementId, HitboxId, IntoElement,
LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
WindowContext,
};
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`]
@ -123,114 +138,136 @@ 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_element: Option<AnyElement>,
child_bounds: Option<Bounds<Pixels>>,
menu_element: Option<AnyElement>,
menu: Rc<RefCell<Option<View<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) {
self.with_element_state(cx, |this, element_state, cx| {
let mut menu_layout_id = None;
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
let mut overlay = overlay().snap_to_window().anchor(this.anchor);
if let Some(child_bounds) = element_state.child_bounds {
overlay = overlay.position(
this.resolved_attach().corner(child_bounds) + this.resolved_offset(cx),
);
}
let mut element = overlay.child(menu.clone()).into_any();
menu_layout_id = Some(element.before_layout(cx));
element
});
let mut child_element = this.child_builder.take().map(|child_builder| {
(child_builder)(element_state.menu.clone(), this.menu_builder.clone())
});
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.before_layout(cx));
let layout_id = cx.request_layout(
&gpui::Style::default(),
menu_layout_id.into_iter().chain(child_layout_id),
);
(
layout_id,
PopoverMenuFrameState {
child_element,
child_layout_id,
menu_element,
},
)
})
}
fn after_layout(
&mut self,
element_state: Option<Self::State>,
_bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (gpui::LayoutId, Self::State) {
let mut menu_layout_id = None;
let (menu, child_bounds) = if let Some(element_state) = element_state {
(element_state.menu, element_state.child_bounds)
} else {
(Rc::default(), None)
};
let menu_element = menu.borrow_mut().as_mut().map(|menu| {
let mut overlay = overlay().snap_to_window().anchor(self.anchor);
if let Some(child_bounds) = child_bounds {
overlay = overlay.position(
self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx),
);
) -> 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);
}
let mut element = overlay.child(menu.clone()).into_any();
menu_layout_id = Some(element.request_layout(cx));
element
});
if let Some(menu) = before_layout.menu_element.as_mut() {
menu.after_layout(cx);
}
let mut child_element = self
.child_builder
.take()
.map(|child_builder| (child_builder)(menu.clone(), self.menu_builder.clone()));
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.request_layout(cx));
let layout_id = cx.request_layout(
&gpui::Style::default(),
menu_layout_id.into_iter().chain(child_layout_id),
);
(
layout_id,
PopoverMenuState {
menu,
child_element,
child_layout_id,
menu_element,
child_bounds,
},
)
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(
&mut self,
_: Bounds<gpui::Pixels>,
element_state: &mut Self::State,
before_layout: &mut Self::BeforeLayout,
child_hitbox: &mut Option<HitboxId>,
cx: &mut ElementContext,
) {
if let Some(mut child) = element_state.child_element.take() {
child.paint(cx);
}
if let Some(child_layout_id) = element_state.child_layout_id.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);
if let Some(child_bounds) = element_state.child_bounds {
let interactive_bounds = InteractiveBounds {
bounds: child_bounds,
stacking_order: cx.stacking_order().clone(),
};
// Mouse-downing outside the menu dismisses it, so we don't
// want a click on the toggle to re-open it.
cx.on_mouse_event(move |e: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble
&& interactive_bounds.visibly_contains(&e.position, cx)
{
cx.stop_propagation()
}
})
self.with_element_state(cx, |_this, _element_state, cx| {
if let Some(mut child) = before_layout.child_element.take() {
child.paint(cx);
}
}
if let Some(mut menu) = before_layout.menu_element.take() {
menu.paint(cx);
if let Some(child_hitbox) = *child_hitbox {
// Mouse-downing outside the menu dismisses it, so we don't
// want a click on the toggle to re-open it.
cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && child_hitbox.is_hovered(cx) {
cx.stop_propagation()
}
})
}
}
})
}
}
impl<M: ManagedView> IntoElement for PopoverMenu<M> {
type Element = Self;
fn element_id(&self) -> Option<gpui::ElementId> {
Some(self.id.clone())
}
fn into_element(self) -> Self::Element {
self
}

View File

@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc};
use gpui::{
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,
};
@ -37,6 +37,21 @@ impl<M: ManagedView> RightClickMenu<M> {
self.attach = Some(attach);
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`]
@ -50,140 +65,172 @@ 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>>>>,
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_element: Option<AnyElement>,
menu_element: Option<AnyElement>,
}
impl<M: ManagedView> Element for RightClickMenu<M> {
type State = MenuHandleState<M>;
type BeforeLayout = MenuHandleFrameState;
type AfterLayout = Hitbox;
fn request_layout(
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 menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
let mut overlay = overlay().snap_to_window();
if let Some(anchor) = this.anchor {
overlay = overlay.anchor(anchor);
}
overlay = overlay.position(*element_state.position.borrow());
let mut element = overlay.child(menu.clone()).into_any();
menu_layout_id = Some(element.before_layout(cx));
element
});
let mut child_element = this
.child_builder
.take()
.map(|child_builder| (child_builder)(element_state.menu.borrow().is_some()));
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.before_layout(cx));
let layout_id = cx.request_layout(
&gpui::Style::default(),
menu_layout_id.into_iter().chain(child_layout_id),
);
(
layout_id,
MenuHandleFrameState {
child_element,
child_layout_id,
menu_element,
},
)
})
}
fn after_layout(
&mut self,
element_state: Option<Self::State>,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
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())
};
) -> Hitbox {
cx.with_element_id(Some(self.id.clone()), |cx| {
let hitbox = cx.insert_hitbox(bounds, false);
let mut menu_layout_id = None;
let menu_element = menu.borrow_mut().as_mut().map(|menu| {
let mut overlay = overlay().snap_to_window();
if let Some(anchor) = self.anchor {
overlay = overlay.anchor(anchor);
if let Some(child) = before_layout.child_element.as_mut() {
child.after_layout(cx);
}
overlay = overlay.position(*position.borrow());
let mut element = overlay.child(menu.clone()).into_any();
menu_layout_id = Some(element.request_layout(cx));
element
});
if let Some(menu) = before_layout.menu_element.as_mut() {
menu.after_layout(cx);
}
let mut child_element = self
.child_builder
.take()
.map(|child_builder| (child_builder)(menu.borrow().is_some()));
let child_layout_id = child_element
.as_mut()
.map(|child_element| child_element.request_layout(cx));
let layout_id = cx.request_layout(
&gpui::Style::default(),
menu_layout_id.into_iter().chain(child_layout_id),
);
(
layout_id,
MenuHandleState {
menu,
position,
child_element,
child_layout_id,
menu_element,
},
)
hitbox
})
}
fn paint(
&mut self,
bounds: Bounds<gpui::Pixels>,
element_state: &mut Self::State,
_bounds: Bounds<gpui::Pixels>,
before_layout: &mut Self::BeforeLayout,
hitbox: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
if let Some(mut child) = element_state.child_element.take() {
child.paint(cx);
}
self.with_element_state(cx, |this, element_state, cx| {
if let Some(mut child) = before_layout.child_element.take() {
child.paint(cx);
}
if let Some(mut menu) = element_state.menu_element.take() {
menu.paint(cx);
return;
}
if let Some(mut menu) = before_layout.menu_element.take() {
menu.paint(cx);
return;
}
let Some(builder) = self.menu_builder.take() else {
return;
};
let menu = element_state.menu.clone();
let position = element_state.position.clone();
let attach = self.attach;
let child_layout_id = element_state.child_layout_id;
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
let Some(builder) = this.menu_builder.take() else {
return;
};
let interactive_bounds = InteractiveBounds {
bounds: bounds.intersect(&cx.content_mask().bounds),
stacking_order: cx.stacking_order().clone(),
};
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble
&& event.button == MouseButton::Right
&& interactive_bounds.visibly_contains(&event.position, cx)
{
cx.stop_propagation();
cx.prevent_default();
let attach = this.attach;
let menu = element_state.menu.clone();
let position = element_state.position.clone();
let child_layout_id = before_layout.child_layout_id;
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
let new_menu = (builder)(cx);
let menu2 = menu.clone();
let previous_focus_handle = cx.focused();
let hitbox_id = hitbox.id;
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble
&& event.button == MouseButton::Right
&& hitbox_id.is_hovered(cx)
{
cx.stop_propagation();
cx.prevent_default();
cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
if modal.focus_handle(cx).contains_focused(cx) {
if let Some(previous_focus_handle) = previous_focus_handle.as_ref() {
cx.focus(previous_focus_handle);
let new_menu = (builder)(cx);
let menu2 = menu.clone();
let previous_focus_handle = cx.focused();
cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
if modal.focus_handle(cx).contains_focused(cx) {
if let Some(previous_focus_handle) = previous_focus_handle.as_ref() {
cx.focus(previous_focus_handle);
}
}
*menu2.borrow_mut() = None;
cx.refresh();
})
.detach();
cx.focus_view(&new_menu);
*menu.borrow_mut() = Some(new_menu);
*position.borrow_mut() = if child_layout_id.is_some() {
if let Some(attach) = attach {
attach.corner(child_bounds)
} else {
cx.mouse_position()
}
}
*menu2.borrow_mut() = None;
cx.refresh();
})
.detach();
cx.focus_view(&new_menu);
*menu.borrow_mut() = Some(new_menu);
*position.borrow_mut() =
if let Some(attach) = attach.filter(|_| child_layout_id.is_some()) {
attach.corner(child_bounds)
} else {
cx.mouse_position()
};
cx.refresh();
}
});
cx.refresh();
}
});
})
}
}
impl<M: ManagedView> IntoElement for RightClickMenu<M> {
type Element = Self;
fn element_id(&self) -> Option<gpui::ElementId> {
Some(self.id.clone())
}
fn into_element(self) -> Self::Element {
self
}

View File

@ -123,7 +123,6 @@ impl RenderOnce for TabBar {
.absolute()
.top_0()
.left_0()
.z_index(1)
.size_full()
.border_b()
.border_color(cx.theme().colors().border),
@ -131,7 +130,6 @@ impl RenderOnce for TabBar {
.child(
h_flex()
.id("tabs")
.z_index(2)
.flex_grow()
.overflow_x_scroll()
.when_some(self.scroll_handle, |cx, scroll_handle| {

View File

@ -7,7 +7,6 @@ use crate::{ElevationIndex, UiTextSize};
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
this.bg(cx.theme().colors().elevated_surface_background)
.z_index(index.z_index())
.rounded(px(8.))
.border()
.border_color(cx.theme().colors().border_variant)

View File

@ -20,17 +20,6 @@ pub enum 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]> {
match self {
ElevationIndex::Surface => smallvec![],
@ -75,16 +64,6 @@ pub enum LayerIndex {
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.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementIndex {
@ -95,16 +74,3 @@ pub enum ElementIndex {
Content,
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,
}
}
}

View File

@ -4,8 +4,8 @@ use crate::{status_bar::StatusItemView, Workspace};
use gpui::{
div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
EventEmitter, FocusHandle, FocusableView, IntoElement, KeyContext, MouseButton, ParentElement,
Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
WindowContext,
Render, SharedString, StyleRefinement, Styled, Subscription, View, ViewContext, VisualContext,
WeakView, WindowContext,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -563,8 +563,7 @@ impl Render for Dock {
cx.stop_propagation();
}
}))
.z_index(1)
.block_mouse();
.occlude();
match self.position() {
DockPosition::Left => {
@ -618,7 +617,12 @@ impl Render for Dock {
Axis::Horizontal => this.min_w(size).h_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)
} else {

View File

@ -139,21 +139,15 @@ impl Render for ModalLayer {
return div();
};
div()
.absolute()
.size_full()
.top_0()
.left_0()
.z_index(169)
.child(
v_flex()
.h(px(0.0))
.top_20()
.flex()
.flex_col()
.items_center()
.track_focus(&active_modal.focus_handle)
.child(h_flex().child(active_modal.modal.view())),
)
div().absolute().size_full().top_0().left_0().child(
v_flex()
.h(px(0.0))
.top_20()
.flex()
.flex_col()
.items_center()
.track_focus(&active_modal.focus_handle)
.child(h_flex().child(active_modal.modal.view())),
)
}
}

View File

@ -1561,7 +1561,6 @@ impl Pane {
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
div()
.absolute()
.z_index(1)
.bottom_0()
.right_0()
.size_0()
@ -1886,7 +1885,6 @@ impl Render for Pane {
.child(
// drag target
div()
.z_index(1)
.invisible()
.absolute()
.bg(theme::color_alpha(

View File

@ -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
}

View File

@ -4,7 +4,7 @@ use call::{ActiveCall, ParticipantLocation};
use collections::HashMap;
use gpui::{
point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels,
Point, View, ViewContext,
Point, StyleRefinement, View, ViewContext,
};
use parking_lot::Mutex;
use project::Project;
@ -239,7 +239,10 @@ impl Member {
.relative()
.flex_1()
.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| {
this.child(
div()
@ -260,7 +263,6 @@ impl Member {
.right_3()
.elevation_2(cx)
.p_1()
.z_index(1)
.child(status_box)
.when_some(
leader_join_data,
@ -588,13 +590,15 @@ impl SplitDirection {
mod element {
use std::mem;
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
use gpui::{
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
WeakView, WindowContext,
px, relative, Along, AnyElement, Axis, Bounds, Element, IntoElement, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, WeakView,
WindowContext,
};
use gpui::{CursorStyle, Hitbox};
use parking_lot::Mutex;
use settings::Settings;
use smallvec::SmallVec;
@ -637,6 +641,22 @@ mod element {
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 {
pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
self.active_pane_ix = active_pane_ix;
@ -733,16 +753,11 @@ mod element {
}
#[allow(clippy::too_many_arguments)]
fn push_handle(
flexes: Arc<Mutex<Vec<f32>>>,
dragged_handle: Rc<RefCell<Option<usize>>>,
fn layout_handle(
axis: Axis,
ix: usize,
pane_bounds: Bounds<Pixels>,
axis_bounds: Bounds<Pixels>,
workspace: WeakView<Workspace>,
cx: &mut ElementContext,
) {
) -> PaneAxisHandleLayout {
let handle_bounds = Bounds {
origin: pane_bounds.origin.apply_along(axis, |origin| {
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)),
};
cx.with_z_index(3, |cx| {
if handle_bounds.contains(&cx.mouse_position()) {
let stacking_order = cx.stacking_order().clone();
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,
)
}
}
});
});
PaneAxisHandleLayout {
hitbox: cx.insert_hitbox(handle_bounds, true),
divider_bounds,
}
}
}
impl IntoElement for PaneAxisElement {
type Element = Self;
fn element_id(&self) -> Option<ui::prelude::ElementId> {
Some(self.basis.into())
}
fn into_element(self) -> Self::Element {
self
}
}
impl Element for PaneAxisElement {
type State = Rc<RefCell<Option<usize>>>;
type BeforeLayout = ();
type AfterLayout = PaneAxisLayout;
fn request_layout(
fn before_layout(
&mut self,
state: Option<Self::State>,
cx: &mut ui::prelude::ElementContext,
) -> (gpui::LayoutId, Self::State) {
) -> (gpui::LayoutId, Self::BeforeLayout) {
let mut style = Style::default();
style.flex_grow = 1.;
style.flex_shrink = 1.;
style.flex_basis = relative(0.).into();
style.size.width = relative(1.).into();
style.size.height = relative(1.).into();
let layout_id = cx.request_layout(&style, None);
let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
(layout_id, dragged_pane)
(cx.request_layout(&style, None), ())
}
fn paint(
fn after_layout(
&mut self,
bounds: gpui::Bounds<ui::prelude::Pixels>,
state: &mut Self::State,
cx: &mut ui::prelude::ElementContext,
) {
bounds: Bounds<Pixels>,
_state: &mut Self::BeforeLayout,
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 len = self.children.len();
debug_assert!(flexes.len() == len);
@ -875,7 +844,11 @@ mod element {
let mut bounding_boxes = self.bounding_boxes.lock();
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
.map(|magnification| {
if self.active_pane_ix == Some(ix) {
@ -896,40 +869,105 @@ mod element {
size: child_size,
};
bounding_boxes.push(Some(child_bounds));
cx.with_z_index(0, |cx| {
child.draw(origin, child_size.into(), cx);
});
child.layout(origin, child_size.into(), cx);
origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
layout.children.push(PaneAxisChildLayout {
bounds: child_bounds,
element: child,
handle: None,
})
}
for (ix, child_layout) in layout.children.iter_mut().enumerate() {
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,
);
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({
let dragged_handle = layout.dragged_handle.clone();
let flexes = self.flexes.clone();
let workspace = self.workspace.clone();
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,
)
}
}
});
}
origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
}
cx.with_z_index(1, |cx| {
cx.on_mouse_event({
let state = state.clone();
move |_: &MouseUpEvent, phase, _cx| {
if phase.bubble() {
state.replace(None);
}
cx.on_mouse_event({
let dragged_handle = layout.dragged_handle.clone();
move |_: &MouseUpEvent, phase, _cx| {
if phase.bubble() {
dragged_handle.replace(None);
}
});
})
}
});
}
}

View File

@ -2756,7 +2756,6 @@ impl Workspace {
Some(
div()
.absolute()
.z_index(100)
.right_3()
.bottom_3()
.w_112()
@ -3832,18 +3831,15 @@ impl Render for Workspace {
.border_t()
.border_b()
.border_color(colors.border)
.child(
canvas({
let this = cx.view().clone();
move |bounds, cx| {
this.update(cx, |this, _cx| {
this.bounds = *bounds;
})
}
})
.child({
let this = cx.view().clone();
canvas(
move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
|_, _, _| {},
)
.absolute()
.size_full(),
)
.size_full()
})
.on_drag_move(
cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
match e.drag(cx).0 {
@ -3868,7 +3864,6 @@ impl Render for Workspace {
}
}),
)
.child(self.modal_layer.clone())
.child(
div()
.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| {
let zoomed_view = view.upgrade()?;
let div = div()
.z_index(1)
.occlude()
.absolute()
.overflow_hidden()
.border_color(colors.border)
@ -3936,7 +3931,8 @@ impl Render for Workspace {
Some(DockPosition::Bottom) => div.top_2().border_t(),
None => div.top_2().bottom_2().left_2().right_2().border(),
})
})),
}))
.children(self.render_notifications(cx)),
)
.child(self.status_bar.clone())
.children(if self.project.read(cx).is_disconnected() {
@ -4662,13 +4658,10 @@ pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
struct DisconnectedOverlay;
impl Element for DisconnectedOverlay {
type State = AnyElement;
type BeforeLayout = AnyElement;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut background = cx.theme().colors().elevated_surface_background;
background.fade_out(0.2);
let mut overlay = div()
@ -4686,29 +4679,33 @@ impl Element for DisconnectedOverlay {
"Your connection to the remote project has been lost.",
))
.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(
&mut self,
bounds: Bounds<Pixels>,
overlay: &mut Self::State,
_: Bounds<Pixels>,
overlay: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
cx.with_z_index(u16::MAX, |cx| {
cx.add_opaque_layer(bounds);
overlay.paint(cx);
})
overlay.paint(cx)
}
}
impl IntoElement for DisconnectedOverlay {
type Element = Self;
fn element_id(&self) -> Option<ui::prelude::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}