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>)>,
checkboxes: Vec<(usize, Bounds<Pixels>)>,
}
pub(super) struct Painted {
block: usize,
part: Part,
range: Range<usize>,
layout: TextLayout,
}
pub(super) struct PaintedRow {
painted: usize,
block: usize,
part: Part,
range: Range<usize>,
bounds: Bounds<Pixels>,
line_start: usize,
wrapped_row: usize,
}
impl BlockLayouts {
pub fn hit(&self, point: Point<Pixels>) -> Option<Cursor> {
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) -> 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 = painted
.layout
.position_for_index(at.offset - painted.range.start)?;
Some((point, painted.layout.line_height()))
}
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);
(range.start < range.end).then(|| range_rects(&painted.layout, &range, 0.0, 0.0))
})
.flatten()
.collect()
}
pub fn step_row(
&self,
at: Cursor,
from: Point<Pixels>,
down: bool,
) -> Option<(Cursor, Pixels)> {
let frames = self.0.borrow();
let ix = frames
.rows
.iter()
.position(|row| {
row.block == at.block
&& row.part == at.part
&& row_contains(row, at.offset)
&& row.bounds.origin.y <= from.y
&& from.y < row.bounds.origin.y + row.bounds.size.height
})
.or_else(|| {
frames.rows.iter().position(|row| {
row.block == at.block && row.part == at.part && row_contains(row, at.offset)
})
})?;
let next = match down {
true => frames.rows.get(ix + 1)?,
false => frames.rows.get(ix.checked_sub(1)?)?,
};
Some((cursor_in_row(&frames, next, from.x)?, next.bounds.origin.y))
}
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 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(super) fn record(&self, block: usize, part: Part, range: Range<usize>, layout: TextLayout) {
let mut frames = self.0.borrow_mut();
let painted = frames.texts.len();
record_rows(&mut frames.rows, painted, block, part, &range, &layout);
frames.texts.push(Painted {
block,
part,
range,
layout,
});
}
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_checkbox(&self, ix: usize, bounds: Bounds<Pixels>) {
self.0.borrow_mut().checkboxes.push((ix, bounds));
}
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.checkboxes.clear();
}
}
pub(super) fn row_contains(row: &PaintedRow, offset: usize) -> bool {
row.range.start <= offset && offset <= row.range.end
}
pub(super) fn cursor_in_row(frames: &Frames, row: &PaintedRow, x: Pixels) -> Option<Cursor> {
let painted = &frames.texts[row.painted];
let line = painted
.layout
.line_layout_for_index(row.line_start - painted.range.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);
Some(Cursor::new(
row.block,
row.part,
(row.line_start + offset).min(row.range.end),
))
}
pub(super) fn record_rows(
rows: &mut Vec<PaintedRow>,
painted: usize,
block: usize,
part: Part,
range: &Range<usize>,
layout: &TextLayout,
) {
let line_height = layout.line_height();
let bounds = layout.bounds();
let mut origin = bounds.origin;
let mut line_start = range.start;
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 mut row_start = 0;
for (row, row_end) in row_ends.enumerate() {
rows.push(PaintedRow {
painted,
block,
part,
range: line_start + row_start..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,
});
row_start = row_end;
}
origin.y += line.size(line_height).height;
line_start += line.len() + 1;
}
}