Skip to main content

cursive_image/kitty/
image.rs

1use super::{super::image::*, command::*};
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            _ = delete(id);
15        }
16    }
17}
18
19//
20// KittyImage
21//
22
23/// Kitty image.
24pub trait KittyImage {
25    /// Allocate (if not already allocated).
26    fn allocate(&self) -> io::Result<usize>;
27
28    /// Free (if not already free).
29    #[allow(unused)]
30    fn free(&self) -> io::Result<()>;
31
32    /// Display.
33    fn display<PositionT, SizeT>(
34        &self,
35        position: PositionT,
36        size: Option<SizeT>,
37        window: Option<Rect>,
38    ) -> io::Result<()>
39    where
40        PositionT: Into<Vec2>,
41        SizeT: Into<Vec2>;
42}
43
44impl KittyImage for Image {
45    fn allocate(&self) -> io::Result<usize> {
46        Ok(match self.get_id() {
47            Some(id) => id,
48
49            None => {
50                let id = new_id();
51                self.set_id(Some(id));
52
53                let mut command = Command::default().with('f', self.format).with('i', id);
54
55                if self.format != ImageFormat::PNG {
56                    // Required for non-PNG data
57                    command.add('s', self.size.x);
58                    command.add('v', self.size.y);
59                }
60
61                match &self.source {
62                    ImageSource::Owned(data, compressed) => {
63                        if *compressed {
64                            command.add('o', 'z');
65                        }
66                        command.execute_with_payload(data.as_slice())?;
67                    }
68
69                    ImageSource::LocalFile(path, compressed) => {
70                        if *compressed {
71                            command.add('o', 'z');
72                        }
73                        command.add('t', 'f');
74                        command.execute_with_path_payload(path)?;
75                    }
76
77                    ImageSource::Stream(stream) => {
78                        let (reader, compressed) = stream.open()?;
79                        if compressed {
80                            command.add('o', 'z');
81                        }
82                        command.execute_with_payload(reader)?;
83                    }
84                };
85
86                id
87            }
88        })
89    }
90
91    fn free(&self) -> io::Result<()> {
92        self.set_placement(None);
93        if let Some(id) = self.get_id() {
94            delete(id)?;
95            self.set_id(None);
96        }
97        Ok(())
98    }
99
100    fn display<PositionT, SizeT>(
101        &self,
102        position: PositionT,
103        size: Option<SizeT>,
104        window: Option<Rect>,
105    ) -> io::Result<()>
106    where
107        PositionT: Into<Vec2>,
108        SizeT: Into<Vec2>,
109    {
110        let id = self.allocate()?;
111        let old_placement = self.get_placement();
112
113        let placement = new_id();
114        self.set_placement(Some(placement));
115
116        _ = move_cursor(position.into());
117
118        // Place
119        let mut command = Command::default().with('a', 'p').with('i', id).with('p', placement);
120
121        if let Some(size) = size {
122            let size = size.into();
123            command.add('c', size.x);
124            command.add('r', size.y);
125        }
126
127        if let Some(window) = window {
128            command.add('x', window.left());
129            command.add('y', window.top());
130            command.add('w', window.width());
131            command.add('h', window.height());
132        }
133
134        command.execute()?;
135
136        // Delete old placement
137        // (We do this *after* creating the new placement in order to avoid flickering, especially when scrolling)
138        if let Some(old_placement) = old_placement {
139            Command::default().with('a', 'd').with('d', 'i').with('i', id).with('p', old_placement).execute()?;
140        }
141
142        Ok(())
143    }
144}
145
146fn new_id() -> usize {
147    NEXT_ID.fetch_add(1, Ordering::SeqCst)
148}
149
150fn move_cursor(position: Vec2) -> io::Result<()> {
151    io::stdout().execute(MoveTo(position.x as u16, position.y as u16))?;
152    Ok(())
153}
154
155fn delete(id: usize) -> io::Result<()> {
156    Command::default().with('a', 'd').with('d', 'I').with('i', id).execute()
157}