nvim_gtk/render/
model_clip_iterator.rs

1use std::cmp::min;
2use std::slice::Iter;
3
4use cairo;
5
6use super::context::CellMetrics;
7use crate::ui_model;
8
9pub struct RowView<'a> {
10    pub line: &'a ui_model::Line,
11    pub cell_metrics: &'a CellMetrics,
12    pub line_y: f64,
13    pub ctx: &'a cairo::Context,
14}
15
16impl<'a> RowView<'a> {
17    pub fn new(
18        row: usize,
19        ctx: &'a cairo::Context,
20        cell_metrics: &'a CellMetrics,
21        line: &'a ui_model::Line,
22    ) -> Self {
23        RowView {
24            line,
25            line_y: row as f64 * cell_metrics.line_height,
26            cell_metrics,
27            ctx,
28        }
29    }
30}
31
32pub struct ModelClipIterator<'a> {
33    model_idx: usize,
34    model_iter: Iter<'a, ui_model::Line>,
35    cell_metrics: &'a CellMetrics,
36    ctx: &'a cairo::Context,
37}
38
39pub trait ModelClipIteratorFactory {
40    fn get_clip_iterator<'a>(
41        &'a self,
42        ctx: &'a cairo::Context,
43        cell_metrics: &'a CellMetrics,
44    ) -> ModelClipIterator;
45
46    fn get_row_view<'a>(
47        &'a self,
48        ctx: &'a cairo::Context,
49        cell_metrics: &'a CellMetrics,
50        col: usize,
51    ) -> RowView<'a>;
52}
53
54impl<'a> Iterator for ModelClipIterator<'a> {
55    type Item = RowView<'a>;
56
57    fn next(&mut self) -> Option<RowView<'a>> {
58        let next = if let Some(line) = self.model_iter.next() {
59            Some(RowView::new(
60                self.model_idx,
61                self.ctx,
62                self.cell_metrics,
63                line,
64            ))
65        } else {
66            None
67        };
68        self.model_idx += 1;
69
70        next
71    }
72}
73
74/// Clip implemented as top - 1/bot + 1
75/// this is because in some cases(like 'g' character) drawing character does not fit to calculated bounds
76/// and if one line must be repainted - also previous and next line must be repainted to
77impl ModelClipIteratorFactory for ui_model::UiModel {
78    fn get_row_view<'a>(
79        &'a self,
80        ctx: &'a cairo::Context,
81        cell_metrics: &'a CellMetrics,
82        col: usize,
83    ) -> RowView<'a> {
84        RowView::new(col, ctx, cell_metrics, &self.model()[col])
85    }
86
87    fn get_clip_iterator<'a>(
88        &'a self,
89        ctx: &'a cairo::Context,
90        cell_metrics: &'a CellMetrics,
91    ) -> ModelClipIterator<'a> {
92        let model = self.model();
93
94        let (x1, y1, x2, y2) = ctx.clip_extents();
95
96        // in case ctx.translate is used y1 can be less then 0
97        // in this case just use 0 as top value
98        let model_clip = ui_model::ModelRect::from_area(cell_metrics, x1, y1.max(0.0), x2, y2);
99
100        let model_clip_top = if model_clip.top == 0 {
101            0
102        } else {
103            // looks like in some cases repaint can come from old model
104            min(model.len() - 1, model_clip.top - 1)
105        };
106        let model_clip_bot = min(model.len() - 1, model_clip.bot + 1);
107
108        debug_assert!(
109            model_clip_top <= model_clip_bot,
110            "model line index starts at {} but ends at {}. model.len = {}",
111            model_clip_top,
112            model_clip_bot,
113            model.len()
114        );
115
116        ModelClipIterator {
117            model_idx: model_clip_top,
118            model_iter: model[model_clip_top..=model_clip_bot].iter(),
119            ctx,
120            cell_metrics,
121        }
122    }
123}