pub struct ChatBuffer { /* private fields */ }Expand description
A scrollable list of laid-out messages.
Implementations§
Source§impl ChatBuffer
impl ChatBuffer
pub fn new(params: LayoutParams) -> ChatBuffer
pub fn len(&self) -> usize
pub fn is_empty(&self) -> bool
pub fn anchor(&self) -> ScrollAnchor
pub fn set_anchor(&mut self, a: ScrollAnchor)
pub fn params(&self) -> &LayoutParams
pub fn generation(&self) -> LayoutGeneration
Sourcepub fn set_max_rows(&mut self, n: usize, measure: &dyn TextMeasure)
pub fn set_max_rows(&mut self, n: usize, measure: &dyn TextMeasure)
Scrollback cap on live rows. Matches rotulus_view_set_max_lines.
pub fn message(&self, id: MessageId) -> Option<&Message>
pub fn message_at(&self, row: usize) -> Option<&Message>
pub fn id_at(&self, row: usize) -> Option<MessageId>
Sourcepub fn row_of(&self, id: MessageId) -> Option<usize>
pub fn row_of(&self, id: MessageId) -> Option<usize>
Row position of id, or None if it has been trimmed/cleared.
Sourcepub fn append(&mut self, msg: Message, measure: &dyn TextMeasure) -> MessageId
pub fn append(&mut self, msg: Message, measure: &dyn TextMeasure) -> MessageId
Append a message; returns its mark.
Sourcepub fn insert_before(
&mut self,
anchor: Option<MessageId>,
msg: Message,
measure: &dyn TextMeasure,
) -> MessageId
pub fn insert_before( &mut self, anchor: Option<MessageId>, msg: Message, measure: &dyn TextMeasure, ) -> MessageId
Insert immediately before anchor, or at the front if anchor
is None or stale. Returns the new mark.
This is the chat-history Load-Older path. Nothing here needs to
compensate the scroll position: the anchor names a row, that row
did not move, so the viewport does not move. xtext had to bump
pagetop_line / pagetop_subline / last_pixel_pos /
old_value by the inserted row’s subline count to approximate
the same effect (xtext.c:5720).
Sourcepub fn remove(&mut self, id: MessageId, measure: &dyn TextMeasure) -> bool
pub fn remove(&mut self, id: MessageId, measure: &dyn TextMeasure) -> bool
Remove the row id names. false if the mark was already stale,
which is not an error — it is how a caller learns the row was
trimmed.
Sourcepub fn replace(
&mut self,
id: MessageId,
msg: Message,
measure: &dyn TextMeasure,
) -> bool
pub fn replace( &mut self, id: MessageId, msg: Message, measure: &dyn TextMeasure, ) -> bool
Replace a message in place, e.g. to attach a decoded image size.
Sourcepub fn set_image_size(
&mut self,
id: MessageId,
token: u32,
size: Option<ImageSize>,
measure: &dyn TextMeasure,
) -> bool
pub fn set_image_size( &mut self, id: MessageId, token: u32, size: Option<ImageSize>, measure: &dyn TextMeasure, ) -> bool
Attach a decoded size to an image block, growing the row.
The single operation the old design was worst at: xtext had to
recompute the entry’s subline list, diff the count, and patch the
buffer’s num_lines plus the scroll anchors. Here it is a height
change, and the anchor absorbs it.
Attach (or with None, clear) the decoded size of an image
block.
None is the decode-failed path: the row must shrink back to its
placeholder height, or the alt text ends up floating inside a
tall empty box the size of an image that never arrived.
Sourcepub fn find_image(&self, token: u32) -> Option<MessageId>
pub fn find_image(&self, token: u32) -> Option<MessageId>
Find the row carrying an image block with token.
pub fn clear(&mut self)
Sourcepub fn set_group_gap_secs(&mut self, secs: i64, measure: &dyn TextMeasure)
pub fn set_group_gap_secs(&mut self, secs: i64, measure: &dyn TextMeasure)
Grouping controls. 0 disables it; rows already in the buffer are re-evaluated, since the flag describes neighbours rather than the message itself.
pub fn group_gap_secs(&self) -> i64
Sourcepub fn set_width(&mut self, width: u32)
pub fn set_width(&mut self, width: u32)
Change the content width. Invalidates layout without recomputing it — the whole point of the lazy cache.
pub fn set_font_generation(&mut self, font: u32)
Sourcepub fn set_zoom_permille(&mut self, zoom: u32)
pub fn set_zoom_permille(&mut self, zoom: u32)
Zoom, in per-mille (1000 = 100%).
pub fn set_word_wrap(&mut self, on: bool)
Sourcepub fn set_avatar_size(&mut self, px: u32)
pub fn set_avatar_size(&mut self, px: u32)
Edge length of the avatar slot in the gutter; 0 turns avatars off. Invalidates every layout, since it changes the gutter width and the height of every group head.
Sourcepub fn set_indent(&mut self, on: bool)
pub fn set_indent(&mut self, on: bool)
Two-column mode.
Sourcepub fn set_stamp_width(&mut self, px: u32)
pub fn set_stamp_width(&mut self, px: u32)
Width to reserve for the timestamp column; 0 turns it off.
pub fn stamp_width(&self) -> u32
Sourcepub fn set_max_indent(&mut self, px: u32)
pub fn set_max_indent(&mut self, px: u32)
Cap on the gutter width.
Sourcepub fn ensure_layout(&mut self, row: usize, measure: &dyn TextMeasure) -> u32
pub fn ensure_layout(&mut self, row: usize, measure: &dyn TextMeasure) -> u32
Ensure row row has a current layout, computing it if not.
Returns its height.
Sourcepub fn ensure_visible(
&mut self,
y: u64,
viewport_height: u32,
measure: &dyn TextMeasure,
) -> Vec<usize>
pub fn ensure_visible( &mut self, y: u64, viewport_height: u32, measure: &dyn TextMeasure, ) -> Vec<usize>
Lay out every row intersecting [y, y + height) and return their
positions.
Every returned row is guaranteed to have a current layout.
That guarantee needs defending, because laying a row out can
invalidate the rows already done: meeting a wider nick widens the
shared gutter, which makes every other row’s cached layout stale.
Without the retry below, the rows laid out earlier in the pass
come back with layout_at(row) == None, the view skips them, and
the first paint of a fresh buffer is visibly shredded — while the
next message, by which time the gutter has settled, looks fine.
The gutter only ever grows and is capped by max_indent, so this
converges fast; the bound is belt-and-braces.
Sourcepub fn source_text(&self, row: usize, source: LineSource) -> Option<&str>
pub fn source_text(&self, row: usize, source: LineSource) -> Option<&str>
Text of one of a message’s sources, for hit-testing and copying.
Sourcepub fn link_range_at(&self, caret: &Caret) -> Option<Range<usize>>
pub fn link_range_at(&self, caret: &Caret) -> Option<Range<usize>>
The link under a caret, as (href, visible text).
Returned by value rather than by reference because the caller is
the widget, which needs to hold it across a popup.
The byte range of the link under caret, in its source’s text.
Split from link_at because the hover underline needs the extent
and not the target — and deriving the extent a second way is how
the underline and the click end up disagreeing about where a link
stops.
Sourcepub fn gutter_range(&self, id: MessageId) -> Option<Range<usize>>
pub fn gutter_range(&self, id: MessageId) -> Option<Range<usize>>
Byte extent of a row’s gutter text, for the nick hover underline.
Sourcepub fn speaker_of(&self, id: MessageId) -> Option<&Speaker>
pub fn speaker_of(&self, id: MessageId) -> Option<&Speaker>
The speaker of a row, if it has one.
pub fn link_at(&self, caret: &Caret) -> Option<(String, String)>
Sourcepub fn word_at(&self, caret: &Caret) -> Option<String>
pub fn word_at(&self, caret: &Caret) -> Option<String>
The whitespace-delimited word around a caret.
Tokenised like xtext’s is_del macro: space, newline, <, >
and NUL. The angle brackets are what let <nick> split into a
bare nick when it is double-clicked.
Sourcepub fn word_bounds(&self, caret: &Caret) -> Option<(usize, usize)>
pub fn word_bounds(&self, caret: &Caret) -> Option<(usize, usize)>
Byte bounds of the word around a caret, in its own source.
Shared by word_at (which the word-click emission needs as a
string) and double-click word-select, so the two can never
disagree about where a word begins.
Sourcepub fn select_word(&self, caret: &Caret) -> Option<Selection>
pub fn select_word(&self, caret: &Caret) -> Option<Selection>
Double-click: select the word under the caret.
Sourcepub fn select_row(&self, row: usize) -> Option<Selection>
pub fn select_row(&self, row: usize) -> Option<Selection>
Triple-click: select the whole row, gutter included.
The gutter is part of what the user sees on that line, so it is part of what a “select this line” gesture should give them — consistent with a whole-row selection dragged from above.
Sourcepub fn search(&self, needle: &str, case_sensitive: bool) -> Vec<Match>
pub fn search(&self, needle: &str, case_sensitive: bool) -> Vec<Match>
Every occurrence of needle, in reading order.
Reading order means row by row, and within a row by
source_rank — the same order
selected_rows walks and the same order the eye does, so
stepping through matches never jumps backwards on screen.
Searches the model, not the layout, so matches in rows that have never been laid out are found too. That matters: the whole point of search is to reach the part of the scrollback you haven’t scrolled to.
Sourcepub fn avatar_at(&mut self, x: i32, y: u64) -> Option<(MessageId, u64)>
pub fn avatar_at(&mut self, x: i32, y: u64) -> Option<(MessageId, u64)>
The speaker whose avatar covers content point (x, y), if any.
The avatar is painted from LayoutCache::avatar, not from a line
box, so the ordinary caret hit-test cannot see it — clicking an
icon found nothing at all. Asking the layout directly keeps the
clickable area and the painted area the same rectangle by
construction.
Sourcepub fn select_all(&self) -> Option<Selection>
pub fn select_all(&self) -> Option<Selection>
A selection covering the whole buffer, or None when empty.
The model owns what “everything” means, because the view kept
getting it wrong: it hard-coded Block(0) at both ends, which
skipped the first row’s gutter and — once markdown started
splitting a body into several blocks — stopped at the end of the
first block of the last row, dropping any code block or quote
after it.
Sourcepub fn matches_in(
all: &[Match],
id: MessageId,
source: LineSource,
) -> Vec<Match>
pub fn matches_in( all: &[Match], id: MessageId, source: LineSource, ) -> Vec<Match>
Matches falling inside one row’s one source, for the renderer.
The renderer asks per source per row while drawing, so this is a filter over the result set rather than a fresh search.
Sourcepub fn reveal(
&mut self,
id: MessageId,
viewport_height: u32,
measure: &dyn TextMeasure,
)
pub fn reveal( &mut self, id: MessageId, viewport_height: u32, measure: &dyn TextMeasure, )
Scroll so id is on screen, centred if it isn’t already visible.
Leaves an already-visible row where it is. Yanking the viewport on every step would make walking a cluster of matches in one screenful feel like the view was fighting you.
Sourcepub fn indent_width(&self) -> u32
pub fn indent_width(&self) -> u32
The settled gutter width. 0 when not in indent mode.
Sourcepub fn indent_pinned(&self) -> bool
pub fn indent_pinned(&self) -> bool
Whether the gutter has been pinned by a separator drag.
Sourcepub fn set_indent_width(&mut self, px: u32) -> bool
pub fn set_indent_width(&mut self, px: u32) -> bool
Pin the gutter to px, as a separator drag does.
Deliberately not clamped to max_indent: that cap governs how
far the gutter may grow on its own, and a user dragging the
separator is saying something the cap has no business overruling.
The caller clamps to the viewport instead. Returns whether
anything moved.
Sourcepub fn unpin_indent(&mut self)
pub fn unpin_indent(&mut self)
Release the pin and let the gutter auto-size again.
Sourcepub fn hit_test(
&mut self,
x: i32,
y: u64,
measure: &dyn TextMeasure,
) -> Option<Caret>
pub fn hit_test( &mut self, x: i32, y: u64, measure: &dyn TextMeasure, ) -> Option<Caret>
Map a content-space pixel to a document position.
x / y are in content coordinates (the view subtracts its own
padding first). y is absolute within the buffer, not
viewport-relative.
Returns None only for an empty buffer; a point past the end
clamps to the last row, because a drag that runs off the bottom
should select to the end rather than stop tracking.
Sourcepub fn row_selection(&self, row: usize, sel: &Selection) -> RowSelection
pub fn row_selection(&self, row: usize, sel: &Selection) -> RowSelection
How much of row a selection covers.
Sourcepub fn sources_of(&self, row: usize) -> Vec<LineSource>
pub fn sources_of(&self, row: usize) -> Vec<LineSource>
The row’s sources in visual order: gutter first, then blocks.
Sourcepub fn covered_range(
&self,
row: usize,
source: LineSource,
sel: &RowSelection,
) -> Option<Range<usize>>
pub fn covered_range( &self, row: usize, source: LineSource, sel: &RowSelection, ) -> Option<Range<usize>>
The byte range of source covered by a row selection, if any.
The one place that resolves a possibly-cross-source Partial
against a single text, so the view and selected_text cannot
disagree about what is highlighted versus what gets copied.
Sourcepub fn selected_rows(&self, sel: &Selection) -> Vec<(usize, String)>
pub fn selected_rows(&self, sel: &Selection) -> Vec<(usize, String)>
The selected text of each covered row, as (row, text).
Exposed per row because a row’s own text may contain hard
newlines — the wrap engine supports them — so a caller cannot
recover row boundaries by splitting the joined string. The
autocopy-timestamp path needs exactly that: it prefixes each
row with that row’s stamp, and iterating lines() over the
joined output drifts onto the wrong rows the moment any selected
message spans more than one line.
Sourcepub fn selected_text(&self, sel: &Selection) -> String
pub fn selected_text(&self, sel: &Selection) -> String
The selected text, rows joined by newlines.
pub fn layout_at(&self, row: usize) -> Option<&LayoutCache>
pub fn total_height(&mut self) -> u64
pub fn index_mut(&mut self) -> &mut HeightIndex
Sourcepub fn scroll_offset(&mut self, viewport_height: u32) -> u64
pub fn scroll_offset(&mut self, viewport_height: u32) -> u64
The scroll adjustment value the current anchor implies.
Sourcepub fn scroll_to(&mut self, y: u64, viewport_height: u32, follow_slop: u32)
pub fn scroll_to(&mut self, y: u64, viewport_height: u32, follow_slop: u32)
Re-anchor from a pixel position — a scrollbar drag.
Sourcepub fn scroll_to_bottom(&mut self)
pub fn scroll_to_bottom(&mut self)
Pin to the bottom and resume following.