Skip to main content

cursive_image/image/
kitty.rs

1use super::{super::kitty::*, image::*, source::*};
2
3use {
4    crossterm::{ExecutableCommand, cursor::*},
5    cursive::*,
6    std::{io, sync::atomic::*},
7};
8
9static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
10
11impl Drop for Image {
12    fn drop(&mut self) {
13        if let Some(id) = self.get_id() {
14            _ = release_image(id);
15        }
16    }
17}
18
19//
20// KittyImage
21//
22
23/// Kitty image.
24pub trait KittyImage {
25    /// Register with terminal (if not already registered).
26    ///
27    /// Returns the ID.
28    fn register(&self) -> io::Result<usize>;
29
30    /// Release from terminal (if registered).
31    fn release(&self) -> io::Result<()>;
32
33    /// Add a placement. Will also remove the existing placement (if there is one).
34    ///
35    /// Position and size are in cell coordinates. If size is set will stretch the image to fit.
36    ///
37    /// If window is set will display only that part of the image. The window dimensions are in
38    /// pixels.
39    fn show<PositionT, SizeT>(&self, position: PositionT, size: Option<SizeT>, window: Option<Rect>) -> io::Result<()>
40    where
41        PositionT: Into<Vec2>,
42        SizeT: Into<Vec2>;
43
44    /// Remove placement (if there is one).
45    fn hide(&self) -> io::Result<()>;
46}
47
48impl KittyImage for Image {
49    fn register(&self) -> io::Result<usize> {
50        Ok(match self.get_id() {
51            Some(id) => id,
52
53            None => {
54                let id = new_id();
55                self.set_id(Some(id));
56
57                let mut command = Command::default().with('f', self.format).with('i', id);
58
59                if self.format.is_raw() {
60                    command.add('s', self.size.x);
61                    command.add('v', self.size.y);
62                }
63
64                match &self.source {
65                    ImageSource::Owned(data, compressed) => {
66                        if *compressed {
67                            command.add('o', 'z');
68                        }
69                        command.execute_with_payload(data.as_slice())?;
70                    }
71
72                    ImageSource::LocalFile(path, compressed) => {
73                        if *compressed {
74                            command.add('o', 'z');
75                        }
76                        command.add('t', 'f');
77                        command.execute_with_path_payload(path)?;
78                    }
79
80                    ImageSource::Stream(stream) => {
81                        let (reader, compressed) = stream.open()?;
82                        if compressed {
83                            command.add('o', 'z');
84                        }
85                        command.execute_with_payload_from(reader)?;
86                    }
87                };
88
89                id
90            }
91        })
92    }
93
94    fn release(&self) -> io::Result<()> {
95        self.set_placement(None);
96        if let Some(id) = self.get_id() {
97            release_image(id)?;
98            self.set_id(None);
99        }
100        Ok(())
101    }
102
103    fn show<PositionT, SizeT>(&self, position: PositionT, size: Option<SizeT>, window: Option<Rect>) -> io::Result<()>
104    where
105        PositionT: Into<Vec2>,
106        SizeT: Into<Vec2>,
107    {
108        let id = self.register()?;
109        let old_placement = self.get_placement();
110
111        let placement = new_id();
112        self.set_placement(Some(placement));
113
114        _ = move_cursor(position.into());
115
116        // Place
117        let mut command = Command::default().with('a', 'p').with('i', id).with('p', placement).with('q', 1);
118
119        if let Some(size) = size {
120            let size = size.into();
121            command.add('c', size.x);
122            command.add('r', size.y);
123        }
124
125        if let Some(window) = window {
126            command.add('x', window.left());
127            command.add('y', window.top());
128            command.add('w', window.width());
129            command.add('h', window.height());
130        }
131
132        command.execute()?;
133
134        // Remove old placement
135        // (We do this *after* creating the new placement in order to avoid flickering, especially when scrolling)
136        if let Some(old_placement) = old_placement {
137            delete_placement(id, old_placement)?;
138        }
139
140        Ok(())
141    }
142
143    fn hide(&self) -> io::Result<()> {
144        if let Some(placement) = self.get_placement()
145            && let Some(id) = self.get_id()
146        {
147            delete_placement(id, placement)?;
148            self.set_placement(None);
149        }
150        Ok(())
151    }
152}
153
154fn new_id() -> usize {
155    NEXT_ID.fetch_add(1, Ordering::SeqCst)
156}
157
158fn move_cursor(position: Vec2) -> io::Result<()> {
159    io::stdout().execute(MoveTo(position.x as u16, position.y as u16))?;
160    Ok(())
161}
162
163fn release_image(id: usize) -> io::Result<()> {
164    Command::default().with('a', 'd').with('d', 'I').with('i', id).execute()
165}
166
167fn delete_placement(id: usize, placement: usize) -> io::Result<()> {
168    Command::default().with('a', 'd').with('d', 'i').with('i', id).with('p', placement).execute()
169}