use super::*;
#[derive(Clone, Default)]
pub struct BlockLayouts(Rc<RefCell<Frames>>);
#[derive(Default)]
pub(super) struct Frames {
texts: Vec<Painted>,
rows: Vec<PaintedRow>,
blocks: Vec<(usize, Bounds<Pixels>)>,
languages: Vec<(usize, Bounds<Pixels>)>,
pictures: Vec<(usize, Bounds<Pixels>)>,
painted: Vec<(usize, Bounds<Pixels>)>,
checkboxes: Vec<(usize, Bounds<Pixels>)>,
cells: Vec<(usize, Part, Bounds<Pixels>)>,
heights: Heights,
reveal: Option<Reveal>,
placed: Option<Placed>,
}
struct Placed {
origin: Point<Pixels>,
tops: Vec<Pixels>,
item_of: super::column::ItemOf,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum RevealMode {
#[default]
Center,
Nearest,
}
pub(super) const REVEAL_MARGIN: f32 = 16.0;
#[derive(Clone, Copy)]
pub(super) struct Reveal {
pub(super) range: Selection,
pub(super) mode: RevealMode,
pub(super) tries: u8,
pub(super) scrolled: bool,
pub(super) top: Option<Pixels>,
}
#[derive(Default)]
pub(super) struct Heights {
by_key: HashMap<u64, Pixels>,
by_index: Vec<Option<Pixels>>,
}
pub(super) struct Painted {
block: usize,
part: Part,
range: Range<usize>,
layout: TextLayout,
shown: Shown,
}
pub(super) struct PaintedRow {
painted: usize,
block: usize,
part: Part,
range: Range<usize>,
bounds: Bounds<Pixels>,
line_start: usize,
wrapped_row: usize,
wraps: bool,
}
impl BlockLayouts {
pub fn hit(&self, point: Point<Pixels>) -> Option<(Cursor, Affinity)> {
let frames = self.0.borrow();
if let Some(row) = frames.rows.iter().find(|row| row.bounds.contains(&point)) {
return cursor_in_row(&frames, row, point.x);
}
frames
.rows
.iter()
.min_by_key(|row| {
let bounds = row.bounds;
let above = (bounds.origin.y - point.y).abs();
let below = (bounds.origin.y + bounds.size.height - point.y).abs();
f32::from(above.min(below)) as i64
})
.and_then(|row| cursor_in_row(&frames, row, point.x))
}
pub fn position(&self, at: Cursor, affinity: Affinity) -> Option<(Point<Pixels>, Pixels)> {
let frames = self.0.borrow();
let painted = frames.texts.iter().find(|painted| {
painted.block == at.block
&& painted.part == at.part
&& painted.range.start <= at.offset
&& at.offset <= painted.range.end
})?;
let point = caret_position(
&painted.layout,
painted.shown.at(at.offset - painted.range.start),
affinity,
)?;
Some((point, painted.layout.line_height()))
}
pub fn row_start(&self, at: Cursor, affinity: Affinity) -> Option<Cursor> {
let frames = self.0.borrow();
let row = row_of(&frames, at, affinity)?;
Some(Cursor::new(row.block, row.part, row.range.start))
}
pub fn row_end(&self, at: Cursor, affinity: Affinity) -> Option<(Cursor, Affinity)> {
let frames = self.0.borrow();
let row = row_of(&frames, at, affinity)?;
Some((
Cursor::new(row.block, row.part, row.range.end),
end_affinity(row),
))
}
pub fn wraps_at(&self, at: Cursor) -> bool {
self.0.borrow().rows.iter().any(|row| {
row.wraps && row.block == at.block && row.part == at.part && row.range.end == at.offset
})
}
pub fn rects(&self, selection: Selection) -> Vec<Bounds<Pixels>> {
let (start, end) = selection.ordered();
self.0
.borrow()
.texts
.iter()
.filter_map(|painted| {
let here = Cursor::new(painted.block, painted.part, 0);
let (from, to) = (
Cursor::new(start.block, start.part, 0),
Cursor::new(end.block, end.part, 0),
);
if here < from || here > to {
return None;
}
let len = painted.range.len();
let first = if here == from { start.offset } else { 0 };
let last = if here == to { end.offset } else { usize::MAX };
let range = first.saturating_sub(painted.range.start).min(len)
..last.saturating_sub(painted.range.start).min(len);
let range = painted.shown.range(&range);
(range.start < range.end).then(|| range_rects(&painted.layout, &range, 0.0, 0.0))
})
.flatten()
.collect()
}
pub fn step_row(
&self,
at: Cursor,
affinity: Affinity,
x: Pixels,
down: bool,
) -> Option<(Cursor, Affinity)> {
let frames = self.0.borrow();
let row = row_of(&frames, at, affinity)?;
let ix = frames.rows.iter().position(|r| std::ptr::eq(r, row))?;
let next = match down {
true => frames.rows.get(ix + 1)?,
false => frames.rows.get(ix.checked_sub(1)?)?,
};
cursor_in_row(&frames, next, x)
}
pub fn over_text(&self, point: Point<Pixels>) -> bool {
self.0
.borrow()
.texts
.iter()
.any(|painted| painted.layout.bounds().contains(&point))
}
pub fn block_at(&self, point: Point<Pixels>) -> Option<usize> {
let blocks = &self.0.borrow().blocks;
blocks
.iter()
.find(|(_, bounds)| bounds.contains(&point))
.or_else(|| {
blocks.iter().min_by_key(|(_, bounds)| {
let above = (bounds.origin.y - point.y).abs();
let below = (bounds.origin.y + bounds.size.height - point.y).abs();
f32::from(above.min(below)) as i64
})
})
.map(|(ix, _)| *ix)
}
pub fn first_row(&self, ix: usize) -> Option<(Pixels, Pixels)> {
let texts = &self.0.borrow().texts;
let painted = texts.iter().find(|painted| painted.block == ix)?;
Some((
painted.layout.bounds().origin.y,
painted.layout.line_height(),
))
}
pub fn block_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
self.0
.borrow()
.blocks
.iter()
.find(|(block, _)| *block == ix)
.map(|(_, bounds)| *bounds)
}
pub fn language_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
self.0
.borrow()
.languages
.iter()
.find(|(block, _)| *block == ix)
.map(|(_, bounds)| *bounds)
}
pub fn painted_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
self.0
.borrow()
.painted
.iter()
.find(|(at, _)| *at == ix)
.map(|(_, bounds)| *bounds)
}
pub fn picture_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
self.0
.borrow()
.pictures
.iter()
.find(|(block, _)| *block == ix)
.map(|(_, bounds)| *bounds)
}
pub fn checkbox_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
self.0
.borrow()
.checkboxes
.iter()
.find(|(block, _)| *block == ix)
.map(|(_, bounds)| *bounds)
}
pub fn cell_bounds(&self, ix: usize, part: Part) -> Option<Bounds<Pixels>> {
self.0
.borrow()
.cells
.iter()
.find(|(block, at, _)| *block == ix && *at == part)
.map(|(_, _, bounds)| *bounds)
}
pub fn cell_at(&self, point: Point<Pixels>) -> Option<(usize, Part)> {
self.0
.borrow()
.cells
.iter()
.find(|(_, _, bounds)| bounds.contains(&point))
.map(|(block, part, _)| (*block, *part))
}
pub fn reveal(&self, range: Selection) {
self.reveal_with(range, RevealMode::Center);
}
pub fn reveal_with(&self, range: Selection, mode: RevealMode) {
self.0.borrow_mut().reveal = Some(Reveal {
range,
mode,
tries: 0,
scrolled: false,
top: None,
});
}
pub(super) fn revealing(&self) -> Option<Reveal> {
self.0.borrow().reveal
}
pub(super) fn set_revealing(&self, reveal: Option<Reveal>) {
self.0.borrow_mut().reveal = reveal;
}
pub(super) fn record(
&self,
block: usize,
part: Part,
range: Range<usize>,
layout: TextLayout,
shown: Shown,
) {
let mut frames = self.0.borrow_mut();
let painted = frames.texts.len();
record_rows(
&mut frames.rows,
painted,
block,
part,
&range,
&layout,
&shown,
);
frames.texts.push(Painted {
block,
part,
range,
layout,
shown,
});
}
pub(super) fn record_block(&self, ix: usize, bounds: Bounds<Pixels>) {
self.0.borrow_mut().blocks.push((ix, bounds));
}
pub(super) fn record_language(&self, ix: usize, bounds: Bounds<Pixels>) {
self.0.borrow_mut().languages.push((ix, bounds));
}
pub(super) fn record_picture(&self, ix: usize, bounds: Bounds<Pixels>) {
self.0.borrow_mut().pictures.push((ix, bounds));
}
pub(super) fn record_painted(&self, ix: usize, bounds: Bounds<Pixels>) {
self.0.borrow_mut().painted.push((ix, bounds));
}
pub(super) fn record_checkbox(&self, ix: usize, bounds: Bounds<Pixels>) {
self.0.borrow_mut().checkboxes.push((ix, bounds));
}
pub(super) fn record_cell(&self, ix: usize, part: Part, bounds: Bounds<Pixels>) {
self.0.borrow_mut().cells.push((ix, part, bounds));
}
pub(super) fn height(&self, ix: usize, key: u64) -> Option<Pixels> {
let heights = &self.0.borrow().heights;
heights
.by_key
.get(&key)
.copied()
.or_else(|| heights.by_index.get(ix).copied().flatten())
}
pub(super) fn record_height(&self, ix: usize, key: u64, height: Pixels) {
let heights = &mut self.0.borrow_mut().heights;
heights.by_key.insert(key, height);
if heights.by_index.len() <= ix {
heights.by_index.resize(ix + 1, None);
}
heights.by_index[ix] = Some(height);
}
pub(super) fn prune(&self, keys: &[u64]) {
let heights = &mut self.0.borrow_mut().heights;
heights.by_index.truncate(keys.len());
if heights.by_key.len() > 2 * keys.len() {
let keep: std::collections::HashSet<u64> = keys.iter().copied().collect();
heights.by_key.retain(|key, _| keep.contains(key));
}
}
pub fn top(&self, at: Cursor) -> Option<Pixels> {
let frames = self.0.borrow();
let placed = frames.placed.as_ref()?;
if let Some(row) = row_of(&frames, at, Affinity::Downstream) {
return Some(row.bounds.top());
}
let item = (placed.item_of)(at);
let top = placed.tops.get(item).or(placed.tops.last())?;
Some(placed.origin.y + *top)
}
pub(super) fn place(
&self,
origin: Point<Pixels>,
tops: Vec<Pixels>,
item_of: super::column::ItemOf,
) -> bool {
let mut frames = self.0.borrow_mut();
let moved = frames.placed.as_ref().is_none_or(|placed| {
placed.tops.len() != tops.len()
|| placed
.tops
.iter()
.zip(&tops)
.any(|(was, now)| (*was - *now).abs() > px(0.5))
});
frames.placed = Some(Placed {
origin,
tops,
item_of,
});
moved
}
pub(super) fn clear(&self) {
let mut frames = self.0.borrow_mut();
frames.texts.clear();
frames.rows.clear();
frames.blocks.clear();
frames.languages.clear();
frames.pictures.clear();
frames.painted.clear();
frames.checkboxes.clear();
frames.cells.clear();
}
}
pub(super) fn row_contains(row: &PaintedRow, offset: usize) -> bool {
row.range.start <= offset && offset <= row.range.end
}
fn row_of(frames: &Frames, at: Cursor, affinity: Affinity) -> Option<&PaintedRow> {
let mut rows = frames
.rows
.iter()
.filter(|row| row.block == at.block && row.part == at.part && row_contains(row, at.offset));
let first = rows.next()?;
let wrapped = first.wraps && first.range.end == at.offset;
match (wrapped, affinity) {
(true, Affinity::Downstream) => rows.next().or(Some(first)),
_ => Some(first),
}
}
fn end_affinity(row: &PaintedRow) -> Affinity {
match row.wraps {
true => Affinity::Upstream,
false => Affinity::Downstream,
}
}
pub(super) fn cursor_in_row(
frames: &Frames,
row: &PaintedRow,
x: Pixels,
) -> Option<(Cursor, Affinity)> {
let painted = &frames.texts[row.painted];
let line = painted.layout.line_layout_for_index(row.line_start)?;
let height = row.bounds.size.height;
let local = point(
x - row.bounds.origin.x,
height * (row.wrapped_row as f32 + 0.5),
);
let (Ok(offset) | Err(offset)) = line.closest_index_for_position(local, height);
let offset =
(painted.range.start + painted.shown.offset(row.line_start + offset)).min(row.range.end);
let affinity = match offset == row.range.end {
true => end_affinity(row),
false => Affinity::Downstream,
};
Some((Cursor::new(row.block, row.part, offset), affinity))
}
pub(super) fn record_rows(
rows: &mut Vec<PaintedRow>,
painted: usize,
block: usize,
part: Part,
range: &Range<usize>,
layout: &TextLayout,
shown: &Shown,
) {
let line_height = layout.line_height();
let bounds = layout.bounds();
let mut origin = bounds.origin;
let mut line_start = 0;
for line in layout.line_layouts() {
let shaped = &line.unwrapped_layout;
let row_ends = line
.wrap_boundaries()
.iter()
.map(|wrap| shaped.runs[wrap.run_ix].glyphs[wrap.glyph_ix].index)
.chain([line.len()]);
let rows_in_line = line.wrap_boundaries().len() + 1;
let mut row_start = 0;
for (row, row_end) in row_ends.enumerate() {
rows.push(PaintedRow {
painted,
block,
part,
range: range.start + shown.offset(line_start + row_start)
..range.start + shown.offset(line_start + row_end),
bounds: Bounds::new(
origin + point(px(0.0), line_height * row as f32),
size(bounds.size.width, line_height),
),
line_start,
wrapped_row: row,
wraps: row + 1 < rows_in_line,
});
row_start = row_end;
}
origin.y += line.size(line_height).height;
line_start += line.len() + 1;
}
}