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::{
14    layout::Size,
15    prelude::{Buffer, Rect},
16};
17
18use crate::{
19    Resize, ResizeEncodeRender,
20    errors::Errors,
21    protocol::{StatefulProtocol, StatefulProtocolType},
22};
23
24/// The only usage of this struct is to call `perform()` on it and pass the completed resize to `ThreadProtocols` `update_protocol()`
25pub struct ResizeRequest {
26    protocol: StatefulProtocol,
27    resize: Resize,
28    size: Size,
29    id: u64,
30}
31
32impl ResizeRequest {
33    pub fn resize_encode(mut self) -> Result<ResizeResponse, Errors> {
34        self.protocol.resize_encode(&self.resize, self.size);
35        self.protocol
36            .last_encoding_result()
37            .expect("The resize has just been performed")?;
38        Ok(ResizeResponse {
39            protocol: self.protocol,
40            id: self.id,
41        })
42    }
43}
44
45/// The only usage of this struct is to pass it to `ThreadProtocols` `update_resize_protocol()`
46pub struct ResizeResponse {
47    protocol: StatefulProtocol,
48    id: u64,
49}
50
51/// The state for a threaded [`crate::StatefulImage`].
52///
53/// Has `inner` [StatefulProtocol] and sents requests through the mspc channel to do the
54/// `resize_encode()` work.
55pub struct ThreadProtocol {
56    inner: Option<StatefulProtocol>,
57    tx: Sender<ResizeRequest>,
58    id: u64,
59}
60
61impl ThreadProtocol {
62    pub fn new(tx: Sender<ResizeRequest>, inner: Option<StatefulProtocol>) -> ThreadProtocol {
63        Self { inner, tx, id: 0 }
64    }
65
66    pub fn replace_protocol(&mut self, proto: StatefulProtocol) {
67        self.inner = Some(proto);
68        self.increment_id();
69    }
70
71    pub fn protocol_type(&self) -> Option<&StatefulProtocolType> {
72        self.inner.as_ref().map(|inner| inner.protocol_type())
73    }
74
75    pub fn protocol_type_owned(self) -> Option<StatefulProtocolType> {
76        self.inner.map(|inner| inner.protocol_type_owned())
77    }
78
79    // Get the background color that fills in when resizing.
80    pub fn background_color(&self) -> Option<Rgba<u8>> {
81        self.inner
82            .as_ref()
83            .and_then(|inner| inner.background_color())
84    }
85
86    /// This function should be used when an image should be updated but the updated image is not yet available
87    pub fn empty_protocol(&mut self) {
88        self.inner = None;
89        self.increment_id();
90    }
91
92    pub fn update_resized_protocol(&mut self, completed: ResizeResponse) -> bool {
93        let equal = self.id == completed.id;
94        if equal {
95            self.inner = Some(completed.protocol)
96        }
97        equal
98    }
99
100    pub fn size_for(&self, resize: Resize, size: Size) -> Option<Size> {
101        self.inner
102            .as_ref()
103            .map(|protocol| protocol.size_for(resize, size))
104    }
105
106    fn increment_id(&mut self) {
107        self.id = self.id.wrapping_add(1);
108    }
109}
110
111impl ResizeEncodeRender for ThreadProtocol {
112    fn needs_resize(&self, resize: &Resize, size: Size) -> Option<Size> {
113        self.inner
114            .as_ref()
115            .and_then(|protocol| protocol.needs_resize(resize, size))
116    }
117
118    /// Senda a `ResizeRequest` through the channel if there already isn't a pending `ResizeRequest`
119    fn resize_encode(&mut self, resize: &Resize, size: Size) {
120        let _ = self.inner.take().map(|protocol| {
121            self.increment_id();
122            _ = self.tx.send(ResizeRequest {
123                protocol,
124                resize: resize.clone(),
125                size,
126                id: self.id,
127            });
128        });
129    }
130
131    /// Render the currently resized and encoded data to the buffer, if there isn't a pending `ResizeRequest`
132    fn render(&mut self, area: Rect, buf: &mut Buffer) {
133        let _ = self
134            .inner
135            .as_mut()
136            .map(|protocol| protocol.render(area, buf));
137    }
138}