use std::cell::RefCell;
use std::rc::Rc;
use cosmic_text::Cursor;
use unicode_segmentation::UnicodeSegmentation;
use crate::Error;
use crate::color::sRGB;
use crate::render::compositor::DataFlags;
use crate::render::{compositor, text};
pub struct Instance {
pub text_buffer: Rc<RefCell<cosmic_text::Buffer>>,
pub padding: crate::PxPerimeter,
pub cursor: Cursor,
pub selection: Option<(Cursor, Cursor)>,
pub selection_bg: sRGB,
pub selection_color: sRGB,
pub color: sRGB,
pub cursor_color: sRGB,
pub scale: f32,
}
impl Instance {
fn draw_box(
x: f32,
y: f32,
mut w: f32,
mut h: f32,
bounds: crate::PxRect,
color: sRGB,
) -> compositor::Data {
w = w.min((bounds.bottomright().x - x).max(0.0));
h = h.min((bounds.bottomright().y - y).max(0.0));
let bx = x.max(bounds.topleft().x);
let by = y.max(bounds.topleft().y);
w -= bx - x;
h -= by - y;
compositor::Data {
pos: [bx.round(), by.round()].into(),
dim: [w.round(), h.round()].into(),
uv: [0.0, 0.0].into(),
uvdim: [0.0, 0.0].into(),
color: color.as_32bit().rgba,
flags: DataFlags::new().with_tex(u8::MAX).with_raw(true).into(),
..Default::default()
}
}
}
impl crate::render::Renderable for Instance {
fn render(
&self,
area: crate::PxRect,
driver: &crate::graphics::Driver,
compositor: &mut compositor::CompositorView<'_>,
) -> Result<(), Error> {
let buffer = self.text_buffer.borrow();
let area = area + self.padding;
let pos = area.topleft();
let bounds = area.intersect(compositor.current_clip());
let bounds_top = bounds.topleft().y as i32;
let bounds_bottom = bounds.bottomright().y as i32;
let bounds_min_x = (bounds.topleft().x as i32).max(0);
let bounds_min_y = bounds_top.max(0);
let bounds_max_x = bounds.bottomright().x as i32;
let bounds_max_y = bounds_bottom;
let color = self.color.into();
let selection_color = self.selection_color.into();
let is_run_visible = |run: &cosmic_text::LayoutRun| {
let start_y_physical = (pos.y + (run.line_top * self.scale)) as i32;
let end_y_physical = start_y_physical + (run.line_height * self.scale) as i32;
start_y_physical <= bounds_bottom && bounds_top <= end_y_physical
};
for run in buffer
.layout_runs()
.skip_while(|run| !is_run_visible(run))
.take_while(is_run_visible)
{
let line_i = run.line_i;
let line_top = run.line_top;
let line_height = run.line_height;
if let Some((start, end)) = &self.selection
&& line_i >= start.line
&& line_i <= end.line
{
let mut range_opt = None;
for glyph in run.glyphs.iter() {
let cluster = &run.text[glyph.start..glyph.end];
let total = cluster.grapheme_indices(true).count();
let mut c_x = glyph.x;
let c_w = glyph.w / total as f32;
for (i, c) in cluster.grapheme_indices(true) {
let c_start = glyph.start + i;
let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index)
&& (end.line != line_i || c_start < end.index)
{
range_opt = match range_opt.take() {
Some((min, max)) => Some((
std::cmp::min(min, c_x as i32),
std::cmp::max(max, (c_x + c_w) as i32),
)),
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
compositor.preprocessed(Self::draw_box(
min as f32 + pos.x,
line_top + pos.y,
std::cmp::max(0, max - min) as f32,
line_height,
bounds,
self.selection_bg,
));
}
c_x += c_w;
}
}
if run.glyphs.is_empty() && end.line > line_i {
range_opt = Some((0, buffer.size().0.unwrap_or(0.0) as i32));
}
if let Some((mut min, mut max)) = range_opt.take() {
if end.line > line_i {
if run.rtl {
min = 0;
} else if let (Some(w), _) = buffer.size() {
max = w.round() as i32;
} else if max == 0 {
max = (buffer.metrics().font_size * 0.5) as i32;
}
}
compositor.preprocessed(Self::draw_box(
min as f32 + pos.x,
line_top + pos.y,
std::cmp::max(0, max - min) as f32,
line_height,
bounds,
self.selection_bg,
));
}
}
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((pos.x, pos.y), self.scale);
let mut color = match glyph.color_opt {
Some(some) => some,
None => color,
};
if let Some((start, end)) = self.selection
&& line_i >= start.line
&& line_i <= end.line
&& (start.line != line_i || glyph.end > start.index)
&& (end.line != line_i || glyph.start < end.index)
{
color = selection_color;
}
text::Instance::write_glyph(
physical_glyph.cache_key,
&mut driver.font_system.write(),
&mut driver.glyphs.write(),
&driver.device,
&driver.queue,
&mut driver.atlas.write(),
&mut driver.swash_cache.write(),
)?;
if let Some(data) = text::Instance::prepare_glyph(
physical_glyph.x,
physical_glyph.y,
run.line_y,
self.scale,
color,
bounds_min_x,
bounds_min_y,
bounds_max_x,
bounds_max_y,
text::Instance::get_glyph(physical_glyph.cache_key, &driver.glyphs.read())
.ok_or(Error::GlyphCacheFailure)?,
)? {
compositor.preprocessed(data);
}
}
if let Some((x, y)) = crate::editor::cursor_position(&self.cursor, &run) {
compositor.preprocessed(Self::draw_box(
x as f32 + pos.x,
y as f32 + pos.y,
1.0,
line_height,
bounds,
self.cursor_color,
));
}
}
Ok(())
}
}