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
61 changes: 61 additions & 0 deletions crates/base/src/text/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
input::Selection,
text::TextViewMultiClickKind,
text::node::LinkMark,
text::range_highlight::RevealAt,
text::selection::word_range_at,
text::state::LineSpan,
text::text_view::{LinkClickHandlerFn, handle_link_click},
Expand Down Expand Up @@ -206,6 +207,8 @@ pub(super) struct Inline {
selection_source: Option<(Arc<Mutex<InlineState>>, Range<usize>)>,
/// Range highlight backgrounds, painted behind the text.
range_backgrounds: Vec<(Range<usize>, Hsla)>,
/// The start of a pending reveal, when it is in this text.
reveal: Option<RevealAt>,
link_click_handler: Option<Arc<LinkClickHandlerFn>>,
/// What this frame's layout was shaped with, to hand the shaped text to
/// the next frame (see [`RetainedLayout`]).
Expand Down Expand Up @@ -367,6 +370,7 @@ impl Inline {
selection_bounds: None,
selection_source: None,
range_backgrounds: Vec::new(),
reveal: None,
link_click_handler,
retained_key: None,
handed_over: false,
Expand Down Expand Up @@ -407,6 +411,61 @@ impl Inline {
self
}

/// Scroll the line `reveal` starts on into view during prepaint.
pub(super) fn reveal(mut self, reveal: Option<RevealAt>) -> Self {
self.reveal = reveal;
self
}

/// Ask the enclosing list to scroll the line of the pending reveal into
/// view, and report where it is and whether it is inside the visible
/// area.
fn request_reveal(&self, window: &mut Window) {
let Some(reveal) = &self.reveal else {
return;
};
let text_layout = self.styled_text.layout();
let bounds = text_layout.bounds();
let line_height = text_layout.line_height();
let glyphs = glyph_boxes(
text_layout,
window.text_style().text_align,
bounds.size.width,
);
// The glyph drawing the text at the offset, or, for text with no
// glyph of its own such as a line break, the next glyph, or the last.
let offset = reveal.offset();
let (row, left, right) = range_boxes(&glyphs, offset..offset + 1)
.first()
.copied()
.or_else(|| {
glyphs
.iter()
.find(|glyph| glyph.text.start >= offset)
.or(glyphs.last())
.map(|glyph| (glyph.row, glyph.left, glyph.right))
})
.unwrap_or((0, Pixels::ZERO, Pixels::ZERO));
let line = Bounds::from_corners(
point(
bounds.left() + left,
bounds.top() + line_height * row as f32,
),
point(
bounds.left() + right.max(left + px(1.)),
bounds.top() + line_height * (row + 1) as f32,
),
);
window.request_autoscroll(line);
// A list scrolls the line to its edge, which layout may miss by a
// fraction of a pixel.
let visible = window.content_mask().bounds.dilate(px(0.5));
reveal.report(
line,
line.top() >= visible.top() && line.bottom() <= visible.bottom(),
);
}

/// Get link at given mouse position.
fn link_for_position(
layout: &TextLayout,
Expand Down Expand Up @@ -826,6 +885,8 @@ impl Element for Inline {
}
}

self.request_reveal(window);

let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
self.retain_styled_text();
hitbox
Expand Down
40 changes: 39 additions & 1 deletion crates/base/src/text/inline_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use super::{
inline::{Inline, InlineHighlight, InlineState, text_runs, text_size_ranges},
inline_object::{InlineObject, MeasuredInlineObject},
node::LinkMark,
range_highlight::RevealAt,
};

const IMAGE_LEN: usize = 1;
Expand Down Expand Up @@ -53,6 +54,8 @@ pub(super) enum InlineFlowItem {
/// Range highlight backgrounds, in this item's byte space. They are
/// only painted, so unlike `highlights` they take no part in layout.
backgrounds: Vec<(Range<usize>, Hsla)>,
/// The start of a pending reveal, when it is in this item.
reveal: Option<RevealAt>,
},
Image {
source: ImageSource,
Expand Down Expand Up @@ -432,8 +435,36 @@ impl Element for InlineFlow {
let text_style = &typography.text_style;
let mut elements = Vec::with_capacity(layout.fragments.len());

// A reveal goes to the fragment it starts in. A line break lays out
// no fragment, so a reveal starting on one, or on an empty line, goes
// to the next fragment of its text, or to the last when none follows.
let reveal_fragment = layout
.fragments
.iter()
.enumerate()
.filter_map(|(ix, fragment)| {
let PositionedFragment::Text {
item_ix,
source_range,
..
} = fragment
else {
return None;
};
let InlineFlowItem::Text {
reveal: Some(reveal),
..
} = &self.items[*item_ix]
else {
return None;
};
Some((ix, source_range.end > reveal.offset()))
})
.reduce(|found, next| if found.1 { found } else { next })
.map(|(ix, _)| ix);

let mut text_fragment_count = 0;
for fragment in &layout.fragments {
for (fragment_ix, fragment) in layout.fragments.iter().enumerate() {
match fragment {
PositionedFragment::Object {
item_ix,
Expand Down Expand Up @@ -496,6 +527,7 @@ impl Element for InlineFlow {
let InlineFlowItem::Text {
state: source_state,
backgrounds,
reveal,
..
} = &self.items[*item_ix]
else {
Expand Down Expand Up @@ -540,6 +572,12 @@ impl Element for InlineFlow {
source_range.end,
|range, color| (range, *color),
))
.reveal(
reveal
.as_ref()
.filter(|_| reveal_fragment == Some(fragment_ix))
.map(|reveal| reveal.clamp(source_range.start, source_range.end)),
)
.text_style(fragment_style.clone())
.selection_bounds(Bounds::new(
point(bounds.left(), bounds.top() + selection_bounds.top()),
Expand Down
29 changes: 24 additions & 5 deletions crates/base/src/text/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::{
text_size_ranges,
},
inline_flow::{InlineFlow, InlineFlowItem, slice_ranges},
range_highlight::RangeHighlightFrame,
range_highlight::{RangeHighlightFrame, RevealAt, RevealRequest},
stream_fade::{StreamFadeFrame, TextLeafKey},
text_view::handle_link_click,
},
Expand Down Expand Up @@ -1891,7 +1891,8 @@ impl CodeBlock {
),
node_cx.link_click_handler.clone(),
)
.range_backgrounds(node_cx.range_backgrounds(leaf_key).to_vec()),
.range_backgrounds(node_cx.range_backgrounds(leaf_key).to_vec())
.reveal(node_cx.reveal_at(leaf_key, 0, self.code().len())),
);
// The id scopes the caller's action ids per code block, so plain ids
// like `"copy"` don't collide across blocks; without actions nothing
Expand Down Expand Up @@ -1941,6 +1942,8 @@ pub(crate) struct NodeContext {
pub(crate) stream_fade: Option<Arc<StreamFadeFrame>>,
/// The application's range highlights, when there are any.
pub(crate) range_highlights: Option<Arc<RangeHighlightFrame>>,
/// The line being scrolled into view, when there is one.
pub(crate) reveal: Option<RevealRequest>,
}

impl NodeContext {
Expand All @@ -1964,6 +1967,12 @@ impl NodeContext {
_ => &[],
}
}

/// The pending reveal, when it starts in the text leaf `key` between
/// `start` and `end`, rebased to `start`.
fn reveal_at(&self, key: Option<TextLeafKey>, start: usize, end: usize) -> Option<RevealAt> {
self.reveal.as_ref()?.at(key, start, end)
}
}

impl PartialEq for NodeContext {
Expand Down Expand Up @@ -2056,7 +2065,7 @@ impl Paragraph {
if self.should_render_inline_flow() {
return InlineFlow::new(
leaf_element_id(fade_key),
self.inline_flow_items(fades, backgrounds, node_cx, cx),
self.inline_flow_items(fade_key, fades, backgrounds, node_cx, cx),
node_cx.link_click_handler.clone(),
)
.into_any_element();
Expand All @@ -2079,6 +2088,7 @@ impl Paragraph {
}
let highlights = fade_highlights(highlights, &slice_fades(fades, 0, text.len()));
let backgrounds = slice_backgrounds(backgrounds, 0, text.len());
let reveal = node_cx.reveal_at(fade_key, 0, text.len());
if let Ok(mut state) = self.state.lock() {
state.set_text(text);
}
Expand All @@ -2089,6 +2099,7 @@ impl Paragraph {
node_cx.link_click_handler.clone(),
)
.range_backgrounds(backgrounds)
.reveal(reveal)
.into_any_element();
}

Expand Down Expand Up @@ -2126,6 +2137,7 @@ impl Paragraph {
consumed,
consumed + text.len(),
))
.reveal(node_cx.reveal_at(fade_key, consumed, consumed + text.len()))
.into_any_element(),
);
}
Expand Down Expand Up @@ -2218,6 +2230,7 @@ impl Paragraph {
node_cx.link_click_handler.clone(),
)
.range_backgrounds(slice_backgrounds(backgrounds, consumed, text_end))
.reveal(node_cx.reveal_at(fade_key, consumed, text_end))
.into_any_element(),
);
}
Expand Down Expand Up @@ -2250,6 +2263,7 @@ impl Paragraph {

fn inline_flow_items(
&self,
leaf_key: Option<TextLeafKey>,
fades: &[(Range<usize>, f32)],
backgrounds: &[(Range<usize>, Hsla)],
node_cx: &NodeContext,
Expand All @@ -2273,13 +2287,15 @@ impl Paragraph {
let item_fades = slice_fades(fades, consumed, consumed + text.len());
let item_backgrounds =
slice_backgrounds(backgrounds, consumed, consumed + text.len());
let item_reveal = node_cx.reveal_at(leaf_key, consumed, consumed + text.len());
consumed += text.len();
items.push(InlineFlowItem::Text {
state: inline_node.state.clone(),
text: std::mem::take(&mut text).into(),
links: std::mem::take(&mut links),
highlights: fade_highlights(std::mem::take(&mut highlights), &item_fades),
backgrounds: item_backgrounds,
reveal: item_reveal,
});
}
let mut object_style = HighlightStyle::default();
Expand Down Expand Up @@ -2339,6 +2355,7 @@ impl Paragraph {
consumed,
consumed + text.len(),
),
reveal: node_cx.reveal_at(leaf_key, consumed, consumed + text.len()),
});
}

Expand Down Expand Up @@ -2394,12 +2411,14 @@ impl Paragraph {
&slice_fades(fades, consumed, consumed + text.len()),
);
let backgrounds = slice_backgrounds(backgrounds, consumed, consumed + text.len());
let reveal = node_cx.reveal_at(leaf_key, consumed, consumed + text.len());
items.push(InlineFlowItem::Text {
state: self.state.clone(),
text: text.into(),
links,
highlights,
backgrounds,
reveal,
});
}

Expand Down Expand Up @@ -2481,7 +2500,7 @@ fn measure_table_columns(
.iter()
.any(|node| node.custom.is_some())
{
let items = cell.children.inline_flow_items(&[], &[], node_cx, cx);
let items = cell.children.inline_flow_items(None, &[], &[], node_cx, cx);
let width = super::inline_flow::intrinsic_width(&items, window, cx);
let border = if ix + 1 < col_count {
CELL_BORDER_PX
Expand Down Expand Up @@ -3461,7 +3480,7 @@ mod tests {
],
..Default::default()
};
let items = paragraph.inline_flow_items(&[], &[], &node_cx, cx);
let items = paragraph.inline_flow_items(None, &[], &[], &node_cx, cx);
let InlineFlowItem::Object { style, link, .. } = &items[0] else {
panic!()
};
Expand Down
Loading
Loading