Detailed changes
@@ -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()
}),
@@ -156,7 +156,7 @@ mod linux {
}
}
-// todo(windows)
+// todo("windows")
#[cfg(target_os = "windows")]
mod windows {
use std::path::Path;
@@ -132,7 +132,7 @@ async fn main() -> Result<()> {
.await
.map_err(|e| anyhow!(e))?;
- // todo(windows)
+ // todo("windows")
#[cfg(windows)]
unimplemented!();
}
@@ -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()
@@ -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();
-
- 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.) }
- },
+ 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.) }
+ },
+ ),
),
- ),
- 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)
}
@@ -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()
}
@@ -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)),
+ )
}
}
@@ -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()
}
})?,
@@ -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;
@@ -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()
}
@@ -22,11 +22,11 @@ use git::diff::DiffHunkStatus;
use gpui::{
div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action,
AnchorCorner, AnyElement, AvailableSpace, Bounds, ContentMask, Corners, CursorStyle,
- DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, InteractiveBounds,
+ DispatchPhase, Edges, Element, ElementContext, ElementInputHandler, Entity, Hitbox, Hsla,
InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
- SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
- TextStyle, View, ViewContext, WindowContext,
+ SharedString, Size, Stateful, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle,
+ TextStyleRefinement, View, ViewContext, WindowContext,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -43,14 +43,14 @@ use std::{
borrow::Cow,
cmp::{self, Ordering},
fmt::Write,
- iter,
+ iter, mem,
ops::Range,
sync::Arc,
};
use sum_tree::Bias;
use theme::{ActiveTheme, PlayerColor};
use ui::prelude::*;
-use ui::{h_flex, ButtonLike, ButtonStyle, IconButton, Tooltip};
+use ui::{h_flex, ButtonLike, ButtonStyle, Tooltip};
use util::ResultExt;
use workspace::item::Item;
@@ -342,30 +342,18 @@ impl EditorElement {
register_action(view, cx, Editor::revert_selected_hunks);
}
- fn register_key_listeners(
- &self,
- cx: &mut ElementContext,
- text_bounds: Bounds<Pixels>,
- layout: &LayoutState,
- ) {
+ fn register_key_listeners(&self, cx: &mut ElementContext, layout: &EditorLayout) {
let position_map = layout.position_map.clone();
- let stacking_order = cx.stacking_order().clone();
cx.on_key_event({
let editor = self.editor.clone();
+ let text_hitbox = layout.text_hitbox.clone();
move |event: &ModifiersChangedEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
editor.update(cx, |editor, cx| {
- Self::modifiers_changed(
- editor,
- event,
- &position_map,
- text_bounds,
- &stacking_order,
- cx,
- )
+ Self::modifiers_changed(editor, event, &position_map, &text_hitbox, cx)
})
}
});
@@ -375,19 +363,16 @@ impl EditorElement {
editor: &mut Editor,
event: &ModifiersChangedEvent,
position_map: &PositionMap,
- text_bounds: Bounds<Pixels>,
- stacking_order: &StackingOrder,
+ text_hitbox: &Hitbox,
cx: &mut ViewContext<Editor>,
) {
let mouse_position = cx.mouse_position();
- if !text_bounds.contains(&mouse_position)
- || !cx.was_top_layer(&mouse_position, stacking_order)
- {
+ if !text_hitbox.is_hovered(cx) {
return;
}
editor.update_hovered_link(
- position_map.point_for_position(text_bounds, mouse_position),
+ position_map.point_for_position(text_hitbox.bounds, mouse_position),
&position_map.snapshot,
event.modifiers,
cx,
@@ -398,26 +383,25 @@ impl EditorElement {
editor: &mut Editor,
event: &MouseDownEvent,
position_map: &PositionMap,
- text_bounds: Bounds<Pixels>,
- gutter_bounds: Bounds<Pixels>,
- stacking_order: &StackingOrder,
+ text_hitbox: &Hitbox,
+ gutter_hitbox: &Hitbox,
cx: &mut ViewContext<Editor>,
) {
+ if cx.default_prevented() {
+ return;
+ }
+
let mut click_count = event.click_count;
let modifiers = event.modifiers;
- if cx.default_prevented() {
- return;
- } else if gutter_bounds.contains(&event.position) {
+ if gutter_hitbox.is_hovered(cx) {
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
- } else if !text_bounds.contains(&event.position) {
- return;
- }
- if !cx.was_top_layer(&event.position, stacking_order) {
+ } else if !text_hitbox.is_hovered(cx) {
return;
}
- let point_for_position = position_map.point_for_position(text_bounds, event.position);
+ let point_for_position =
+ position_map.point_for_position(text_hitbox.bounds, event.position);
let position = point_for_position.previous_valid;
if modifiers.shift && modifiers.alt {
editor.select(
@@ -453,13 +437,14 @@ impl EditorElement {
editor: &mut Editor,
event: &MouseDownEvent,
position_map: &PositionMap,
- text_bounds: Bounds<Pixels>,
+ text_hitbox: &Hitbox,
cx: &mut ViewContext<Editor>,
) {
- if !text_bounds.contains(&event.position) {
+ if !text_hitbox.is_hovered(cx) {
return;
}
- let point_for_position = position_map.point_for_position(text_bounds, event.position);
+ let point_for_position =
+ position_map.point_for_position(text_hitbox.bounds, event.position);
mouse_context_menu::deploy_context_menu(
editor,
event.position,
@@ -473,9 +458,7 @@ impl EditorElement {
editor: &mut Editor,
event: &MouseUpEvent,
position_map: &PositionMap,
- text_bounds: Bounds<Pixels>,
- interactive_bounds: &InteractiveBounds,
- stacking_order: &StackingOrder,
+ text_hitbox: &Hitbox,
cx: &mut ViewContext<Editor>,
) {
let end_selection = editor.has_pending_selection();
@@ -485,13 +468,8 @@ impl EditorElement {
editor.select(SelectPhase::End, cx);
}
- if interactive_bounds.visibly_contains(&event.position, cx)
- && !pending_nonempty_selections
- && event.modifiers.command
- && text_bounds.contains(&event.position)
- && cx.was_top_layer(&event.position, stacking_order)
- {
- let point = position_map.point_for_position(text_bounds, event.position);
+ if !pending_nonempty_selections && event.modifiers.command && text_hitbox.is_hovered(cx) {
+ let point = position_map.point_for_position(text_hitbox.bounds, event.position);
editor.handle_click_hovered_link(point, event.modifiers, cx);
cx.stop_propagation();
@@ -505,8 +483,6 @@ impl EditorElement {
event: &MouseMoveEvent,
position_map: &PositionMap,
text_bounds: Bounds<Pixels>,
- _gutter_bounds: Bounds<Pixels>,
- _stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>,
) {
if !editor.has_pending_selection() {
@@ -549,21 +525,18 @@ impl EditorElement {
editor: &mut Editor,
event: &MouseMoveEvent,
position_map: &PositionMap,
- text_bounds: Bounds<Pixels>,
- gutter_bounds: Bounds<Pixels>,
- stacking_order: &StackingOrder,
+ text_hitbox: &Hitbox,
+ gutter_hitbox: &Hitbox,
cx: &mut ViewContext<Editor>,
) {
let modifiers = event.modifiers;
- let text_hovered = text_bounds.contains(&event.position);
- let gutter_hovered = gutter_bounds.contains(&event.position);
- let was_top = cx.was_top_layer(&event.position, stacking_order);
-
+ let gutter_hovered = gutter_hitbox.is_hovered(cx);
editor.set_gutter_hovered(gutter_hovered, cx);
// Don't trigger hover popover if mouse is hovering over context menu
- if text_hovered && was_top {
- let point_for_position = position_map.point_for_position(text_bounds, event.position);
+ if text_hitbox.is_hovered(cx) {
+ let point_for_position =
+ position_map.point_for_position(text_hitbox.bounds, event.position);
editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx);
@@ -574,7 +547,7 @@ impl EditorElement {
} else {
editor.hide_hovered_link(cx);
hover_at(editor, None, cx);
- if gutter_hovered && was_top {
+ if gutter_hovered {
cx.stop_propagation();
}
}
@@ -625,2120 +598,1920 @@ impl EditorElement {
cx.notify()
}
- fn paint_background(
+ fn layout_selections(
&self,
- gutter_bounds: Bounds<Pixels>,
- text_bounds: Bounds<Pixels>,
- layout: &LayoutState,
+ start_anchor: Anchor,
+ end_anchor: Anchor,
+ snapshot: &EditorSnapshot,
+ start_row: u32,
+ end_row: u32,
cx: &mut ElementContext,
+ ) -> (
+ Vec<(PlayerColor, Vec<SelectionLayout>)>,
+ BTreeMap<u32, bool>,
+ Option<DisplayPoint>,
) {
- let bounds = gutter_bounds.union(&text_bounds);
- let scroll_top =
- layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
- let gutter_bg = cx.theme().colors().editor_gutter_background;
- cx.paint_quad(fill(gutter_bounds, gutter_bg));
- cx.paint_quad(fill(text_bounds, self.style.background));
-
- if let EditorMode::Full = layout.mode {
- let mut active_rows = layout.active_rows.iter().peekable();
- while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
- let mut end_row = *start_row;
- while active_rows.peek().map_or(false, |r| {
- *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
- }) {
- active_rows.next().unwrap();
- end_row += 1;
+ let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
+ let mut active_rows = BTreeMap::new();
+ let mut newest_selection_head = None;
+ let editor = self.editor.read(cx);
+
+ if editor.show_local_selections {
+ let mut local_selections: Vec<Selection<Point>> = editor
+ .selections
+ .disjoint_in_range(start_anchor..end_anchor, cx);
+ local_selections.extend(editor.selections.pending(cx));
+ let mut layouts = Vec::new();
+ let newest = editor.selections.newest(cx);
+ for selection in local_selections.drain(..) {
+ let is_empty = selection.start == selection.end;
+ let is_newest = selection == newest;
+
+ let layout = SelectionLayout::new(
+ selection,
+ editor.selections.line_mode,
+ editor.cursor_shape,
+ &snapshot.display_snapshot,
+ is_newest,
+ editor.leader_peer_id.is_none(),
+ None,
+ );
+ if is_newest {
+ newest_selection_head = Some(layout.head);
}
- if !contains_non_empty_selection {
- let origin = point(
- bounds.origin.x,
- bounds.origin.y + (layout.position_map.line_height * *start_row as f32)
- - scroll_top,
- );
- let size = size(
- bounds.size.width,
- layout.position_map.line_height * (end_row - start_row + 1) as f32,
- );
- let active_line_bg = cx.theme().colors().editor_active_line_background;
- cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
+ for row in cmp::max(layout.active_rows.start, start_row)
+ ..=cmp::min(layout.active_rows.end, end_row)
+ {
+ let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
+ *contains_non_empty_selection |= !is_empty;
}
+ layouts.push(layout);
}
- let mut paint_highlight = |highlight_row_start: u32, highlight_row_end: u32, color| {
- let origin = point(
- bounds.origin.x,
- bounds.origin.y
- + (layout.position_map.line_height * highlight_row_start as f32)
- - scroll_top,
- );
- let size = size(
- bounds.size.width,
- layout.position_map.line_height
- * (highlight_row_end + 1 - highlight_row_start) as f32,
- );
- cx.paint_quad(fill(Bounds { origin, size }, color));
+ let player = if editor.read_only(cx) {
+ cx.theme().players().read_only()
+ } else {
+ self.style.local_player
};
- let mut last_row = None;
- let mut highlight_row_start = 0u32;
- let mut highlight_row_end = 0u32;
- for (&row, &color) in &layout.highlighted_rows {
- let paint = last_row.map_or(false, |(last_row, last_color)| {
- last_color != color || last_row + 1 < row
- });
- if paint {
- let paint_range_is_unfinished = highlight_row_end == 0;
- if paint_range_is_unfinished {
- highlight_row_end = row;
- last_row = None;
- }
- paint_highlight(highlight_row_start, highlight_row_end, color);
- highlight_row_start = 0;
- highlight_row_end = 0;
- if !paint_range_is_unfinished {
- highlight_row_start = row;
- last_row = Some((row, color));
- }
- } else {
- if last_row.is_none() {
- highlight_row_start = row;
- } else {
- highlight_row_end = row;
+ selections.push((player, layouts));
+ }
+
+ if let Some(collaboration_hub) = &editor.collaboration_hub {
+ // When following someone, render the local selections in their color.
+ if let Some(leader_id) = editor.leader_peer_id {
+ if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
+ if let Some(participant_index) = collaboration_hub
+ .user_participant_indices(cx)
+ .get(&collaborator.user_id)
+ {
+ if let Some((local_selection_style, _)) = selections.first_mut() {
+ *local_selection_style = cx
+ .theme()
+ .players()
+ .color_for_participant(participant_index.0);
+ }
}
- last_row = Some((row, color));
}
}
- if let Some((row, hsla)) = last_row {
- highlight_row_end = row;
- paint_highlight(highlight_row_start, highlight_row_end, hsla);
- }
-
- let scroll_left =
- layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
- for (wrap_position, active) in layout.wrap_guides.iter() {
- let x = (text_bounds.origin.x + *wrap_position + layout.position_map.em_width / 2.)
- - scroll_left;
+ let mut remote_selections = HashMap::default();
+ for selection in snapshot.remote_selections_in_range(
+ &(start_anchor..end_anchor),
+ collaboration_hub.as_ref(),
+ cx,
+ ) {
+ let selection_style = if let Some(participant_index) = selection.participant_index {
+ cx.theme()
+ .players()
+ .color_for_participant(participant_index.0)
+ } else {
+ cx.theme().players().absent()
+ };
- if x < text_bounds.origin.x
- || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
- {
+ // Don't re-render the leader's selections, since the local selections
+ // match theirs.
+ if Some(selection.peer_id) == editor.leader_peer_id {
continue;
}
-
- let color = if *active {
- cx.theme().colors().editor_active_wrap_guide
- } else {
- cx.theme().colors().editor_wrap_guide
+ let key = HoveredCursor {
+ replica_id: selection.replica_id,
+ selection_id: selection.selection.id,
};
- cx.paint_quad(fill(
- Bounds {
- origin: point(x, text_bounds.origin.y),
- size: size(px(1.), text_bounds.size.height),
- },
- color,
- ));
+
+ let is_shown =
+ editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
+
+ remote_selections
+ .entry(selection.replica_id)
+ .or_insert((selection_style, Vec::new()))
+ .1
+ .push(SelectionLayout::new(
+ selection.selection,
+ selection.line_mode,
+ selection.cursor_shape,
+ &snapshot.display_snapshot,
+ false,
+ false,
+ if is_shown { selection.user_name } else { None },
+ ));
}
+
+ selections.extend(remote_selections.into_values());
}
+ (selections, active_rows, newest_selection_head)
}
- fn paint_gutter(
- &mut self,
- bounds: Bounds<Pixels>,
- layout: &mut LayoutState,
+ #[allow(clippy::too_many_arguments)]
+ fn layout_folds(
+ &self,
+ snapshot: &EditorSnapshot,
+ content_origin: gpui::Point<Pixels>,
+ visible_anchor_range: Range<Anchor>,
+ visible_display_row_range: Range<u32>,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ line_height: Pixels,
+ line_layouts: &[LineWithInvisibles],
cx: &mut ElementContext,
- ) {
- let line_height = layout.position_map.line_height;
+ ) -> Vec<FoldLayout> {
+ snapshot
+ .folds_in_range(visible_anchor_range.clone())
+ .filter_map(|fold| {
+ let fold_range = fold.range.clone();
+ let display_range = fold.range.start.to_display_point(&snapshot)
+ ..fold.range.end.to_display_point(&snapshot);
+ debug_assert_eq!(display_range.start.row(), display_range.end.row());
+ let row = display_range.start.row();
+ debug_assert!(row < visible_display_row_range.end);
+ let line_layout = line_layouts
+ .get((row - visible_display_row_range.start) as usize)
+ .map(|l| &l.line)?;
+
+ let start_x = content_origin.x
+ + line_layout.x_for_index(display_range.start.column() as usize)
+ - scroll_pixel_position.x;
+ let start_y = content_origin.y + row as f32 * line_height - scroll_pixel_position.y;
+ let end_x = content_origin.x
+ + line_layout.x_for_index(display_range.end.column() as usize)
+ - scroll_pixel_position.x;
+
+ let fold_bounds = Bounds {
+ origin: point(start_x, start_y),
+ size: size(end_x - start_x, line_height),
+ };
- let scroll_position = layout.position_map.snapshot.scroll_position();
- let scroll_top = scroll_position.y * line_height;
+ let mut hover_element = div()
+ .id(fold.id)
+ .size_full()
+ .cursor_pointer()
+ .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
+ .on_click(
+ cx.listener_for(&self.editor, move |editor: &mut Editor, _, cx| {
+ editor.unfold_ranges(
+ [fold_range.start..fold_range.end],
+ true,
+ false,
+ cx,
+ );
+ cx.stop_propagation();
+ }),
+ )
+ .into_any();
+ hover_element.layout(fold_bounds.origin, fold_bounds.size.into(), cx);
+ Some(FoldLayout {
+ display_range,
+ hover_element,
+ })
+ })
+ .collect()
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn layout_cursors(
+ &self,
+ snapshot: &EditorSnapshot,
+ selections: &[(PlayerColor, Vec<SelectionLayout>)],
+ visible_display_row_range: Range<u32>,
+ line_layouts: &[LineWithInvisibles],
+ text_hitbox: &Hitbox,
+ content_origin: gpui::Point<Pixels>,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ line_height: Pixels,
+ em_width: Pixels,
+ cx: &mut ElementContext,
+ ) -> Vec<CursorLayout> {
+ self.editor.update(cx, |editor, cx| {
+ let mut cursors = Vec::new();
+ for (player_color, selections) in selections {
+ for selection in selections {
+ let cursor_position = selection.head;
+ if (selection.is_local && !editor.show_local_cursors(cx))
+ || !visible_display_row_range.contains(&cursor_position.row())
+ {
+ continue;
+ }
+
+ let cursor_row_layout = &line_layouts
+ [(cursor_position.row() - visible_display_row_range.start) as usize]
+ .line;
+ let cursor_column = cursor_position.column() as usize;
+
+ let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
+ let mut block_width =
+ cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
+ if block_width == Pixels::ZERO {
+ block_width = em_width;
+ }
+ let block_text = if let CursorShape::Block = selection.cursor_shape {
+ snapshot
+ .chars_at(cursor_position)
+ .next()
+ .and_then(|(character, _)| {
+ let text = if character == '\n' {
+ SharedString::from(" ")
+ } else {
+ SharedString::from(character.to_string())
+ };
+ let len = text.len();
+ cx.text_system()
+ .shape_line(
+ text,
+ cursor_row_layout.font_size,
+ &[TextRun {
+ len,
+ font: self.style.text.font(),
+ color: self.style.background,
+ background_color: None,
+ strikethrough: None,
+ underline: None,
+ }],
+ )
+ .log_err()
+ })
+ } else {
+ None
+ };
- if bounds.contains(&cx.mouse_position()) {
- let stacking_order = cx.stacking_order().clone();
- cx.set_cursor_style(CursorStyle::Arrow, stacking_order);
+ let x = cursor_character_x - scroll_pixel_position.x;
+ let y = cursor_position.row() as f32 * line_height - scroll_pixel_position.y;
+ if selection.is_newest {
+ editor.pixel_position_of_newest_cursor = Some(point(
+ text_hitbox.origin.x + x + block_width / 2.,
+ text_hitbox.origin.y + y + line_height / 2.,
+ ))
+ }
+
+ let mut cursor = CursorLayout {
+ color: player_color.cursor,
+ block_width,
+ origin: point(x, y),
+ line_height,
+ shape: selection.cursor_shape,
+ block_text,
+ cursor_name: None,
+ };
+ let cursor_name = selection.user_name.clone().map(|name| CursorName {
+ string: name,
+ color: self.style.background,
+ is_top_row: cursor_position.row() == 0,
+ });
+ cx.with_element_context(|cx| cursor.layout(content_origin, cursor_name, cx));
+ cursors.push(cursor);
+ }
+ }
+ cursors
+ })
+ }
+
+ fn layout_scrollbar(
+ &self,
+ snapshot: &EditorSnapshot,
+ bounds: Bounds<Pixels>,
+ scroll_position: gpui::Point<f32>,
+ line_height: Pixels,
+ height_in_lines: f32,
+ cx: &mut ElementContext,
+ ) -> Option<ScrollbarLayout> {
+ let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
+ let show_scrollbars = match scrollbar_settings.show {
+ ShowScrollbar::Auto => {
+ let editor = self.editor.read(cx);
+ let is_singleton = editor.is_singleton(cx);
+ // Git
+ (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
+ ||
+ // Selections
+ (is_singleton && scrollbar_settings.selections && editor.has_background_highlights::<BufferSearchHighlights>())
+ ||
+ // Symbols Selections
+ (is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
+ ||
+ // Diagnostics
+ (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
+ ||
+ // Scrollmanager
+ editor.scroll_manager.scrollbars_visible()
+ }
+ ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
+ ShowScrollbar::Always => true,
+ ShowScrollbar::Never => false,
+ };
+ if snapshot.mode != EditorMode::Full {
+ return None;
}
- let show_git_gutter = matches!(
- ProjectSettings::get_global(cx).git.git_gutter,
- Some(GitGutterSetting::TrackedFiles)
- );
+ let visible_row_range = scroll_position.y..scroll_position.y + height_in_lines;
- if show_git_gutter {
- Self::paint_diff_hunks(bounds, layout, cx);
+ // If a drag took place after we started dragging the scrollbar,
+ // cancel the scrollbar drag.
+ if cx.has_active_drag() {
+ self.editor.update(cx, |editor, cx| {
+ editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
+ });
}
- let gutter_settings = EditorSettings::get_global(cx).gutter;
+ let track_bounds = Bounds::from_corners(
+ point(self.scrollbar_left(&bounds), bounds.origin.y),
+ point(bounds.lower_right().x, bounds.lower_left().y),
+ );
- for (ix, line) in layout.line_numbers.iter().enumerate() {
- if let Some(line) = line {
- let line_origin = bounds.origin
- + point(
- bounds.size.width - line.width - layout.gutter_dimensions.right_padding,
- ix as f32 * line_height - (scroll_top % line_height),
- );
+ let scroll_height = snapshot.max_point().row() as f32 + height_in_lines;
+ let mut height = bounds.size.height;
+ let mut first_row_y_offset = px(0.0);
- line.paint(line_origin, line_height, cx).log_err();
- }
+ // Impose a minimum height on the scrollbar thumb
+ let row_height = height / scroll_height;
+ let min_thumb_height = line_height;
+ let thumb_height = height_in_lines * row_height;
+ if thumb_height < min_thumb_height {
+ first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
+ height -= min_thumb_height - thumb_height;
}
- cx.with_z_index(1, |cx| {
- for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
- if let Some(fold_indicator) = fold_indicator {
- debug_assert!(gutter_settings.folds);
- let mut fold_indicator = fold_indicator.into_any_element();
- let available_space = size(
- AvailableSpace::MinContent,
- AvailableSpace::Definite(line_height * 0.55),
- );
- let fold_indicator_size = fold_indicator.measure(available_space, cx);
+ Some(ScrollbarLayout {
+ hitbox: cx.insert_hitbox(track_bounds, false),
+ visible_row_range,
+ height,
+ scroll_height,
+ first_row_y_offset,
+ row_height,
+ visible: show_scrollbars,
+ })
+ }
- let position = point(
- bounds.size.width - layout.gutter_dimensions.right_padding,
- ix as f32 * line_height - (scroll_top % line_height),
- );
- let centering_offset = point(
- (layout.gutter_dimensions.right_padding + layout.gutter_dimensions.margin
- - fold_indicator_size.width)
- / 2.,
- (line_height - fold_indicator_size.height) / 2.,
- );
- let origin = bounds.origin + position + centering_offset;
- fold_indicator.draw(origin, available_space, cx);
- }
- }
+ #[allow(clippy::too_many_arguments)]
+ fn layout_gutter_fold_indicators(
+ &self,
+ fold_statuses: Vec<Option<(FoldStatus, u32, bool)>>,
+ line_height: Pixels,
+ gutter_dimensions: &GutterDimensions,
+ gutter_settings: crate::editor_settings::Gutter,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ gutter_hitbox: &Hitbox,
+ cx: &mut ElementContext,
+ ) -> Vec<Option<AnyElement>> {
+ let mut indicators = self.editor.update(cx, |editor, cx| {
+ editor.render_fold_indicators(
+ fold_statuses,
+ &self.style,
+ editor.gutter_hovered,
+ line_height,
+ gutter_dimensions.margin,
+ cx,
+ )
+ });
- if let Some(indicator) = layout.code_actions_indicator.take() {
- debug_assert!(gutter_settings.code_actions);
- let mut button = indicator.button.into_any_element();
+ for (ix, fold_indicator) in indicators.iter_mut().enumerate() {
+ if let Some(fold_indicator) = fold_indicator {
+ debug_assert!(gutter_settings.folds);
let available_space = size(
AvailableSpace::MinContent,
- AvailableSpace::Definite(line_height),
+ AvailableSpace::Definite(line_height * 0.55),
);
- let indicator_size = button.measure(available_space, cx);
-
- let mut x = Pixels::ZERO;
- let mut y = indicator.row as f32 * line_height - scroll_top;
- // Center indicator.
- x += (layout.gutter_dimensions.margin + layout.gutter_dimensions.left_padding
- - indicator_size.width)
- / 2.;
- y += (line_height - indicator_size.height) / 2.;
+ let fold_indicator_size = fold_indicator.measure(available_space, cx);
- button.draw(bounds.origin + point(x, y), available_space, cx);
+ let position = point(
+ gutter_dimensions.width - gutter_dimensions.right_padding,
+ ix as f32 * line_height - (scroll_pixel_position.y % line_height),
+ );
+ let centering_offset = point(
+ (gutter_dimensions.right_padding + gutter_dimensions.margin
+ - fold_indicator_size.width)
+ / 2.,
+ (line_height - fold_indicator_size.height) / 2.,
+ );
+ let origin = gutter_hitbox.origin + position + centering_offset;
+ fold_indicator.layout(origin, available_space, cx);
}
- });
+ }
+
+ indicators
}
- fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut ElementContext) {
- let line_height = layout.position_map.line_height;
+ //Folds contained in a hunk are ignored apart from shrinking visual size
+ //If a fold contains any hunks then that fold line is marked as modified
+ fn layout_git_gutters(
+ &self,
+ display_rows: Range<u32>,
+ snapshot: &EditorSnapshot,
+ ) -> Vec<DisplayDiffHunk> {
+ let buffer_snapshot = &snapshot.buffer_snapshot;
- let scroll_position = layout.position_map.snapshot.scroll_position();
- let scroll_top = scroll_position.y * line_height;
+ let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
+ .to_point(snapshot)
+ .row;
+ let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
+ .to_point(snapshot)
+ .row;
- for hunk in &layout.display_hunks {
- let (display_row_range, status) = match hunk {
- //TODO: This rendering is entirely a horrible hack
- &DisplayDiffHunk::Folded { display_row: row } => {
- let start_y = row as f32 * line_height - scroll_top;
- let end_y = start_y + line_height;
-
- let width = 0.275 * line_height;
- let highlight_origin = bounds.origin + point(-width, start_y);
- let highlight_size = size(width * 2., end_y - start_y);
- let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
- cx.paint_quad(quad(
- highlight_bounds,
- Corners::all(1. * line_height),
- cx.theme().status().modified,
- Edges::default(),
- transparent_black(),
- ));
+ buffer_snapshot
+ .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
+ .map(|hunk| diff_hunk_to_display(hunk, snapshot))
+ .dedup()
+ .collect()
+ }
- continue;
- }
+ fn layout_code_actions_indicator(
+ &self,
+ line_height: Pixels,
+ newest_selection_head: DisplayPoint,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ gutter_dimensions: &GutterDimensions,
+ gutter_hitbox: &Hitbox,
+ cx: &mut ElementContext,
+ ) -> Option<AnyElement> {
+ let mut active = false;
+ let mut button = None;
+ self.editor.update(cx, |editor, cx| {
+ active = matches!(
+ editor.context_menu.read().as_ref(),
+ Some(crate::ContextMenu::CodeActions(_))
+ );
+ button = editor.render_code_actions_indicator(&self.style, active, cx);
+ });
- DisplayDiffHunk::Unfolded {
- display_row_range,
- status,
- } => (display_row_range, status),
- };
+ let mut button = button?.into_any_element();
+ let available_space = size(
+ AvailableSpace::MinContent,
+ AvailableSpace::Definite(line_height),
+ );
+ let indicator_size = button.measure(available_space, cx);
+
+ let mut x = Pixels::ZERO;
+ let mut y = newest_selection_head.row() as f32 * line_height - scroll_pixel_position.y;
+ // Center indicator.
+ x +=
+ (gutter_dimensions.margin + gutter_dimensions.left_padding - indicator_size.width) / 2.;
+ y += (line_height - indicator_size.height) / 2.;
+ button.layout(gutter_hitbox.origin + point(x, y), available_space, cx);
+ Some(button)
+ }
- let color = match status {
- DiffHunkStatus::Added => cx.theme().status().created,
- DiffHunkStatus::Modified => cx.theme().status().modified,
-
- //TODO: This rendering is entirely a horrible hack
- DiffHunkStatus::Removed => {
- let row = display_row_range.start;
-
- let offset = line_height / 2.;
- let start_y = row as f32 * line_height - offset - scroll_top;
- let end_y = start_y + line_height;
-
- let width = 0.275 * line_height;
- let highlight_origin = bounds.origin + point(-width, start_y);
- let highlight_size = size(width * 2., end_y - start_y);
- let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
- cx.paint_quad(quad(
- highlight_bounds,
- Corners::all(1. * line_height),
- cx.theme().status().deleted,
- Edges::default(),
- transparent_black(),
- ));
+ fn calculate_relative_line_numbers(
+ &self,
+ snapshot: &EditorSnapshot,
+ rows: &Range<u32>,
+ relative_to: Option<u32>,
+ ) -> HashMap<u32, u32> {
+ let mut relative_rows: HashMap<u32, u32> = Default::default();
+ let Some(relative_to) = relative_to else {
+ return relative_rows;
+ };
- continue;
- }
- };
+ let start = rows.start.min(relative_to);
+ let end = rows.end.max(relative_to);
- let start_row = display_row_range.start;
- let end_row = display_row_range.end;
- // If we're in a multibuffer, row range span might include an
- // excerpt header, so if we were to draw the marker straight away,
- // the hunk might include the rows of that header.
- // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
- // Instead, we simply check whether the range we're dealing with includes
- // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
- let end_row_in_current_excerpt = layout
- .position_map
- .snapshot
- .blocks_in_range(start_row..end_row)
- .find_map(|(start_row, block)| {
- if matches!(block, TransformBlock::ExcerptHeader { .. }) {
- Some(start_row)
- } else {
- None
- }
- })
- .unwrap_or(end_row);
-
- let start_y = start_row as f32 * line_height - scroll_top;
- let end_y = end_row_in_current_excerpt as f32 * line_height - scroll_top;
-
- let width = 0.275 * line_height;
- let highlight_origin = bounds.origin + point(-width, start_y);
- let highlight_size = size(width * 2., end_y - start_y);
- let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
- cx.paint_quad(quad(
- highlight_bounds,
- Corners::all(0.05 * line_height),
- color,
- Edges::default(),
- transparent_black(),
- ));
- }
- }
+ let buffer_rows = snapshot
+ .buffer_rows(start)
+ .take(1 + (end - start) as usize)
+ .collect::<Vec<_>>();
- fn paint_text(
- &mut self,
- text_bounds: Bounds<Pixels>,
- layout: &mut LayoutState,
- cx: &mut ElementContext,
- ) {
- let start_row = layout.visible_display_row_range.start;
- // Offset the content_bounds from the text_bounds by the gutter margin (which is roughly half a character wide) to make hit testing work more like how we want.
- let content_origin =
- text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO);
- let line_end_overshoot = 0.15 * layout.position_map.line_height;
- let whitespace_setting = self
- .editor
- .read(cx)
- .buffer
- .read(cx)
- .settings_at(0, cx)
- .show_whitespaces;
-
- cx.with_content_mask(
- Some(ContentMask {
- bounds: text_bounds,
- }),
- |cx| {
- let interactive_text_bounds = InteractiveBounds {
- bounds: text_bounds,
- stacking_order: cx.stacking_order().clone(),
- };
- if text_bounds.contains(&cx.mouse_position()) {
- if self
- .editor
- .read(cx)
- .hovered_link_state
- .as_ref()
- .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
- {
- cx.set_cursor_style(
- CursorStyle::PointingHand,
- interactive_text_bounds.stacking_order.clone(),
- );
- } else {
- cx.set_cursor_style(
- CursorStyle::IBeam,
- interactive_text_bounds.stacking_order.clone(),
- );
- }
+ let head_idx = relative_to - start;
+ let mut delta = 1;
+ let mut i = head_idx + 1;
+ while i < buffer_rows.len() as u32 {
+ if buffer_rows[i as usize].is_some() {
+ if rows.contains(&(i + start)) {
+ relative_rows.insert(i + start, delta);
}
+ delta += 1;
+ }
+ i += 1;
+ }
+ delta = 1;
+ i = head_idx.min(buffer_rows.len() as u32 - 1);
+ while i > 0 && buffer_rows[i as usize].is_none() {
+ i -= 1;
+ }
- let fold_corner_radius = 0.15 * layout.position_map.line_height;
- cx.with_element_id(Some("folds"), |cx| {
- let snapshot = &layout.position_map.snapshot;
-
- for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) {
- let fold_range = fold.range.clone();
- let display_range = fold.range.start.to_display_point(&snapshot)
- ..fold.range.end.to_display_point(&snapshot);
- debug_assert_eq!(display_range.start.row(), display_range.end.row());
- let row = display_range.start.row();
- debug_assert!(row < layout.visible_display_row_range.end);
- let Some(line_layout) = &layout
- .position_map
- .line_layouts
- .get((row - layout.visible_display_row_range.start) as usize)
- .map(|l| &l.line)
- else {
- continue;
- };
-
- let start_x = content_origin.x
- + line_layout.x_for_index(display_range.start.column() as usize)
- - layout.position_map.scroll_position.x;
- let start_y = content_origin.y
- + row as f32 * layout.position_map.line_height
- - layout.position_map.scroll_position.y;
- let end_x = content_origin.x
- + line_layout.x_for_index(display_range.end.column() as usize)
- - layout.position_map.scroll_position.x;
-
- let fold_bounds = Bounds {
- origin: point(start_x, start_y),
- size: size(end_x - start_x, layout.position_map.line_height),
- };
-
- let fold_background = cx.with_z_index(1, |cx| {
- div()
- .id(fold.id)
- .size_full()
- .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
- .on_click(cx.listener_for(
- &self.editor,
- move |editor: &mut Editor, _, cx| {
- editor.unfold_ranges(
- [fold_range.start..fold_range.end],
- true,
- false,
- cx,
- );
- cx.stop_propagation();
- },
- ))
- .draw_and_update_state(
- fold_bounds.origin,
- fold_bounds.size,
- cx,
- |fold_element_state, cx| {
- if fold_element_state.is_active() {
- cx.theme().colors().ghost_element_active
- } else if fold_bounds.contains(&cx.mouse_position()) {
- cx.theme().colors().ghost_element_hover
- } else {
- cx.theme().colors().ghost_element_background
- }
- },
- )
- });
-
- self.paint_highlighted_range(
- display_range.clone(),
- fold_background,
- fold_corner_radius,
- fold_corner_radius * 2.,
- layout,
- content_origin,
- text_bounds,
- cx,
- );
- }
- });
-
- for (range, color) in &layout.highlighted_ranges {
- self.paint_highlighted_range(
- range.clone(),
- *color,
- Pixels::ZERO,
- line_end_overshoot,
- layout,
- content_origin,
- text_bounds,
- cx,
- );
+ while i > 0 {
+ i -= 1;
+ if buffer_rows[i as usize].is_some() {
+ if rows.contains(&(i + start)) {
+ relative_rows.insert(i + start, delta);
}
+ delta += 1;
+ }
+ }
- let mut cursors = SmallVec::<[Cursor; 32]>::new();
- let corner_radius = 0.15 * layout.position_map.line_height;
- let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
-
- for (participant_ix, (player_color, selections)) in
- layout.selections.iter().enumerate()
- {
- for selection in selections.into_iter() {
- self.paint_highlighted_range(
- selection.range.clone(),
- player_color.selection,
- corner_radius,
- corner_radius * 2.,
- layout,
- content_origin,
- text_bounds,
- cx,
- );
+ relative_rows
+ }
- if selection.is_local && !selection.range.is_empty() {
- invisible_display_ranges.push(selection.range.clone());
- }
+ fn layout_line_numbers(
+ &self,
+ rows: Range<u32>,
+ active_rows: &BTreeMap<u32, bool>,
+ newest_selection_head: Option<DisplayPoint>,
+ snapshot: &EditorSnapshot,
+ cx: &ElementContext,
+ ) -> (
+ Vec<Option<ShapedLine>>,
+ Vec<Option<(FoldStatus, BufferRow, bool)>>,
+ ) {
+ let editor = self.editor.read(cx);
+ let is_singleton = editor.is_singleton(cx);
+ let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
+ let newest = editor.selections.newest::<Point>(cx);
+ SelectionLayout::new(
+ newest,
+ editor.selections.line_mode,
+ editor.cursor_shape,
+ &snapshot.display_snapshot,
+ true,
+ true,
+ None,
+ )
+ .head
+ });
+ let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
+ let include_line_numbers =
+ EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full;
+ let include_fold_statuses =
+ EditorSettings::get_global(cx).gutter.folds && snapshot.mode == EditorMode::Full;
+ let mut shaped_line_numbers = Vec::with_capacity(rows.len());
+ let mut fold_statuses = Vec::with_capacity(rows.len());
+ let mut line_number = String::new();
+ let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
+ let relative_to = if is_relative {
+ Some(newest_selection_head.row())
+ } else {
+ None
+ };
- if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
- let cursor_position = selection.head;
- if layout
- .visible_display_row_range
- .contains(&cursor_position.row())
- {
- let cursor_row_layout = &layout.position_map.line_layouts
- [(cursor_position.row() - start_row) as usize]
- .line;
- let cursor_column = cursor_position.column() as usize;
-
- let cursor_character_x =
- cursor_row_layout.x_for_index(cursor_column);
- let mut block_width = cursor_row_layout
- .x_for_index(cursor_column + 1)
- - cursor_character_x;
- if block_width == Pixels::ZERO {
- block_width = layout.position_map.em_width;
- }
- let block_text = if let CursorShape::Block = selection.cursor_shape
- {
- layout
- .position_map
- .snapshot
- .chars_at(cursor_position)
- .next()
- .and_then(|(character, _)| {
- let text = if character == '\n' {
- SharedString::from(" ")
- } else {
- SharedString::from(character.to_string())
- };
- let len = text.len();
- cx.text_system()
- .shape_line(
- text,
- cursor_row_layout.font_size,
- &[TextRun {
- len,
- font: self.style.text.font(),
- color: self.style.background,
- background_color: None,
- strikethrough: None,
- underline: None,
- }],
- )
- .log_err()
- })
- } else {
- None
- };
+ let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to);
- let x = cursor_character_x - layout.position_map.scroll_position.x;
- let y = cursor_position.row() as f32
- * layout.position_map.line_height
- - layout.position_map.scroll_position.y;
- if selection.is_newest {
- self.editor.update(cx, |editor, _| {
- editor.pixel_position_of_newest_cursor = Some(point(
- text_bounds.origin.x + x + block_width / 2.,
- text_bounds.origin.y
- + y
- + layout.position_map.line_height / 2.,
- ))
- });
- }
-
- cursors.push(Cursor {
- color: player_color.cursor,
- block_width,
- origin: point(x, y),
- line_height: layout.position_map.line_height,
- shape: selection.cursor_shape,
- block_text,
- cursor_name: selection.user_name.clone().map(|name| {
- CursorName {
- string: name,
- color: self.style.background,
- is_top_row: cursor_position.row() == 0,
- z_index: (participant_ix % 256).try_into().unwrap(),
- }
- }),
- });
- }
- }
- }
+ for (ix, row) in snapshot
+ .buffer_rows(rows.start)
+ .take((rows.end - rows.start) as usize)
+ .enumerate()
+ {
+ let display_row = rows.start + ix as u32;
+ let (active, color) = if active_rows.contains_key(&display_row) {
+ (true, cx.theme().colors().editor_active_line_number)
+ } else {
+ (false, cx.theme().colors().editor_line_number)
+ };
+ if let Some(buffer_row) = row {
+ if include_line_numbers {
+ line_number.clear();
+ let default_number = buffer_row + 1;
+ let number = relative_rows
+ .get(&(ix as u32 + rows.start))
+ .unwrap_or(&default_number);
+ write!(&mut line_number, "{}", number).unwrap();
+ let run = TextRun {
+ len: line_number.len(),
+ font: self.style.text.font(),
+ color,
+ background_color: None,
+ underline: None,
+ strikethrough: None,
+ };
+ let shaped_line = cx
+ .text_system()
+ .shape_line(line_number.clone().into(), font_size, &[run])
+ .unwrap();
+ shaped_line_numbers.push(Some(shaped_line));
}
-
- for (ix, line_with_invisibles) in
- layout.position_map.line_layouts.iter().enumerate()
- {
- let row = start_row + ix as u32;
- line_with_invisibles.draw(
- layout,
- row,
- content_origin,
- whitespace_setting,
- &invisible_display_ranges,
- cx,
+ if include_fold_statuses {
+ fold_statuses.push(
+ is_singleton
+ .then(|| {
+ snapshot
+ .fold_for_line(buffer_row)
+ .map(|fold_status| (fold_status, buffer_row, active))
+ })
+ .flatten(),
)
}
+ } else {
+ fold_statuses.push(None);
+ shaped_line_numbers.push(None);
+ }
+ }
- cx.with_z_index(0, |cx| self.paint_redactions(text_bounds, &layout, cx));
-
- cx.with_z_index(1, |cx| {
- for cursor in cursors {
- cursor.paint(content_origin, cx);
- }
- });
- },
- )
+ (shaped_line_numbers, fold_statuses)
}
- fn paint_redactions(
- &mut self,
- text_bounds: Bounds<Pixels>,
- layout: &LayoutState,
- cx: &mut ElementContext,
- ) {
- let content_origin =
- text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO);
- let line_end_overshoot = layout.line_end_overshoot();
+ fn layout_lines(
+ &self,
+ rows: Range<u32>,
+ line_number_layouts: &[Option<ShapedLine>],
+ snapshot: &EditorSnapshot,
+ cx: &ElementContext,
+ ) -> Vec<LineWithInvisibles> {
+ if rows.start >= rows.end {
+ return Vec::new();
+ }
- // A softer than perfect black
- let redaction_color = gpui::rgb(0x0e1111);
+ // Show the placeholder when the editor is empty
+ if snapshot.is_empty() {
+ let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
+ let placeholder_color = cx.theme().colors().text_placeholder;
+ let placeholder_text = snapshot.placeholder_text();
- for range in layout.redacted_ranges.iter() {
- self.paint_highlighted_range(
- range.clone(),
- redaction_color.into(),
- Pixels::ZERO,
- line_end_overshoot,
- layout,
- content_origin,
- text_bounds,
+ let placeholder_lines = placeholder_text
+ .as_ref()
+ .map_or("", AsRef::as_ref)
+ .split('\n')
+ .skip(rows.start as usize)
+ .chain(iter::repeat(""))
+ .take(rows.len());
+ placeholder_lines
+ .filter_map(move |line| {
+ let run = TextRun {
+ len: line.len(),
+ font: self.style.text.font(),
+ color: placeholder_color,
+ background_color: None,
+ underline: Default::default(),
+ strikethrough: None,
+ };
+ cx.text_system()
+ .shape_line(line.to_string().into(), font_size, &[run])
+ .log_err()
+ })
+ .map(|line| LineWithInvisibles {
+ line,
+ invisibles: Vec::new(),
+ })
+ .collect()
+ } else {
+ let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
+ LineWithInvisibles::from_chunks(
+ chunks,
+ &self.style.text,
+ MAX_LINE_LEN,
+ rows.len(),
+ line_number_layouts,
+ snapshot.mode,
cx,
- );
+ )
}
}
- fn paint_overlays(
- &mut self,
- text_bounds: Bounds<Pixels>,
- layout: &mut LayoutState,
+ #[allow(clippy::too_many_arguments)]
+ fn build_blocks(
+ &self,
+ rows: Range<u32>,
+ snapshot: &EditorSnapshot,
+ hitbox: &Hitbox,
+ text_hitbox: &Hitbox,
+ scroll_width: &mut Pixels,
+ gutter_dimensions: &GutterDimensions,
+ em_width: Pixels,
+ text_x: Pixels,
+ line_height: Pixels,
+ line_layouts: &[LineWithInvisibles],
cx: &mut ElementContext,
- ) {
- let content_origin =
- text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO);
- let start_row = layout.visible_display_row_range.start;
- if let Some((position, mut context_menu)) = layout.context_menu.take() {
- let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
- let context_menu_size = context_menu.measure(available_space, cx);
-
- let cursor_row_layout =
- &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
- let x = cursor_row_layout.x_for_index(position.column() as usize)
- - layout.position_map.scroll_position.x;
- let y = (position.row() + 1) as f32 * layout.position_map.line_height
- - layout.position_map.scroll_position.y;
- let mut list_origin = content_origin + point(x, y);
- let list_width = context_menu_size.width;
- let list_height = context_menu_size.height;
-
- // Snap the right edge of the list to the right edge of the window if
- // its horizontal bounds overflow.
- if list_origin.x + list_width > cx.viewport_size().width {
- list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
- }
-
- if list_origin.y + list_height > text_bounds.lower_right().y {
- list_origin.y -= layout.position_map.line_height + list_height;
- }
-
- cx.break_content_mask(|cx| context_menu.draw(list_origin, available_space, cx));
- }
-
- if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() {
- let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
-
- // This is safe because we check on layout whether the required row is available
- let hovered_row_layout =
- &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
-
- // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
- // height. This is the size we will use to decide whether to render popovers above or below
- // the hovered line.
- let first_size = hover_popovers[0].measure(available_space, cx);
- let height_to_reserve =
- first_size.height + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height;
-
- // Compute Hovered Point
- let x = hovered_row_layout.x_for_index(position.column() as usize)
- - layout.position_map.scroll_position.x;
- let y = position.row() as f32 * layout.position_map.line_height
- - layout.position_map.scroll_position.y;
- let hovered_point = content_origin + point(x, y);
-
- if hovered_point.y - height_to_reserve > Pixels::ZERO {
- // There is enough space above. Render popovers above the hovered point
- let mut current_y = hovered_point.y;
- for mut hover_popover in hover_popovers {
- let size = hover_popover.measure(available_space, cx);
- let mut popover_origin = point(hovered_point.x, current_y - size.height);
-
- let x_out_of_bounds =
- text_bounds.upper_right().x - (popover_origin.x + size.width);
- if x_out_of_bounds < Pixels::ZERO {
- popover_origin.x = popover_origin.x + x_out_of_bounds;
- }
-
- if cx.was_top_layer(&popover_origin, cx.stacking_order()) {
- cx.break_content_mask(|cx| {
- hover_popover.draw(popover_origin, available_space, cx)
- });
- }
+ ) -> Vec<BlockLayout> {
+ let mut block_id = 0;
+ let (fixed_blocks, non_fixed_blocks) = snapshot
+ .blocks_in_range(rows.clone())
+ .partition::<Vec<_>, _>(|(_, block)| match block {
+ TransformBlock::ExcerptHeader { .. } => false,
+ TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
+ });
- current_y = popover_origin.y - HOVER_POPOVER_GAP;
- }
- } else {
- // There is not enough space above. Render popovers below the hovered point
- let mut current_y = hovered_point.y + layout.position_map.line_height;
- for mut hover_popover in hover_popovers {
- let size = hover_popover.measure(available_space, cx);
- let mut popover_origin = point(hovered_point.x, current_y);
-
- let x_out_of_bounds =
- text_bounds.upper_right().x - (popover_origin.x + size.width);
- if x_out_of_bounds < Pixels::ZERO {
- popover_origin.x = popover_origin.x + x_out_of_bounds;
- }
+ let render_block = |block: &TransformBlock,
+ available_space: Size<AvailableSpace>,
+ block_id: usize,
+ cx: &mut ElementContext| {
+ let mut element = match block {
+ TransformBlock::Custom(block) => {
+ let align_to = block
+ .position()
+ .to_point(&snapshot.buffer_snapshot)
+ .to_display_point(snapshot);
+ let anchor_x = text_x
+ + if rows.contains(&align_to.row()) {
+ line_layouts[(align_to.row() - rows.start) as usize]
+ .line
+ .x_for_index(align_to.column() as usize)
+ } else {
+ layout_line(align_to.row(), snapshot, &self.style, cx)
+ .unwrap()
+ .x_for_index(align_to.column() as usize)
+ };
+
+ block.render(&mut BlockContext {
+ context: cx,
+ anchor_x,
+ gutter_dimensions,
+ line_height,
+ em_width,
+ block_id,
+ max_width: text_hitbox.size.width.max(*scroll_width),
+ editor_style: &self.style,
+ })
+ }
+
+ TransformBlock::ExcerptHeader {
+ buffer,
+ range,
+ starts_new_buffer,
+ ..
+ } => {
+ let include_root = self
+ .editor
+ .read(cx)
+ .project
+ .as_ref()
+ .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
+ .unwrap_or_default();
+
+ let jump_handler = project::File::from_dyn(buffer.file()).map(|file| {
+ let jump_path = ProjectPath {
+ worktree_id: file.worktree_id(cx),
+ path: file.path.clone(),
+ };
+ let jump_anchor = range
+ .primary
+ .as_ref()
+ .map_or(range.context.start, |primary| primary.start);
+ let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
+
+ cx.listener_for(&self.editor, move |editor, _, cx| {
+ editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
+ })
+ });
- hover_popover.draw(popover_origin, available_space, cx);
+ let element = if *starts_new_buffer {
+ let path = buffer.resolve_file_path(cx, include_root);
+ let mut filename = None;
+ let mut parent_path = None;
+ // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
+ if let Some(path) = path {
+ filename = path.file_name().map(|f| f.to_string_lossy().to_string());
+ parent_path = path
+ .parent()
+ .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
+ }
- current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
+ v_flex()
+ .id(("path header container", block_id))
+ .size_full()
+ .justify_center()
+ .p(gpui::px(6.))
+ .child(
+ h_flex()
+ .id("path header block")
+ .size_full()
+ .pl(gpui::px(12.))
+ .pr(gpui::px(8.))
+ .rounded_md()
+ .shadow_md()
+ .border()
+ .border_color(cx.theme().colors().border)
+ .bg(cx.theme().colors().editor_subheader_background)
+ .justify_between()
+ .hover(|style| style.bg(cx.theme().colors().element_hover))
+ .child(
+ h_flex().gap_3().child(
+ h_flex()
+ .gap_2()
+ .child(
+ filename
+ .map(SharedString::from)
+ .unwrap_or_else(|| "untitled".into()),
+ )
+ .when_some(parent_path, |then, path| {
+ then.child(
+ div().child(path).text_color(
+ cx.theme().colors().text_muted,
+ ),
+ )
+ }),
+ ),
+ )
+ .when_some(jump_handler, |this, jump_handler| {
+ this.cursor_pointer()
+ .tooltip(|cx| {
+ Tooltip::for_action(
+ "Jump to Buffer",
+ &OpenExcerpts,
+ cx,
+ )
+ })
+ .on_mouse_down(MouseButton::Left, |_, cx| {
+ cx.stop_propagation()
+ })
+ .on_click(jump_handler)
+ }),
+ )
+ } else {
+ h_flex()
+ .id(("collapsed context", block_id))
+ .size_full()
+ .gap(gutter_dimensions.left_padding + gutter_dimensions.right_padding)
+ .child(
+ h_flex()
+ .justify_end()
+ .flex_none()
+ .w(gutter_dimensions.width
+ - (gutter_dimensions.left_padding
+ + gutter_dimensions.right_padding))
+ .h_full()
+ .text_buffer(cx)
+ .text_color(cx.theme().colors().editor_line_number)
+ .child("..."),
+ )
+ .child(
+ ButtonLike::new("jump to collapsed context")
+ .style(ButtonStyle::Transparent)
+ .full_width()
+ .child(
+ div()
+ .h_px()
+ .w_full()
+ .bg(cx.theme().colors().border_variant)
+ .group_hover("", |style| {
+ style.bg(cx.theme().colors().border)
+ }),
+ )
+ .when_some(jump_handler, |this, jump_handler| {
+ this.on_click(jump_handler).tooltip(|cx| {
+ Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx)
+ })
+ }),
+ )
+ };
+ element.into_any()
}
- }
- }
+ };
- if let Some(mouse_context_menu) = self.editor.read(cx).mouse_context_menu.as_ref() {
- let element = overlay()
- .position(mouse_context_menu.position)
- .child(mouse_context_menu.context_menu.clone())
- .anchor(AnchorCorner::TopLeft)
- .snap_to_window();
- element.into_any().draw(
- gpui::Point::default(),
- size(AvailableSpace::MinContent, AvailableSpace::MinContent),
- cx,
+ let size = element.measure(available_space, cx);
+ (element, size)
+ };
+
+ let mut fixed_block_max_width = Pixels::ZERO;
+ let mut blocks = Vec::new();
+ for (row, block) in fixed_blocks {
+ let available_space = size(
+ AvailableSpace::MinContent,
+ AvailableSpace::Definite(block.height() as f32 * line_height),
);
+ let (element, element_size) = render_block(block, available_space, block_id, cx);
+ block_id += 1;
+ fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
+ blocks.push(BlockLayout {
+ row,
+ element,
+ available_space,
+ style: BlockStyle::Fixed,
+ });
+ }
+ for (row, block) in non_fixed_blocks {
+ let style = match block {
+ TransformBlock::Custom(block) => block.style(),
+ TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
+ };
+ let width = match style {
+ BlockStyle::Sticky => hitbox.size.width,
+ BlockStyle::Flex => hitbox
+ .size
+ .width
+ .max(fixed_block_max_width)
+ .max(gutter_dimensions.width + *scroll_width),
+ BlockStyle::Fixed => unreachable!(),
+ };
+ let available_space = size(
+ AvailableSpace::Definite(width),
+ AvailableSpace::Definite(block.height() as f32 * line_height),
+ );
+ let (element, _) = render_block(block, available_space, block_id, cx);
+ block_id += 1;
+ blocks.push(BlockLayout {
+ row,
+ element,
+ available_space,
+ style,
+ });
}
- }
- fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
- bounds.upper_right().x - self.style.scrollbar_width
+ *scroll_width = (*scroll_width).max(fixed_block_max_width - gutter_dimensions.width);
+ blocks
}
- fn paint_scrollbar(
- &mut self,
- bounds: Bounds<Pixels>,
- layout: &mut LayoutState,
+ fn layout_blocks(
+ &self,
+ blocks: &mut Vec<BlockLayout>,
+ hitbox: &Hitbox,
+ line_height: Pixels,
+ scroll_pixel_position: gpui::Point<Pixels>,
cx: &mut ElementContext,
) {
- if layout.mode != EditorMode::Full {
- return;
+ for block in blocks {
+ let mut origin = hitbox.origin
+ + point(
+ Pixels::ZERO,
+ block.row as f32 * line_height - scroll_pixel_position.y,
+ );
+ if !matches!(block.style, BlockStyle::Sticky) {
+ origin += point(-scroll_pixel_position.x, Pixels::ZERO);
+ }
+ block.element.layout(origin, block.available_space, cx);
}
+ }
- // If a drag took place after we started dragging the scrollbar,
- // cancel the scrollbar drag.
- if cx.has_active_drag() {
- self.editor.update(cx, |editor, cx| {
- editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
- });
- }
+ #[allow(clippy::too_many_arguments)]
+ fn layout_context_menu(
+ &self,
+ line_height: Pixels,
+ hitbox: &Hitbox,
+ text_hitbox: &Hitbox,
+ content_origin: gpui::Point<Pixels>,
+ start_row: u32,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ line_layouts: &[LineWithInvisibles],
+ newest_selection_head: DisplayPoint,
+ cx: &mut ElementContext,
+ ) -> bool {
+ let max_height = cmp::min(
+ 12. * line_height,
+ cmp::max(3. * line_height, (hitbox.size.height - line_height) / 2.),
+ );
+ let Some((position, mut context_menu)) = self.editor.update(cx, |editor, cx| {
+ if editor.context_menu_visible() {
+ editor.render_context_menu(newest_selection_head, &self.style, max_height, cx)
+ } else {
+ None
+ }
+ }) else {
+ return false;
+ };
- let top = bounds.origin.y;
- let bottom = bounds.lower_left().y;
- let right = bounds.lower_right().x;
- let left = self.scrollbar_left(&bounds);
- let row_range = layout.scrollbar_row_range.clone();
- let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
+ let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
+ let context_menu_size = context_menu.measure(available_space, cx);
- let mut height = bounds.size.height;
- let mut first_row_y_offset = px(0.0);
+ let cursor_row_layout = &line_layouts[(position.row() - start_row) as usize].line;
+ let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
+ let y = (position.row() + 1) as f32 * line_height - scroll_pixel_position.y;
+ let mut list_origin = content_origin + point(x, y);
+ let list_width = context_menu_size.width;
+ let list_height = context_menu_size.height;
- // Impose a minimum height on the scrollbar thumb
- let row_height = height / max_row;
- let min_thumb_height = layout.position_map.line_height;
- let thumb_height = (row_range.end - row_range.start) * row_height;
- if thumb_height < min_thumb_height {
- first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
- height -= min_thumb_height - thumb_height;
+ // Snap the right edge of the list to the right edge of the window if
+ // its horizontal bounds overflow.
+ if list_origin.x + list_width > cx.viewport_size().width {
+ list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
}
- let y_for_row = |row: f32| -> Pixels { top + first_row_y_offset + row * row_height };
-
- let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
- let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
- let track_bounds = Bounds::from_corners(point(left, top), point(right, bottom));
- let thumb_bounds = Bounds::from_corners(point(left, thumb_top), point(right, thumb_bottom));
-
- if layout.show_scrollbars {
- cx.paint_quad(quad(
- track_bounds,
- Corners::default(),
- cx.theme().colors().scrollbar_track_background,
- Edges {
- top: Pixels::ZERO,
- right: Pixels::ZERO,
- bottom: Pixels::ZERO,
- left: px(1.),
- },
- cx.theme().colors().scrollbar_track_border,
- ));
- let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
- if layout.is_singleton && scrollbar_settings.selections {
- let start_anchor = Anchor::min();
- let end_anchor = Anchor::max();
- let background_ranges = self
- .editor
- .read(cx)
- .background_highlight_row_ranges::<BufferSearchHighlights>(
- start_anchor..end_anchor,
- &layout.position_map.snapshot,
- 50000,
- );
- for range in background_ranges {
- let start_y = y_for_row(range.start().row() as f32);
- let mut end_y = y_for_row(range.end().row() as f32);
- if end_y - start_y < px(1.) {
- end_y = start_y + px(1.);
- }
- let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
- cx.paint_quad(quad(
- bounds,
- Corners::default(),
- cx.theme().status().info,
- Edges {
- top: Pixels::ZERO,
- right: px(1.),
- bottom: Pixels::ZERO,
- left: px(1.),
- },
- cx.theme().colors().scrollbar_thumb_border,
- ));
- }
- }
+ if list_origin.y + list_height > text_hitbox.lower_right().y {
+ list_origin.y -= line_height + list_height;
+ }
- if layout.is_singleton && scrollbar_settings.symbols_selections {
- let selection_ranges = self.editor.read(cx).background_highlights_in_range(
- Anchor::min()..Anchor::max(),
- &layout.position_map.snapshot,
- cx.theme().colors(),
- );
- for hunk in selection_ranges {
- let start_display = Point::new(hunk.0.start.row(), 0)
- .to_display_point(&layout.position_map.snapshot.display_snapshot);
- let end_display = Point::new(hunk.0.end.row(), 0)
- .to_display_point(&layout.position_map.snapshot.display_snapshot);
- let start_y = y_for_row(start_display.row() as f32);
- let mut end_y = if hunk.0.start == hunk.0.end {
- y_for_row((end_display.row() + 1) as f32)
- } else {
- y_for_row((end_display.row()) as f32)
- };
+ cx.defer_draw(context_menu, list_origin, 1);
+ true
+ }
- if end_y - start_y < px(1.) {
- end_y = start_y + px(1.);
- }
- let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
-
- cx.paint_quad(quad(
- bounds,
- Corners::default(),
- cx.theme().status().info,
- Edges {
- top: Pixels::ZERO,
- right: px(1.),
- bottom: Pixels::ZERO,
- left: px(1.),
- },
- cx.theme().colors().scrollbar_thumb_border,
- ));
+ fn layout_mouse_context_menu(&self, cx: &mut ElementContext) -> Option<AnyElement> {
+ let mouse_context_menu = self.editor.read(cx).mouse_context_menu.as_ref()?;
+ let mut element = overlay()
+ .position(mouse_context_menu.position)
+ .child(mouse_context_menu.context_menu.clone())
+ .anchor(AnchorCorner::TopLeft)
+ .snap_to_window()
+ .into_any();
+ element.layout(gpui::Point::default(), AvailableSpace::min_size(), cx);
+ Some(element)
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn layout_hover_popovers(
+ &self,
+ snapshot: &EditorSnapshot,
+ hitbox: &Hitbox,
+ text_hitbox: &Hitbox,
+ visible_display_row_range: Range<u32>,
+ content_origin: gpui::Point<Pixels>,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ line_layouts: &[LineWithInvisibles],
+ line_height: Pixels,
+ em_width: Pixels,
+ cx: &mut ElementContext,
+ ) {
+ let max_size = size(
+ (120. * em_width) // Default size
+ .min(hitbox.size.width / 2.) // Shrink to half of the editor width
+ .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
+ (16. * line_height) // Default size
+ .min(hitbox.size.height / 2.) // Shrink to half of the editor height
+ .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
+ );
+
+ let hover_popovers = self.editor.update(cx, |editor, cx| {
+ editor.hover_state.render(
+ &snapshot,
+ &self.style,
+ visible_display_row_range.clone(),
+ max_size,
+ editor.workspace.as_ref().map(|(w, _)| w.clone()),
+ cx,
+ )
+ });
+ let Some((position, mut hover_popovers)) = hover_popovers else {
+ return;
+ };
+
+ let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
+
+ // This is safe because we check on layout whether the required row is available
+ let hovered_row_layout =
+ &line_layouts[(position.row() - visible_display_row_range.start) as usize].line;
+
+ // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
+ // height. This is the size we will use to decide whether to render popovers above or below
+ // the hovered line.
+ let first_size = hover_popovers[0].measure(available_space, cx);
+ let height_to_reserve = first_size.height + 1.5 * MIN_POPOVER_LINE_HEIGHT * line_height;
+
+ // Compute Hovered Point
+ let x =
+ hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
+ let y = position.row() as f32 * line_height - scroll_pixel_position.y;
+ let hovered_point = content_origin + point(x, y);
+
+ if hovered_point.y - height_to_reserve > Pixels::ZERO {
+ // There is enough space above. Render popovers above the hovered point
+ let mut current_y = hovered_point.y;
+ for mut hover_popover in hover_popovers {
+ let size = hover_popover.measure(available_space, cx);
+ let mut popover_origin = point(hovered_point.x, current_y - size.height);
+
+ let x_out_of_bounds = text_hitbox.upper_right().x - (popover_origin.x + size.width);
+ if x_out_of_bounds < Pixels::ZERO {
+ popover_origin.x = popover_origin.x + x_out_of_bounds;
+ }
+
+ cx.defer_draw(hover_popover, popover_origin, 2);
+
+ current_y = popover_origin.y - HOVER_POPOVER_GAP;
+ }
+ } else {
+ // There is not enough space above. Render popovers below the hovered point
+ let mut current_y = hovered_point.y + line_height;
+ for mut hover_popover in hover_popovers {
+ let size = hover_popover.measure(available_space, cx);
+ let mut popover_origin = point(hovered_point.x, current_y);
+
+ let x_out_of_bounds = text_hitbox.upper_right().x - (popover_origin.x + size.width);
+ if x_out_of_bounds < Pixels::ZERO {
+ popover_origin.x = popover_origin.x + x_out_of_bounds;
}
+
+ cx.defer_draw(hover_popover, popover_origin, 2);
+
+ current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
}
+ }
+ }
- if layout.is_singleton && scrollbar_settings.git_diff {
- for hunk in layout
- .position_map
- .snapshot
- .buffer_snapshot
- .git_diff_hunks_in_range(0..(max_row.floor() as u32))
- {
- let start_display = Point::new(hunk.associated_range.start, 0)
- .to_display_point(&layout.position_map.snapshot.display_snapshot);
- let end_display = Point::new(hunk.associated_range.end, 0)
- .to_display_point(&layout.position_map.snapshot.display_snapshot);
- let start_y = y_for_row(start_display.row() as f32);
- let mut end_y = if hunk.associated_range.start == hunk.associated_range.end {
- y_for_row((end_display.row() + 1) as f32)
- } else {
- y_for_row((end_display.row()) as f32)
- };
+ fn paint_background(&self, layout: &EditorLayout, cx: &mut ElementContext) {
+ cx.paint_layer(layout.hitbox.bounds, |cx| {
+ let scroll_top =
+ layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
+ let gutter_bg = cx.theme().colors().editor_gutter_background;
+ cx.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg));
+ cx.paint_quad(fill(layout.text_hitbox.bounds, self.style.background));
+
+ if let EditorMode::Full = layout.mode {
+ let mut active_rows = layout.active_rows.iter().peekable();
+ while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
+ let mut end_row = *start_row;
+ while active_rows.peek().map_or(false, |r| {
+ *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
+ }) {
+ active_rows.next().unwrap();
+ end_row += 1;
+ }
- if end_y - start_y < px(1.) {
- end_y = start_y + px(1.);
+ if !contains_non_empty_selection {
+ let origin = point(
+ layout.hitbox.origin.x,
+ layout.hitbox.origin.y
+ + (layout.position_map.line_height * *start_row as f32)
+ - scroll_top,
+ );
+ let size = size(
+ layout.hitbox.size.width,
+ layout.position_map.line_height * (end_row - start_row + 1) as f32,
+ );
+ let active_line_bg = cx.theme().colors().editor_active_line_background;
+ cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
}
- let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
+ }
- let color = match hunk.status() {
- DiffHunkStatus::Added => cx.theme().status().created,
- DiffHunkStatus::Modified => cx.theme().status().modified,
- DiffHunkStatus::Removed => cx.theme().status().deleted,
+ let mut paint_highlight =
+ |highlight_row_start: u32, highlight_row_end: u32, color| {
+ let origin = point(
+ layout.hitbox.origin.x,
+ layout.hitbox.origin.y
+ + (layout.position_map.line_height * highlight_row_start as f32)
+ - scroll_top,
+ );
+ let size = size(
+ layout.hitbox.size.width,
+ layout.position_map.line_height
+ * (highlight_row_end + 1 - highlight_row_start) as f32,
+ );
+ cx.paint_quad(fill(Bounds { origin, size }, color));
};
- cx.paint_quad(quad(
- bounds,
- Corners::default(),
- color,
- Edges {
- top: Pixels::ZERO,
- right: px(1.),
- bottom: Pixels::ZERO,
- left: px(1.),
- },
- cx.theme().colors().scrollbar_thumb_border,
- ));
- }
- }
- if layout.is_singleton && scrollbar_settings.diagnostics {
- let max_point = layout
- .position_map
- .snapshot
- .display_snapshot
- .buffer_snapshot
- .max_point();
+ let mut last_row = None;
+ let mut highlight_row_start = 0u32;
+ let mut highlight_row_end = 0u32;
+ for (&row, &color) in &layout.highlighted_rows {
+ let paint = last_row.map_or(false, |(last_row, last_color)| {
+ last_color != color || last_row + 1 < row
+ });
- let diagnostics = layout
- .position_map
- .snapshot
- .buffer_snapshot
- .diagnostics_in_range::<_, Point>(Point::zero()..max_point, false)
- // We want to sort by severity, in order to paint the most severe diagnostics last.
- .sorted_by_key(|diagnostic| std::cmp::Reverse(diagnostic.diagnostic.severity));
-
- for diagnostic in diagnostics {
- let start_display = diagnostic
- .range
- .start
- .to_display_point(&layout.position_map.snapshot.display_snapshot);
- let end_display = diagnostic
- .range
- .end
- .to_display_point(&layout.position_map.snapshot.display_snapshot);
- let start_y = y_for_row(start_display.row() as f32);
- let mut end_y = if diagnostic.range.start == diagnostic.range.end {
- y_for_row((end_display.row() + 1) as f32)
+ if paint {
+ let paint_range_is_unfinished = highlight_row_end == 0;
+ if paint_range_is_unfinished {
+ highlight_row_end = row;
+ last_row = None;
+ }
+ paint_highlight(highlight_row_start, highlight_row_end, color);
+ highlight_row_start = 0;
+ highlight_row_end = 0;
+ if !paint_range_is_unfinished {
+ highlight_row_start = row;
+ last_row = Some((row, color));
+ }
} else {
- y_for_row((end_display.row()) as f32)
- };
+ if last_row.is_none() {
+ highlight_row_start = row;
+ } else {
+ highlight_row_end = row;
+ }
+ last_row = Some((row, color));
+ }
+ }
+ if let Some((row, hsla)) = last_row {
+ highlight_row_end = row;
+ paint_highlight(highlight_row_start, highlight_row_end, hsla);
+ }
+
+ let scroll_left =
+ layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
- if end_y - start_y < px(1.) {
- end_y = start_y + px(1.);
+ for (wrap_position, active) in layout.wrap_guides.iter() {
+ let x = (layout.text_hitbox.origin.x
+ + *wrap_position
+ + layout.position_map.em_width / 2.)
+ - scroll_left;
+
+ let show_scrollbars = layout
+ .scrollbar_layout
+ .as_ref()
+ .map_or(false, |scrollbar| scrollbar.visible);
+ if x < layout.text_hitbox.origin.x
+ || (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
+ {
+ continue;
}
- let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
- let color = match diagnostic.diagnostic.severity {
- DiagnosticSeverity::ERROR => cx.theme().status().error,
- DiagnosticSeverity::WARNING => cx.theme().status().warning,
- DiagnosticSeverity::INFORMATION => cx.theme().status().info,
- _ => cx.theme().status().hint,
+ let color = if *active {
+ cx.theme().colors().editor_active_wrap_guide
+ } else {
+ cx.theme().colors().editor_wrap_guide
};
- cx.paint_quad(quad(
- bounds,
- Corners::default(),
- color,
- Edges {
- top: Pixels::ZERO,
- right: px(1.),
- bottom: Pixels::ZERO,
- left: px(1.),
+ cx.paint_quad(fill(
+ Bounds {
+ origin: point(x, layout.text_hitbox.origin.y),
+ size: size(px(1.), layout.text_hitbox.size.height),
},
- cx.theme().colors().scrollbar_thumb_border,
+ color,
));
}
}
+ })
+ }
- cx.paint_quad(quad(
- thumb_bounds,
- Corners::default(),
- cx.theme().colors().scrollbar_thumb_background,
- Edges {
- top: Pixels::ZERO,
- right: px(1.),
- bottom: Pixels::ZERO,
- left: px(1.),
- },
- cx.theme().colors().scrollbar_thumb_border,
- ));
+ fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
+ let line_height = layout.position_map.line_height;
+
+ let scroll_position = layout.position_map.snapshot.scroll_position();
+ let scroll_top = scroll_position.y * line_height;
+
+ cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
+
+ let show_git_gutter = matches!(
+ ProjectSettings::get_global(cx).git.git_gutter,
+ Some(GitGutterSetting::TrackedFiles)
+ );
+
+ if show_git_gutter {
+ Self::paint_diff_hunks(layout, cx);
}
- let interactive_track_bounds = InteractiveBounds {
- bounds: track_bounds,
- stacking_order: cx.stacking_order().clone(),
- };
- let mut mouse_position = cx.mouse_position();
- if track_bounds.contains(&mouse_position) {
- cx.set_cursor_style(
- CursorStyle::Arrow,
- interactive_track_bounds.stacking_order.clone(),
- );
+ for (ix, line) in layout.line_numbers.iter().enumerate() {
+ if let Some(line) = line {
+ let line_origin = layout.gutter_hitbox.origin
+ + point(
+ layout.gutter_hitbox.size.width
+ - line.width
+ - layout.gutter_dimensions.right_padding,
+ ix as f32 * line_height - (scroll_top % line_height),
+ );
+
+ line.paint(line_origin, line_height, cx).log_err();
+ }
}
- cx.on_mouse_event({
- let editor = self.editor.clone();
- move |event: &MouseMoveEvent, phase, cx| {
- if phase == DispatchPhase::Capture {
- return;
+ cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
+ cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
+ for fold_indicator in layout.fold_indicators.iter_mut().flatten() {
+ fold_indicator.paint(cx);
}
+ });
- editor.update(cx, |editor, cx| {
- if event.pressed_button == Some(MouseButton::Left)
- && editor.scroll_manager.is_dragging_scrollbar()
- {
- let y = mouse_position.y;
- let new_y = event.position.y;
- if (track_bounds.top()..track_bounds.bottom()).contains(&y) {
- let mut position = editor.scroll_position(cx);
- position.y += (new_y - y) * max_row / height;
- if position.y < 0.0 {
- position.y = 0.0;
- }
- editor.set_scroll_position(position, cx);
- }
-
- mouse_position = event.position;
- cx.stop_propagation();
- } else {
- editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
- if interactive_track_bounds.visibly_contains(&event.position, cx) {
- editor.scroll_manager.show_scrollbar(cx);
- }
- }
- })
+ if let Some(indicator) = layout.code_actions_indicator.as_mut() {
+ indicator.paint(cx);
}
- });
+ })
+ }
- if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
- cx.on_mouse_event({
- let editor = self.editor.clone();
- move |_: &MouseUpEvent, phase, cx| {
- if phase == DispatchPhase::Capture {
- return;
- }
+ fn paint_diff_hunks(layout: &EditorLayout, cx: &mut ElementContext) {
+ if layout.display_hunks.is_empty() {
+ return;
+ }
- editor.update(cx, |editor, cx| {
- editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
- cx.stop_propagation();
- });
- }
- });
- } else {
- cx.on_mouse_event({
- let editor = self.editor.clone();
- move |event: &MouseDownEvent, phase, cx| {
- if phase == DispatchPhase::Capture {
- return;
+ let line_height = layout.position_map.line_height;
+
+ let scroll_position = layout.position_map.snapshot.scroll_position();
+ let scroll_top = scroll_position.y * line_height;
+
+ cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
+ for hunk in &layout.display_hunks {
+ let (display_row_range, status) = match hunk {
+ //TODO: This rendering is entirely a horrible hack
+ &DisplayDiffHunk::Folded { display_row: row } => {
+ let start_y = row as f32 * line_height - scroll_top;
+ let end_y = start_y + line_height;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = layout.gutter_hitbox.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(quad(
+ highlight_bounds,
+ Corners::all(1. * line_height),
+ cx.theme().status().modified,
+ Edges::default(),
+ transparent_black(),
+ ));
+
+ continue;
}
- editor.update(cx, |editor, cx| {
- if track_bounds.contains(&event.position) {
- editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
-
- let y = event.position.y;
- if y < thumb_top || thumb_bottom < y {
- let center_row = ((y - top) * max_row / height).round() as u32;
- let top_row = center_row
- .saturating_sub((row_range.end - row_range.start) as u32 / 2);
- let mut position = editor.scroll_position(cx);
- position.y = top_row as f32;
- editor.set_scroll_position(position, cx);
- } else {
- editor.scroll_manager.show_scrollbar(cx);
- }
+ DisplayDiffHunk::Unfolded {
+ display_row_range,
+ status,
+ } => (display_row_range, status),
+ };
- cx.stop_propagation();
- }
- });
- }
- });
- }
- }
+ let color = match status {
+ DiffHunkStatus::Added => cx.theme().status().created,
+ DiffHunkStatus::Modified => cx.theme().status().modified,
+
+ //TODO: This rendering is entirely a horrible hack
+ DiffHunkStatus::Removed => {
+ let row = display_row_range.start;
+
+ let offset = line_height / 2.;
+ let start_y = row as f32 * line_height - offset - scroll_top;
+ let end_y = start_y + line_height;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = layout.gutter_hitbox.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(quad(
+ highlight_bounds,
+ Corners::all(1. * line_height),
+ cx.theme().status().deleted,
+ Edges::default(),
+ transparent_black(),
+ ));
- #[allow(clippy::too_many_arguments)]
- fn paint_highlighted_range(
- &self,
- range: Range<DisplayPoint>,
- color: Hsla,
- corner_radius: Pixels,
- line_end_overshoot: Pixels,
- layout: &LayoutState,
- content_origin: gpui::Point<Pixels>,
- bounds: Bounds<Pixels>,
- cx: &mut ElementContext,
- ) {
- let start_row = layout.visible_display_row_range.start;
- let end_row = layout.visible_display_row_range.end;
- if range.start != range.end {
- let row_range = if range.end.column() == 0 {
- cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
- } else {
- cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
- };
+ continue;
+ }
+ };
- let highlighted_range = HighlightedRange {
- color,
- line_height: layout.position_map.line_height,
- corner_radius,
- start_y: content_origin.y
- + row_range.start as f32 * layout.position_map.line_height
- - layout.position_map.scroll_position.y,
- lines: row_range
- .into_iter()
- .map(|row| {
- let line_layout =
- &layout.position_map.line_layouts[(row - start_row) as usize].line;
- HighlightedRangeLine {
- start_x: if row == range.start.row() {
- content_origin.x
- + line_layout.x_for_index(range.start.column() as usize)
- - layout.position_map.scroll_position.x
- } else {
- content_origin.x - layout.position_map.scroll_position.x
- },
- end_x: if row == range.end.row() {
- content_origin.x
- + line_layout.x_for_index(range.end.column() as usize)
- - layout.position_map.scroll_position.x
- } else {
- content_origin.x + line_layout.width + line_end_overshoot
- - layout.position_map.scroll_position.x
- },
+ let start_row = display_row_range.start;
+ let end_row = display_row_range.end;
+ // If we're in a multibuffer, row range span might include an
+ // excerpt header, so if we were to draw the marker straight away,
+ // the hunk might include the rows of that header.
+ // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
+ // Instead, we simply check whether the range we're dealing with includes
+ // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
+ let end_row_in_current_excerpt = layout
+ .position_map
+ .snapshot
+ .blocks_in_range(start_row..end_row)
+ .find_map(|(start_row, block)| {
+ if matches!(block, TransformBlock::ExcerptHeader { .. }) {
+ Some(start_row)
+ } else {
+ None
}
})
- .collect(),
- };
+ .unwrap_or(end_row);
+
+ let start_y = start_row as f32 * line_height - scroll_top;
+ let end_y = end_row_in_current_excerpt as f32 * line_height - scroll_top;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = layout.gutter_hitbox.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(quad(
+ highlight_bounds,
+ Corners::all(0.05 * line_height),
+ color,
+ Edges::default(),
+ transparent_black(),
+ ));
+ }
+ })
+ }
- highlighted_range.paint(bounds, cx);
- }
+ fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
+ cx.with_content_mask(
+ Some(ContentMask {
+ bounds: layout.text_hitbox.bounds,
+ }),
+ |cx| {
+ let cursor_style = if self
+ .editor
+ .read(cx)
+ .hovered_link_state
+ .as_ref()
+ .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
+ {
+ CursorStyle::PointingHand
+ } else {
+ CursorStyle::IBeam
+ };
+ cx.set_cursor_style(cursor_style, &layout.text_hitbox);
+
+ cx.with_element_id(Some("folds"), |cx| self.paint_folds(layout, cx));
+ let invisible_display_ranges = self.paint_highlights(layout, cx);
+ self.paint_lines(&invisible_display_ranges, layout, cx);
+ self.paint_redactions(layout, cx);
+ self.paint_cursors(layout, cx);
+ },
+ )
}
- fn paint_blocks(
+ fn paint_highlights(
&mut self,
- bounds: Bounds<Pixels>,
- layout: &mut LayoutState,
+ layout: &mut EditorLayout,
cx: &mut ElementContext,
- ) {
- let scroll_position = layout.position_map.snapshot.scroll_position();
- let scroll_left = scroll_position.x * layout.position_map.em_width;
- let scroll_top = scroll_position.y * layout.position_map.line_height;
-
- for mut block in layout.blocks.drain(..) {
- let mut origin = bounds.origin
- + point(
+ ) -> SmallVec<[Range<DisplayPoint>; 32]> {
+ cx.paint_layer(layout.text_hitbox.bounds, |cx| {
+ let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
+ let line_end_overshoot = 0.15 * layout.position_map.line_height;
+ for (range, color) in &layout.highlighted_ranges {
+ self.paint_highlighted_range(
+ range.clone(),
+ *color,
Pixels::ZERO,
- block.row as f32 * layout.position_map.line_height - scroll_top,
+ line_end_overshoot,
+ layout,
+ cx,
);
- if !matches!(block.style, BlockStyle::Sticky) {
- origin += point(-scroll_left, Pixels::ZERO);
}
- block.element.draw(origin, block.available_space, cx);
- }
+
+ let corner_radius = 0.15 * layout.position_map.line_height;
+
+ for (player_color, selections) in &layout.selections {
+ for selection in selections.into_iter() {
+ self.paint_highlighted_range(
+ selection.range.clone(),
+ player_color.selection,
+ corner_radius,
+ corner_radius * 2.,
+ layout,
+ cx,
+ );
+
+ if selection.is_local && !selection.range.is_empty() {
+ invisible_display_ranges.push(selection.range.clone());
+ }
+ }
+ }
+ invisible_display_ranges
+ })
}
- fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
- let style = &self.style;
- let font_size = style.text.font_size.to_pixels(cx.rem_size());
- let layout = cx
- .text_system()
- .shape_line(
- SharedString::from(" ".repeat(column)),
- font_size,
- &[TextRun {
- len: column,
- font: style.text.font(),
- color: Hsla::default(),
- background_color: None,
- underline: None,
- strikethrough: None,
- }],
- )
- .unwrap();
+ fn paint_lines(
+ &mut self,
+ invisible_display_ranges: &[Range<DisplayPoint>],
+ layout: &EditorLayout,
+ cx: &mut ElementContext,
+ ) {
+ let whitespace_setting = self
+ .editor
+ .read(cx)
+ .buffer
+ .read(cx)
+ .settings_at(0, cx)
+ .show_whitespaces;
- layout.width
+ for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
+ let row = layout.visible_display_row_range.start + ix as u32;
+ line_with_invisibles.draw(
+ layout,
+ row,
+ layout.content_origin,
+ whitespace_setting,
+ invisible_display_ranges,
+ cx,
+ )
+ }
}
- fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
- let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
- self.column_pixels(digit_count, cx)
- }
+ fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
+ if layout.redacted_ranges.is_empty() {
+ return;
+ }
- //Folds contained in a hunk are ignored apart from shrinking visual size
- //If a fold contains any hunks then that fold line is marked as modified
- fn layout_git_gutters(
- &self,
- display_rows: Range<u32>,
- snapshot: &EditorSnapshot,
- ) -> Vec<DisplayDiffHunk> {
- let buffer_snapshot = &snapshot.buffer_snapshot;
+ let line_end_overshoot = layout.line_end_overshoot();
- let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
- .to_point(snapshot)
- .row;
- let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
- .to_point(snapshot)
- .row;
+ // A softer than perfect black
+ let redaction_color = gpui::rgb(0x0e1111);
- buffer_snapshot
- .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
- .map(|hunk| diff_hunk_to_display(hunk, snapshot))
- .dedup()
- .collect()
+ cx.paint_layer(layout.text_hitbox.bounds, |cx| {
+ for range in layout.redacted_ranges.iter() {
+ self.paint_highlighted_range(
+ range.clone(),
+ redaction_color.into(),
+ Pixels::ZERO,
+ line_end_overshoot,
+ layout,
+ cx,
+ );
+ }
+ });
}
- fn calculate_relative_line_numbers(
- &self,
- snapshot: &EditorSnapshot,
- rows: &Range<u32>,
- relative_to: Option<u32>,
- ) -> HashMap<u32, u32> {
- let mut relative_rows: HashMap<u32, u32> = Default::default();
- let Some(relative_to) = relative_to else {
- return relative_rows;
+ fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
+ cx.paint_layer(layout.text_hitbox.bounds, |cx| {
+ for cursor in &mut layout.cursors {
+ cursor.paint(layout.content_origin, cx);
+ }
+ });
+ }
+
+ fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
+ let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else {
+ return;
};
- let start = rows.start.min(relative_to);
- let end = rows.end.max(relative_to);
+ let thumb_bounds = scrollbar_layout.thumb_bounds();
+ if scrollbar_layout.visible {
+ cx.paint_layer(scrollbar_layout.hitbox.bounds, |cx| {
+ cx.paint_quad(quad(
+ scrollbar_layout.hitbox.bounds,
+ Corners::default(),
+ cx.theme().colors().scrollbar_track_background,
+ Edges {
+ top: Pixels::ZERO,
+ right: Pixels::ZERO,
+ bottom: Pixels::ZERO,
+ left: px(1.),
+ },
+ cx.theme().colors().scrollbar_track_border,
+ ));
+ let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
+ let is_singleton = self.editor.read(cx).is_singleton(cx);
+ if is_singleton && scrollbar_settings.selections {
+ let start_anchor = Anchor::min();
+ let end_anchor = Anchor::max();
+ let background_ranges = self
+ .editor
+ .read(cx)
+ .background_highlight_row_ranges::<BufferSearchHighlights>(
+ start_anchor..end_anchor,
+ &layout.position_map.snapshot,
+ 50000,
+ );
+ for range in background_ranges {
+ let start_y = scrollbar_layout.y_for_row(range.start().row() as f32);
+ let mut end_y = scrollbar_layout.y_for_row(range.end().row() as f32);
+ if end_y - start_y < px(1.) {
+ end_y = start_y + px(1.);
+ }
+ let bounds = Bounds::from_corners(
+ point(scrollbar_layout.hitbox.left(), start_y),
+ point(scrollbar_layout.hitbox.right(), end_y),
+ );
+ cx.paint_quad(quad(
+ bounds,
+ Corners::default(),
+ cx.theme().status().info,
+ Edges {
+ top: Pixels::ZERO,
+ right: px(1.),
+ bottom: Pixels::ZERO,
+ left: px(1.),
+ },
+ cx.theme().colors().scrollbar_thumb_border,
+ ));
+ }
+ }
- let buffer_rows = snapshot
- .buffer_rows(start)
- .take(1 + (end - start) as usize)
- .collect::<Vec<_>>();
+ if is_singleton && scrollbar_settings.symbols_selections {
+ let selection_ranges = self.editor.read(cx).background_highlights_in_range(
+ Anchor::min()..Anchor::max(),
+ &layout.position_map.snapshot,
+ cx.theme().colors(),
+ );
+ for hunk in selection_ranges {
+ let start_display = Point::new(hunk.0.start.row(), 0)
+ .to_display_point(&layout.position_map.snapshot.display_snapshot);
+ let end_display = Point::new(hunk.0.end.row(), 0)
+ .to_display_point(&layout.position_map.snapshot.display_snapshot);
+ let start_y = scrollbar_layout.y_for_row(start_display.row() as f32);
+ let mut end_y = if hunk.0.start == hunk.0.end {
+ scrollbar_layout.y_for_row((end_display.row() + 1) as f32)
+ } else {
+ scrollbar_layout.y_for_row(end_display.row() as f32)
+ };
- let head_idx = relative_to - start;
- let mut delta = 1;
- let mut i = head_idx + 1;
- while i < buffer_rows.len() as u32 {
- if buffer_rows[i as usize].is_some() {
- if rows.contains(&(i + start)) {
- relative_rows.insert(i + start, delta);
+ if end_y - start_y < px(1.) {
+ end_y = start_y + px(1.);
+ }
+ let bounds = Bounds::from_corners(
+ point(scrollbar_layout.hitbox.left(), start_y),
+ point(scrollbar_layout.hitbox.right(), end_y),
+ );
+
+ cx.paint_quad(quad(
+ bounds,
+ Corners::default(),
+ cx.theme().status().info,
+ Edges {
+ top: Pixels::ZERO,
+ right: px(1.),
+ bottom: Pixels::ZERO,
+ left: px(1.),
+ },
+ cx.theme().colors().scrollbar_thumb_border,
+ ));
+ }
}
- delta += 1;
- }
- i += 1;
- }
- delta = 1;
- i = head_idx.min(buffer_rows.len() as u32 - 1);
- while i > 0 && buffer_rows[i as usize].is_none() {
- i -= 1;
- }
- while i > 0 {
- i -= 1;
- if buffer_rows[i as usize].is_some() {
- if rows.contains(&(i + start)) {
- relative_rows.insert(i + start, delta);
+ if is_singleton && scrollbar_settings.git_diff {
+ for hunk in layout
+ .position_map
+ .snapshot
+ .buffer_snapshot
+ .git_diff_hunks_in_range(0..layout.max_row)
+ {
+ let start_display = Point::new(hunk.associated_range.start, 0)
+ .to_display_point(&layout.position_map.snapshot.display_snapshot);
+ let end_display = Point::new(hunk.associated_range.end, 0)
+ .to_display_point(&layout.position_map.snapshot.display_snapshot);
+ let start_y = scrollbar_layout.y_for_row(start_display.row() as f32);
+ let mut end_y = if hunk.associated_range.start == hunk.associated_range.end
+ {
+ scrollbar_layout.y_for_row((end_display.row() + 1) as f32)
+ } else {
+ scrollbar_layout.y_for_row(end_display.row() as f32)
+ };
+
+ if end_y - start_y < px(1.) {
+ end_y = start_y + px(1.);
+ }
+ let bounds = Bounds::from_corners(
+ point(scrollbar_layout.hitbox.left(), start_y),
+ point(scrollbar_layout.hitbox.right(), end_y),
+ );
+
+ let color = match hunk.status() {
+ DiffHunkStatus::Added => cx.theme().status().created,
+ DiffHunkStatus::Modified => cx.theme().status().modified,
+ DiffHunkStatus::Removed => cx.theme().status().deleted,
+ };
+ cx.paint_quad(quad(
+ bounds,
+ Corners::default(),
+ color,
+ Edges {
+ top: Pixels::ZERO,
+ right: px(1.),
+ bottom: Pixels::ZERO,
+ left: px(1.),
+ },
+ cx.theme().colors().scrollbar_thumb_border,
+ ));
+ }
}
- delta += 1;
- }
- }
- relative_rows
- }
+ if is_singleton && scrollbar_settings.diagnostics {
+ let max_point = layout
+ .position_map
+ .snapshot
+ .display_snapshot
+ .buffer_snapshot
+ .max_point();
+
+ let diagnostics = layout
+ .position_map
+ .snapshot
+ .buffer_snapshot
+ .diagnostics_in_range::<_, Point>(Point::zero()..max_point, false)
+ // We want to sort by severity, in order to paint the most severe diagnostics last.
+ .sorted_by_key(|diagnostic| {
+ std::cmp::Reverse(diagnostic.diagnostic.severity)
+ });
- fn shape_line_numbers(
- &self,
- rows: Range<u32>,
- active_rows: &BTreeMap<u32, bool>,
- newest_selection_head: DisplayPoint,
- is_singleton: bool,
- snapshot: &EditorSnapshot,
- cx: &ViewContext<Editor>,
- ) -> (
- Vec<Option<ShapedLine>>,
- Vec<Option<(FoldStatus, BufferRow, bool)>>,
- ) {
- let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
- let include_line_numbers =
- EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full;
- let include_fold_statuses =
- EditorSettings::get_global(cx).gutter.folds && snapshot.mode == EditorMode::Full;
- let mut shaped_line_numbers = Vec::with_capacity(rows.len());
- let mut fold_statuses = Vec::with_capacity(rows.len());
- let mut line_number = String::new();
- let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
- let relative_to = if is_relative {
- Some(newest_selection_head.row())
- } else {
- None
- };
+ for diagnostic in diagnostics {
+ let start_display = diagnostic
+ .range
+ .start
+ .to_display_point(&layout.position_map.snapshot.display_snapshot);
+ let end_display = diagnostic
+ .range
+ .end
+ .to_display_point(&layout.position_map.snapshot.display_snapshot);
+ let start_y = scrollbar_layout.y_for_row(start_display.row() as f32);
+ let mut end_y = if diagnostic.range.start == diagnostic.range.end {
+ scrollbar_layout.y_for_row((end_display.row() + 1) as f32)
+ } else {
+ scrollbar_layout.y_for_row(end_display.row() as f32)
+ };
- let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to);
+ if end_y - start_y < px(1.) {
+ end_y = start_y + px(1.);
+ }
+ let bounds = Bounds::from_corners(
+ point(scrollbar_layout.hitbox.left(), start_y),
+ point(scrollbar_layout.hitbox.right(), end_y),
+ );
- for (ix, row) in snapshot
- .buffer_rows(rows.start)
- .take((rows.end - rows.start) as usize)
- .enumerate()
- {
- let display_row = rows.start + ix as u32;
- let (active, color) = if active_rows.contains_key(&display_row) {
- (true, cx.theme().colors().editor_active_line_number)
- } else {
- (false, cx.theme().colors().editor_line_number)
- };
- if let Some(buffer_row) = row {
- if include_line_numbers {
- line_number.clear();
- let default_number = buffer_row + 1;
- let number = relative_rows
- .get(&(ix as u32 + rows.start))
- .unwrap_or(&default_number);
- write!(&mut line_number, "{}", number).unwrap();
- let run = TextRun {
- len: line_number.len(),
- font: self.style.text.font(),
- color,
- background_color: None,
- underline: None,
- strikethrough: None,
- };
- let shaped_line = cx
- .text_system()
- .shape_line(line_number.clone().into(), font_size, &[run])
- .unwrap();
- shaped_line_numbers.push(Some(shaped_line));
- }
- if include_fold_statuses {
- fold_statuses.push(
- is_singleton
- .then(|| {
- snapshot
- .fold_for_line(buffer_row)
- .map(|fold_status| (fold_status, buffer_row, active))
- })
- .flatten(),
- )
+ let color = match diagnostic.diagnostic.severity {
+ DiagnosticSeverity::ERROR => cx.theme().status().error,
+ DiagnosticSeverity::WARNING => cx.theme().status().warning,
+ DiagnosticSeverity::INFORMATION => cx.theme().status().info,
+ _ => cx.theme().status().hint,
+ };
+ cx.paint_quad(quad(
+ bounds,
+ Corners::default(),
+ color,
+ Edges {
+ top: Pixels::ZERO,
+ right: px(1.),
+ bottom: Pixels::ZERO,
+ left: px(1.),
+ },
+ cx.theme().colors().scrollbar_thumb_border,
+ ));
+ }
}
- } else {
- fold_statuses.push(None);
- shaped_line_numbers.push(None);
- }
+
+ cx.paint_quad(quad(
+ thumb_bounds,
+ Corners::default(),
+ cx.theme().colors().scrollbar_thumb_background,
+ Edges {
+ top: Pixels::ZERO,
+ right: px(1.),
+ bottom: Pixels::ZERO,
+ left: px(1.),
+ },
+ cx.theme().colors().scrollbar_thumb_border,
+ ));
+ });
}
- (shaped_line_numbers, fold_statuses)
- }
+ cx.set_cursor_style(CursorStyle::Arrow, &scrollbar_layout.hitbox);
- fn layout_lines(
- &self,
- rows: Range<u32>,
- line_number_layouts: &[Option<ShapedLine>],
- snapshot: &EditorSnapshot,
- cx: &mut ViewContext<Editor>,
- ) -> Vec<LineWithInvisibles> {
- if rows.start >= rows.end {
- return Vec::new();
- }
+ let scroll_height = scrollbar_layout.scroll_height;
+ let height = scrollbar_layout.height;
+ let row_range = scrollbar_layout.visible_row_range.clone();
- // Show the placeholder when the editor is empty
- if snapshot.is_empty() {
- let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
- let placeholder_color = cx.theme().colors().text_placeholder;
- let placeholder_text = snapshot.placeholder_text(cx);
+ cx.on_mouse_event({
+ let editor = self.editor.clone();
+ let hitbox = scrollbar_layout.hitbox.clone();
+ let mut mouse_position = cx.mouse_position();
+ move |event: &MouseMoveEvent, phase, cx| {
+ if phase == DispatchPhase::Capture {
+ return;
+ }
- let placeholder_lines = placeholder_text
- .as_ref()
- .map_or("", AsRef::as_ref)
- .split('\n')
- .skip(rows.start as usize)
- .chain(iter::repeat(""))
- .take(rows.len());
- placeholder_lines
- .filter_map(move |line| {
- let run = TextRun {
- len: line.len(),
- font: self.style.text.font(),
- color: placeholder_color,
- background_color: None,
- underline: Default::default(),
- strikethrough: None,
- };
- cx.text_system()
- .shape_line(line.to_string().into(), font_size, &[run])
- .log_err()
- })
- .map(|line| LineWithInvisibles {
- line,
- invisibles: Vec::new(),
- })
- .collect()
- } else {
- let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
- LineWithInvisibles::from_chunks(
- chunks,
- &self.style.text,
- MAX_LINE_LEN,
- rows.len(),
- line_number_layouts,
- snapshot.mode,
- cx,
- )
- }
- }
-
- fn compute_layout(&mut self, bounds: Bounds<Pixels>, cx: &mut ElementContext) -> LayoutState {
- self.editor.update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- let style = self.style.clone();
-
- let font_id = cx.text_system().resolve_font(&style.text.font());
- let font_size = style.text.font_size.to_pixels(cx.rem_size());
- let line_height = style.text.line_height_in_pixels(cx.rem_size());
- let em_width = cx
- .text_system()
- .typographic_bounds(font_id, font_size, 'm')
- .unwrap()
- .size
- .width;
- let em_advance = cx
- .text_system()
- .advance(font_id, font_size, 'm')
- .unwrap()
- .width;
-
- let gutter_dimensions = snapshot.gutter_dimensions(
- font_id,
- font_size,
- em_width,
- self.max_line_number_width(&snapshot, cx),
- cx,
- );
-
- editor.gutter_width = gutter_dimensions.width;
-
- let text_width = bounds.size.width - gutter_dimensions.width;
- let overscroll = size(em_width, px(0.));
- let _snapshot = {
- editor.set_visible_line_count(bounds.size.height / line_height, cx);
-
- let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
- let wrap_width = match editor.soft_wrap_mode(cx) {
- SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
- SoftWrap::EditorWidth => editor_width,
- SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
- };
-
- if editor.set_wrap_width(Some(wrap_width), cx) {
- editor.snapshot(cx)
- } else {
- snapshot
- }
- };
-
- let wrap_guides = editor
- .wrap_guides(cx)
- .iter()
- .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
- .collect::<SmallVec<[_; 2]>>();
-
- let gutter_size = size(gutter_dimensions.width, bounds.size.height);
- let text_size = size(text_width, bounds.size.height);
-
- let autoscroll_horizontally =
- editor.autoscroll_vertically(bounds.size.height, line_height, cx);
- let mut snapshot = editor.snapshot(cx);
-
- let scroll_position = snapshot.scroll_position();
- // The scroll position is a fractional point, the whole number of which represents
- // the top of the window in terms of display rows.
- let start_row = scroll_position.y as u32;
- let height_in_lines = bounds.size.height / line_height;
- let max_row = snapshot.max_point().row();
-
- // Add 1 to ensure selections bleed off screen
- let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
-
- let start_anchor = if start_row == 0 {
- Anchor::min()
- } else {
- snapshot
- .buffer_snapshot
- .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
- };
- let end_anchor = if end_row > max_row {
- Anchor::max()
- } else {
- snapshot
- .buffer_snapshot
- .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
- };
-
- let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
- let mut active_rows = BTreeMap::new();
- let is_singleton = editor.is_singleton(cx);
-
- let highlighted_rows = editor.highlighted_display_rows(cx);
- let highlighted_ranges = editor.background_highlights_in_range(
- start_anchor..end_anchor,
- &snapshot.display_snapshot,
- cx.theme().colors(),
- );
-
- let redacted_ranges = editor.redacted_ranges(start_anchor..end_anchor, &snapshot.display_snapshot, cx);
-
- let mut newest_selection_head = None;
-
- if editor.show_local_selections {
- let mut local_selections: Vec<Selection<Point>> = editor
- .selections
- .disjoint_in_range(start_anchor..end_anchor, cx);
- local_selections.extend(editor.selections.pending(cx));
- let mut layouts = Vec::new();
- let newest = editor.selections.newest(cx);
- for selection in local_selections.drain(..) {
- let is_empty = selection.start == selection.end;
- let is_newest = selection == newest;
-
- let layout = SelectionLayout::new(
- selection,
- editor.selections.line_mode,
- editor.cursor_shape,
- &snapshot.display_snapshot,
- is_newest,
- editor.leader_peer_id.is_none(),
- None,
- );
- if is_newest {
- newest_selection_head = Some(layout.head);
- }
-
- for row in cmp::max(layout.active_rows.start, start_row)
- ..=cmp::min(layout.active_rows.end, end_row)
+ editor.update(cx, |editor, cx| {
+ if event.pressed_button == Some(MouseButton::Left)
+ && editor.scroll_manager.is_dragging_scrollbar()
{
- let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
- *contains_non_empty_selection |= !is_empty;
- }
- layouts.push(layout);
- }
-
- let player = if editor.read_only(cx) {
- cx.theme().players().read_only()
- } else {
- style.local_player
- };
-
- selections.push((player, layouts));
- }
-
- if let Some(collaboration_hub) = &editor.collaboration_hub {
- // When following someone, render the local selections in their color.
- if let Some(leader_id) = editor.leader_peer_id {
- if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
- if let Some(participant_index) = collaboration_hub
- .user_participant_indices(cx)
- .get(&collaborator.user_id)
- {
- if let Some((local_selection_style, _)) = selections.first_mut() {
- *local_selection_style = cx
- .theme()
- .players()
- .color_for_participant(participant_index.0);
+ let y = mouse_position.y;
+ let new_y = event.position.y;
+ if (hitbox.top()..hitbox.bottom()).contains(&y) {
+ let mut position = editor.scroll_position(cx);
+ position.y += (new_y - y) * scroll_height / height;
+ if position.y < 0.0 {
+ position.y = 0.0;
}
+ editor.set_scroll_position(position, cx);
}
- }
- }
- let mut remote_selections = HashMap::default();
- for selection in snapshot.remote_selections_in_range(
- &(start_anchor..end_anchor),
- collaboration_hub.as_ref(),
- cx,
- ) {
- let selection_style = if let Some(participant_index) = selection.participant_index {
- cx.theme()
- .players()
- .color_for_participant(participant_index.0)
+ mouse_position = event.position;
+ cx.stop_propagation();
} else {
- cx.theme().players().absent()
- };
-
- // Don't re-render the leader's selections, since the local selections
- // match theirs.
- if Some(selection.peer_id) == editor.leader_peer_id {
- continue;
+ editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
+ if hitbox.is_hovered(cx) {
+ editor.scroll_manager.show_scrollbar(cx);
+ }
}
- let key = HoveredCursor{replica_id: selection.replica_id, selection_id: selection.selection.id};
-
- let is_shown = editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
-
- remote_selections
- .entry(selection.replica_id)
- .or_insert((selection_style, Vec::new()))
- .1
- .push(SelectionLayout::new(
- selection.selection,
- selection.line_mode,
- selection.cursor_shape,
- &snapshot.display_snapshot,
- false,
- false,
- if is_shown {
- selection.user_name
- } else {
- None
- },
- ));
- }
-
- selections.extend(remote_selections.into_values());
+ })
}
+ });
- let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
- let show_scrollbars = match scrollbar_settings.show {
- ShowScrollbar::Auto => {
- // Git
- (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
- ||
- // Selections
- (is_singleton && scrollbar_settings.selections && editor.has_background_highlights::<BufferSearchHighlights>())
- ||
- // Symbols Selections
- (is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
- ||
- // Diagnostics
- (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
- ||
- // Scrollmanager
- editor.scroll_manager.scrollbars_visible()
- }
- ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
- ShowScrollbar::Always => true,
- ShowScrollbar::Never => false,
- };
-
- let head_for_relative = newest_selection_head.unwrap_or_else(|| {
- let newest = editor.selections.newest::<Point>(cx);
- SelectionLayout::new(
- newest,
- editor.selections.line_mode,
- editor.cursor_shape,
- &snapshot.display_snapshot,
- true,
- true,
- None,
- )
- .head
- });
-
- let (line_numbers, fold_statuses) = self.shape_line_numbers(
- start_row..end_row,
- &active_rows,
- head_for_relative,
- is_singleton,
- &snapshot,
- cx,
- );
-
- let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
-
- let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
+ if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
+ cx.on_mouse_event({
+ let editor = self.editor.clone();
+ move |_: &MouseUpEvent, phase, cx| {
+ if phase == DispatchPhase::Capture {
+ return;
+ }
- let mut max_visible_line_width = Pixels::ZERO;
- let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
- for line_with_invisibles in &line_layouts {
- if line_with_invisibles.line.width > max_visible_line_width {
- max_visible_line_width = line_with_invisibles.line.width;
+ editor.update(cx, |editor, cx| {
+ editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
+ cx.stop_propagation();
+ });
}
- }
-
- let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
- .unwrap()
- .width;
- let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
-
- let (scroll_width, blocks) = cx.with_element_context(|cx| {
- cx.with_element_id(Some("editor_blocks"), |cx| {
- self.layout_blocks(
- start_row..end_row,
- &snapshot,
- bounds.size.width,
- scroll_width,
- text_width,
- &gutter_dimensions,
- em_width,
- gutter_dimensions.width + gutter_dimensions.margin,
- line_height,
- &style,
- &line_layouts,
- editor,
- cx,
- )
- })
});
-
- let scroll_max = point(
- ((scroll_width - text_size.width) / em_width).max(0.0),
- max_row as f32,
- );
-
- let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
-
- let autoscrolled = if autoscroll_horizontally {
- editor.autoscroll_horizontally(
- start_row,
- text_size.width,
- scroll_width,
- em_width,
- &line_layouts,
- cx,
- )
- } else {
- false
- };
-
- if clamped || autoscrolled {
- snapshot = editor.snapshot(cx);
- }
-
- let gutter_settings = EditorSettings::get_global(cx).gutter;
-
- let mut context_menu = None;
- let mut code_actions_indicator = None;
- if let Some(newest_selection_head) = newest_selection_head {
- if (start_row..end_row).contains(&newest_selection_head.row()) {
- if editor.context_menu_visible() {
- let max_height = cmp::min(
- 12. * line_height,
- cmp::max(
- 3. * line_height,
- (bounds.size.height - line_height) / 2.,
- )
- );
- context_menu =
- editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
+ } else {
+ cx.on_mouse_event({
+ let editor = self.editor.clone();
+ let hitbox = scrollbar_layout.hitbox.clone();
+ move |event: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
+ return;
}
- let active = matches!(
- editor.context_menu.read().as_ref(),
- Some(crate::ContextMenu::CodeActions(_))
- );
+ editor.update(cx, |editor, cx| {
+ editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
+
+ let y = event.position.y;
+ if y < thumb_bounds.top() || thumb_bounds.bottom() < y {
+ let center_row =
+ ((y - hitbox.top()) * scroll_height / height).round() as u32;
+ let top_row = center_row
+ .saturating_sub((row_range.end - row_range.start) as u32 / 2);
+ let mut position = editor.scroll_position(cx);
+ position.y = top_row as f32;
+ editor.set_scroll_position(position, cx);
+ } else {
+ editor.scroll_manager.show_scrollbar(cx);
+ }
- if gutter_settings.code_actions {
- code_actions_indicator = editor
- .render_code_actions_indicator(&style, active, cx)
- .map(|element| CodeActionsIndicator {
- row: newest_selection_head.row(),
- button: element,
- });
- }
+ cx.stop_propagation();
+ });
}
- }
-
- let visible_rows = start_row..start_row + line_layouts.len() as u32;
- let max_size = size(
- (120. * em_width) // Default size
- .min(bounds.size.width / 2.) // Shrink to half of the editor width
- .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
- (16. * line_height) // Default size
- .min(bounds.size.height / 2.) // Shrink to half of the editor height
- .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
- );
-
- let hover = if context_menu.is_some() {
- None
- } else {
- editor.hover_state.render(
- &snapshot,
- &style,
- visible_rows,
- max_size,
- editor.workspace.as_ref().map(|(w, _)| w.clone()),
- cx,
- )
- };
-
- let editor_view = cx.view().clone();
- let fold_indicators = if gutter_settings.folds {
- cx.with_element_context(|cx| {
- cx.with_element_id(Some("gutter_fold_indicators"), |_cx| {
- editor.render_fold_indicators(
- fold_statuses,
- &style,
- editor.gutter_hovered,
- line_height,
- gutter_dimensions.margin,
- editor_view,
- )
- })
- })
- } else {
- Vec::new()
- };
-
- let invisible_symbol_font_size = font_size / 2.;
- let tab_invisible = cx
- .text_system()
- .shape_line(
- "→".into(),
- invisible_symbol_font_size,
- &[TextRun {
- len: "→".len(),
- font: self.style.text.font(),
- color: cx.theme().colors().editor_invisible,
- background_color: None,
- underline: None,
- strikethrough: None,
- }],
- )
- .unwrap();
- let space_invisible = cx
- .text_system()
- .shape_line(
- "•".into(),
- invisible_symbol_font_size,
- &[TextRun {
- len: "•".len(),
- font: self.style.text.font(),
- color: cx.theme().colors().editor_invisible,
- background_color: None,
- underline: None,
- strikethrough: None,
- }],
- )
- .unwrap();
-
- LayoutState {
- mode: snapshot.mode,
- position_map: Arc::new(PositionMap {
- size: bounds.size,
- scroll_position: point(
- scroll_position.x * em_width,
- scroll_position.y * line_height,
- ),
- scroll_max,
- line_layouts,
- line_height,
- em_width,
- em_advance,
- snapshot,
- }),
- visible_anchor_range: start_anchor..end_anchor,
- visible_display_row_range: start_row..end_row,
- wrap_guides,
- gutter_size,
- gutter_dimensions,
- text_size,
- scrollbar_row_range,
- show_scrollbars,
- is_singleton,
- max_row,
- active_rows,
- highlighted_rows,
- highlighted_ranges,
- redacted_ranges,
- line_numbers,
- display_hunks,
- blocks,
- selections,
- context_menu,
- code_actions_indicator,
- fold_indicators,
- tab_invisible,
- space_invisible,
- hover_popovers: hover,
- }
- })
+ });
+ }
}
#[allow(clippy::too_many_arguments)]
- fn layout_blocks(
+ fn paint_highlighted_range(
&self,
- rows: Range<u32>,
- snapshot: &EditorSnapshot,
- editor_width: Pixels,
- scroll_width: Pixels,
- text_width: Pixels,
- gutter_dimensions: &GutterDimensions,
- em_width: Pixels,
- text_x: Pixels,
- line_height: Pixels,
- style: &EditorStyle,
- line_layouts: &[LineWithInvisibles],
- editor: &mut Editor,
+ range: Range<DisplayPoint>,
+ color: Hsla,
+ corner_radius: Pixels,
+ line_end_overshoot: Pixels,
+ layout: &EditorLayout,
cx: &mut ElementContext,
- ) -> (Pixels, Vec<BlockLayout>) {
- let mut block_id = 0;
- let (fixed_blocks, non_fixed_blocks) = snapshot
- .blocks_in_range(rows.clone())
- .partition::<Vec<_>, _>(|(_, block)| match block {
- TransformBlock::ExcerptHeader { .. } => false,
- TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
- });
-
- let render_block = |block: &TransformBlock,
- available_space: Size<AvailableSpace>,
- block_id: usize,
- editor: &mut Editor,
- cx: &mut ElementContext| {
- let mut element = match block {
- TransformBlock::Custom(block) => {
- let align_to = block
- .position()
- .to_point(&snapshot.buffer_snapshot)
- .to_display_point(snapshot);
- let anchor_x = text_x
- + if rows.contains(&align_to.row()) {
- line_layouts[(align_to.row() - rows.start) as usize]
- .line
- .x_for_index(align_to.column() as usize)
- } else {
- layout_line(align_to.row(), snapshot, style, cx)
- .unwrap()
- .x_for_index(align_to.column() as usize)
- };
-
- block.render(&mut BlockContext {
- context: cx,
- anchor_x,
- gutter_dimensions,
- line_height,
- em_width,
- block_id,
- max_width: scroll_width.max(text_width),
- editor_style: &self.style,
- })
- }
-
- TransformBlock::ExcerptHeader {
- buffer,
- range,
- starts_new_buffer,
- ..
- } => {
- let include_root = editor
- .project
- .as_ref()
- .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
- .unwrap_or_default();
-
- let jump_handler = project::File::from_dyn(buffer.file()).map(|file| {
- let jump_path = ProjectPath {
- worktree_id: file.worktree_id(cx),
- path: file.path.clone(),
- };
- let jump_anchor = range
- .primary
- .as_ref()
- .map_or(range.context.start, |primary| primary.start);
- let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
-
- cx.listener_for(&self.editor, move |editor, _, cx| {
- editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
- })
- });
-
- let element = if *starts_new_buffer {
- let path = buffer.resolve_file_path(cx, include_root);
- let mut filename = None;
- let mut parent_path = None;
- // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
- if let Some(path) = path {
- filename = path.file_name().map(|f| f.to_string_lossy().to_string());
- parent_path = path
- .parent()
- .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
- }
-
- v_flex()
- .id(("path header container", block_id))
- .size_full()
- .justify_center()
- .p(gpui::px(6.))
- .child(
- h_flex()
- .id("path header block")
- .size_full()
- .pl(gpui::px(12.))
- .pr(gpui::px(8.))
- .rounded_md()
- .shadow_md()
- .border()
- .border_color(cx.theme().colors().border)
- .bg(cx.theme().colors().editor_subheader_background)
- .justify_between()
- .hover(|style| style.bg(cx.theme().colors().element_hover))
- .child(
- h_flex().gap_3().child(
- h_flex()
- .gap_2()
- .child(
- filename
- .map(SharedString::from)
- .unwrap_or_else(|| "untitled".into()),
- )
- .when_some(parent_path, |then, path| {
- then.child(
- div().child(path).text_color(
- cx.theme().colors().text_muted,
- ),
- )
- }),
- ),
- )
- .when_some(jump_handler, |this, jump_handler| {
- this.cursor_pointer()
- .tooltip(|cx| {
- Tooltip::for_action(
- "Jump to Buffer",
- &OpenExcerpts,
- cx,
- )
- })
- .on_mouse_down(MouseButton::Left, |_, cx| {
- cx.stop_propagation()
- })
- .on_click(jump_handler)
- }),
- )
- } else {
- h_flex()
- .id(("collapsed context", block_id))
- .size_full()
- .gap(gutter_dimensions.left_padding + gutter_dimensions.right_padding)
- .child(
- h_flex()
- .justify_end()
- .flex_none()
- .w(gutter_dimensions.width
- - (gutter_dimensions.left_padding
- + gutter_dimensions.right_padding))
- .h_full()
- .text_buffer(cx)
- .text_color(cx.theme().colors().editor_line_number)
- .child("..."),
- )
- .child(
- ButtonLike::new("jump to collapsed context")
- .style(ButtonStyle::Transparent)
- .full_width()
- .child(
- div()
- .h_px()
- .w_full()
- .bg(cx.theme().colors().border_variant)
- .group_hover("", |style| {
- style.bg(cx.theme().colors().border)
- }),
- )
- .when_some(jump_handler, |this, jump_handler| {
- this.on_click(jump_handler).tooltip(|cx| {
- Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx)
- })
- }),
- )
- };
- element.into_any()
- }
+ ) {
+ let start_row = layout.visible_display_row_range.start;
+ let end_row = layout.visible_display_row_range.end;
+ if range.start != range.end {
+ let row_range = if range.end.column() == 0 {
+ cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
+ } else {
+ cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
};
- let size = element.measure(available_space, cx);
- (element, size)
- };
-
- let mut fixed_block_max_width = Pixels::ZERO;
- let mut blocks = Vec::new();
- for (row, block) in fixed_blocks {
- let available_space = size(
- AvailableSpace::MinContent,
- AvailableSpace::Definite(block.height() as f32 * line_height),
- );
- let (element, element_size) =
- render_block(block, available_space, block_id, editor, cx);
- block_id += 1;
- fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
- blocks.push(BlockLayout {
- row,
- element,
- available_space,
- style: BlockStyle::Fixed,
- });
- }
- for (row, block) in non_fixed_blocks {
- let style = match block {
- TransformBlock::Custom(block) => block.style(),
- TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
- };
- let width = match style {
- BlockStyle::Sticky => editor_width,
- BlockStyle::Flex => editor_width
- .max(fixed_block_max_width)
- .max(gutter_dimensions.width + scroll_width),
- BlockStyle::Fixed => unreachable!(),
+ let highlighted_range = HighlightedRange {
+ color,
+ line_height: layout.position_map.line_height,
+ corner_radius,
+ start_y: layout.content_origin.y
+ + row_range.start as f32 * layout.position_map.line_height
+ - layout.position_map.scroll_pixel_position.y,
+ lines: row_range
+ .into_iter()
+ .map(|row| {
+ let line_layout =
+ &layout.position_map.line_layouts[(row - start_row) as usize].line;
+ HighlightedRangeLine {
+ start_x: if row == range.start.row() {
+ layout.content_origin.x
+ + line_layout.x_for_index(range.start.column() as usize)
+ - layout.position_map.scroll_pixel_position.x
+ } else {
+ layout.content_origin.x
+ - layout.position_map.scroll_pixel_position.x
+ },
+ end_x: if row == range.end.row() {
+ layout.content_origin.x
+ + line_layout.x_for_index(range.end.column() as usize)
+ - layout.position_map.scroll_pixel_position.x
+ } else {
+ layout.content_origin.x + line_layout.width + line_end_overshoot
+ - layout.position_map.scroll_pixel_position.x
+ },
+ }
+ })
+ .collect(),
};
- let available_space = size(
- AvailableSpace::Definite(width),
- AvailableSpace::Definite(block.height() as f32 * line_height),
- );
- let (element, _) = render_block(block, available_space, block_id, editor, cx);
- block_id += 1;
- blocks.push(BlockLayout {
- row,
- element,
- available_space,
- style,
- });
+
+ highlighted_range.paint(layout.text_hitbox.bounds, cx);
}
- (
- scroll_width.max(fixed_block_max_width - gutter_dimensions.width),
- blocks,
- )
}
- fn paint_scroll_wheel_listener(
- &mut self,
- interactive_bounds: &InteractiveBounds,
- layout: &LayoutState,
- cx: &mut ElementContext,
- ) {
+ fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
+ if layout.folds.is_empty() {
+ return;
+ }
+
+ cx.paint_layer(layout.text_hitbox.bounds, |cx| {
+ let fold_corner_radius = 0.15 * layout.position_map.line_height;
+ for mut fold in mem::take(&mut layout.folds) {
+ fold.hover_element.paint(cx);
+
+ let hover_element = fold.hover_element.downcast_mut::<Stateful<Div>>().unwrap();
+ let fold_background = if hover_element.interactivity().active.unwrap() {
+ cx.theme().colors().ghost_element_active
+ } else if hover_element.interactivity().hovered.unwrap() {
+ cx.theme().colors().ghost_element_hover
+ } else {
+ cx.theme().colors().ghost_element_background
+ };
+
+ self.paint_highlighted_range(
+ fold.display_range.clone(),
+ fold_background,
+ fold_corner_radius,
+ fold_corner_radius * 2.,
+ layout,
+ cx,
+ );
+ }
+ })
+ }
+
+ fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
+ for mut block in layout.blocks.drain(..) {
+ block.element.paint(cx);
+ }
+ }
+
+ fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
+ if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
+ mouse_context_menu.paint(cx);
+ }
+ }
+
+ fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
cx.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
- let interactive_bounds = interactive_bounds.clone();
+ let hitbox = layout.hitbox.clone();
let mut delta = ScrollDelta::default();
move |event: &ScrollWheelEvent, phase, cx| {
- if phase == DispatchPhase::Bubble
- && interactive_bounds.visibly_contains(&event.position, cx)
- {
+ if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
delta = delta.coalesce(event.delta);
editor.update(cx, |editor, cx| {
- let position = event.position;
let position_map: &PositionMap = &position_map;
- let bounds = &interactive_bounds;
- if !bounds.visibly_contains(&position, cx) {
- return;
- }
let line_height = position_map.line_height;
let max_glyph_width = position_map.em_width;
@@ -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};
@@ -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};
@@ -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);
@@ -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(),
)
}))
@@ -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);
- }
- }
}
}
@@ -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();
@@ -0,0 +1,292 @@
+use crate::{Bounds, Half};
+use std::{
+ cmp,
+ fmt::Debug,
+ ops::{Add, Sub},
+};
+
+#[derive(Debug)]
+pub(crate) struct BoundsTree<U>
+where
+ U: Default + Clone + Debug,
+{
+ root: Option<usize>,
+ nodes: Vec<Node<U>>,
+ stack: Vec<usize>,
+}
+
+impl<U> BoundsTree<U>
+where
+ U: Clone + Debug + PartialOrd + Add<U, Output = U> + Sub<Output = U> + Half + Default,
+{
+ pub fn clear(&mut self) {
+ self.root = None;
+ self.nodes.clear();
+ self.stack.clear();
+ }
+
+ pub fn insert(&mut self, new_bounds: Bounds<U>) -> u32 {
+ // If the tree is empty, make the root the new leaf.
+ if self.root.is_none() {
+ let new_node = self.push_leaf(new_bounds, 1);
+ self.root = Some(new_node);
+ return 1;
+ }
+
+ // Search for the best place to add the new leaf based on heuristics.
+ let mut max_intersecting_ordering = 0;
+ let mut index = self.root.unwrap();
+ while let Node::Internal {
+ left,
+ right,
+ bounds: node_bounds,
+ ..
+ } = &mut self.nodes[index]
+ {
+ let left = *left;
+ let right = *right;
+ *node_bounds = node_bounds.union(&new_bounds);
+ self.stack.push(index);
+
+ // Descend to the best-fit child, based on which one would increase
+ // the surface area the least. This attempts to keep the tree balanced
+ // in terms of surface area. If there is an intersection with the other child,
+ // add its keys to the intersections vector.
+ let left_cost = new_bounds
+ .union(&self.nodes[left].bounds())
+ .half_perimeter();
+ let right_cost = new_bounds
+ .union(&self.nodes[right].bounds())
+ .half_perimeter();
+ if left_cost < right_cost {
+ max_intersecting_ordering =
+ self.find_max_ordering(right, &new_bounds, max_intersecting_ordering);
+ index = left;
+ } else {
+ max_intersecting_ordering =
+ self.find_max_ordering(left, &new_bounds, max_intersecting_ordering);
+ index = right;
+ }
+ }
+
+ // We've found a leaf ('index' now refers to a leaf node).
+ // We'll insert a new parent node above the leaf and attach our new leaf to it.
+ let sibling = index;
+
+ // Check for collision with the located leaf node
+ let Node::Leaf {
+ bounds: sibling_bounds,
+ order: sibling_ordering,
+ ..
+ } = &self.nodes[index]
+ else {
+ unreachable!();
+ };
+ if sibling_bounds.intersects(&new_bounds) {
+ max_intersecting_ordering = cmp::max(max_intersecting_ordering, *sibling_ordering);
+ }
+
+ let ordering = max_intersecting_ordering + 1;
+ let new_node = self.push_leaf(new_bounds, ordering);
+ let new_parent = self.push_internal(sibling, new_node);
+
+ // If there was an old parent, we need to update its children indices.
+ if let Some(old_parent) = self.stack.last().copied() {
+ let Node::Internal { left, right, .. } = &mut self.nodes[old_parent] else {
+ unreachable!();
+ };
+
+ if *left == sibling {
+ *left = new_parent;
+ } else {
+ *right = new_parent;
+ }
+ } else {
+ // If the old parent was the root, the new parent is the new root.
+ self.root = Some(new_parent);
+ }
+
+ for node_index in self.stack.drain(..) {
+ let Node::Internal {
+ max_order: max_ordering,
+ ..
+ } = &mut self.nodes[node_index]
+ else {
+ unreachable!()
+ };
+ *max_ordering = cmp::max(*max_ordering, ordering);
+ }
+
+ ordering
+ }
+
+ fn find_max_ordering(&self, index: usize, bounds: &Bounds<U>, mut max_ordering: u32) -> u32 {
+ match &self.nodes[index] {
+ Node::Leaf {
+ bounds: node_bounds,
+ order: ordering,
+ ..
+ } => {
+ if bounds.intersects(node_bounds) {
+ max_ordering = cmp::max(*ordering, max_ordering);
+ }
+ }
+ Node::Internal {
+ left,
+ right,
+ bounds: node_bounds,
+ max_order: node_max_ordering,
+ ..
+ } => {
+ if bounds.intersects(node_bounds) && max_ordering < *node_max_ordering {
+ let left_max_ordering = self.nodes[*left].max_ordering();
+ let right_max_ordering = self.nodes[*right].max_ordering();
+ if left_max_ordering > right_max_ordering {
+ max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
+ max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
+ } else {
+ max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
+ max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
+ }
+ }
+ }
+ }
+ max_ordering
+ }
+
+ fn push_leaf(&mut self, bounds: Bounds<U>, order: u32) -> usize {
+ self.nodes.push(Node::Leaf { bounds, order });
+ self.nodes.len() - 1
+ }
+
+ fn push_internal(&mut self, left: usize, right: usize) -> usize {
+ let left_node = &self.nodes[left];
+ let right_node = &self.nodes[right];
+ let new_bounds = left_node.bounds().union(right_node.bounds());
+ let max_ordering = cmp::max(left_node.max_ordering(), right_node.max_ordering());
+ self.nodes.push(Node::Internal {
+ bounds: new_bounds,
+ left,
+ right,
+ max_order: max_ordering,
+ });
+ self.nodes.len() - 1
+ }
+}
+
+impl<U> Default for BoundsTree<U>
+where
+ U: Default + Clone + Debug,
+{
+ fn default() -> Self {
+ BoundsTree {
+ root: None,
+ nodes: Vec::new(),
+ stack: Vec::new(),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+enum Node<U>
+where
+ U: Clone + Default + Debug,
+{
+ Leaf {
+ bounds: Bounds<U>,
+ order: u32,
+ },
+ Internal {
+ left: usize,
+ right: usize,
+ bounds: Bounds<U>,
+ max_order: u32,
+ },
+}
+
+impl<U> Node<U>
+where
+ U: Clone + Default + Debug,
+{
+ fn bounds(&self) -> &Bounds<U> {
+ match self {
+ Node::Leaf { bounds, .. } => bounds,
+ Node::Internal { bounds, .. } => bounds,
+ }
+ }
+
+ fn max_ordering(&self) -> u32 {
+ match self {
+ Node::Leaf {
+ order: ordering, ..
+ } => *ordering,
+ Node::Internal {
+ max_order: max_ordering,
+ ..
+ } => *max_ordering,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{Bounds, Point, Size};
+
+ #[test]
+ fn test_insert() {
+ let mut tree = BoundsTree::<f32>::default();
+ let bounds1 = Bounds {
+ origin: Point { x: 0.0, y: 0.0 },
+ size: Size {
+ width: 10.0,
+ height: 10.0,
+ },
+ };
+ let bounds2 = Bounds {
+ origin: Point { x: 5.0, y: 5.0 },
+ size: Size {
+ width: 10.0,
+ height: 10.0,
+ },
+ };
+ let bounds3 = Bounds {
+ origin: Point { x: 10.0, y: 10.0 },
+ size: Size {
+ width: 10.0,
+ height: 10.0,
+ },
+ };
+
+ // Insert the bounds into the tree and verify the order is correct
+ assert_eq!(tree.insert(bounds1), 1);
+ assert_eq!(tree.insert(bounds2), 2);
+ assert_eq!(tree.insert(bounds3), 3);
+
+ // Insert non-overlapping bounds and verify they can reuse orders
+ let bounds4 = Bounds {
+ origin: Point { x: 20.0, y: 20.0 },
+ size: Size {
+ width: 10.0,
+ height: 10.0,
+ },
+ };
+ let bounds5 = Bounds {
+ origin: Point { x: 40.0, y: 40.0 },
+ size: Size {
+ width: 10.0,
+ height: 10.0,
+ },
+ };
+ let bounds6 = Bounds {
+ origin: Point { x: 25.0, y: 25.0 },
+ size: Size {
+ width: 10.0,
+ height: 10.0,
+ },
+ };
+ assert_eq!(tree.insert(bounds4), 1); // bounds4 does not overlap with bounds1, bounds2, or bounds3
+ assert_eq!(tree.insert(bounds5), 1); // bounds5 does not overlap with any other bounds
+ assert_eq!(tree.insert(bounds6), 2); // bounds6 overlaps with bounds4, so it should have a different order
+ }
+}
@@ -15,9 +15,6 @@
//!
//! But some state is too simple and voluminous to store in every view that needs it, e.g.
//! 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 before_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
- fn request_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 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 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 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 before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
+ Drawable::before_layout(self, cx)
}
- fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
- DrawableElement::request_layout(self.as_mut().unwrap(), 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 before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+ let layout_id = self.before_layout(cx);
+ (layout_id, ())
+ }
- fn request_layout(
+ fn after_layout(
&mut self,
- _: Option<Self::State>,
+ _: Bounds<Pixels>,
+ _: &mut Self::BeforeLayout,
cx: &mut ElementContext,
- ) -> (LayoutId, Self::State) {
- let layout_id = self.request_layout(cx);
- (layout_id, ())
+ ) {
+ self.after_layout(cx)
}
- fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut ElementContext) {
+ 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,
) {
}
@@ -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
}
@@ -17,13 +17,12 @@
use crate::{
point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
- ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, Global,
- IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton,
+ ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, Global, Hitbox,
+ HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render,
- ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task,
- View, Visibility, WindowContext,
+ ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility,
+ WindowContext,
};
-
use collections::HashMap;
use refineable::Refineable;
use smallvec::SmallVec;
@@ -85,10 +84,8 @@ impl Interactivity {
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) {
self.mouse_down_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Bubble
- && event.button == button
- && bounds.visibly_contains(&event.position, cx)
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Bubble && event.button == button && hitbox.is_hovered(cx)
{
(listener)(event, cx)
}
@@ -104,8 +101,8 @@ impl Interactivity {
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) {
self.mouse_down_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) {
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Capture && hitbox.is_hovered(cx) {
(listener)(event, cx)
}
}));
@@ -120,8 +117,8 @@ impl Interactivity {
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) {
self.mouse_down_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
(listener)(event, cx)
}
}));
@@ -137,10 +134,8 @@ impl Interactivity {
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
) {
self.mouse_up_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Bubble
- && event.button == button
- && bounds.visibly_contains(&event.position, cx)
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Bubble && event.button == button && hitbox.is_hovered(cx)
{
(listener)(event, cx)
}
@@ -156,8 +151,8 @@ impl Interactivity {
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
) {
self.mouse_up_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) {
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Capture && hitbox.is_hovered(cx) {
(listener)(event, cx)
}
}));
@@ -172,8 +167,8 @@ impl Interactivity {
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
) {
self.mouse_up_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
(listener)(event, cx)
}
}));
@@ -189,9 +184,8 @@ impl Interactivity {
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) {
self.mouse_down_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
- {
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Capture && !hitbox.is_hovered(cx) {
(listener)(event, cx)
}
}));
@@ -208,10 +202,10 @@ impl Interactivity {
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
) {
self.mouse_up_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
+ .push(Box::new(move |event, phase, hitbox, cx| {
if phase == DispatchPhase::Capture
&& event.button == button
- && !bounds.visibly_contains(&event.position, cx)
+ && !hitbox.is_hovered(cx)
{
(listener)(event, cx);
}
@@ -227,8 +221,8 @@ impl Interactivity {
listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
) {
self.mouse_move_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
(listener)(event, cx);
}
}));
@@ -248,7 +242,7 @@ impl Interactivity {
T: 'static,
{
self.mouse_move_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
+ .push(Box::new(move |event, phase, hitbox, cx| {
if phase == DispatchPhase::Capture
&& cx
.active_drag
@@ -258,7 +252,7 @@ impl Interactivity {
(listener)(
&DragMoveEvent {
event: event.clone(),
- bounds: bounds.bounds,
+ bounds: hitbox.bounds,
drag: PhantomData,
},
cx,
@@ -276,8 +270,8 @@ impl Interactivity {
listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
) {
self.scroll_wheel_listeners
- .push(Box::new(move |event, bounds, phase, cx| {
- if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
+ .push(Box::new(move |event, phase, hitbox, cx| {
+ if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
(listener)(event, cx);
}
}));
@@ -482,8 +476,8 @@ impl Interactivity {
/// Block the mouse from interacting with this element or any of its children
/// The imperative API equivalent to [`InteractiveElement::block_mouse`]
- pub fn block_mouse(&mut self) {
- self.block_mouse = true;
+ pub fn occlude_mouse(&mut self) {
+ self.occlude_mouse = true;
}
}
@@ -836,8 +830,8 @@ pub trait InteractiveElement: Sized {
/// Block the mouse from interacting with this element or any of its children
/// The fluent API equivalent to [`Interactivity::block_mouse`]
- fn block_mouse(mut self) -> Self {
- self.interactivity().block_mouse();
+ fn occlude(mut self) -> Self {
+ self.interactivity().occlude_mouse();
self
}
}
@@ -872,7 +866,7 @@ pub trait StatefulInteractiveElement: InteractiveElement {
/// Track the scroll state of this element with the given handle.
fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
- self.interactivity().scroll_handle = Some(scroll_handle.clone());
+ self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
self
}
@@ -979,15 +973,15 @@ pub trait FocusableElement: InteractiveElement {
}
pub(crate) type MouseDownListener =
- Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
+ Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut WindowContext) + 'static>;
pub(crate) type MouseUpListener =
- Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
+ Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut WindowContext) + 'static>;
pub(crate) type MouseMoveListener =
- Box<dyn Fn(&MouseMoveEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
+ Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut WindowContext) + 'static>;
pub(crate) type ScrollWheelListener =
- Box<dyn Fn(&ScrollWheelEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
+ Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut WindowContext) + 'static>;
pub(crate) type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
@@ -1031,6 +1025,16 @@ pub struct Div {
children: SmallVec<[AnyElement; 2]>,
}
+/// A frame state for a `Div` element, which contains layout IDs for its children.
+///
+/// This struct is used internally by the `Div` element to manage the layout state of its children
+/// during the UI update cycle. It holds a small vector of `LayoutId` values, each corresponding to
+/// a child element of the `Div`. These IDs are used to query the layout engine for the computed
+/// bounds of the children after the layout phase is complete.
+pub struct DivFrameState {
+ child_layout_ids: SmallVec<[LayoutId; 2]>,
+}
+
impl Styled for Div {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style
@@ -1050,54 +1054,41 @@ impl ParentElement for Div {
}
impl Element for Div {
- type State = DivState;
+ type BeforeLayout = DivFrameState;
+ type AfterLayout = Option<Hitbox>;
- fn request_layout(
- &mut self,
- element_state: Option<Self::State>,
- cx: &mut ElementContext,
- ) -> (LayoutId, Self::State) {
+ fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut child_layout_ids = SmallVec::new();
- let (layout_id, interactive_state) = self.interactivity.layout(
- element_state.map(|s| s.interactive_state),
- cx,
- |style, cx| {
- cx.with_text_style(style.text_style().cloned(), |cx| {
- child_layout_ids = self
- .children
- .iter_mut()
- .map(|child| child.request_layout(cx))
- .collect::<SmallVec<_>>();
- cx.request_layout(&style, child_layout_ids.iter().copied())
- })
- },
- );
- (
- layout_id,
- DivState {
- interactive_state,
- child_layout_ids,
- },
- )
+ let layout_id = self.interactivity.before_layout(cx, |style, cx| {
+ cx.with_text_style(style.text_style().cloned(), |cx| {
+ child_layout_ids = self
+ .children
+ .iter_mut()
+ .map(|child| child.before_layout(cx))
+ .collect::<SmallVec<_>>();
+ cx.request_layout(&style, child_layout_ids.iter().copied())
+ })
+ });
+ (layout_id, DivFrameState { child_layout_ids })
}
- fn paint(
+ fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
- element_state: &mut Self::State,
+ before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
- ) {
+ ) -> Option<Hitbox> {
let mut child_min = point(Pixels::MAX, Pixels::MAX);
let mut child_max = Point::default();
- let content_size = if element_state.child_layout_ids.is_empty() {
+ let content_size = if before_layout.child_layout_ids.is_empty() {
bounds.size
- } else if let Some(scroll_handle) = self.interactivity.scroll_handle.as_ref() {
+ } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
let mut state = scroll_handle.0.borrow_mut();
- state.child_bounds = Vec::with_capacity(element_state.child_layout_ids.len());
+ state.child_bounds = Vec::with_capacity(before_layout.child_layout_ids.len());
state.bounds = bounds;
let requested = state.requested_scroll_top.take();
- for (ix, child_layout_id) in element_state.child_layout_ids.iter().enumerate() {
+ for (ix, child_layout_id) in before_layout.child_layout_ids.iter().enumerate() {
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());
@@ -1112,7 +1103,7 @@ impl Element for Div {
}
(child_max - child_min).into()
} else {
- 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());
@@ -1120,60 +1111,62 @@ impl Element for Div {
(child_max - child_min).into()
};
- self.interactivity.paint(
+ self.interactivity.after_layout(
bounds,
content_size,
- &mut element_state.interactive_state,
cx,
- |_style, scroll_offset, cx| {
+ |_style, scroll_offset, hitbox, cx| {
cx.with_element_offset(scroll_offset, |cx| {
for child in &mut self.children {
- child.paint(cx);
+ child.after_layout(cx);
}
- })
+ });
+ hitbox
},
- );
+ )
+ }
+
+ fn paint(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ _before_layout: &mut Self::BeforeLayout,
+ hitbox: &mut Option<Hitbox>,
+ cx: &mut ElementContext,
+ ) {
+ self.interactivity
+ .paint(bounds, hitbox.as_ref(), cx, |_style, cx| {
+ for child in &mut self.children {
+ child.paint(cx);
+ }
+ });
}
}
impl IntoElement for Div {
type Element = Self;
- fn element_id(&self) -> Option<ElementId> {
- self.interactivity.element_id.clone()
- }
-
fn into_element(self) -> Self::Element {
self
}
}
-/// The state a div needs to keep track of between frames.
-pub struct DivState {
- child_layout_ids: SmallVec<[LayoutId; 2]>,
- interactive_state: InteractiveElementState,
-}
-
-impl DivState {
- /// Is the div currently being clicked on?
- pub fn is_active(&self) -> bool {
- self.interactive_state
- .pending_mouse_down
- .as_ref()
- .map_or(false, |pending| pending.borrow().is_some())
- }
-}
-
/// The interactivity struct. Powers all of the general-purpose
/// interactivity in the `Div` element.
#[derive(Default)]
pub struct Interactivity {
/// The element ID of the element
pub element_id: Option<ElementId>,
+ /// Whether the element was clicked. This will only be present after layout.
+ pub active: Option<bool>,
+ /// Whether the element was hovered. This will only be present after paint if an hitbox
+ /// was created for the interactive element.
+ pub hovered: Option<bool>,
+ pub(crate) content_size: Size<Pixels>,
pub(crate) key_context: Option<KeyContext>,
pub(crate) focusable: bool,
pub(crate) tracked_focus_handle: Option<FocusHandle>,
- pub(crate) scroll_handle: Option<ScrollHandle>,
+ pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
+ pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
pub(crate) group: Option<SharedString>,
/// The base style of the element, before any modifications are applied
/// by focus, active, etc.
@@ -1202,7 +1195,7 @@ pub struct Interactivity {
pub(crate) drag_listener: Option<(Box<dyn Any>, DragListener)>,
pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
pub(crate) tooltip_builder: Option<TooltipBuilder>,
- pub(crate) block_mouse: bool,
+ pub(crate) occlude_mouse: bool,
#[cfg(debug_assertions)]
pub(crate) location: Option<core::panic::Location<'static>>,
@@ -1211,68 +1204,176 @@ pub struct Interactivity {
pub(crate) debug_selector: Option<String>,
}
-/// The bounds and depth of an element in the computed element tree.
-#[derive(Clone, Debug)]
-pub struct InteractiveBounds {
- /// The 2D bounds of the element
- pub bounds: Bounds<Pixels>,
- /// The 'stacking order', or depth, for this element
- pub stacking_order: StackingOrder,
-}
+impl Interactivity {
+ /// Layout this element according to this interactivity state's configured styles
+ pub fn before_layout(
+ &mut self,
+ cx: &mut ElementContext,
+ f: impl FnOnce(Style, &mut ElementContext) -> LayoutId,
+ ) -> LayoutId {
+ cx.with_element_state::<InteractiveElementState, _>(
+ self.element_id.clone(),
+ |element_state, cx| {
+ let mut element_state =
+ element_state.map(|element_state| element_state.unwrap_or_default());
+
+ if let Some(element_state) = element_state.as_ref() {
+ if cx.has_active_drag() {
+ if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref()
+ {
+ *pending_mouse_down.borrow_mut() = None;
+ }
+ if let Some(clicked_state) = element_state.clicked_state.as_ref() {
+ *clicked_state.borrow_mut() = ElementClickedState::default();
+ }
+ }
+ }
-impl InteractiveBounds {
- /// Checks whether this point was inside these bounds in the rendered frame, and that these bounds where the topmost layer
- /// Never call this during paint to perform hover calculations. It will reference the previous frame and could cause flicker.
- pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
- self.bounds.contains(point) && cx.was_top_layer(point, &self.stacking_order)
- }
+ // Ensure we store a focus handle in our element state if we're focusable.
+ // If there's an explicit focus handle we're tracking, use that. Otherwise
+ // create a new handle and store it in the element state, which lives for as
+ // as frames contain an element with this id.
+ if self.focusable {
+ if self.tracked_focus_handle.is_none() {
+ if let Some(element_state) = element_state.as_mut() {
+ self.tracked_focus_handle = Some(
+ element_state
+ .focus_handle
+ .get_or_insert_with(|| cx.focus_handle())
+ .clone(),
+ );
+ }
+ }
+ }
+
+ if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
+ self.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
+ } else if self.base_style.overflow.x == Some(Overflow::Scroll)
+ || self.base_style.overflow.y == Some(Overflow::Scroll)
+ {
+ if let Some(element_state) = element_state.as_mut() {
+ self.scroll_offset = Some(
+ element_state
+ .scroll_offset
+ .get_or_insert_with(|| Rc::default())
+ .clone(),
+ );
+ }
+ }
- /// Checks whether this point was inside these bounds, and that these bounds where the topmost layer
- /// under an active drag
- pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
- self.bounds.contains(point)
- && cx.was_top_layer_under_active_drag(point, &self.stacking_order)
+ let style = self.compute_style_internal(None, element_state.as_mut(), cx);
+ let layout_id = f(style, cx);
+ (layout_id, element_state)
+ },
+ )
}
-}
-impl Interactivity {
- /// Layout this element according to this interactivity state's configured styles
- pub fn layout(
+ /// Commit the bounds of this element according to this interactivity state's configured styles.
+ pub fn after_layout<R>(
&mut self,
- element_state: Option<InteractiveElementState>,
+ bounds: Bounds<Pixels>,
+ content_size: Size<Pixels>,
cx: &mut ElementContext,
- f: impl FnOnce(Style, &mut ElementContext) -> LayoutId,
- ) -> (LayoutId, InteractiveElementState) {
- let mut element_state = element_state.unwrap_or_default();
+ f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut ElementContext) -> R,
+ ) -> R {
+ self.content_size = content_size;
+ cx.with_element_state::<InteractiveElementState, _>(
+ self.element_id.clone(),
+ |element_state, cx| {
+ let mut element_state =
+ element_state.map(|element_state| element_state.unwrap_or_default());
+ let style = self.compute_style_internal(None, element_state.as_mut(), cx);
+
+ if let Some(element_state) = element_state.as_ref() {
+ if let Some(clicked_state) = element_state.clicked_state.as_ref() {
+ let clicked_state = clicked_state.borrow();
+ self.active = Some(clicked_state.element);
+ }
- if cx.has_active_drag() {
- if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
- *pending_mouse_down.borrow_mut() = None;
- }
- if let Some(clicked_state) = element_state.clicked_state.as_ref() {
- *clicked_state.borrow_mut() = ElementClickedState::default();
- }
- }
+ if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
+ if let Some(active_tooltip) = active_tooltip.borrow().as_ref() {
+ if let Some(tooltip) = active_tooltip.tooltip.clone() {
+ cx.set_tooltip(tooltip);
+ }
+ }
+ }
+ }
- // Ensure we store a focus handle in our element state if we're focusable.
- // If there's an explicit focus handle we're tracking, use that. Otherwise
- // create a new handle and store it in the element state, which lives for as
- // as frames contain an element with this id.
- if self.focusable {
- element_state.focus_handle.get_or_insert_with(|| {
- self.tracked_focus_handle
- .clone()
- .unwrap_or_else(|| cx.focus_handle())
- });
- }
+ cx.with_text_style(style.text_style().cloned(), |cx| {
+ cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
+ let hitbox = if self.occlude_mouse
+ || style.mouse_cursor.is_some()
+ || self.group.is_some()
+ || self.has_hover_styles()
+ || self.has_mouse_listeners()
+ {
+ Some(cx.insert_hitbox(bounds, self.occlude_mouse))
+ } else {
+ None
+ };
- if let Some(scroll_handle) = self.scroll_handle.as_ref() {
- element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
- }
+ let scroll_offset = self.clamp_scroll_position(bounds, &style, cx);
+ let result = f(&style, scroll_offset, hitbox, cx);
+ (result, element_state)
+ })
+ })
+ },
+ )
+ }
+
+ fn has_hover_styles(&self) -> bool {
+ self.hover_style.is_some() || self.group_hover_style.is_some()
+ }
+
+ fn has_mouse_listeners(&self) -> bool {
+ !self.mouse_up_listeners.is_empty()
+ || !self.mouse_down_listeners.is_empty()
+ || !self.mouse_move_listeners.is_empty()
+ || !self.scroll_wheel_listeners.is_empty()
+ || self.drag_listener.is_some()
+ || !self.drop_listeners.is_empty()
+ }
+
+ fn clamp_scroll_position(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ style: &Style,
+ cx: &mut ElementContext,
+ ) -> Point<Pixels> {
+ if let Some(scroll_offset) = self.scroll_offset.as_ref() {
+ if let Some(scroll_handle) = &self.tracked_scroll_handle {
+ scroll_handle.0.borrow_mut().overflow = style.overflow;
+ }
- let style = self.compute_style(None, &mut element_state, cx);
- let layout_id = f(style, cx);
- (layout_id, element_state)
+ let rem_size = cx.rem_size();
+ let padding_size = size(
+ style
+ .padding
+ .left
+ .to_pixels(bounds.size.width.into(), rem_size)
+ + style
+ .padding
+ .right
+ .to_pixels(bounds.size.width.into(), rem_size),
+ style
+ .padding
+ .top
+ .to_pixels(bounds.size.height.into(), rem_size)
+ + style
+ .padding
+ .bottom
+ .to_pixels(bounds.size.height.into(), rem_size),
+ );
+ let scroll_max = (self.content_size + padding_size - bounds.size).max(&Size::default());
+ // Clamp scroll offset in case scroll max is smaller now (e.g., if children
+ // were removed or the bounds became larger).
+ let mut scroll_offset = scroll_offset.borrow_mut();
+ scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.));
+ scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.));
+ *scroll_offset
+ } else {
+ Point::default()
+ }
}
/// Paint this element according to this interactivity state's configured styles
@@ -1286,731 +1387,660 @@ impl Interactivity {
pub fn paint(
&mut self,
bounds: Bounds<Pixels>,
- content_size: Size<Pixels>,
- element_state: &mut InteractiveElementState,
+ hitbox: Option<&Hitbox>,
cx: &mut ElementContext,
- f: impl FnOnce(&Style, Point<Pixels>, &mut ElementContext),
+ f: impl FnOnce(&Style, &mut ElementContext),
) {
- let style = self.compute_style(Some(bounds), element_state, cx);
- let z_index = style.z_index.unwrap_or(0);
-
- #[cfg(any(feature = "test-support", test))]
- if let Some(debug_selector) = &self.debug_selector {
- cx.window
- .next_frame
- .debug_bounds
- .insert(debug_selector.clone(), bounds);
- }
+ self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(cx));
+ cx.with_element_state::<InteractiveElementState, _>(
+ self.element_id.clone(),
+ |element_state, cx| {
+ let mut element_state =
+ element_state.map(|element_state| element_state.unwrap_or_default());
+
+ let style = self.compute_style_internal(hitbox, element_state.as_mut(), cx);
+
+ #[cfg(any(feature = "test-support", test))]
+ if let Some(debug_selector) = &self.debug_selector {
+ cx.window
+ .next_frame
+ .debug_bounds
+ .insert(debug_selector.clone(), bounds);
+ }
- let paint_hover_group_handler = |cx: &mut ElementContext| {
- let hover_group_bounds = self
- .group_hover_style
- .as_ref()
- .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
-
- if let Some(group_bounds) = hover_group_bounds {
- let hovered = group_bounds.contains(&cx.mouse_position());
- cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
- if phase == DispatchPhase::Capture
- && group_bounds.contains(&event.position) != hovered
- {
- cx.refresh();
- }
- });
- }
- };
+ self.paint_hover_group_handler(cx);
- if style.visibility == Visibility::Hidden {
- cx.with_z_index(z_index, |cx| paint_hover_group_handler(cx));
- return;
- }
+ if style.visibility == Visibility::Hidden {
+ return ((), element_state);
+ }
- cx.with_z_index(z_index, |cx| {
- style.paint(bounds, cx, |cx: &mut ElementContext| {
- cx.with_text_style(style.text_style().cloned(), |cx| {
- cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
- #[cfg(debug_assertions)]
- if self.element_id.is_some()
- && (style.debug
- || style.debug_below
- || cx.has_global::<crate::DebugBelow>())
- && bounds.contains(&cx.mouse_position())
- {
- const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
- let element_id = format!("{:?}", self.element_id.as_ref().unwrap());
- let str_len = element_id.len();
-
- let render_debug_text = |cx: &mut ElementContext| {
- if let Some(text) = cx
- .text_system()
- .shape_text(
- element_id.into(),
- FONT_SIZE,
- &[cx.text_style().to_run(str_len)],
- None,
- )
- .ok()
- .and_then(|mut text| text.pop())
- {
- text.paint(bounds.origin, FONT_SIZE, cx).ok();
+ style.paint(bounds, cx, |cx: &mut ElementContext| {
+ cx.with_text_style(style.text_style().cloned(), |cx| {
+ cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
+ if let Some(hitbox) = hitbox {
+ #[cfg(debug_assertions)]
+ self.paint_debug_info(hitbox, &style, cx);
- let text_bounds = crate::Bounds {
- origin: bounds.origin,
- size: text.size(FONT_SIZE),
- };
- if self.location.is_some()
- && text_bounds.contains(&cx.mouse_position())
- && cx.modifiers().command
- {
- let command_held = cx.modifiers().command;
- cx.on_key_event({
- move |e: &crate::ModifiersChangedEvent, _phase, cx| {
- if e.modifiers.command != command_held
- && text_bounds.contains(&cx.mouse_position())
- {
- cx.refresh();
- }
- }
- });
-
- let hovered = bounds.contains(&cx.mouse_position());
- cx.on_mouse_event(
- move |event: &MouseMoveEvent, phase, cx| {
- if phase == DispatchPhase::Capture
- && bounds.contains(&event.position) != hovered
- {
- cx.refresh();
- }
- },
- );
-
- cx.on_mouse_event({
- let location = self.location.unwrap();
- move |e: &crate::MouseDownEvent, phase, cx| {
- if text_bounds.contains(&e.position)
- && phase.capture()
- {
- cx.stop_propagation();
- let Ok(dir) = std::env::current_dir() else {
- return;
- };
-
- eprintln!(
- "This element was created at:\n{}:{}:{}",
- dir.join(location.file()).to_string_lossy(),
- location.line(),
- location.column()
- );
- }
- }
- });
- cx.paint_quad(crate::outline(
- crate::Bounds {
- origin: bounds.origin
- + crate::point(
- crate::px(0.),
- FONT_SIZE - px(2.),
- ),
- size: crate::Size {
- width: text_bounds.size.width,
- height: crate::px(1.),
- },
- },
- crate::red(),
- ))
+ if !cx.has_active_drag() {
+ if let Some(mouse_cursor) = style.mouse_cursor {
+ cx.set_cursor_style(mouse_cursor, hitbox);
}
}
- };
- cx.with_z_index(1, |cx| {
- cx.with_text_style(
- Some(crate::TextStyleRefinement {
- color: Some(crate::red()),
- line_height: Some(FONT_SIZE.into()),
- background_color: Some(crate::white()),
- ..Default::default()
- }),
- render_debug_text,
- )
- });
- }
+ if let Some(group) = self.group.clone() {
+ GroupHitboxes::push(group, hitbox.id, cx);
+ }
- let interactive_bounds = InteractiveBounds {
- bounds: bounds.intersect(&cx.content_mask().bounds),
- stacking_order: cx.stacking_order().clone(),
- };
+ self.paint_mouse_listeners(hitbox, element_state.as_mut(), cx);
+ self.paint_scroll_listener(hitbox, &style, cx);
+ }
- if self.block_mouse
- || style.background.as_ref().is_some_and(|fill| {
- fill.color().is_some_and(|color| !color.is_transparent())
- })
- {
- cx.add_opaque_layer(interactive_bounds.bounds);
- }
+ self.paint_keyboard_listeners(cx);
+ f(&style, cx);
- if !cx.has_active_drag() {
- if let Some(mouse_cursor) = style.mouse_cursor {
- let hovered = bounds.contains(&cx.mouse_position());
- if hovered {
- cx.set_cursor_style(
- mouse_cursor,
- interactive_bounds.stacking_order.clone(),
- );
+ if hitbox.is_some() {
+ if let Some(group) = self.group.as_ref() {
+ GroupHitboxes::pop(group, cx);
}
}
- }
+ });
+ });
+ });
+
+ ((), element_state)
+ },
+ );
+ }
+
+ #[cfg(debug_assertions)]
+ fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) {
+ if self.element_id.is_some()
+ && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
+ && hitbox.is_hovered(cx)
+ {
+ const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
+ let element_id = format!("{:?}", self.element_id.as_ref().unwrap());
+ let str_len = element_id.len();
+
+ let render_debug_text = |cx: &mut ElementContext| {
+ if let Some(text) = cx
+ .text_system()
+ .shape_text(
+ element_id.into(),
+ FONT_SIZE,
+ &[cx.text_style().to_run(str_len)],
+ None,
+ )
+ .ok()
+ .and_then(|mut text| text.pop())
+ {
+ text.paint(hitbox.origin, FONT_SIZE, cx).ok();
+
+ let text_bounds = crate::Bounds {
+ origin: hitbox.origin,
+ size: text.size(FONT_SIZE),
+ };
+ if self.location.is_some()
+ && text_bounds.contains(&cx.mouse_position())
+ && cx.modifiers().command
+ {
+ let command_held = cx.modifiers().command;
+ cx.on_key_event({
+ move |e: &crate::ModifiersChangedEvent, _phase, cx| {
+ if e.modifiers.command != command_held
+ && text_bounds.contains(&cx.mouse_position())
+ {
+ cx.refresh();
+ }
+ }
+ });
- // If this element can be focused, register a mouse down listener
- // that will automatically transfer focus when hitting the element.
- // This behavior can be suppressed by using `cx.prevent_default()`.
- if let Some(focus_handle) = element_state.focus_handle.clone() {
- cx.on_mouse_event({
- let interactive_bounds = interactive_bounds.clone();
- move |event: &MouseDownEvent, phase, cx| {
- if phase == DispatchPhase::Bubble
- && !cx.default_prevented()
- && interactive_bounds.visibly_contains(&event.position, cx)
- {
- cx.focus(&focus_handle);
- // If there is a parent that is also focusable, prevent it
- // from transferring focus because we already did so.
- cx.prevent_default();
+ let was_hovered = hitbox.is_hovered(cx);
+ cx.on_mouse_event({
+ let hitbox = hitbox.clone();
+ move |_: &MouseMoveEvent, phase, cx| {
+ if phase == DispatchPhase::Capture {
+ let hovered = hitbox.is_hovered(cx);
+ if hovered != was_hovered {
+ cx.refresh();
}
}
- });
- }
+ }
+ });
+
+ cx.on_mouse_event({
+ let hitbox = hitbox.clone();
+ let location = self.location.unwrap();
+ move |e: &crate::MouseDownEvent, phase, cx| {
+ if text_bounds.contains(&e.position)
+ && phase.capture()
+ && hitbox.is_hovered(cx)
+ {
+ cx.stop_propagation();
+ let Ok(dir) = std::env::current_dir() else {
+ return;
+ };
- for listener in self.mouse_down_listeners.drain(..) {
- let interactive_bounds = interactive_bounds.clone();
- cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
- listener(event, &interactive_bounds, phase, cx);
- })
- }
+ eprintln!(
+ "This element was created at:\n{}:{}:{}",
+ dir.join(location.file()).to_string_lossy(),
+ location.line(),
+ location.column()
+ );
+ }
+ }
+ });
+ cx.paint_quad(crate::outline(
+ crate::Bounds {
+ origin: hitbox.origin
+ + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
+ size: crate::Size {
+ width: text_bounds.size.width,
+ height: crate::px(1.),
+ },
+ },
+ crate::red(),
+ ))
+ }
+ }
+ };
+
+ cx.with_text_style(
+ Some(crate::TextStyleRefinement {
+ color: Some(crate::red()),
+ line_height: Some(FONT_SIZE.into()),
+ background_color: Some(crate::white()),
+ ..Default::default()
+ }),
+ render_debug_text,
+ )
+ }
+ }
- for listener in self.mouse_up_listeners.drain(..) {
- let interactive_bounds = interactive_bounds.clone();
- cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
- listener(event, &interactive_bounds, phase, cx);
- })
- }
+ fn paint_mouse_listeners(
+ &mut self,
+ hitbox: &Hitbox,
+ element_state: Option<&mut InteractiveElementState>,
+ cx: &mut ElementContext,
+ ) {
+ // If this element can be focused, register a mouse down listener
+ // that will automatically transfer focus when hitting the element.
+ // This behavior can be suppressed by using `cx.prevent_default()`.
+ if let Some(focus_handle) = self.tracked_focus_handle.clone() {
+ let hitbox = hitbox.clone();
+ cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble
+ && hitbox.is_hovered(cx)
+ && !cx.default_prevented()
+ {
+ cx.focus(&focus_handle);
+ // If there is a parent that is also focusable, prevent it
+ // from transferring focus because we already did so.
+ cx.prevent_default();
+ }
+ });
+ }
- for listener in self.mouse_move_listeners.drain(..) {
- let interactive_bounds = interactive_bounds.clone();
- cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
- listener(event, &interactive_bounds, phase, cx);
- })
- }
+ for listener in self.mouse_down_listeners.drain(..) {
+ let hitbox = hitbox.clone();
+ cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
+ listener(event, phase, &hitbox, cx);
+ })
+ }
- for listener in self.scroll_wheel_listeners.drain(..) {
- let interactive_bounds = interactive_bounds.clone();
- cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
- listener(event, &interactive_bounds, phase, cx);
- })
- }
+ for listener in self.mouse_up_listeners.drain(..) {
+ let hitbox = hitbox.clone();
+ cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
+ listener(event, phase, &hitbox, cx);
+ })
+ }
- paint_hover_group_handler(cx);
+ for listener in self.mouse_move_listeners.drain(..) {
+ let hitbox = hitbox.clone();
+ cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
+ listener(event, phase, &hitbox, cx);
+ })
+ }
- if self.hover_style.is_some()
- || self.base_style.mouse_cursor.is_some()
- || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
- {
- let bounds = bounds.intersect(&cx.content_mask().bounds);
- let hovered = bounds.contains(&cx.mouse_position());
- cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
- if phase == DispatchPhase::Capture
- && bounds.contains(&event.position) != hovered
- {
- cx.refresh();
- }
- });
- }
+ for listener in self.scroll_wheel_listeners.drain(..) {
+ let hitbox = hitbox.clone();
+ cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
+ listener(event, phase, &hitbox, cx);
+ })
+ }
- let mut drag_listener = mem::take(&mut self.drag_listener);
- let drop_listeners = mem::take(&mut self.drop_listeners);
- let click_listeners = mem::take(&mut self.click_listeners);
- let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
-
- if !drop_listeners.is_empty() {
- cx.on_mouse_event({
- let interactive_bounds = interactive_bounds.clone();
- move |event: &MouseUpEvent, phase, cx| {
- if let Some(drag) = &cx.active_drag {
- if phase == DispatchPhase::Bubble
- && interactive_bounds
- .drag_target_contains(&event.position, cx)
- {
- let drag_state_type = drag.value.as_ref().type_id();
- for (drop_state_type, listener) in &drop_listeners {
- if *drop_state_type == drag_state_type {
- let drag = cx.active_drag.take().expect(
- "checked for type drag state type above",
- );
-
- let mut can_drop = true;
- if let Some(predicate) = &can_drop_predicate {
- can_drop = predicate(
- drag.value.as_ref(),
- cx.deref_mut(),
- );
- }
-
- if can_drop {
- listener(
- drag.value.as_ref(),
- cx.deref_mut(),
- );
- cx.refresh();
- cx.stop_propagation();
- }
- }
- }
- }
+ if self.hover_style.is_some()
+ || self.base_style.mouse_cursor.is_some()
+ || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
+ {
+ let hitbox = hitbox.clone();
+ let was_hovered = hitbox.is_hovered(cx);
+ cx.on_mouse_event(move |_: &MouseMoveEvent, phase, cx| {
+ let hovered = hitbox.is_hovered(cx);
+ if phase == DispatchPhase::Capture && hovered != was_hovered {
+ cx.refresh();
+ }
+ });
+ }
+
+ let mut drag_listener = mem::take(&mut self.drag_listener);
+ let drop_listeners = mem::take(&mut self.drop_listeners);
+ let click_listeners = mem::take(&mut self.click_listeners);
+ let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
+
+ if !drop_listeners.is_empty() {
+ let hitbox = hitbox.clone();
+ cx.on_mouse_event({
+ move |_: &MouseUpEvent, phase, cx| {
+ if let Some(drag) = &cx.active_drag {
+ if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
+ let drag_state_type = drag.value.as_ref().type_id();
+ for (drop_state_type, listener) in &drop_listeners {
+ if *drop_state_type == drag_state_type {
+ let drag = cx
+ .active_drag
+ .take()
+ .expect("checked for type drag state type above");
+
+ let mut can_drop = true;
+ if let Some(predicate) = &can_drop_predicate {
+ can_drop = predicate(drag.value.as_ref(), cx.deref_mut());
}
- }
- });
- }
- if !click_listeners.is_empty() || drag_listener.is_some() {
- let pending_mouse_down = element_state
- .pending_mouse_down
- .get_or_insert_with(Default::default)
- .clone();
-
- let clicked_state = element_state
- .clicked_state
- .get_or_insert_with(Default::default)
- .clone();
-
- cx.on_mouse_event({
- let interactive_bounds = interactive_bounds.clone();
- let pending_mouse_down = pending_mouse_down.clone();
- move |event: &MouseDownEvent, phase, cx| {
- if phase == DispatchPhase::Bubble
- && event.button == MouseButton::Left
- && interactive_bounds.visibly_contains(&event.position, cx)
- {
- *pending_mouse_down.borrow_mut() = Some(event.clone());
+ if can_drop {
+ listener(drag.value.as_ref(), cx.deref_mut());
cx.refresh();
+ cx.stop_propagation();
}
}
- });
+ }
+ }
+ }
+ }
+ });
+ }
- cx.on_mouse_event({
- let pending_mouse_down = pending_mouse_down.clone();
- move |event: &MouseMoveEvent, phase, cx| {
- if phase == DispatchPhase::Capture {
- return;
- }
+ if let Some(element_state) = element_state {
+ if !click_listeners.is_empty() || drag_listener.is_some() {
+ let pending_mouse_down = element_state
+ .pending_mouse_down
+ .get_or_insert_with(Default::default)
+ .clone();
+
+ let clicked_state = element_state
+ .clicked_state
+ .get_or_insert_with(Default::default)
+ .clone();
+
+ cx.on_mouse_event({
+ let pending_mouse_down = pending_mouse_down.clone();
+ let hitbox = hitbox.clone();
+ move |event: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble
+ && event.button == MouseButton::Left
+ && hitbox.is_hovered(cx)
+ {
+ *pending_mouse_down.borrow_mut() = Some(event.clone());
+ cx.refresh();
+ }
+ }
+ });
- let mut pending_mouse_down = pending_mouse_down.borrow_mut();
- if let Some(mouse_down) = pending_mouse_down.clone() {
- if !cx.has_active_drag()
- && (event.position - mouse_down.position).magnitude()
- > DRAG_THRESHOLD
- {
- if let Some((drag_value, drag_listener)) =
- drag_listener.take()
- {
- *clicked_state.borrow_mut() =
- ElementClickedState::default();
- let cursor_offset = event.position - bounds.origin;
- let drag = (drag_listener)(drag_value.as_ref(), cx);
- cx.active_drag = Some(AnyDrag {
- view: drag,
- value: drag_value,
- cursor_offset,
- });
- pending_mouse_down.take();
- cx.refresh();
- cx.stop_propagation();
- }
- }
- }
- }
- });
-
- cx.on_mouse_event({
- let interactive_bounds = interactive_bounds.clone();
- let mut captured_mouse_down = None;
- move |event: &MouseUpEvent, phase, cx| match phase {
- // Clear the pending mouse down during the capture phase,
- // so that it happens even if another event handler stops
- // propagation.
- DispatchPhase::Capture => {
- let mut pending_mouse_down =
- pending_mouse_down.borrow_mut();
- if pending_mouse_down.is_some() {
- captured_mouse_down = pending_mouse_down.take();
- cx.refresh();
- }
- }
- // Fire click handlers during the bubble phase.
- DispatchPhase::Bubble => {
- if let Some(mouse_down) = captured_mouse_down.take() {
- if interactive_bounds
- .visibly_contains(&event.position, cx)
- {
- let mouse_click = ClickEvent {
- down: mouse_down,
- up: event.clone(),
- };
- for listener in &click_listeners {
- listener(&mouse_click, cx);
- }
- }
- }
- }
- }
- });
+ cx.on_mouse_event({
+ let pending_mouse_down = pending_mouse_down.clone();
+ let hitbox = hitbox.clone();
+ move |event: &MouseMoveEvent, phase, cx| {
+ if phase == DispatchPhase::Capture {
+ return;
}
- if let Some(hover_listener) = self.hover_listener.take() {
- let was_hovered = element_state
- .hover_state
- .get_or_insert_with(Default::default)
- .clone();
- let has_mouse_down = element_state
- .pending_mouse_down
- .get_or_insert_with(Default::default)
- .clone();
- let interactive_bounds = interactive_bounds.clone();
-
- cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
- if phase != DispatchPhase::Bubble {
- return;
+ let mut pending_mouse_down = pending_mouse_down.borrow_mut();
+ if let Some(mouse_down) = pending_mouse_down.clone() {
+ if !cx.has_active_drag()
+ && (event.position - mouse_down.position).magnitude()
+ > DRAG_THRESHOLD
+ {
+ if let Some((drag_value, drag_listener)) = drag_listener.take() {
+ *clicked_state.borrow_mut() = ElementClickedState::default();
+ let cursor_offset = event.position - hitbox.origin;
+ let drag = (drag_listener)(drag_value.as_ref(), cx);
+ cx.active_drag = Some(AnyDrag {
+ view: drag,
+ value: drag_value,
+ cursor_offset,
+ });
+ pending_mouse_down.take();
+ cx.refresh();
+ cx.stop_propagation();
}
- let is_hovered = interactive_bounds
- .visibly_contains(&event.position, cx)
- && has_mouse_down.borrow().is_none()
- && !cx.has_active_drag();
- let mut was_hovered = was_hovered.borrow_mut();
-
- if is_hovered != *was_hovered {
- *was_hovered = is_hovered;
- drop(was_hovered);
+ }
+ }
+ }
+ });
- hover_listener(&is_hovered, cx.deref_mut());
+ cx.on_mouse_event({
+ let mut captured_mouse_down = None;
+ let hitbox = hitbox.clone();
+ move |event: &MouseUpEvent, phase, cx| match phase {
+ // Clear the pending mouse down during the capture phase,
+ // so that it happens even if another event handler stops
+ // propagation.
+ DispatchPhase::Capture => {
+ let mut pending_mouse_down = pending_mouse_down.borrow_mut();
+ if pending_mouse_down.is_some() && hitbox.is_hovered(cx) {
+ captured_mouse_down = pending_mouse_down.take();
+ cx.refresh();
+ }
+ }
+ // Fire click handlers during the bubble phase.
+ DispatchPhase::Bubble => {
+ if let Some(mouse_down) = captured_mouse_down.take() {
+ let mouse_click = ClickEvent {
+ down: mouse_down,
+ up: event.clone(),
+ };
+ for listener in &click_listeners {
+ listener(&mouse_click, cx);
}
- });
+ }
}
+ }
+ });
+ }
- if let Some(tooltip_builder) = self.tooltip_builder.take() {
- let active_tooltip = element_state
- .active_tooltip
- .get_or_insert_with(Default::default)
- .clone();
- let pending_mouse_down = element_state
- .pending_mouse_down
- .get_or_insert_with(Default::default)
- .clone();
- let interactive_bounds = interactive_bounds.clone();
-
- cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
- let is_hovered = interactive_bounds
- .visibly_contains(&event.position, cx)
- && pending_mouse_down.borrow().is_none();
- if !is_hovered {
- active_tooltip.borrow_mut().take();
- return;
- }
+ if let Some(hover_listener) = self.hover_listener.take() {
+ let hitbox = hitbox.clone();
+ let was_hovered = element_state
+ .hover_state
+ .get_or_insert_with(Default::default)
+ .clone();
+ let has_mouse_down = element_state
+ .pending_mouse_down
+ .get_or_insert_with(Default::default)
+ .clone();
+
+ cx.on_mouse_event(move |_: &MouseMoveEvent, phase, cx| {
+ if phase != DispatchPhase::Bubble {
+ return;
+ }
+ let is_hovered = has_mouse_down.borrow().is_none()
+ && !cx.has_active_drag()
+ && hitbox.is_hovered(cx);
+ let mut was_hovered = was_hovered.borrow_mut();
- if phase != DispatchPhase::Bubble {
- return;
- }
+ if is_hovered != *was_hovered {
+ *was_hovered = is_hovered;
+ drop(was_hovered);
- 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| {
- active_tooltip.borrow_mut().replace(
- ActiveTooltip {
- tooltip: Some(AnyTooltip {
- view: tooltip_builder(cx),
- cursor_offset: cx.mouse_position(),
- }),
- _task: None,
- },
- );
- cx.refresh();
- })
- .ok();
- }
- });
+ hover_listener(&is_hovered, cx.deref_mut());
+ }
+ });
+ }
+
+ if let Some(tooltip_builder) = self.tooltip_builder.take() {
+ let active_tooltip = element_state
+ .active_tooltip
+ .get_or_insert_with(Default::default)
+ .clone();
+ let pending_mouse_down = element_state
+ .pending_mouse_down
+ .get_or_insert_with(Default::default)
+ .clone();
+ let hitbox = hitbox.clone();
+
+ cx.on_mouse_event(move |_: &MouseMoveEvent, phase, cx| {
+ let is_hovered = pending_mouse_down.borrow().is_none() && hitbox.is_hovered(cx);
+ if !is_hovered {
+ active_tooltip.borrow_mut().take();
+ return;
+ }
+
+ 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| {
active_tooltip.borrow_mut().replace(ActiveTooltip {
- tooltip: None,
- _task: Some(task),
+ tooltip: Some(AnyTooltip {
+ view: tooltip_builder(cx),
+ cursor_offset: cx.mouse_position(),
+ }),
+ _task: None,
});
- }
- });
-
- let active_tooltip = element_state
- .active_tooltip
- .get_or_insert_with(Default::default)
- .clone();
- cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
- active_tooltip.borrow_mut().take();
- });
-
- if let Some(active_tooltip) = element_state
- .active_tooltip
- .get_or_insert_with(Default::default)
- .borrow()
- .as_ref()
- {
- if let Some(tooltip) = active_tooltip.tooltip.clone() {
- cx.set_tooltip(tooltip);
- }
+ cx.refresh();
+ })
+ .ok();
}
- }
+ });
+ active_tooltip.borrow_mut().replace(ActiveTooltip {
+ tooltip: None,
+ _task: Some(task),
+ });
+ }
+ });
- let active_state = element_state
- .clicked_state
- .get_or_insert_with(Default::default)
- .clone();
- if active_state.borrow().is_clicked() {
- cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
- if phase == DispatchPhase::Capture {
- *active_state.borrow_mut() = ElementClickedState::default();
- cx.refresh();
- }
- });
- } else {
- let active_group_bounds = self
- .group_active_style
- .as_ref()
- .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
- let interactive_bounds = interactive_bounds.clone();
- cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
- if phase == DispatchPhase::Bubble && !cx.default_prevented() {
- let group = active_group_bounds
- .map_or(false, |bounds| bounds.contains(&down.position));
- let element =
- interactive_bounds.visibly_contains(&down.position, cx);
- if group || element {
- *active_state.borrow_mut() =
- ElementClickedState { group, element };
- cx.refresh();
- }
- }
- });
- }
+ let active_tooltip = element_state
+ .active_tooltip
+ .get_or_insert_with(Default::default)
+ .clone();
+ cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
+ active_tooltip.borrow_mut().take();
+ });
+ }
- let overflow = style.overflow;
- if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
- if let Some(scroll_handle) = &self.scroll_handle {
- scroll_handle.0.borrow_mut().overflow = overflow;
- }
+ let active_state = element_state
+ .clicked_state
+ .get_or_insert_with(Default::default)
+ .clone();
+ if active_state.borrow().is_clicked() {
+ cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
+ if phase == DispatchPhase::Capture {
+ *active_state.borrow_mut() = ElementClickedState::default();
+ cx.refresh();
+ }
+ });
+ } else {
+ let active_group_hitbox = self
+ .group_active_style
+ .as_ref()
+ .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
+ let hitbox = hitbox.clone();
+ cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble && !cx.default_prevented() {
+ let group_hovered = active_group_hitbox
+ .map_or(false, |group_hitbox_id| group_hitbox_id.is_hovered(cx));
+ let element_hovered = hitbox.is_hovered(cx);
+ if group_hovered || element_hovered {
+ *active_state.borrow_mut() = ElementClickedState {
+ group: group_hovered,
+ element: element_hovered,
+ };
+ cx.refresh();
+ }
+ }
+ });
+ }
+ }
+ }
- let scroll_offset = element_state
- .scroll_offset
- .get_or_insert_with(Rc::default)
- .clone();
- let line_height = cx.line_height();
- let rem_size = cx.rem_size();
- let padding_size = size(
- style
- .padding
- .left
- .to_pixels(bounds.size.width.into(), rem_size)
- + style
- .padding
- .right
- .to_pixels(bounds.size.width.into(), rem_size),
- style
- .padding
- .top
- .to_pixels(bounds.size.height.into(), rem_size)
- + style
- .padding
- .bottom
- .to_pixels(bounds.size.height.into(), rem_size),
- );
- let scroll_max =
- (content_size + padding_size - bounds.size).max(&Size::default());
- // Clamp scroll offset in case scroll max is smaller now (e.g., if children
- // were removed or the bounds became larger).
- {
- let mut scroll_offset = scroll_offset.borrow_mut();
- scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.));
- scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.));
- }
+ fn paint_keyboard_listeners(&mut self, cx: &mut ElementContext) {
+ let key_down_listeners = mem::take(&mut self.key_down_listeners);
+ let key_up_listeners = mem::take(&mut self.key_up_listeners);
+ let action_listeners = mem::take(&mut self.action_listeners);
+ if let Some(context) = self.key_context.clone() {
+ cx.set_key_context(context);
+ }
+ if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
+ cx.set_focus_handle(focus_handle);
+ }
- let interactive_bounds = interactive_bounds.clone();
- cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
- if phase == DispatchPhase::Bubble
- && interactive_bounds.visibly_contains(&event.position, cx)
- {
- let mut scroll_offset = scroll_offset.borrow_mut();
- let old_scroll_offset = *scroll_offset;
- let delta = event.delta.pixel_delta(line_height);
-
- if overflow.x == Overflow::Scroll {
- let mut delta_x = Pixels::ZERO;
- if !delta.x.is_zero() {
- delta_x = delta.x;
- } else if overflow.y != Overflow::Scroll {
- delta_x = delta.y;
- }
-
- scroll_offset.x = (scroll_offset.x + delta_x)
- .clamp(-scroll_max.width, px(0.));
- }
+ for listener in key_down_listeners {
+ cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
+ listener(event, phase, cx);
+ })
+ }
- if overflow.y == Overflow::Scroll {
- let mut delta_y = Pixels::ZERO;
- if !delta.y.is_zero() {
- delta_y = delta.y;
- } else if overflow.x != Overflow::Scroll {
- delta_y = delta.x;
- }
+ for listener in key_up_listeners {
+ cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
+ listener(event, phase, cx);
+ })
+ }
- scroll_offset.y = (scroll_offset.y + delta_y)
- .clamp(-scroll_max.height, px(0.));
- }
+ for (action_type, listener) in action_listeners {
+ cx.on_action(action_type, listener)
+ }
+ }
- if *scroll_offset != old_scroll_offset {
- cx.refresh();
- cx.stop_propagation();
- }
- }
- });
- }
+ fn paint_hover_group_handler(&self, cx: &mut ElementContext) {
+ let group_hitbox = self
+ .group_hover_style
+ .as_ref()
+ .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
+
+ if let Some(group_hitbox) = group_hitbox {
+ let was_hovered = group_hitbox.is_hovered(cx);
+ cx.on_mouse_event(move |_: &MouseMoveEvent, phase, cx| {
+ let hovered = group_hitbox.is_hovered(cx);
+ if phase == DispatchPhase::Capture && hovered != was_hovered {
+ cx.refresh();
+ }
+ });
+ }
+ }
- if let Some(group) = self.group.clone() {
- GroupBounds::push(group, bounds, cx);
+ fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) {
+ if let Some(scroll_offset) = self.scroll_offset.clone() {
+ let overflow = style.overflow;
+ let line_height = cx.line_height();
+ let hitbox = hitbox.clone();
+ cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
+ let mut scroll_offset = scroll_offset.borrow_mut();
+ let old_scroll_offset = *scroll_offset;
+ let delta = event.delta.pixel_delta(line_height);
+
+ if overflow.x == Overflow::Scroll {
+ let mut delta_x = Pixels::ZERO;
+ if !delta.x.is_zero() {
+ delta_x = delta.x;
+ } else if overflow.y != Overflow::Scroll {
+ delta_x = delta.y;
}
- let scroll_offset = element_state
- .scroll_offset
- .as_ref()
- .map(|scroll_offset| *scroll_offset.borrow());
-
- let key_down_listeners = mem::take(&mut self.key_down_listeners);
- let key_up_listeners = mem::take(&mut self.key_up_listeners);
- let action_listeners = mem::take(&mut self.action_listeners);
- cx.with_key_dispatch(
- self.key_context.clone(),
- element_state.focus_handle.clone(),
- |_, cx| {
- for listener in key_down_listeners {
- cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
- listener(event, phase, cx);
- })
- }
-
- for listener in key_up_listeners {
- cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
- listener(event, phase, cx);
- })
- }
+ scroll_offset.x += delta_x;
+ }
- for (action_type, listener) in action_listeners {
- cx.on_action(action_type, listener)
- }
+ if overflow.y == Overflow::Scroll {
+ let mut delta_y = Pixels::ZERO;
+ if !delta.y.is_zero() {
+ delta_y = delta.y;
+ } else if overflow.x != Overflow::Scroll {
+ delta_y = delta.x;
+ }
- f(&style, scroll_offset.unwrap_or_default(), cx)
- },
- );
+ scroll_offset.y += delta_y;
+ }
- if let Some(group) = self.group.as_ref() {
- GroupBounds::pop(group, cx);
- }
- });
- });
+ if *scroll_offset != old_scroll_offset {
+ cx.refresh();
+ cx.stop_propagation();
+ }
+ }
});
- });
+ }
}
/// Compute the visual style for this element, based on the current bounds and the element's state.
- pub fn compute_style(
+ pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut ElementContext) -> Style {
+ cx.with_element_state(self.element_id.clone(), |element_state, cx| {
+ let mut element_state =
+ element_state.map(|element_state| element_state.unwrap_or_default());
+ let style = self.compute_style_internal(hitbox, element_state.as_mut(), cx);
+ (style, element_state)
+ })
+ }
+
+ /// Called from internal methods that have already called with_element_state.
+ fn compute_style_internal(
&self,
- bounds: Option<Bounds<Pixels>>,
- element_state: &mut InteractiveElementState,
+ hitbox: Option<&Hitbox>,
+ element_state: Option<&mut InteractiveElementState>,
cx: &mut ElementContext,
) -> Style {
let mut style = Style::default();
style.refine(&self.base_style);
- cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
- if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
- if let Some(in_focus_style) = self.in_focus_style.as_ref() {
- if focus_handle.within_focused(cx) {
- style.refine(in_focus_style);
- }
+ if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
+ if let Some(in_focus_style) = self.in_focus_style.as_ref() {
+ if focus_handle.within_focused(cx) {
+ style.refine(in_focus_style);
}
+ }
- if let Some(focus_style) = self.focus_style.as_ref() {
- if focus_handle.is_focused(cx) {
- style.refine(focus_style);
- }
+ if let Some(focus_style) = self.focus_style.as_ref() {
+ if focus_handle.is_focused(cx) {
+ style.refine(focus_style);
}
}
+ }
- if let Some(bounds) = bounds {
- let mouse_position = cx.mouse_position();
- if !cx.has_active_drag() {
- if let Some(group_hover) = self.group_hover_style.as_ref() {
- if let Some(group_bounds) =
- GroupBounds::get(&group_hover.group, cx.deref_mut())
- {
- if group_bounds.contains(&mouse_position) {
- style.refine(&group_hover.style);
- }
- }
- }
-
- if let Some(hover_style) = self.hover_style.as_ref() {
- if bounds
- .intersect(&cx.content_mask().bounds)
- .contains(&mouse_position)
- {
- style.refine(hover_style);
+ if let Some(hitbox) = hitbox {
+ if !cx.has_active_drag() {
+ if let Some(group_hover) = self.group_hover_style.as_ref() {
+ if let Some(group_hitbox_id) =
+ GroupHitboxes::get(&group_hover.group, cx.deref_mut())
+ {
+ if group_hitbox_id.is_hovered(cx) {
+ style.refine(&group_hover.style);
}
}
}
- if let Some(drag) = cx.active_drag.take() {
- let mut can_drop = true;
- if let Some(can_drop_predicate) = &self.can_drop_predicate {
- can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut());
+ if let Some(hover_style) = self.hover_style.as_ref() {
+ if hitbox.is_hovered(cx) {
+ style.refine(hover_style);
}
+ }
+ }
- if can_drop {
- for (state_type, group_drag_style) in &self.group_drag_over_styles {
- if let Some(group_bounds) =
- GroupBounds::get(&group_drag_style.group, cx.deref_mut())
- {
- if *state_type == drag.value.as_ref().type_id()
- && group_bounds.contains(&mouse_position)
- {
- style.refine(&group_drag_style.style);
- }
- }
- }
+ if let Some(drag) = cx.active_drag.take() {
+ let mut can_drop = true;
+ if let Some(can_drop_predicate) = &self.can_drop_predicate {
+ can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut());
+ }
- for (state_type, build_drag_over_style) in &self.drag_over_styles {
+ if can_drop {
+ for (state_type, group_drag_style) in &self.group_drag_over_styles {
+ if let Some(group_hitbox_id) =
+ GroupHitboxes::get(&group_drag_style.group, cx.deref_mut())
+ {
if *state_type == drag.value.as_ref().type_id()
- && bounds
- .intersect(&cx.content_mask().bounds)
- .contains(&mouse_position)
- && cx.was_top_layer_under_active_drag(
- &mouse_position,
- cx.stacking_order(),
- )
+ && group_hitbox_id.is_hovered(cx)
{
- style.refine(&build_drag_over_style(drag.value.as_ref(), cx));
+ style.refine(&group_drag_style.style);
}
}
}
- cx.active_drag = Some(drag);
+ for (state_type, build_drag_over_style) in &self.drag_over_styles {
+ if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(cx) {
+ style.refine(&build_drag_over_style(drag.value.as_ref(), cx));
+ }
+ }
}
+
+ cx.active_drag = Some(drag);
}
+ }
+ if let Some(element_state) = element_state {
let clicked_state = element_state
.clicked_state
.get_or_insert_with(Default::default)
@@ -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 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 request_layout(
+ 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!(),
- };
-
- 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) => {
+ 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();
}
-
- #[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
}
@@ -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
@@ -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
}
@@ -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
}
@@ -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 before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+ self.text.before_layout(cx)
+ }
- fn request_layout(
+ fn after_layout(
&mut self,
- state: Option<Self::State>,
+ bounds: Bounds<Pixels>,
+ state: &mut Self::BeforeLayout,
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)
- }
+ ) -> Hitbox {
+ self.text.after_layout(bounds, state, cx);
+ cx.insert_hitbox(bounds, false)
}
- 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);
- }
- }
-
- 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,
- )
+ cx.set_cursor_style(crate::CursorStyle::PointingHand, hitbox)
}
+ }
- mouse_down.take();
- cx.refresh();
+ 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,
+ )
+ }
+
+ mouse_down.take();
+ cx.refresh();
+ }
+ });
+ } 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();
+ }
+ }
+ });
}
- });
- } 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.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(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();
- if phase != DispatchPhase::Bubble {
- return;
- }
+ 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 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();
+ 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();
- });
-
- 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)
+ self.text.paint(bounds, text_state, &mut (), 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
}
@@ -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 (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 element_state = UniformListState {
- interactive,
- item_size,
- };
+ 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,
+ });
- (layout_id, element_state)
+ let height = match available_space.height {
+ AvailableSpace::Definite(height) => desired_height.min(height),
+ AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
+ };
+ size(width, height)
+ })
+ });
+
+ (
+ 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
}
@@ -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.
@@ -70,6 +70,7 @@ mod app;
mod arena;
mod assets;
+mod bounds_tree;
mod color;
mod element;
mod elements;
@@ -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);
+
+ 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);
+ }
- if let Some(view_id) = view_id {
+ pub fn set_view_id(&mut self, view_id: EntityId) {
+ if self.view_stack.last().copied() != Some(view_id) {
+ let node_id = *self.node_stack.last().unwrap();
+ self.nodes[node_id.0].view_id = Some(view_id);
self.view_node_ids.insert(view_id, node_id);
+ self.view_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()
}
}
@@ -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())
@@ -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(
@@ -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!()
}
@@ -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,187 +44,171 @@ impl Scene {
&self.paths
}
- pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
- BatchIterator {
- shadows: &self.shadows,
- shadows_start: 0,
- shadows_iter: self.shadows.iter().peekable(),
- quads: &self.quads,
- quads_start: 0,
- quads_iter: self.quads.iter().peekable(),
- paths: &self.paths,
- paths_start: 0,
- paths_iter: self.paths.iter().peekable(),
- underlines: &self.underlines,
- underlines_start: 0,
- underlines_iter: self.underlines.iter().peekable(),
- monochrome_sprites: &self.monochrome_sprites,
- monochrome_sprites_start: 0,
- monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
- polychrome_sprites: &self.polychrome_sprites,
- polychrome_sprites_start: 0,
- polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
- surfaces: &self.surfaces,
- surfaces_start: 0,
- surfaces_iter: self.surfaces.iter().peekable(),
- }
+ 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(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
- let primitive = primitive.into();
+ 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.size.width <= ScaledPixels(0.)
- || clipped_bounds.size.height <= ScaledPixels(0.)
- {
+
+ if clipped_bounds.is_empty() {
return;
}
- let layer_id = self.layer_id_for_order(order);
- match primitive {
- Primitive::Shadow(mut shadow) => {
- shadow.layer_id = layer_id;
- self.shadows.push(shadow);
+ 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(mut quad) => {
- quad.layer_id = layer_id;
- self.quads.push(quad);
+ Primitive::Quad(quad) => {
+ quad.order = order;
+ self.quads.push(quad.clone());
}
- Primitive::Path(mut path) => {
- path.layer_id = layer_id;
+ Primitive::Path(path) => {
+ path.order = order;
path.id = PathId(self.paths.len());
- self.paths.push(path);
+ self.paths.push(path.clone());
}
- Primitive::Underline(mut underline) => {
- underline.layer_id = layer_id;
- self.underlines.push(underline);
+ Primitive::Underline(underline) => {
+ underline.order = order;
+ self.underlines.push(underline.clone());
}
- Primitive::MonochromeSprite(mut sprite) => {
- sprite.layer_id = layer_id;
- self.monochrome_sprites.push(sprite);
+ Primitive::MonochromeSprite(sprite) => {
+ sprite.order = order;
+ self.monochrome_sprites.push(sprite.clone());
}
- Primitive::PolychromeSprite(mut sprite) => {
- sprite.layer_id = layer_id;
- self.polychrome_sprites.push(sprite);
+ Primitive::PolychromeSprite(sprite) => {
+ sprite.order = order;
+ self.polychrome_sprites.push(sprite.clone());
}
- Primitive::Surface(mut surface) => {
- surface.layer_id = layer_id;
- self.surfaces.push(surface);
+ Primitive::Surface(surface) => {
+ surface.order = order;
+ self.surfaces.push(surface.clone());
}
}
+ self.paint_operations
+ .push(PaintOperation::Primitive(primitive));
}
- 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;
- }
- }
-
- 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 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) {
- 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);
+ self.shadows.sort();
+ self.quads.sort();
+ self.paths.sort();
+ self.underlines.sort();
+ self.monochrome_sprites.sort();
+ self.polychrome_sprites.sort();
+ self.surfaces.sort();
+ }
- for quad in &mut self.quads {
- quad.order = orders[quad.layer_id as usize];
+ pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
+ BatchIterator {
+ shadows: &self.shadows,
+ shadows_start: 0,
+ shadows_iter: self.shadows.iter().peekable(),
+ quads: &self.quads,
+ quads_start: 0,
+ quads_iter: self.quads.iter().peekable(),
+ paths: &self.paths,
+ paths_start: 0,
+ paths_iter: self.paths.iter().peekable(),
+ underlines: &self.underlines,
+ underlines_start: 0,
+ underlines_iter: self.underlines.iter().peekable(),
+ monochrome_sprites: &self.monochrome_sprites,
+ monochrome_sprites_start: 0,
+ monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
+ polychrome_sprites: &self.polychrome_sprites,
+ polychrome_sprites_start: 0,
+ polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
+ surfaces: &self.surfaces,
+ surfaces_start: 0,
+ surfaces_iter: self.surfaces.iter().peekable(),
}
- 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);
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
+pub(crate) enum PrimitiveKind {
+ Shadow,
+ #[default]
+ Quad,
+ Path,
+ Underline,
+ MonochromeSprite,
+ PolychromeSprite,
+ Surface,
+}
- for underline in &mut self.underlines {
- underline.order = orders[underline.layer_id as usize];
- }
- self.underlines.sort_by_key(|underline| underline.order);
+pub(crate) enum PaintOperation {
+ Primitive(Primitive),
+ StartLayer(Bounds<ScaledPixels>),
+ EndLayer,
+}
- 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);
+#[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),
+}
- for polychrome_sprite in &mut self.polychrome_sprites {
- polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize];
+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,
}
- self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
+ }
- for surface in &mut self.surfaces {
- surface.order = orders[surface.layer_id as usize];
+ 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,
}
- 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),
@@ -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 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(),
- );
-
- 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);
- },
- );
+ 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 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);
+ },
+ );
}
#[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,
@@ -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 {
@@ -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 {
@@ -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.
@@ -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();
- }
- }
- 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,
- },
- ));
+ 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,
+ )?;
}
-
- 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(())
+ })
}
@@ -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();
- 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);
+ 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());
}
- 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);
+ 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());
}
- std::mem::swap(&mut *prev_frame_wrapped, &mut *curr_frame_wrapped);
- }
-
- pub fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
- self.view_stack.lock().push(view_id);
- let result = f();
- self.view_stack.lock().pop();
- result
}
- fn parent_view_id(&self) -> Option<EntityId> {
- self.view_stack.lock().last().copied()
+ pub fn finish_frame(&self) {
+ let mut prev_frame = self.previous_frame.lock();
+ let mut curr_frame = self.current_frame.write();
+ std::mem::swap(&mut *prev_frame, &mut *curr_frame);
+ 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
}
}
@@ -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(
+ 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.before_layout(cx);
+ (layout_id, element)
+ })
+ }
+
+ fn after_layout(
&mut self,
- _state: Option<Self::State>,
+ _: Bounds<Pixels>,
+ element: &mut Self::BeforeLayout,
cx: &mut ElementContext,
- ) -> (LayoutId, Self::State) {
- cx.with_view_id(self.entity_id(), |cx| {
- let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
- let layout_id = element.request_layout(cx);
- (layout_id, Some(element))
+ ) {
+ 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::State, cx: &mut ElementContext) {
- cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(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 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 request_layout(
+ fn after_layout(
&mut self,
- state: Option<Self::State>,
+ bounds: Bounds<Pixels>,
+ element: &mut Self::BeforeLayout,
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)
- })
+ ) -> 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();
+
+ let content_mask = cx.content_mask();
+ let text_style = cx.text_style();
+
+ if let Some(mut element_state) = element_state {
+ if element_state.cache_key.bounds == bounds
+ && element_state.cache_key.content_mask == content_mask
+ && element_state.cache_key.text_style == text_style
+ && !cx.window.dirty_views.contains(&self.entity_id())
+ && !cx.window.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));
+ }
+ }
+
+ 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>, state: &mut Self::State, cx: &mut ElementContext) {
- cx.paint_view(self.entity_id(), |cx| {
- if !self.cache {
- state.element.take().unwrap().paint(cx);
- return;
- }
-
- if let Some(cache_key) = state.cache_key.as_mut() {
- 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;
- }
- }
-
- 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);
- }
-
- 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(),
- });
- })
+ 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())
}
}
@@ -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;
@@ -16,92 +16,139 @@ use std::{
any::{Any, TypeId},
borrow::{Borrow, BorrowMut, Cow},
mem,
+ ops::Range,
rc::Rc,
sync::Arc,
};
use anyhow::Result;
-use collections::{FxHashMap, FxHashSet};
+use collections::FxHashMap;
use derive_more::{Deref, DerefMut};
#[cfg(target_os = "macos")]
use media::core_video::CVImageBuffer;
use smallvec::SmallVec;
-use util::post_inc;
use crate::{
- prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask,
- Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox,
- EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
- InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad,
- Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams,
- RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext,
- StackingOrder, StrikethroughStyle, Style, TextStyleRefinement, Underline, UnderlineStyle,
- Window, WindowContext, SUBPIXEL_VARIANTS,
+ prelude::*, size, AnyElement, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow,
+ ContentMask, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchPhase, DispatchTree,
+ DrawPhase, ElementId, ElementStateBox, EntityId, FocusHandle, FocusId, FontId, GlobalElementId,
+ GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyContext, KeyEvent, LayoutId,
+ LineLayoutIndex, MonochromeSprite, MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler,
+ Point, PolychromeSprite, Quad, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene,
+ Shadow, SharedString, Size, StrikethroughStyle, Style, TextStyleRefinement, Underline,
+ UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS,
};
-type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
+pub(crate) type AnyMouseListener =
+ Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
-pub(crate) struct RequestedInputHandler {
- pub(crate) view_id: EntityId,
- pub(crate) handler: Option<PlatformInputHandler>,
+#[derive(Clone)]
+pub(crate) struct CursorStyleRequest {
+ pub(crate) hitbox_id: HitboxId,
+ pub(crate) style: CursorStyle,
}
-pub(crate) struct TooltipRequest {
- pub(crate) view_id: EntityId,
- pub(crate) tooltip: AnyTooltip,
+/// An identifier for a [Hitbox].
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+pub struct HitboxId(usize);
+
+impl HitboxId {
+ /// Checks if the hitbox with this id is currently hovered.
+ pub fn is_hovered(&self, cx: &WindowContext) -> bool {
+ cx.window.mouse_hit_test.0.contains(self)
+ }
}
-#[derive(Clone)]
-pub(crate) struct CursorStyleRequest {
- pub(crate) style: CursorStyle,
- stacking_order: StackingOrder,
+/// A rectangular region that potentially blocks hitboxes inserted prior.
+/// See [ElementContext::insert_hitbox] for more details.
+#[derive(Clone, Debug, Eq, PartialEq, Deref)]
+pub struct Hitbox {
+ /// A unique identifier for the hitbox
+ pub id: HitboxId,
+ /// The bounds of the hitbox
+ #[deref]
+ pub bounds: Bounds<Pixels>,
+ /// Whether the hitbox occludes other hitboxes inserted prior.
+ pub opaque: bool,
+}
+
+impl Hitbox {
+ /// Checks if the hitbox is currently hovered.
+ pub fn is_hovered(&self, cx: &WindowContext) -> bool {
+ self.id.is_hovered(cx)
+ }
+}
+
+#[derive(Default)]
+pub(crate) struct HitTest(SmallVec<[HitboxId; 8]>);
+
+pub(crate) struct DeferredDraw {
+ priority: usize,
+ parent_node: DispatchNodeId,
+ element_id_stack: GlobalElementId,
+ text_style_stack: Vec<TextStyleRefinement>,
+ element: Option<AnyElement>,
+ absolute_offset: Point<Pixels>,
+ layout_range: Range<AfterLayoutIndex>,
+ paint_range: Range<PaintIndex>,
}
pub(crate) struct Frame {
pub(crate) focus: Option<FocusId>,
pub(crate) window_active: bool,
- pub(crate) element_states: FxHashMap<GlobalElementId, ElementStateBox>,
- pub(crate) mouse_listeners: FxHashMap<TypeId, Vec<(StackingOrder, EntityId, AnyMouseListener)>>,
+ pub(crate) element_states: FxHashMap<(GlobalElementId, TypeId), ElementStateBox>,
+ accessed_element_states: Vec<(GlobalElementId, TypeId)>,
+ pub(crate) mouse_listeners: Vec<Option<AnyMouseListener>>,
pub(crate) dispatch_tree: DispatchTree,
pub(crate) scene: Scene,
- pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds<Pixels>)>,
- pub(crate) z_index_stack: StackingOrder,
- pub(crate) next_stacking_order_ids: Vec<u16>,
- pub(crate) next_root_z_index: u16,
+ pub(crate) hitboxes: Vec<Hitbox>,
+ pub(crate) deferred_draws: Vec<DeferredDraw>,
pub(crate) content_mask_stack: Vec<ContentMask<Pixels>>,
pub(crate) element_offset_stack: Vec<Point<Pixels>>,
- pub(crate) requested_input_handler: Option<RequestedInputHandler>,
- pub(crate) tooltip_request: Option<TooltipRequest>,
- pub(crate) cursor_styles: FxHashMap<EntityId, CursorStyleRequest>,
- pub(crate) requested_cursor_style: Option<CursorStyleRequest>,
- pub(crate) view_stack: Vec<EntityId>,
- pub(crate) reused_views: FxHashSet<EntityId>,
-
+ pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>,
+ pub(crate) tooltip_requests: Vec<Option<AnyTooltip>>,
+ pub(crate) cursor_styles: Vec<CursorStyleRequest>,
#[cfg(any(test, feature = "test-support"))]
pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>,
}
+#[derive(Clone, Default)]
+pub(crate) struct AfterLayoutIndex {
+ hitboxes_index: usize,
+ tooltips_index: usize,
+ deferred_draws_index: usize,
+ dispatch_tree_index: usize,
+ accessed_element_states_index: usize,
+ line_layout_index: LineLayoutIndex,
+}
+
+#[derive(Clone, Default)]
+pub(crate) struct PaintIndex {
+ scene_index: usize,
+ mouse_listeners_index: usize,
+ input_handlers_index: usize,
+ cursor_styles_index: usize,
+ accessed_element_states_index: usize,
+ line_layout_index: LineLayoutIndex,
+}
+
impl Frame {
pub(crate) fn new(dispatch_tree: DispatchTree) -> Self {
Frame {
focus: None,
window_active: false,
element_states: FxHashMap::default(),
- mouse_listeners: FxHashMap::default(),
+ accessed_element_states: Vec::new(),
+ mouse_listeners: Vec::new(),
dispatch_tree,
scene: Scene::default(),
- depth_map: Vec::new(),
- z_index_stack: StackingOrder::default(),
- next_stacking_order_ids: vec![0],
- next_root_z_index: 0,
+ hitboxes: Vec::new(),
+ deferred_draws: Vec::new(),
content_mask_stack: Vec::new(),
element_offset_stack: Vec::new(),
- requested_input_handler: None,
- tooltip_request: None,
- cursor_styles: FxHashMap::default(),
- requested_cursor_style: None,
- view_stack: Vec::new(),
- reused_views: FxHashSet::default(),
+ input_handlers: Vec::new(),
+ tooltip_requests: Vec::new(),
+ cursor_styles: Vec::new(),
#[cfg(any(test, feature = "test-support"))]
debug_bounds: FxHashMap::default(),
@@ -110,18 +157,28 @@ impl Frame {
pub(crate) fn clear(&mut self) {
self.element_states.clear();
- self.mouse_listeners.values_mut().for_each(Vec::clear);
+ self.accessed_element_states.clear();
+ self.mouse_listeners.clear();
self.dispatch_tree.clear();
- self.depth_map.clear();
- self.next_stacking_order_ids = vec![0];
- self.next_root_z_index = 0;
- self.reused_views.clear();
self.scene.clear();
- self.requested_input_handler.take();
- self.tooltip_request.take();
+ self.input_handlers.clear();
+ self.tooltip_requests.clear();
self.cursor_styles.clear();
- self.requested_cursor_style.take();
- debug_assert_eq!(self.view_stack.len(), 0);
+ self.hitboxes.clear();
+ self.deferred_draws.clear();
+ }
+
+ pub(crate) fn hit_test(&self, position: Point<Pixels>) -> HitTest {
+ let mut hit_test = HitTest::default();
+ for hitbox in self.hitboxes.iter().rev() {
+ if hitbox.bounds.contains(&position) {
+ hit_test.0.push(hitbox.id);
+ if hitbox.opaque {
+ break;
+ }
+ }
+ }
+ hit_test
}
pub(crate) fn focus_path(&self) -> SmallVec<[FocusId; 8]> {
@@ -131,38 +188,13 @@ impl Frame {
}
pub(crate) fn finish(&mut self, prev_frame: &mut Self) {
- // Reuse mouse listeners that didn't change since the last frame.
- for (type_id, listeners) in &mut prev_frame.mouse_listeners {
- let next_listeners = self.mouse_listeners.entry(*type_id).or_default();
- for (order, view_id, listener) in listeners.drain(..) {
- if self.reused_views.contains(&view_id) {
- next_listeners.push((order, view_id, listener));
- }
- }
- }
-
- // Reuse entries in the depth map that didn't change since the last frame.
- for (order, view_id, bounds) in prev_frame.depth_map.drain(..) {
- if self.reused_views.contains(&view_id) {
- match self
- .depth_map
- .binary_search_by(|(level, _, _)| order.cmp(level))
- {
- Ok(i) | Err(i) => self.depth_map.insert(i, (order, view_id, bounds)),
- }
- }
- }
-
- // Retain element states for views that didn't change since the last frame.
- for (element_id, state) in prev_frame.element_states.drain() {
- if self.reused_views.contains(&state.parent_view_id) {
- self.element_states.entry(element_id).or_insert(state);
+ for element_state_key in &self.accessed_element_states {
+ if let Some(element_state) = prev_frame.element_states.remove(element_state_key) {
+ self.element_states
+ .insert(element_state_key.clone(), element_state);
}
}
- // Reuse geometry that didn't change since the last frame.
- self.scene
- .reuse_views(&self.reused_views, &mut prev_frame.scene);
self.scene.finish();
}
}
@@ -316,68 +348,218 @@ impl<'a> VisualContext for ElementContext<'a> {
}
impl<'a> ElementContext<'a> {
- pub(crate) fn reuse_view(&mut self, next_stacking_order_id: u16) {
- let view_id = self.parent_view_id();
- let grafted_view_ids = self
- .cx
- .window
- .next_frame
- .dispatch_tree
- .reuse_view(view_id, &mut self.cx.window.rendered_frame.dispatch_tree);
- for view_id in grafted_view_ids {
- assert!(self.window.next_frame.reused_views.insert(view_id));
-
- // Reuse the previous input handler requested during painting of the reused view.
- if self
- .window
- .rendered_frame
- .requested_input_handler
- .as_ref()
- .map_or(false, |requested| requested.view_id == view_id)
- {
- self.window.next_frame.requested_input_handler =
- self.window.rendered_frame.requested_input_handler.take();
- }
+ pub(crate) fn draw_roots(&mut self) {
+ self.window.draw_phase = DrawPhase::Layout;
+
+ // Layout all root elements.
+ let mut root_element = self.window.root_view.as_ref().unwrap().clone().into_any();
+ root_element.layout(Point::default(), self.window.viewport_size.into(), self);
+
+ let mut prompt_element = None;
+ let mut active_drag_element = None;
+ let mut tooltip_element = None;
+ if let Some(prompt) = self.window.prompt.take() {
+ let mut element = prompt.view.any_view().into_any();
+ element.layout(Point::default(), self.window.viewport_size.into(), self);
+ prompt_element = Some(element);
+ self.window.prompt = Some(prompt);
+ } else if let Some(active_drag) = self.app.active_drag.take() {
+ let mut element = active_drag.view.clone().into_any();
+ let offset = self.mouse_position() - active_drag.cursor_offset;
+ element.layout(offset, AvailableSpace::min_size(), self);
+ active_drag_element = Some(element);
+ self.app.active_drag = Some(active_drag);
+ } else if let Some(tooltip_request) =
+ self.window.next_frame.tooltip_requests.last().cloned()
+ {
+ let tooltip_request = tooltip_request.unwrap();
+ let mut element = tooltip_request.view.clone().into_any();
+ let offset = tooltip_request.cursor_offset;
+ element.layout(offset, AvailableSpace::min_size(), self);
+ tooltip_element = Some(element);
+ }
+
+ let mut sorted_deferred_draws =
+ (0..self.window.next_frame.deferred_draws.len()).collect::<SmallVec<[_; 8]>>();
+ sorted_deferred_draws
+ .sort_unstable_by_key(|ix| self.window.next_frame.deferred_draws[*ix].priority);
+ self.layout_deferred_draws(&sorted_deferred_draws);
+
+ self.window.mouse_hit_test = self.window.next_frame.hit_test(self.window.mouse_position);
+
+ // Now actually paint the elements.
+ self.window.draw_phase = DrawPhase::Paint;
+ root_element.paint(self);
+
+ if let Some(mut prompt_element) = prompt_element {
+ prompt_element.paint(self)
+ } else if let Some(mut drag_element) = active_drag_element {
+ drag_element.paint(self);
+ } else if let Some(mut tooltip_element) = tooltip_element {
+ tooltip_element.paint(self);
+ }
+
+ self.paint_deferred_draws(&sorted_deferred_draws);
+ }
+
+ fn layout_deferred_draws(&mut self, deferred_draw_indices: &[usize]) {
+ assert_eq!(self.window.element_id_stack.len(), 0);
- // Reuse the tooltip previously requested during painting of the reused view.
- if self
- .window
- .rendered_frame
- .tooltip_request
- .as_ref()
- .map_or(false, |requested| requested.view_id == view_id)
- {
- self.window.next_frame.tooltip_request =
- self.window.rendered_frame.tooltip_request.take();
+ let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws);
+ for deferred_draw_ix in deferred_draw_indices {
+ let deferred_draw = &mut deferred_draws[*deferred_draw_ix];
+ self.window.element_id_stack = deferred_draw.element_id_stack.clone();
+ self.window.text_style_stack = deferred_draw.text_style_stack.clone();
+ self.window
+ .next_frame
+ .dispatch_tree
+ .set_active_node(deferred_draw.parent_node);
+
+ let layout_start = self.after_layout_index();
+ if let Some(element) = deferred_draw.element.as_mut() {
+ self.with_absolute_element_offset(deferred_draw.absolute_offset, |cx| {
+ element.after_layout(cx)
+ });
+ } else {
+ self.reuse_after_layout(deferred_draw.layout_range.clone());
}
+ let layout_end = self.after_layout_index();
+ deferred_draw.layout_range = layout_start..layout_end;
+ }
+ assert_eq!(
+ self.window.next_frame.deferred_draws.len(),
+ 0,
+ "cannot call defer_draw during deferred drawing"
+ );
+ self.window.next_frame.deferred_draws = deferred_draws;
+ self.window.element_id_stack.clear();
+ }
+
+ fn paint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) {
+ assert_eq!(self.window.element_id_stack.len(), 0);
+
+ let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws);
+ for deferred_draw_ix in deferred_draw_indices {
+ let mut deferred_draw = &mut deferred_draws[*deferred_draw_ix];
+ self.window.element_id_stack = deferred_draw.element_id_stack.clone();
+ self.window
+ .next_frame
+ .dispatch_tree
+ .set_active_node(deferred_draw.parent_node);
- // Reuse the cursor styles previously requested during painting of the reused view.
- if let Some(cursor_style_request) =
- self.window.rendered_frame.cursor_styles.remove(&view_id)
- {
- self.set_cursor_style(
- cursor_style_request.style,
- cursor_style_request.stacking_order,
- );
+ let paint_start = self.paint_index();
+ if let Some(element) = deferred_draw.element.as_mut() {
+ element.paint(self);
+ } else {
+ self.reuse_paint(deferred_draw.paint_range.clone());
}
+ let paint_end = self.paint_index();
+ deferred_draw.paint_range = paint_start..paint_end;
}
+ self.window.next_frame.deferred_draws = deferred_draws;
+ self.window.element_id_stack.clear();
+ }
- debug_assert!(
- next_stacking_order_id
- >= self
- .window
- .next_frame
- .next_stacking_order_ids
- .last()
- .copied()
- .unwrap()
+ pub(crate) fn after_layout_index(&self) -> AfterLayoutIndex {
+ AfterLayoutIndex {
+ hitboxes_index: self.window.next_frame.hitboxes.len(),
+ tooltips_index: self.window.next_frame.tooltip_requests.len(),
+ deferred_draws_index: self.window.next_frame.deferred_draws.len(),
+ dispatch_tree_index: self.window.next_frame.dispatch_tree.len(),
+ accessed_element_states_index: self.window.next_frame.accessed_element_states.len(),
+ line_layout_index: self.window.text_system.layout_index(),
+ }
+ }
+
+ pub(crate) fn reuse_after_layout(&mut self, range: Range<AfterLayoutIndex>) {
+ let window = &mut self.window;
+ window.next_frame.hitboxes.extend(
+ window.rendered_frame.hitboxes[range.start.hitboxes_index..range.end.hitboxes_index]
+ .iter()
+ .cloned(),
+ );
+ window.next_frame.tooltip_requests.extend(
+ window.rendered_frame.tooltip_requests
+ [range.start.tooltips_index..range.end.tooltips_index]
+ .iter_mut()
+ .map(|request| request.take()),
+ );
+ window.next_frame.accessed_element_states.extend(
+ window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index
+ ..range.end.accessed_element_states_index]
+ .iter()
+ .cloned(),
+ );
+ window
+ .text_system
+ .reuse_layouts(range.start.line_layout_index..range.end.line_layout_index);
+
+ let reused_subtree = window.next_frame.dispatch_tree.reuse_subtree(
+ range.start.dispatch_tree_index..range.end.dispatch_tree_index,
+ &mut window.rendered_frame.dispatch_tree,
+ );
+ window.next_frame.deferred_draws.extend(
+ window.rendered_frame.deferred_draws
+ [range.start.deferred_draws_index..range.end.deferred_draws_index]
+ .iter()
+ .map(|deferred_draw| DeferredDraw {
+ parent_node: reused_subtree.refresh_node_id(deferred_draw.parent_node),
+ element_id_stack: deferred_draw.element_id_stack.clone(),
+ text_style_stack: deferred_draw.text_style_stack.clone(),
+ priority: deferred_draw.priority,
+ element: None,
+ absolute_offset: deferred_draw.absolute_offset,
+ layout_range: deferred_draw.layout_range.clone(),
+ paint_range: deferred_draw.paint_range.clone(),
+ }),
+ );
+ }
+
+ pub(crate) fn paint_index(&self) -> PaintIndex {
+ PaintIndex {
+ scene_index: self.window.next_frame.scene.len(),
+ mouse_listeners_index: self.window.next_frame.mouse_listeners.len(),
+ input_handlers_index: self.window.next_frame.input_handlers.len(),
+ cursor_styles_index: self.window.next_frame.cursor_styles.len(),
+ accessed_element_states_index: self.window.next_frame.accessed_element_states.len(),
+ line_layout_index: self.window.text_system.layout_index(),
+ }
+ }
+
+ pub(crate) fn reuse_paint(&mut self, range: Range<PaintIndex>) {
+ let window = &mut self.cx.window;
+
+ window.next_frame.cursor_styles.extend(
+ window.rendered_frame.cursor_styles
+ [range.start.cursor_styles_index..range.end.cursor_styles_index]
+ .iter()
+ .cloned(),
+ );
+ window.next_frame.input_handlers.extend(
+ window.rendered_frame.input_handlers
+ [range.start.input_handlers_index..range.end.input_handlers_index]
+ .iter_mut()
+ .map(|handler| handler.take()),
+ );
+ window.next_frame.mouse_listeners.extend(
+ window.rendered_frame.mouse_listeners
+ [range.start.mouse_listeners_index..range.end.mouse_listeners_index]
+ .iter_mut()
+ .map(|listener| listener.take()),
+ );
+ window.next_frame.accessed_element_states.extend(
+ window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index
+ ..range.end.accessed_element_states_index]
+ .iter()
+ .cloned(),
+ );
+ window
+ .text_system
+ .reuse_layouts(range.start.line_layout_index..range.end.line_layout_index);
+ window.next_frame.scene.replay(
+ range.start.scene_index..range.end.scene_index,
+ &window.rendered_frame.scene,
);
- *self
- .window
- .next_frame
- .next_stacking_order_ids
- .last_mut()
- .unwrap() = next_stacking_order_id;
}
/// Push a text style onto the stack, and call a function with that style active.
@@ -387,9 +569,9 @@ impl<'a> ElementContext<'a> {
F: FnOnce(&mut Self) -> R,
{
if let Some(style) = style {
- self.push_text_style(style);
+ self.window.text_style_stack.push(style);
let result = f(self);
- self.pop_text_style();
+ self.window.text_style_stack.pop();
result
} else {
f(self)
@@ -397,33 +579,19 @@ impl<'a> ElementContext<'a> {
}
/// Updates the cursor style at the platform level.
- pub fn set_cursor_style(&mut self, style: CursorStyle, stacking_order: StackingOrder) {
- let view_id = self.parent_view_id();
- let style_request = CursorStyleRequest {
- style,
- stacking_order,
- };
- if self
- .window
- .next_frame
- .requested_cursor_style
- .as_ref()
- .map_or(true, |prev_style_request| {
- style_request.stacking_order >= prev_style_request.stacking_order
- })
- {
- self.window.next_frame.requested_cursor_style = Some(style_request.clone());
- }
+ pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) {
self.window
.next_frame
.cursor_styles
- .insert(view_id, style_request);
+ .push(CursorStyleRequest {
+ hitbox_id: hitbox.id,
+ style,
+ });
}
/// Sets a tooltip to be rendered for the upcoming frame
pub fn set_tooltip(&mut self, tooltip: AnyTooltip) {
- let view_id = self.parent_view_id();
- self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip });
+ self.window.next_frame.tooltip_requests.push(Some(tooltip));
}
/// Pushes the given element id onto the global stack and invokes the given closure
@@ -465,65 +633,6 @@ impl<'a> ElementContext<'a> {
}
}
- /// Invoke the given function with the content mask reset to that
- /// of the window.
- pub fn break_content_mask<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
- let mask = ContentMask {
- bounds: Bounds {
- origin: Point::default(),
- size: self.window().viewport_size,
- },
- };
-
- let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index);
- let new_stacking_order_id = post_inc(
- self.window_mut()
- .next_frame
- .next_stacking_order_ids
- .last_mut()
- .unwrap(),
- );
- let new_context = StackingContext {
- z_index: new_root_z_index,
- id: new_stacking_order_id,
- };
-
- let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack);
-
- self.window_mut().next_frame.z_index_stack.push(new_context);
- self.window_mut().next_frame.content_mask_stack.push(mask);
- let result = f(self);
- self.window_mut().next_frame.content_mask_stack.pop();
- self.window_mut().next_frame.z_index_stack = old_stacking_order;
-
- result
- }
-
- /// Called during painting to invoke the given closure in a new stacking context. The given
- /// z-index is interpreted relative to the previous call to `stack`.
- pub fn with_z_index<R>(&mut self, z_index: u16, f: impl FnOnce(&mut Self) -> R) -> R {
- let new_stacking_order_id = post_inc(
- self.window_mut()
- .next_frame
- .next_stacking_order_ids
- .last_mut()
- .unwrap(),
- );
- self.window_mut().next_frame.next_stacking_order_ids.push(0);
- let new_context = StackingContext {
- z_index,
- id: new_stacking_order_id,
- };
-
- self.window_mut().next_frame.z_index_stack.push(new_context);
- let result = f(self);
- self.window_mut().next_frame.z_index_stack.pop();
-
- self.window_mut().next_frame.next_stacking_order_ids.pop();
-
- result
- }
-
/// Updates the global element offset relative to the current offset. This is used to implement
/// scrolling.
pub fn with_element_offset<R>(
@@ -592,30 +701,37 @@ impl<'a> ElementContext<'a> {
/// when drawing the next frame.
pub fn with_element_state<S, R>(
&mut self,
- id: ElementId,
- f: impl FnOnce(Option<S>, &mut Self) -> (R, S),
+ element_id: Option<ElementId>,
+ f: impl FnOnce(Option<Option<S>>, &mut Self) -> (R, Option<S>),
) -> R
where
S: 'static,
{
- self.with_element_id(Some(id), |cx| {
+ let id_is_none = element_id.is_none();
+ self.with_element_id(element_id, |cx| {
+ if id_is_none {
+ let (result, state) = f(None, cx);
+ debug_assert!(state.is_none(), "you must not return an element state when passing None for the element id");
+ result
+ } else {
let global_id = cx.window().element_id_stack.clone();
+ let key = (global_id, TypeId::of::<S>());
+ cx.window.next_frame.accessed_element_states.push(key.clone());
if let Some(any) = cx
.window_mut()
.next_frame
.element_states
- .remove(&global_id)
+ .remove(&key)
.or_else(|| {
cx.window_mut()
.rendered_frame
.element_states
- .remove(&global_id)
+ .remove(&key)
})
{
let ElementStateBox {
inner,
- parent_view_id,
#[cfg(debug_assertions)]
type_name
} = any;
@@ -646,29 +762,26 @@ impl<'a> ElementContext<'a> {
// Requested: () <- AnyElement
let state = state_box
.take()
- .expect("element state is already on the stack");
- let (result, state) = f(Some(state), cx);
- state_box.replace(state);
+ .expect("reentrant call to with_element_state for the same state type and element id");
+ let (result, state) = f(Some(Some(state)), cx);
+ state_box.replace(state.expect("you must return "));
cx.window_mut()
.next_frame
.element_states
- .insert(global_id, ElementStateBox {
+ .insert(key, ElementStateBox {
inner: state_box,
- parent_view_id,
#[cfg(debug_assertions)]
type_name
});
result
} else {
- let (result, state) = f(None, cx);
- let parent_view_id = cx.parent_view_id();
+ let (result, state) = f(Some(None), cx);
cx.window_mut()
.next_frame
.element_states
- .insert(global_id,
+ .insert(key,
ElementStateBox {
- inner: Box::new(Some(state)),
- parent_view_id,
+ inner: Box::new(Some(state.expect("you must return Some<State> when you pass some element id"))),
#[cfg(debug_assertions)]
type_name: std::any::type_name::<S>()
}
@@ -676,8 +789,61 @@ impl<'a> ElementContext<'a> {
);
result
}
- })
+ }
+ })
+ }
+
+ /// Defers the drawing of the given element, scheduling it to be painted on top of the currently-drawn tree
+ /// at a later time. The `priority` parameter determines the drawing order relative to other deferred elements,
+ /// with higher values being drawn on top.
+ pub fn defer_draw(
+ &mut self,
+ element: AnyElement,
+ absolute_offset: Point<Pixels>,
+ priority: usize,
+ ) {
+ let window = &mut self.cx.window;
+ assert_eq!(
+ window.draw_phase,
+ DrawPhase::Layout,
+ "defer_draw can only be called during before_layout or after_layout"
+ );
+ let parent_node = window.next_frame.dispatch_tree.active_node_id().unwrap();
+ window.next_frame.deferred_draws.push(DeferredDraw {
+ parent_node,
+ element_id_stack: window.element_id_stack.clone(),
+ text_style_stack: window.text_style_stack.clone(),
+ priority,
+ element: Some(element),
+ absolute_offset,
+ layout_range: AfterLayoutIndex::default()..AfterLayoutIndex::default(),
+ paint_range: PaintIndex::default()..PaintIndex::default(),
+ });
+ }
+
+ /// Creates a new painting layer for the specified bounds. A "layer" is a batch
+ /// of geometry that are non-overlapping and have the same draw order. This is typically used
+ /// for performance reasons.
+ pub fn paint_layer<R>(&mut self, bounds: Bounds<Pixels>, f: impl FnOnce(&mut Self) -> R) -> R {
+ let scale_factor = self.scale_factor();
+ let content_mask = self.content_mask();
+ let clipped_bounds = bounds.intersect(&content_mask.bounds);
+ if !clipped_bounds.is_empty() {
+ self.window
+ .next_frame
+ .scene
+ .push_layer(clipped_bounds.scale(scale_factor));
+ }
+
+ let result = f(self);
+
+ if !clipped_bounds.is_empty() {
+ self.window.next_frame.scene.pop_layer();
+ }
+
+ result
}
+
/// Paint one or more drop shadows into the scene for the next frame at the current z-index.
pub fn paint_shadows(
&mut self,
@@ -687,26 +853,19 @@ impl<'a> ElementContext<'a> {
) {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
- let window = &mut *self.window;
for shadow in shadows {
let mut shadow_bounds = bounds;
shadow_bounds.origin += shadow.offset;
shadow_bounds.dilate(shadow.spread_radius);
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- Shadow {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds: shadow_bounds.scale(scale_factor),
- content_mask: content_mask.scale(scale_factor),
- corner_radii: corner_radii.scale(scale_factor),
- color: shadow.color,
- blur_radius: shadow.blur_radius.scale(scale_factor),
- pad: 0,
- },
- );
+ self.window.next_frame.scene.insert_primitive(Shadow {
+ order: 0,
+ bounds: shadow_bounds.scale(scale_factor),
+ content_mask: content_mask.scale(scale_factor),
+ corner_radii: corner_radii.scale(scale_factor),
+ color: shadow.color,
+ blur_radius: shadow.blur_radius.scale(scale_factor),
+ pad: 0,
+ });
}
}
@@ -716,39 +875,27 @@ impl<'a> ElementContext<'a> {
pub fn paint_quad(&mut self, quad: PaintQuad) {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
-
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- Quad {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds: quad.bounds.scale(scale_factor),
- content_mask: content_mask.scale(scale_factor),
- background: quad.background,
- border_color: quad.border_color,
- corner_radii: quad.corner_radii.scale(scale_factor),
- border_widths: quad.border_widths.scale(scale_factor),
- },
- );
+ self.window.next_frame.scene.insert_primitive(Quad {
+ order: 0,
+ bounds: quad.bounds.scale(scale_factor),
+ content_mask: content_mask.scale(scale_factor),
+ background: quad.background,
+ border_color: quad.border_color,
+ corner_radii: quad.corner_radii.scale(scale_factor),
+ border_widths: quad.border_widths.scale(scale_factor),
+ });
}
/// Paint the given `Path` into the scene for the next frame at the current z-index.
pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
-
path.content_mask = content_mask;
path.color = color.into();
- path.view_id = view_id.into();
- let window = &mut *self.window;
- window
+ self.window
.next_frame
.scene
- .insert(&window.next_frame.z_index_stack, path.scale(scale_factor));
+ .insert_primitive(path.scale(scale_factor));
}
/// Paint an underline into the scene for the next frame at the current z-index.
@@ -769,22 +916,15 @@ impl<'a> ElementContext<'a> {
size: size(width, height),
};
let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
-
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- Underline {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds: bounds.scale(scale_factor),
- content_mask: content_mask.scale(scale_factor),
- color: style.color.unwrap_or_default(),
- thickness: style.thickness.scale(scale_factor),
- wavy: style.wavy,
- },
- );
+
+ self.window.next_frame.scene.insert_primitive(Underline {
+ order: 0,
+ bounds: bounds.scale(scale_factor),
+ content_mask: content_mask.scale(scale_factor),
+ color: style.color.unwrap_or_default(),
+ thickness: style.thickness.scale(scale_factor),
+ wavy: style.wavy,
+ });
}
/// Paint a strikethrough into the scene for the next frame at the current z-index.
@@ -801,22 +941,15 @@ impl<'a> ElementContext<'a> {
size: size(width, height),
};
let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
-
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- Underline {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds: bounds.scale(scale_factor),
- content_mask: content_mask.scale(scale_factor),
- thickness: style.thickness.scale(scale_factor),
- color: style.color.unwrap_or_default(),
- wavy: false,
- },
- );
+
+ self.window.next_frame.scene.insert_primitive(Underline {
+ order: 0,
+ bounds: bounds.scale(scale_factor),
+ content_mask: content_mask.scale(scale_factor),
+ thickness: style.thickness.scale(scale_factor),
+ color: style.color.unwrap_or_default(),
+ wavy: false,
+ });
}
/// Paints a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index.
@@ -862,20 +995,16 @@ impl<'a> ElementContext<'a> {
size: tile.bounds.size.map(Into::into),
};
let content_mask = self.content_mask().scale(scale_factor);
- let view_id = self.parent_view_id();
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- MonochromeSprite {
- view_id: view_id.into(),
- layer_id: 0,
+ self.window
+ .next_frame
+ .scene
+ .insert_primitive(MonochromeSprite {
order: 0,
bounds,
content_mask,
color,
tile,
- },
- );
+ });
}
Ok(())
}
@@ -919,14 +1048,11 @@ impl<'a> ElementContext<'a> {
size: tile.bounds.size.map(Into::into),
};
let content_mask = self.content_mask().scale(scale_factor);
- let view_id = self.parent_view_id();
- let window = &mut *self.window;
-
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- PolychromeSprite {
- view_id: view_id.into(),
- layer_id: 0,
+
+ self.window
+ .next_frame
+ .scene
+ .insert_primitive(PolychromeSprite {
order: 0,
bounds,
corner_radii: Default::default(),
@@ -934,8 +1060,7 @@ impl<'a> ElementContext<'a> {
tile,
grayscale: false,
pad: 0,
- },
- );
+ });
}
Ok(())
}
@@ -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;
}
@@ -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)
}
@@ -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)
@@ -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 {
@@ -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(),
);
}
@@ -77,7 +77,7 @@ impl super::LspAdapter for OmniSharpAdapter {
archive.unpack(container_dir).await?;
}
- // todo(windows)
+ // todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(
@@ -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(
@@ -79,7 +79,7 @@ impl super::LspAdapter for LuaLspAdapter {
archive.unpack(container_dir).await?;
}
- // todo(windows)
+ // todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(
@@ -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(
@@ -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(
@@ -73,7 +73,7 @@ impl LspAdapter for ZlsAdapter {
archive.unpack(container_dir).await?;
}
- // todo(windows)
+ // todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(
@@ -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
@@ -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::*;
@@ -1,172 +0,0 @@
-use gpui::{px, rgb, Div, IntoElement, Render, RenderOnce};
-use story::Story;
-use ui::prelude::*;
-
-/// A reimplementation of the MDN `z-index` example, found here:
-/// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
-pub struct ZIndexStory;
-
-impl Render for ZIndexStory {
- fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
- Story::container().child(Story::title("z-index")).child(
- div()
- .flex()
- .child(
- div()
- .w(px(250.))
- .child(Story::label("z-index: auto"))
- .child(ZIndexExample::new(0)),
- )
- .child(
- div()
- .w(px(250.))
- .child(Story::label("z-index: 1"))
- .child(ZIndexExample::new(1)),
- )
- .child(
- div()
- .w(px(250.))
- .child(Story::label("z-index: 3"))
- .child(ZIndexExample::new(3)),
- )
- .child(
- div()
- .w(px(250.))
- .child(Story::label("z-index: 5"))
- .child(ZIndexExample::new(5)),
- )
- .child(
- div()
- .w(px(250.))
- .child(Story::label("z-index: 7"))
- .child(ZIndexExample::new(7)),
- ),
- )
- }
-}
-
-trait Styles: Styled + Sized {
- // Trailing `_` is so we don't collide with `block` style `StyleHelpers`.
- fn block_(self) -> Self {
- self.absolute()
- .w(px(150.))
- .h(px(50.))
- .text_color(rgb(0x000000))
- }
-
- fn blue(self) -> Self {
- self.bg(rgb(0xe5e8fc))
- .border_5()
- .border_color(rgb(0x112382))
- .line_height(px(55.))
- // HACK: Simulate `text-align: center`.
- .pl(px(24.))
- }
-
- fn red(self) -> Self {
- self.bg(rgb(0xfce5e7))
- .border_5()
- .border_color(rgb(0xe3a1a7))
- // HACK: Simulate `text-align: center`.
- .pl(px(8.))
- }
-}
-
-impl Styles for Div {}
-
-#[derive(IntoElement)]
-struct ZIndexExample {
- z_index: u16,
-}
-
-impl RenderOnce for ZIndexExample {
- fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
- div()
- .relative()
- .size_full()
- // Example element.
- .child(
- div()
- .absolute()
- .top(px(15.))
- .left(px(15.))
- .w(px(180.))
- .h(px(230.))
- .bg(rgb(0xfcfbe5))
- .text_color(rgb(0x000000))
- .border_5()
- .border_color(rgb(0xe3e0a1))
- .line_height(px(215.))
- // HACK: Simulate `text-align: center`.
- .pl(px(24.))
- .z_index(self.z_index)
- .child(SharedString::from(format!(
- "z-index: {}",
- if self.z_index == 0 {
- "auto".to_string()
- } else {
- self.z_index.to_string()
- }
- ))),
- )
- // Blue blocks.
- .child(
- div()
- .blue()
- .block_()
- .top(px(0.))
- .left(px(0.))
- .z_index(6)
- .child("z-index: 6"),
- )
- .child(
- div()
- .blue()
- .block_()
- .top(px(30.))
- .left(px(30.))
- .z_index(4)
- .child("z-index: 4"),
- )
- .child(
- div()
- .blue()
- .block_()
- .top(px(60.))
- .left(px(60.))
- .z_index(2)
- .child("z-index: 2"),
- )
- // Red blocks.
- .child(
- div()
- .red()
- .block_()
- .top(px(150.))
- .left(px(0.))
- .child("z-index: auto"),
- )
- .child(
- div()
- .red()
- .block_()
- .top(px(180.))
- .left(px(30.))
- .child("z-index: auto"),
- )
- .child(
- div()
- .red()
- .block_()
- .top(px(210.))
- .left(px(60.))
- .child("z-index: auto"),
- )
- }
-}
-
-impl ZIndexExample {
- pub fn new(z_index: u16) -> Self {
- Self { z_index }
- }
-}
@@ -35,7 +35,6 @@ pub enum ComponentStory {
ToggleButton,
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(),
}
}
@@ -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();
@@ -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 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 request_layout(
+ 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);
-
- layout_id
+ bounds: Bounds<Pixels>,
+ _: &mut Self::BeforeLayout,
+ cx: &mut ElementContext,
+ ) -> Self::AfterLayout {
+ self.interactivity
+ .after_layout(bounds, bounds.size, cx, |_, _, hitbox, cx| {
+ let hitbox = hitbox.unwrap();
+ let settings = ThemeSettings::get_global(cx).clone();
+
+ let 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);
- }
- });
+ 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 cursor) = cursor {
+ cursor.paint(origin, cx);
+ }
}
- if let Some(mut element) = layout.hyperlink_tooltip.take() {
- element.draw(origin, bounds.size.map(AvailableSpace::Definite), 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
}
@@ -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.))
@@ -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)))
}
}
@@ -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)
@@ -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 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 request_layout(
+ 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
- });
-
- 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,
- },
- )
+ if let Some(menu) = before_layout.menu_element.as_mut() {
+ menu.after_layout(cx);
+ }
+
+ before_layout.child_layout_id.map(|layout_id| {
+ let bounds = cx.layout_bounds(layout_id);
+ element_state.child_bounds = Some(bounds);
+ cx.insert_hitbox(bounds, false).id
+ })
+ })
}
fn paint(
&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);
- }
+ 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(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) = before_layout.menu_element.take() {
+ menu.paint(cx);
- 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()
- }
- })
+ 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
}
@@ -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 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 request_layout(
+ 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())
- };
-
- 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);
+ ) -> Hitbox {
+ cx.with_element_id(Some(self.id.clone()), |cx| {
+ let hitbox = cx.insert_hitbox(bounds, false);
+
+ if let Some(child) = before_layout.child_element.as_mut() {
+ child.after_layout(cx);
}
- overlay = overlay.position(*position.borrow());
-
- let mut element = overlay.child(menu.clone()).into_any();
- menu_layout_id = Some(element.request_layout(cx));
- element
- });
-
- 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,
- },
- )
+
+ if let Some(menu) = before_layout.menu_element.as_mut() {
+ menu.after_layout(cx);
+ }
+
+ 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 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 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);
+ let Some(builder) = this.menu_builder.take() else {
+ return;
+ };
+
+ 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 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();
+
+ 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
}
@@ -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| {
@@ -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)
@@ -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,
- }
- }
-}
@@ -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 {
@@ -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())),
+ )
}
}
@@ -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(
@@ -1,239 +0,0 @@
-use super::DraggedItem;
-use crate::{Pane, SplitDirection, Workspace};
-use gpui::{
- color::Color,
- elements::{Canvas, MouseEventHandler, ParentComponent, Stack},
- geometry::{rect::RectF, vector::Vector2F},
- platform::MouseButton,
- scene::MouseUp,
- AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
-};
-use project2::ProjectEntryId;
-
-pub fn dragged_item_receiver<Tag, D, F>(
- pane: &Pane,
- region_id: usize,
- drop_index: usize,
- allow_same_pane: bool,
- split_margin: Option<f32>,
- cx: &mut ViewContext<Pane>,
- render_child: F,
-) -> MouseEventHandler<Pane>
-where
- Tag: 'static,
- D: Element<Pane>,
- F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
-{
- let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
- let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
- drag_and_drop
- .currently_dragged::<DraggedItem>(cx.window())
- .map(|(drag_position, _)| drag_position)
- .or_else(|| {
- drag_and_drop
- .currently_dragged::<ProjectEntryId>(cx.window())
- .map(|(drag_position, _)| drag_position)
- })
- } else {
- None
- };
-
- let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
- // Observing hovered will cause a render when the mouse enters regardless
- // of if mouse position was accessed before
- let drag_position = if state.dragging() {
- drag_position
- } else {
- None
- };
- Stack::new()
- .with_child(render_child(state, cx))
- .with_children(drag_position.map(|drag_position| {
- Canvas::new(move |bounds, _, _, cx| {
- if bounds.contains_point(drag_position) {
- let overlay_region = split_margin
- .and_then(|split_margin| {
- drop_split_direction(drag_position, bounds, split_margin)
- .map(|dir| (dir, split_margin))
- })
- .map(|(dir, margin)| dir.along_edge(bounds, margin))
- .unwrap_or(bounds);
-
- cx.scene().push_stacking_context(None, None);
- let background = overlay_color(cx);
- cx.scene().push_quad(Quad {
- bounds: overlay_region,
- background: Some(background),
- border: Default::default(),
- corner_radii: Default::default(),
- });
- cx.scene().pop_stacking_context();
- }
- })
- }))
- });
-
- if drag_position.is_some() {
- handler = handler
- .on_up(MouseButton::Left, {
- move |event, pane, cx| {
- let workspace = pane.workspace.clone();
- let pane = cx.weak_handle();
- handle_dropped_item(
- event,
- workspace,
- &pane,
- drop_index,
- allow_same_pane,
- split_margin,
- cx,
- );
- cx.notify();
- }
- })
- .on_move(|_, _, cx| {
- let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
-
- if drag_and_drop
- .currently_dragged::<DraggedItem>(cx.window())
- .is_some()
- || drag_and_drop
- .currently_dragged::<ProjectEntryId>(cx.window())
- .is_some()
- {
- cx.notify();
- } else {
- cx.propagate_event();
- }
- })
- }
-
- handler
-}
-
-pub fn handle_dropped_item<V: 'static>(
- event: MouseUp,
- workspace: WeakViewHandle<Workspace>,
- pane: &WeakViewHandle<Pane>,
- index: usize,
- allow_same_pane: bool,
- split_margin: Option<f32>,
- cx: &mut EventContext<V>,
-) {
- enum Action {
- Move(WeakViewHandle<Pane>, usize),
- Open(ProjectEntryId),
- }
- let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
- let action = if let Some((_, dragged_item)) =
- drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
- {
- Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
- } else if let Some((_, project_entry)) =
- drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
- {
- Action::Open(*project_entry)
- } else {
- cx.propagate_event();
- return;
- };
-
- if let Some(split_direction) =
- split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
- {
- let pane_to_split = pane.clone();
- match action {
- Action::Move(from, item_id_to_move) => {
- cx.window_context().defer(move |cx| {
- if let Some(workspace) = workspace.upgrade(cx) {
- workspace.update(cx, |workspace, cx| {
- workspace.split_pane_with_item(
- pane_to_split,
- split_direction,
- from,
- item_id_to_move,
- cx,
- );
- })
- }
- });
- }
- Action::Open(project_entry) => {
- cx.window_context().defer(move |cx| {
- if let Some(workspace) = workspace.upgrade(cx) {
- workspace.update(cx, |workspace, cx| {
- if let Some(task) = workspace.split_pane_with_project_entry(
- pane_to_split,
- split_direction,
- project_entry,
- cx,
- ) {
- task.detach_and_log_err(cx);
- }
- })
- }
- });
- }
- };
- } else {
- match action {
- Action::Move(from, item_id) => {
- if pane != &from || allow_same_pane {
- let pane = pane.clone();
- cx.window_context().defer(move |cx| {
- if let Some(((workspace, from), to)) = workspace
- .upgrade(cx)
- .zip(from.upgrade(cx))
- .zip(pane.upgrade(cx))
- {
- workspace.update(cx, |workspace, cx| {
- workspace.move_item(from, to, item_id, index, cx);
- })
- }
- });
- } else {
- cx.propagate_event();
- }
- }
- Action::Open(project_entry) => {
- let pane = pane.clone();
- cx.window_context().defer(move |cx| {
- if let Some(workspace) = workspace.upgrade(cx) {
- workspace.update(cx, |workspace, cx| {
- if let Some(path) =
- workspace.project.read(cx).path_for_entry(project_entry, cx)
- {
- workspace
- .open_path(path, Some(pane), true, cx)
- .detach_and_log_err(cx);
- }
- });
- }
- });
- }
- }
- }
-}
-
-fn drop_split_direction(
- position: Vector2F,
- region: RectF,
- split_margin: f32,
-) -> Option<SplitDirection> {
- let mut min_direction = None;
- let mut min_distance = split_margin;
- for direction in SplitDirection::all() {
- let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
-
- if edge_distance < min_distance {
- min_direction = Some(direction);
- min_distance = edge_distance;
- }
- }
-
- min_direction
-}
-
-fn overlay_color(cx: &AppContext) -> Color {
- theme2::current(cx).workspace.drop_target_overlay_color
-}
@@ -4,7 +4,7 @@ use call::{ActiveCall, ParticipantLocation};
use collections::HashMap;
use 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));
+ }
}
+ }
- origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
+ 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);
}
- cx.with_z_index(1, |cx| {
- cx.on_mouse_event({
- let state = state.clone();
- move |_: &MouseUpEvent, phase, _cx| {
- if phase.bubble() {
- state.replace(None);
+ 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,
+ )
+ }
}
+ });
+ }
+ }
+
+ cx.on_mouse_event({
+ let dragged_handle = layout.dragged_handle.clone();
+ move |_: &MouseUpEvent, phase, _cx| {
+ if phase.bubble() {
+ dragged_handle.replace(None);
}
- });
- })
+ }
+ });
}
}
@@ -2756,7 +2756,6 @@ impl Workspace {
Some(
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 paint(
+ fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
- overlay: &mut Self::State,
+ overlay: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) {
- cx.with_z_index(u16::MAX, |cx| {
- cx.add_opaque_layer(bounds);
- overlay.paint(cx);
- })
+ cx.insert_hitbox(bounds, true);
+ overlay.after_layout(cx);
+ }
+
+ fn paint(
+ &mut self,
+ _: Bounds<Pixels>,
+ overlay: &mut Self::BeforeLayout,
+ _: &mut Self::AfterLayout,
+ cx: &mut ElementContext,
+ ) {
+ 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
}