Skip to main content

cursive_image/
view.rs

1use super::{image::*, sizing::*, state::*, vec2f::*};
2
3use {
4    cursive::*,
5    image::*,
6    std::io::{self, Write},
7    viuer::*,
8};
9
10const TYPE_NAME: &str = "ImageView";
11
12impl ImageView {
13    fn config(&self, printer: &Printer, image: &DynamicImage) -> (Config, Option<Rect>) {
14        let mut config = Config::default();
15
16        let mut crop = None;
17
18        let constraint = printer.output_size.into_vec2f();
19        let size = match self.sizing {
20            Sizing::Fit => self.image_size_in_cells_fit(image, constraint),
21
22            Sizing::Scale(scale) => {
23                let mut size = self.image_size_in_cells(image).scale(scale);
24
25                let too_wide = size.x > constraint.x;
26                let too_high = size.y > constraint.y;
27
28                if too_wide || too_high {
29                    if too_wide {
30                        size.x = constraint.x;
31                    }
32
33                    if too_high {
34                        size.y = constraint.y;
35                    }
36
37                    // Crop rect is in pixels
38                    crop = Some(Rect::from_size(
39                        self.into_pixels(printer.content_offset.into_vec2f()),
40                        self.into_pixels(size),
41                    ));
42                }
43
44                size
45            }
46        };
47
48        config.width = Some(size.x.round() as u32);
49        config.height = Some(size.y.round() as u32);
50
51        let size = size.into_vec2();
52        config.x = (printer.offset.x + self.align.h.get_offset(size.x, printer.output_size.x)) as u16;
53        config.y = (printer.offset.y + self.align.v.get_offset(size.y, printer.output_size.y)) as i16;
54
55        (config, crop)
56    }
57
58    fn delete(&self, ready: bool) {
59        let state = self.state.lock().unwrap();
60        let mut state = state.borrow_mut();
61
62        if state.is_visible() {
63            // https://sw.kovidgoyal.net/kitty/graphics-protocol/#deleting-images
64            let mut out = io::stdout();
65            _ = write!(out, "\x1b_Ga=d,d=n,i=0\x1b\\");
66            _ = out.flush();
67        }
68
69        *state = if ready { State::Ready } else { State::Hidden };
70    }
71
72    fn into_pixels(&self, vec: Vec2f) -> Vec2 {
73        vec.scale2(self.cell_size).into_vec2()
74    }
75
76    fn image_size_in_cells(&self, image: &DynamicImage) -> Vec2f {
77        image_size_in_pixels(image).scale2r(self.cell_size)
78    }
79
80    fn image_size_in_cells_fit(&self, image: &DynamicImage, constraint: Vec2f) -> Vec2f {
81        // Note that the image size is in pixels
82        // Because we are already scaling it to cells there is no need to convert it to cells
83        // However, we do need to convert it to the cell aspect ratio
84        let size: Vec2f = (image.width() as f64 * self.cell_aspect_ratio, image.height() as f64).into();
85        size.scale_to_fit(constraint)
86    }
87}
88
89impl View for ImageView {
90    fn type_name(&self) -> &'static str {
91        TYPE_NAME
92    }
93
94    fn required_size(&mut self, constraint: Vec2) -> Vec2 {
95        match &self.image {
96            Some(image) => {
97                let size = self.image_size_in_cells(image);
98                match self.sizing {
99                    Sizing::Fit => size.scale_to_fit(constraint.into_vec2f()),
100                    Sizing::Scale(scale) => size.scale(scale),
101                }
102                .into_vec2()
103            }
104
105            None => (1, 1).into(),
106        }
107    }
108
109    fn needs_relayout(&self) -> bool {
110        false
111    }
112
113    fn layout(&mut self, size: Vec2) {
114        if self.size != Some(size) {
115            self.delete(true);
116        }
117
118        self.size = Some(size);
119    }
120
121    fn draw(&self, printer: &Printer) {
122        let Some(image) = &self.image else {
123            return;
124        };
125
126        let delete = {
127            let content_offset = self.content_offset.lock().unwrap();
128            let mut content_offset = content_offset.borrow_mut();
129
130            if *content_offset != Some(printer.content_offset) {
131                *content_offset = Some(printer.content_offset);
132                true
133            } else {
134                false
135            }
136        };
137
138        if delete {
139            self.delete(true);
140        }
141
142        let state = self.state.lock().unwrap();
143        let mut state = state.borrow_mut();
144
145        if state.is_ready() {
146            let (config, crop) = self.config(printer, image);
147
148            _ = match crop {
149                Some(crop) => print(
150                    &image.crop_imm(crop.left() as u32, crop.top() as u32, crop.width() as u32, crop.height() as u32),
151                    &config,
152                ),
153
154                None => print(image, &config),
155            };
156
157            *state = State::Visible;
158        }
159    }
160}
161
162// Utils
163
164fn image_size_in_pixels(image: &DynamicImage) -> Vec2f {
165    (image.width() as f64, image.height() as f64).into()
166}