Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions crates/base/src/global_state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::rc::{Rc, Weak};
#[cfg(not(target_family = "wasm"))]
use std::time::Instant;
use std::{
cell::{Cell, RefCell},
rc::{Rc, Weak},
};
#[cfg(target_family = "wasm")]
use web_time::Instant;

Expand All @@ -20,8 +23,13 @@ pub struct GlobalState {
app_menus: Vec<OwnedMenu>,
deferred_popovers: Vec<Weak<()>>,
suppress_text_selection: bool,
pub(crate) text_view_state_stack: Vec<Entity<TextViewState>>,
selection_document_order: u64,
/// The text views being prepainted or painted, innermost last. Kept
/// apart from what views read of this global: pushing and popping as
/// they draw is not a change of it.
text_view_state_stack: RefCell<Vec<Entity<TextViewState>>>,
/// The order the next selectable text painted this frame takes, which
/// changes as text paints without changing this global.
selection_document_order: Cell<u64>,
/// When a finger last went down. A tap reaches controls as a mouse press;
/// this is how they tell it from one.
last_touch: Option<Instant>,
Expand All @@ -35,8 +43,8 @@ impl GlobalState {
app_menus: Vec::new(),
deferred_popovers: Vec::new(),
suppress_text_selection: false,
text_view_state_stack: Vec::new(),
selection_document_order: 1,
text_view_state_stack: RefCell::new(Vec::new()),
selection_document_order: Cell::new(1),
last_touch: None,
}
}
Expand Down Expand Up @@ -93,18 +101,26 @@ impl GlobalState {
cx.global_mut::<Self>()
}

