1use std::cell::RefCell;
5use std::rc::Rc;
6
7use cosmic_text::Cursor;
8use unicode_segmentation::UnicodeSegmentation;
9
10use crate::Error;
11use crate::color::sRGB;
12use crate::render::compositor::DataFlags;
13use crate::render::{compositor, text};
14
15pub struct Instance {
16 pub text_buffer: Rc<RefCell<cosmic_text::Buffer>>,
17 pub padding: crate::PxPerimeter,
18 pub cursor: Cursor,
19 pub selection: Option<(Cursor, Cursor)>,
20 pub selection_bg: sRGB,
21 pub selection_color: sRGB,
22 pub color: sRGB,
23 pub cursor_color: sRGB,
24 pub scale: f32,
25}
26
27impl Instance {
28 fn draw_box(
29 x: f32,
30 y: f32,
31 mut w: f32,
32 mut h: f32,
33 bounds: crate::PxRect,
34 color: sRGB,
35 ) -> compositor::Data {
36 w = w.min((bounds.bottomright().x - x).max(0.0));
42 h = h.min((bounds.bottomright().y - y).max(0.0));
43 let bx = x.max(bounds.topleft().x);
44 let by = y.max(bounds.topleft().y);
45 w -= bx - x;
46 h -= by - y;
47
48 compositor::Data {
49 pos: [bx.round(), by.round()].into(),
50 dim: [w.round(), h.round()].into(),
51 uv: [0.0, 0.0].into(),
52 uvdim: [0.0, 0.0].into(),
53 color: color.as_32bit().rgba,
54 flags: DataFlags::new().with_tex(u8::MAX).with_raw(true).into(),
55 ..Default::default()
56 }
57 }
58}
59
60impl crate::render::Renderable for Instance {
61 fn render(
62 &self,
63 area: crate::PxRect,
64 driver: &crate::graphics::Driver,
65 compositor: &mut compositor::CompositorView<'_>,
66 ) -> Result<(), Error> {
67 let buffer = self.text_buffer.borrow();
68 let area = area + self.padding;
72 let pos = area.topleft();
73
74 let bounds = area.intersect(compositor.current_clip());
75
76 let bounds_top = bounds.topleft().y as i32;
77 let bounds_bottom = bounds.bottomright().y as i32;
78 let bounds_min_x = (bounds.topleft().x as i32).max(0);
79 let bounds_min_y = bounds_top.max(0);
80 let bounds_max_x = bounds.bottomright().x as i32;
81 let bounds_max_y = bounds_bottom;
82 let color = self.color.into();
83 let selection_color = self.selection_color.into();
84
85 let is_run_visible = |run: &cosmic_text::LayoutRun| {
86 let start_y_physical = (pos.y + (run.line_top * self.scale)) as i32;
87 let end_y_physical = start_y_physical + (run.line_height * self.scale) as i32;
88
89 start_y_physical <= bounds_bottom && bounds_top <= end_y_physical
90 };
91
92 for run in buffer
93 .layout_runs()
94 .skip_while(|run| !is_run_visible(run))
95 .take_while(is_run_visible)
96 {
97 let line_i = run.line_i;
98 let line_top = run.line_top;
99 let line_height = run.line_height;
100
101 if let Some((start, end)) = &self.selection
103 && line_i >= start.line
104 && line_i <= end.line
105 {
106 let mut range_opt = None;
107 for glyph in run.glyphs.iter() {
108 let cluster = &run.text[glyph.start..glyph.end];
110 let total = cluster.grapheme_indices(true).count();
111 let mut c_x = glyph.x;
112 let c_w = glyph.w / total as f32;
113 for (i, c) in cluster.grapheme_indices(true) {
114 let c_start = glyph.start + i;
115 let c_end = glyph.start + i + c.len();
116 if (start.line != line_i || c_end > start.index)
117 && (end.line != line_i || c_start < end.index)
118 {
119 range_opt = match range_opt.take() {
120 Some((min, max)) => Some((
121 std::cmp::min(min, c_x as i32),
122 std::cmp::max(max, (c_x + c_w) as i32),
123 )),
124 None => Some((c_x as i32, (c_x + c_w) as i32)),
125 };
126 } else if let Some((min, max)) = range_opt.take() {
127 compositor.preprocessed(Self::draw_box(
128 min as f32 + pos.x,
129 line_top + pos.y,
130 std::cmp::max(0, max - min) as f32,
131 line_height,
132 bounds,
133 self.selection_bg,
134 ));
135 }
136 c_x += c_w;
137 }
138 }
139
140 if run.glyphs.is_empty() && end.line > line_i {
141 range_opt = Some((0, buffer.size().0.unwrap_or(0.0) as i32));
143 }
144
145 if let Some((mut min, mut max)) = range_opt.take() {
146 if end.line > line_i {
147 if run.rtl {
149 min = 0;
150 } else if let (Some(w), _) = buffer.size() {
151 max = w.round() as i32;
152 } else if max == 0 {
153 max = (buffer.metrics().font_size * 0.5) as i32;
154 }
155 }
156 compositor.preprocessed(Self::draw_box(
157 min as f32 + pos.x,
158 line_top + pos.y,
159 std::cmp::max(0, max - min) as f32,
160 line_height,
161 bounds,
162 self.selection_bg,
163 ));
164 }
165 }
166
167 for glyph in run.glyphs.iter() {
169 let physical_glyph = glyph.physical((pos.x, pos.y), self.scale);
170
171 let mut color = match glyph.color_opt {
172 Some(some) => some,
173 None => color,
174 };
175
176 if let Some((start, end)) = self.selection
177 && line_i >= start.line
178 && line_i <= end.line
179 && (start.line != line_i || glyph.end > start.index)
180 && (end.line != line_i || glyph.start < end.index)
181 {
182 color = selection_color;
183 }
184
185 text::Instance::write_glyph(
186 physical_glyph.cache_key,
187 &mut driver.font_system.write(),
188 &mut driver.glyphs.write(),
189 &driver.device,
190 &driver.queue,
191 &mut driver.atlas.write(),
192 &mut driver.swash_cache.write(),
193 )?;
194
195 if let Some(data) = text::Instance::prepare_glyph(
196 physical_glyph.x,
197 physical_glyph.y,
198 run.line_y,
199 self.scale,
200 color,
201 bounds_min_x,
202 bounds_min_y,
203 bounds_max_x,
204 bounds_max_y,
205 text::Instance::get_glyph(physical_glyph.cache_key, &driver.glyphs.read())
206 .ok_or(Error::GlyphCacheFailure)?,
207 )? {
208 compositor.preprocessed(data);
209 }
210 }
211
212 if let Some((x, y)) = crate::editor::cursor_position(&self.cursor, &run) {
214 compositor.preprocessed(Self::draw_box(
215 x as f32 + pos.x,
216 y as f32 + pos.y,
217 1.0,
218 line_height,
219 bounds,
220 self.cursor_color,
221 ));
222 }
223 }
224
225 Ok(())
226 }
227}