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