pub(crate) fn text_view_state(&self) -> Option<&Entity<TextViewState>> {
self.text_view_state_stack.last()
pub(crate) fn text_view_state(&self) -> Option<Entity<TextViewState>> {
self.text_view_state_stack.borrow().last().cloned()
}

pub(crate) fn push_text_view_state(&self, state: Entity<TextViewState>) {
self.text_view_state_stack.borrow_mut().push(state);
}

pub(crate) fn pop_text_view_state(&self) {
self.text_view_state_stack.borrow_mut().pop();
}

#[doc(hidden)]
pub fn begin_selection_frame(&mut self) {
self.selection_document_order = 1;
pub fn begin_selection_frame(&self) {
self.selection_document_order.set(1);
}

pub(crate) fn next_selection_document_order(&mut self) -> u64 {
let order = self.selection_document_order;
self.selection_document_order = self.selection_document_order.wrapping_add(1);
pub(crate) fn next_selection_document_order(&self) -> u64 {
let order = self.selection_document_order.get();
self.selection_document_order.set(order.wrapping_add(1));
order
}

Expand Down
34 changes: 16 additions & 18 deletions crates/base/src/text/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ impl Element for Inline {
// `max_lines` can snap its clip to a whole-line boundary. The state
// stack only holds an entry during prepaint when that view set
// `max_lines`, so this is a no-op otherwise.
if let Some(text_view_state) = GlobalState::global(cx).text_view_state().cloned() {
if let Some(text_view_state) = GlobalState::global(cx).text_view_state() {
let state = text_view_state.read(cx);
if state.max_lines.is_some()
&& let Ok(mut line_spans) = state.line_spans.lock()
Expand Down Expand Up @@ -1006,39 +1006,37 @@ impl Element for Inline {
.unwrap_or_else(|| crate::Theme::global(cx).tokens.colors.selection);
Self::paint_selection(selection, &text_layout, &bounds, window, color);
if let Some((start, end)) = Self::selection_edges(selection, &text_layout)
&& let Some(text_view_state) = GlobalState::global(cx).text_view_state().cloned()
&& let Some(text_view_state) = GlobalState::global(cx).text_view_state()
{
text_view_state.update(cx, |state, _| {
state.selection_adapter.register_selection_edges(start, end);
});
text_view_state
.read(cx)
.selection_adapter
.register_selection_edges(start, end);
}
}

if is_selectable {
if let Some(text_view_state) = GlobalState::global(cx).text_view_state().cloned() {
if let Some(text_view_state) = GlobalState::global(cx).text_view_state() {
let text_bounds = Self::text_line_bounds(
&text_layout,
text_layout.line_height(),
window.content_mask().bounds,
);
text_view_state.update(cx, |state, _| {
state.selection_adapter.register_inline(text_bounds);
state
.selection_adapter
.register_text_run(crate::TextSelectionRun::new(
self.text.clone(),
text_layout.clone(),
hitbox.bounds,
));
});
let adapter = &text_view_state.read(cx).selection_adapter;
adapter.register_inline(text_bounds);
adapter.register_text_run(crate::TextSelectionRun::new(
self.text.clone(),
text_layout.clone(),
hitbox.bounds,
));
}

window.on_mouse_event({
let hitbox = hitbox.clone();
let text_layout = text_layout.clone();
let inline_state = self.state.clone();
let text = self.text.clone();
let text_view_state = GlobalState::global(cx).text_view_state().cloned();
let text_view_state = GlobalState::global(cx).text_view_state();
let line_bounds = self.selection_bounds;
move |event: &MouseDownEvent, phase, window, cx| {
if !phase.bubble()
Expand Down Expand Up @@ -1140,7 +1138,7 @@ impl Element for Inline {
let links = self.links.clone();
let text_layout = text_layout.clone();
let hitbox = hitbox.clone();
let text_view_state = GlobalState::global(cx).text_view_state().cloned();
let text_view_state = GlobalState::global(cx).text_view_state();
let link_click_handler = self.link_click_handler.clone();

move |event: &MouseUpEvent, phase, window, cx| {
Expand Down
5 changes: 4 additions & 1 deletion crates/base/src/text/inline_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1705,7 +1705,10 @@ mod tests {
draw(cx);
let before = layouts();

answer.update(cx, |answer, _| answer.width = px(120.));
answer.update(cx, |answer, cx| {
answer.width = px(120.);
cx.notify();
});
draw(cx);

assert!(layouts() > before, "a narrower flow wraps differently");
Expand Down
10 changes: 6 additions & 4 deletions crates/base/src/text/inline_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ impl Element for InlineObject {
window: &mut Window,
cx: &mut App,
) {
let view = GlobalState::global(cx).text_view_state().cloned();
let view = GlobalState::global(cx).text_view_state();
let selectable = view
.as_ref()
.is_some_and(|view| view.read(cx).is_selectable());
Expand Down Expand Up @@ -365,9 +365,11 @@ impl Element for InlineObject {
}
let visible = bounds.intersect(&window.content_mask().bounds);
if visible.size.width > Pixels::ZERO && visible.size.height > Pixels::ZERO {
view.as_ref().unwrap().update(cx, |state, _| {
state.selection_adapter.register_inline(vec![visible]);
});
view.as_ref()
.unwrap()
.read(cx)
.selection_adapter
.register_inline(vec![visible]);
}
let hitbox = hitbox.clone();
let selected_state = self.selected.clone();
Expand Down
8 changes: 4 additions & 4 deletions crates/base/src/text/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ impl Element for CustomBlockElement {
window: &mut Window,
cx: &mut App,
) {
let view = GlobalState::global(cx).text_view_state().cloned();
let view = GlobalState::global(cx).text_view_state();
let selection = view.as_ref().and_then(|view| {
let state = view.read(cx);
if !state.is_selectable() {
Expand Down Expand Up @@ -236,9 +236,9 @@ impl Element for CustomBlockElement {
&& visible.size.width > Pixels::ZERO
&& visible.size.height > Pixels::ZERO
{
view.update(cx, |state, _| {
state.selection_adapter.register_inline(vec![visible]);
});
view.read(cx)
.selection_adapter
.register_inline(vec![visible]);
}
}
self.content.paint(window, cx);
Expand Down
46 changes: 27 additions & 19 deletions crates/base/src/text/selection_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,21 @@ impl VirtualBlockSelection {
#[derive(Clone)]
pub(super) struct TextViewSelectionAdapter {
selection: TextSelectionHandle,
/// What the view's text painted this frame, shared by the adapter's
/// clones. Inlines report it as they paint, through a read of the view's
/// state: reporting it is not a change of the view.
frame: Rc<RefCell<PaintedText>>,
layout_revision: Option<usize>,
}

/// The text a [`TextViewSelectionAdapter`]'s view painted this frame.
#[derive(Default)]
struct PaintedText {
text_bounds: Vec<Bounds<Pixels>>,
text_runs: Vec<TextSelectionRun>,
/// The caret boxes at the first and last selected character painted this
/// frame, where the touch handles go.
selection_edges: Option<(Bounds<Pixels>, Bounds<Pixels>)>,
layout_revision: Option<usize>,
}

impl TextViewSelectionAdapter {
Expand Down Expand Up @@ -175,9 +184,7 @@ impl TextViewSelectionAdapter {

Self {
selection,
text_bounds: Vec::new(),
text_runs: Vec::new(),
selection_edges: None,
frame: Rc::default(),
layout_revision: None,
}
}
Expand All @@ -192,25 +199,24 @@ impl TextViewSelectionAdapter {
changed && !is_selecting
}

pub(super) fn begin_frame(&mut self) {
self.text_bounds.clear();
self.text_runs.clear();
self.selection_edges = None;
pub(super) fn begin_frame(&self) {
*self.frame.borrow_mut() = PaintedText::default();
}

/// Records one inline's painted selection ends. Inlines paint in document
/// order, so the first start and the last end are the view's.
pub(super) fn register_selection_edges(&mut self, start: Bounds<Pixels>, end: Bounds<Pixels>) {
let first = self.selection_edges.map_or(start, |(first, _)| first);
self.selection_edges = Some((first, end));
pub(super) fn register_selection_edges(&self, start: Bounds<Pixels>, end: Bounds<Pixels>) {
let mut frame = self.frame.borrow_mut();
let first = frame.selection_edges.map_or(start, |(first, _)| first);
frame.selection_edges = Some((first, end));
}

pub(super) fn register_text_run(&mut self, run: TextSelectionRun) {
self.text_runs.push(run);
pub(super) fn register_text_run(&self, run: TextSelectionRun) {
self.frame.borrow_mut().text_runs.push(run);
}

pub(super) fn register_inline(&mut self, bounds: Vec<Bounds<Pixels>>) {
self.text_bounds.extend(bounds);
pub(super) fn register_inline(&self, bounds: Vec<Bounds<Pixels>>) {
self.frame.borrow_mut().text_bounds.extend(bounds);
}

#[allow(clippy::too_many_arguments)]
Expand All @@ -224,17 +230,19 @@ impl TextViewSelectionAdapter {
window: &mut Window,
cx: &mut App,
) {
self.selection.set_hit_test_runs(&self.text_runs, cx);
let frame = self.frame.borrow();
self.selection.set_hit_test_runs(&frame.text_runs, cx);
let registration = TextSelectionRegistration::new(hitbox, bounds)
.with_scroll_offset(scroll_offset)
.with_document_order(document_order)
.with_text_bounds(self.text_bounds.clone())
.with_text_bounds(frame.text_bounds.clone())
.with_self_scroll(self_scroll)
.with_rendered_element(&self.selection, window, cx);
let registration = match self.selection_edges {
let registration = match frame.selection_edges {
Some((start, end)) => registration.with_selection_edges(start, end),
None => registration,
};
drop(frame);
self.selection.register(registration, window, cx);
}

Expand Down Expand Up @@ -283,6 +291,6 @@ impl TextViewSelectionAdapter {

#[cfg(test)]
pub(super) fn text_bounds(&self) -> Vec<Bounds<Pixels>> {
self.text_bounds.clone()
self.frame.borrow().text_bounds.clone()
}
}
16 changes: 6 additions & 10 deletions crates/base/src/text/text_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,13 +758,11 @@ impl Element for TextView {
}
// Descendant `Inline`s report their line spans through the state
// stack during prepaint (in addition to the paint-time push below).
GlobalState::global_mut(cx)
.text_view_state_stack
.push(state.clone());
GlobalState::global(cx).push_text_view_state(state.clone());
}
request_layout.element.prepaint(window, cx);
if max_lines_active {
GlobalState::global_mut(cx).text_view_state_stack.pop();
GlobalState::global(cx).pop_text_view_state();
}

let mut clip_bottom = None;
Expand Down Expand Up @@ -826,12 +824,10 @@ impl Element for TextView {
) {
let state = &request_layout.state;
if self.selectable {
state.update(cx, |state, _| state.selection_adapter.begin_frame());
state.read(cx).selection_adapter.begin_frame();
}

GlobalState::global_mut(cx)
.text_view_state_stack
.push(state.clone());
GlobalState::global(cx).push_text_view_state(state.clone());
if let Some(clip_bottom) = prepaint.clip_bottom {
// Snap the `max_lines` clip to the last whole line that fits, so a
// line of glyphs is never cut in half.
Expand All @@ -844,7 +840,7 @@ impl Element for TextView {
} else {
request_layout.element.paint(window, cx);
}
GlobalState::global_mut(cx).text_view_state_stack.pop();
GlobalState::global(cx).pop_text_view_state();

// Every list has scrolled by now, so the line of a reveal is where
// it ends up this frame.
Expand Down Expand Up @@ -876,7 +872,7 @@ impl Element for TextView {
state.text_view_style.selection().alpha(1.),
)
};
let document_order = GlobalState::global_mut(cx).next_selection_document_order();
let document_order = GlobalState::global(cx).next_selection_document_order();
adapter.register(
prepaint.hitbox.clone(),
content_bounds,
Expand Down
Loading
Loading