Skip to main content

ratatui_image/
thread.rs

1//! Widget that separates resize+encode from rendering.
2//! This allows for rendering to be non-blocking, offloading resize+encode into another thread.
3//! See examples/thread.rs and examples/tokio.rs for how to setup the threads and channels.
4//! At least one worker thread for resize+encode is required, the example shows how to combine
5//! the needs-resize-polling with other terminal events into one event loop.
6
7#[cfg(not(feature = "tokio"))]
8use std::sync::mpsc::Sender;
9#[cfg(feature = "tokio")]
10use tokio::sync::mpsc::UnboundedSender as Sender;
11
12use image::Rgba;
13use ratatui::prelude::{Buffer, Rect};
14
15use crate::{
16    Resize, ResizeEncodeRender,
17    errors::Errors,
18    protocol::{StatefulProtocol, StatefulProtocolType},
19};
20
21/// The only usage of this struct is to call `perform()` on it and pass the completed resize to `ThreadProtocols` `update_protocol()`
22pub struct ResizeRequest {
23    protocol: StatefulProtocol,
24    resize: Resize,
25    area: Rect,
26    id: u64,
27}
28
29impl ResizeRequest {
30    pub fn resize_encode(mut self) -> Result<ResizeResponse, Errors> {
31        self.protocol.resize_encode(&self.resize, self.area);
32        self.protocol
33            .last_encoding_result()
34            .expect("The resize has just been performed")?;
35        Ok(ResizeResponse {
36            protocol: self.protocol,
37            id: self.id,
38        })
39    }
40}
41
42/// The only usage of this struct is to pass it to `ThreadProtocols` `update_resize_protocol()`
43pub struct ResizeResponse {
44    protocol: StatefulProtocol,
45    id: u64,
46}
47
48/// The state of a ThreadImage.
49///
50/// Has `inner` [StatefulProtocol] and sents requests through the mspc channel to do the
51/// `resize_encode()` work.
52pub struct ThreadProtocol {
53    inner: Option<StatefulProtocol>,
54    tx: Sender<ResizeRequest>,
55    id: u64,
56}
57
58impl ThreadProtocol {
59    pub fn new(tx: Sender<ResizeRequest>, inner: Option<StatefulProtocol>) -> ThreadProtocol {
60        Self { inner, tx, id: 0 }
61    }
62
63    pub fn replace_protocol(&mut self, proto: StatefulProtocol) {
64        self.inner = Some(proto);
65        self.increment_id();
66    }
67
68    pub fn protocol_type(&self) -> Option<&StatefulProtocolType> {
69        self.inner.as_ref().map(|inner| inner.protocol_type())
70    }
71
72    pub fn protocol_type_owned(self) -> Option<StatefulProtocolType> {
73        self.inner.map(|inner| inner.protocol_type_owned())
74    }
75
76    // Get the background color that fills in when resizing.
77    pub fn background_color(&self) -> Option<Rgba<u8>> {
78        self.inner.as_ref().map(|inner| inner.background_color())
79    }
80
81    /// This function should be used when an image should be updated but the updated image is not yet available
82    pub fn empty_protocol(&mut self) {
83        self.inner = None;
84        self.increment_id();
85    }
86
87    pub fn update_resized_protocol(&mut self, completed: ResizeResponse) -> bool {
88        let equal = self.id == completed.id;
89        if equal {
90            self.inner = Some(completed.protocol)
91        }
92        equal
93    }
94
95    pub fn size_for(&self, resize: Resize, area: Rect) -> Option<Rect> {
96        self.inner
97            .as_ref()
98            .map(|protocol| protocol.size_for(resize, area))
99    }
100
101    fn increment_id(&mut self) {
102        self.id = self.id.wrapping_add(1);
103    }
104}
105
106impl ResizeEncodeRender for ThreadProtocol {
107    fn needs_resize(&self, resize: &Resize, area: Rect) -> Option<Rect> {
108        self.inner
109            .as_ref()
110            .and_then(|protocol| protocol.needs_resize(resize, area))
111    }
112
113    /// Senda a `ResizeRequest` through the channel if there already isn't a pending `ResizeRequest`
114    fn resize_encode(&mut self, resize: &Resize, area: Rect) {
115        let _ = self.inner.take().map(|protocol| {
116            self.increment_id();
117            _ = self.tx.send(ResizeRequest {
118                protocol,
119                resize: resize.clone(),
120                area,
121                id: self.id,
122            });
123        });
124    }
125
126    /// Render the currently resized and encoded data to the buffer, if there isn't a pending `ResizeRequest`
127    fn render(&mut self, area: Rect, buf: &mut Buffer) {
128        let _ = self
129            .inner
130            .as_mut()
131            .map(|protocol| protocol.render(area, buf));
132    }
133}