nvim_gtk/render/
mod.rs

1mod context;
2mod itemize;
3mod model_clip_iterator;
4
5pub use self::context::CellMetrics;
6pub use self::context::{Context, FontFeatures};
7use self::model_clip_iterator::{ModelClipIteratorFactory, RowView};
8
9use crate::color;
10use crate::sys::pangocairo::*;
11use cairo;
12use pango;
13use pangocairo;
14
15use crate::cursor::{cursor_rect, Cursor};
16use crate::highlight::HighlightMap;
17use crate::ui_model;
18
19trait ContextAlpha {
20    fn set_source_rgbo(&self, _: &color::Color, _: Option<f64>);
21}
22
23impl ContextAlpha for cairo::Context {
24    fn set_source_rgbo(&self, color: &color::Color, alpha: Option<f64>) {
25        if let Some(alpha) = alpha {
26            self.set_source_rgba(color.0, color.1, color.2, alpha);
27        } else {
28            self.set_source_rgb(color.0, color.1, color.2);
29        }
30    }
31}
32
33pub fn fill_background(ctx: &cairo::Context, hl: &HighlightMap, alpha: Option<f64>) {
34    ctx.set_source_rgbo(hl.bg(), alpha);
35    ctx.paint();
36}
37
38pub fn render<C: Cursor>(
39    ctx: &cairo::Context,
40    cursor: &C,
41    font_ctx: &context::Context,
42    ui_model: &ui_model::UiModel,
43    hl: &HighlightMap,
44    bg_alpha: Option<f64>,
45) {
46    let cell_metrics = font_ctx.cell_metrics();
47    let &CellMetrics { char_width, .. } = cell_metrics;
48
49    // draw background
50    // disable antialiase for rectangle borders, so they will not be visible
51    ctx.set_antialias(cairo::Antialias::None);
52    for row_view in ui_model.get_clip_iterator(ctx, cell_metrics) {
53        let mut line_x = 0.0;
54
55        for (col, cell) in row_view.line.line.iter().enumerate() {
56            draw_cell_bg(&row_view, hl, cell, col, line_x, bg_alpha);
57            line_x += char_width;
58        }
59    }
60    ctx.set_antialias(cairo::Antialias::Default);
61
62    // draw text
63    for row_view in ui_model.get_clip_iterator(ctx, cell_metrics) {
64        let mut line_x = 0.0;
65
66        for (col, cell) in row_view.line.line.iter().enumerate() {
67            draw_cell(&row_view, hl, cell, col, line_x, 0.0);
68            draw_underline_strikethrough(&row_view, hl, cell, line_x, 0.0);
69
70            line_x += char_width;
71        }
72    }
73
74    draw_cursor(ctx, cursor, font_ctx, ui_model, hl, bg_alpha);
75}
76
77fn draw_cursor<C: Cursor>(
78    ctx: &cairo::Context,
79    cursor: &C,
80    font_ctx: &context::Context,
81    ui_model: &ui_model::UiModel,
82    hl: &HighlightMap,
83    bg_alpha: Option<f64>,
84) {
85    let cell_metrics = font_ctx.cell_metrics();
86    let (cursor_row, cursor_col) = ui_model.get_cursor();
87
88    let (x1, y1, x2, y2) = ctx.clip_extents();
89    let line_x = cursor_col as f64 * cell_metrics.char_width;
90    let line_y = cursor_row as f64 * cell_metrics.line_height;
91
92    if line_x < x1 || line_y < y1 || line_x > x2 || line_y > y2 || !cursor.is_visible() {
93        return;
94    }
95
96    let cell_metrics = font_ctx.cell_metrics();
97    let row_view = ui_model.get_row_view(ctx, cell_metrics, cursor_row);
98    let cell_start_col = row_view.line.cell_to_item(cursor_col);
99
100    if let Some(cursor_line) = ui_model.model().get(cursor_row) {
101        let double_width = cursor_line
102            .line
103            .get(cursor_col + 1)
104            .map_or(false, |c| c.double_width);
105
106        if cell_start_col >= 0 {
107            let cell = &cursor_line[cursor_col];
108
109            // clip cursor position
110            let (clip_y, clip_width, clip_height) =
111                cursor_rect(cursor.mode_info(), cell_metrics, line_y, double_width);
112            ctx.rectangle(line_x, clip_y, clip_width, clip_height);
113            ctx.clip();
114
115            // repaint cell backgound
116            // disable antialiase for rectangle borders, so they will not be visible
117            ctx.set_antialias(cairo::Antialias::None);
118            ctx.set_operator(cairo::Operator::Source);
119            fill_background(ctx, hl, bg_alpha);
120            draw_cell_bg(&row_view, hl, cell, cursor_col, line_x, bg_alpha);
121            ctx.set_antialias(cairo::Antialias::Default);
122
123            // reapint cursor and text
124            ctx.set_operator(cairo::Operator::Over);
125            ctx.move_to(line_x, line_y);
126            let cursor_alpha = cursor.draw(ctx, font_ctx, line_y, double_width, &hl);
127
128            let cell_start_line_x =
129                line_x - (cursor_col as i32 - cell_start_col) as f64 * cell_metrics.char_width;
130
131            debug_assert!(cell_start_line_x >= 0.0);
132
133            ctx.set_operator(cairo::Operator::Xor);
134            draw_cell(
135                &row_view,
136                hl,
137                cell,
138                cell_start_col as usize,
139                cell_start_line_x,
140                cursor_alpha,
141            );
142            draw_underline_strikethrough(&row_view, hl, cell, line_x, cursor_alpha);
143        } else {
144            ctx.move_to(line_x, line_y);
145            cursor.draw(ctx, font_ctx, line_y, double_width, &hl);
146        }
147    }
148}
149
150fn draw_underline_strikethrough(
151    cell_view: &RowView,
152    hl: &HighlightMap,
153    cell: &ui_model::Cell,
154    line_x: f64,
155    inverse_level: f64,
156) {
157    if cell.hl.underline || cell.hl.undercurl || cell.hl.strikethrough {
158        let &RowView {
159            ctx,
160            line_y,
161            cell_metrics:
162                &CellMetrics {
163                    line_height,
164                    char_width,
165                    underline_position,
166                    underline_thickness,
167                    strikethrough_position,
168                    strikethrough_thickness,
169                    ..
170                },
171            ..
172        } = cell_view;
173
174        if cell.hl.strikethrough {
175            let fg = hl.actual_cell_fg(cell).inverse(inverse_level);
176            ctx.set_source_rgb(fg.0, fg.1, fg.2);
177            ctx.set_line_width(strikethrough_thickness);
178            ctx.move_to(line_x, line_y + strikethrough_position);
179            ctx.line_to(line_x + char_width, line_y + strikethrough_position);
180            ctx.stroke();
181        }
182
183        if cell.hl.undercurl {
184            let sp = hl.actual_cell_sp(cell).inverse(inverse_level);
185            ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7);
186
187            let max_undercurl_height = (line_height - underline_position) * 2.0;
188            let undercurl_height = (underline_thickness * 4.0).min(max_undercurl_height);
189            let undercurl_y = line_y + underline_position - undercurl_height / 2.0;
190
191            pangocairo::functions::show_error_underline(
192                ctx,
193                line_x,
194                undercurl_y,
195                char_width,
196                undercurl_height,
197            );
198        } else if cell.hl.underline {
199            let fg = hl.actual_cell_fg(cell).inverse(inverse_level);
200            ctx.set_source_rgb(fg.0, fg.1, fg.2);
201            ctx.set_line_width(underline_thickness);
202            ctx.move_to(line_x, line_y + underline_position);
203            ctx.line_to(line_x + char_width, line_y + underline_position);
204            ctx.stroke();
205        }
206    }
207}
208
209fn draw_cell_bg(
210    cell_view: &RowView,
211    hl: &HighlightMap,
212    cell: &ui_model::Cell,
213    col: usize,
214    line_x: f64,
215    bg_alpha: Option<f64>,
216) {
217    let &RowView {
218        ctx,
219        line,
220        line_y,
221        cell_metrics:
222            &CellMetrics {
223                char_width,
224                line_height,
225                ..
226            },
227        ..
228    } = cell_view;
229
230    let bg = hl.cell_bg(cell);
231
232    if let Some(bg) = bg {
233        if !line.is_binded_to_item(col) {
234            if bg != hl.bg() {
235                ctx.set_source_rgbo(bg, bg_alpha);
236                ctx.rectangle(line_x, line_y, char_width, line_height);
237                ctx.fill();
238            }
239        } else {
240            ctx.set_source_rgbo(bg, bg_alpha);
241            ctx.rectangle(
242                line_x,
243                line_y,
244                char_width * line.item_len_from_idx(col) as f64,
245                line_height,
246            );
247            ctx.fill();
248        }
249    }
250}
251
252fn draw_cell(
253    row_view: &RowView,
254    hl: &HighlightMap,
255    cell: &ui_model::Cell,
256    col: usize,
257    line_x: f64,
258    inverse_level: f64,
259) {
260    let &RowView {
261        ctx,
262        line,
263        line_y,
264        cell_metrics: &CellMetrics { ascent, .. },
265        ..
266    } = row_view;
267
268    if let Some(item) = line.item_line[col].as_ref() {
269        if let Some(ref glyphs) = item.glyphs {
270            let fg = hl.actual_cell_fg(cell).inverse(inverse_level);
271
272            ctx.move_to(line_x, line_y + ascent);
273            ctx.set_source_rgb(fg.0, fg.1, fg.2);
274
275            show_glyph_string(ctx, item.font(), glyphs);
276        }
277    }
278}
279
280pub fn shape_dirty(ctx: &context::Context, ui_model: &mut ui_model::UiModel, hl: &HighlightMap) {
281    for line in ui_model.model_mut() {
282        if !line.dirty_line {
283            continue;
284        }
285
286        let styled_line = ui_model::StyledLine::from(line, hl, ctx.font_features());
287        let items = ctx.itemize(&styled_line);
288        line.merge(&styled_line, &items);
289
290        for (col, cell) in line.line.iter_mut().enumerate() {
291            if cell.dirty {
292                if let Some(item) = line.item_line[col].as_mut() {
293                    let mut glyphs = pango::GlyphString::new();
294                    {
295                        let analysis = item.analysis();
296                        let offset = item.item.offset() as usize;
297                        let length = item.item.length() as usize;
298                        if let Some(line_str) = styled_line.line_str.get(offset..offset + length) {
299                            pango::shape(&line_str, analysis, &mut glyphs);
300                        } else {
301                            warn!("Wrong itemize split");
302                        }
303                    }
304
305                    item.set_glyphs(ctx, glyphs);
306                }
307            }
308
309            cell.dirty = false;
310        }
311
312        line.dirty_line = false;
313    }
314}