Skip to main content

cursive_image/
image.rs

1use super::{sizing::*, state::*, vec2f::*};
2
3use {
4    crossterm::terminal::*,
5    cursive::{align::*, *},
6    image::*,
7    std::{cell::*, path::*, sync::*},
8};
9
10//
11// ImageView
12//
13
14/// Image view.
15///
16/// It will, of course, only work on terminals that support graphics. Relying on the
17/// [viuer](https://github.com/atanunq/viuer) library, it supports the
18/// [Kitty](https://sw.kovidgoyal.net/kitty/graphics-protocol.html),
19/// [iTerm](https://iterm2.com/documentation-images.html), and
20/// [Sixel](https://github.com/saitoha/libsixel) (with the `sixel` feature) terminal graphics
21/// protocols.
22///
23/// The view supports fit or scale sizing modes (fit is the default), image alignment, and
24/// scrolling.
25pub struct ImageView {
26    pub(crate) image: Option<DynamicImage>,
27    pub(crate) sizing: Sizing,
28    pub(crate) align: Align,
29
30    pub(crate) size: Option<Vec2>,
31    pub(crate) content_offset: Mutex<RefCell<Option<Vec2>>>,
32    pub(crate) state: Mutex<RefCell<State>>,
33
34    pub(crate) cell_size: XY<f64>,
35    pub(crate) cell_aspect_ratio: f64,
36}
37
38impl ImageView {
39    /// Image.
40    pub fn image(&self) -> Option<&DynamicImage> {
41        self.image.as_ref()
42    }
43
44    /// Image.
45    pub fn image_mut(&mut self) -> Option<&mut DynamicImage> {
46        self.image.as_mut()
47    }
48
49    /// Set image.
50    pub fn set_image(&mut self, image: DynamicImage) {
51        self.image = Some(image);
52        self.size = None;
53    }
54
55    /// Set image.
56    ///
57    /// Chainable.
58    pub fn with_image(self, image: DynamicImage) -> Self {
59        self.with(|self_| self_.set_image(image))
60    }
61
62    /// Set image.
63    pub fn set_image_file<PathT>(&mut self, path: PathT)
64    where
65        PathT: AsRef<Path>,
66    {
67        let image = ImageReader::open(path).unwrap().decode().unwrap();
68        self.set_image(image);
69    }
70
71    /// Set image.
72    ///
73    /// Chainable.
74    pub fn with_image_file<PathT>(self, path: PathT) -> Self
75    where
76        PathT: AsRef<Path>,
77    {
78        self.with(|self_| self_.set_image_file(path))
79    }
80
81    /// Sizing.
82    pub fn sizing(&self) -> Sizing {
83        self.sizing
84    }
85
86    /// Set sizing.
87    pub fn set_sizing(&mut self, sizing: Sizing) {
88        if self.sizing != sizing {
89            self.sizing = sizing;
90            self.size = None;
91        }
92    }
93
94    /// Set sizing.
95    ///
96    /// Chainable.
97    pub fn with_sizing(self, sizing: Sizing) -> Self {
98        self.with(|self_| self_.set_sizing(sizing))
99    }
100
101    /// Align.
102    pub fn align(&self) -> Align {
103        self.align
104    }
105
106    /// Set align.
107    pub fn set_align(&mut self, align: Align) {
108        if self.align != align {
109            self.align = align;
110            self.size = None;
111        }
112    }
113
114    /// Set align.
115    ///
116    /// Chainable.
117    pub fn with_align(self, align: Align) -> Self {
118        self.with(|self_| self_.set_align(align))
119    }
120}
121
122impl Default for ImageView {
123    fn default() -> Self {
124        let cell_size = cell_size();
125        Self {
126            image: None,
127            sizing: Default::default(),
128            align: Align::center(),
129            size: None,
130            content_offset: Default::default(),
131            state: Default::default(),
132            cell_size,
133            cell_aspect_ratio: cell_size.aspect_ratio(),
134        }
135    }
136}
137
138// Utils
139
140// Cell size in pixels.
141fn cell_size() -> Vec2f {
142    match window_size() {
143        Ok(size) if size.width != 0 && size.height != 0 && size.columns != 0 && size.rows != 0 => {
144            (size.width as f64 / size.columns as f64, size.height as f64 / size.rows as f64)
145        }
146
147        _ => (8., 8.),
148    }
149    .into()
150}