Skip to main content

tui_lipan/widgets/image/
mod.rs

1//! Image widget.
2
3mod layout;
4mod node;
5mod reconcile;
6
7pub use layout::measure_image;
8pub use node::ImageNode;
9pub use reconcile::reconcile_image;
10
11use std::sync::Arc;
12
13use crate::core::element::{Element, ElementKind};
14use crate::style::{Length, Style};
15
16/// Source data for an [`Image`] widget.
17#[derive(Clone, Debug, PartialEq, Eq, Hash)]
18pub enum ImageSource {
19    /// File path to an image on disk.
20    Path(Arc<str>),
21    /// In-memory encoded image bytes (for example PNG/JPEG/WebP data).
22    Bytes(Arc<[u8]>),
23}
24
25/// Resize behavior when fitting an image into widget bounds.
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
27pub enum ImageFit {
28    /// Keep aspect ratio and fit inside available area.
29    #[default]
30    Contain,
31    /// Crop image to fill available area.
32    Crop,
33    /// Keep aspect ratio and scale both up and down to fit.
34    Scale,
35}
36
37/// Requested terminal image protocol.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
39pub enum ImageProtocol {
40    /// Auto-detect supported protocol, fallback to block rendering.
41    #[default]
42    Auto,
43    /// Force Kitty graphics protocol.
44    Kitty,
45    /// Force iTerm2 inline-image protocol.
46    Iterm2,
47    /// Force Sixel protocol.
48    Sixel,
49    /// Force unicode half-block rendering.
50    Halfblocks,
51}
52
53/// Playback state for animated images.
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
55pub enum ImagePlayback {
56    /// Frames advance according to animation timing.
57    #[default]
58    Playing,
59    /// Keep displaying the current frame.
60    Paused,
61}
62
63/// Loop mode for animated images.
64#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
65pub enum ImageRepeat {
66    /// Restart from the first frame after the last frame.
67    #[default]
68    Loop,
69    /// Stop on the last frame.
70    Once,
71}
72
73/// A terminal image widget with protocol-aware rendering.
74#[derive(Clone)]
75pub struct Image {
76    /// Source of image data.
77    pub source: ImageSource,
78    /// Base style used for textual fallback rendering.
79    pub style: Style,
80    /// Requested width.
81    /// Default: `Length::Auto`.
82    pub width: Length,
83    /// Requested height.
84    /// Default: `Length::Auto`.
85    pub height: Length,
86    /// Resize behavior.
87    pub fit: ImageFit,
88    /// Preferred image protocol.
89    pub protocol: ImageProtocol,
90    /// Optional fallback text shown on decode/render failure.
91    pub alt: Option<Arc<str>>,
92    /// Playback state for animated formats.
93    pub playback: ImagePlayback,
94    /// Loop behavior for animated formats.
95    pub repeat: ImageRepeat,
96    /// Playback speed in percent (100 = normal speed).
97    pub speed_percent: u16,
98}
99
100impl Image {
101    /// Create an image from file path.
102    pub fn new(src: impl Into<Arc<str>>) -> Self {
103        Self {
104            source: ImageSource::Path(src.into()),
105            style: Style::default(),
106            width: Length::Auto,
107            height: Length::Auto,
108            fit: ImageFit::default(),
109            protocol: ImageProtocol::default(),
110            alt: None,
111            playback: ImagePlayback::default(),
112            repeat: ImageRepeat::default(),
113            speed_percent: 100,
114        }
115    }
116
117    /// Create an image from encoded in-memory bytes.
118    pub fn from_bytes(bytes: impl Into<Arc<[u8]>>) -> Self {
119        Self {
120            source: ImageSource::Bytes(bytes.into()),
121            style: Style::default(),
122            width: Length::Auto,
123            height: Length::Auto,
124            fit: ImageFit::default(),
125            protocol: ImageProtocol::default(),
126            alt: None,
127            playback: ImagePlayback::default(),
128            repeat: ImageRepeat::default(),
129            speed_percent: 100,
130        }
131    }
132
133    /// Replace image source with file path.
134    pub fn src(mut self, src: impl Into<Arc<str>>) -> Self {
135        self.source = ImageSource::Path(src.into());
136        self
137    }
138
139    /// Replace image source with encoded bytes.
140    pub fn bytes(mut self, bytes: impl Into<Arc<[u8]>>) -> Self {
141        self.source = ImageSource::Bytes(bytes.into());
142        self
143    }
144
145    /// Set base style used by fallback rendering.
146    pub fn style(mut self, style: Style) -> Self {
147        self.style = style;
148        self
149    }
150
151    /// Set requested width.
152    pub fn width(mut self, width: Length) -> Self {
153        self.width = width;
154        self
155    }
156
157    /// Set requested height.
158    pub fn height(mut self, height: Length) -> Self {
159        self.height = height;
160        self
161    }
162
163    /// Set resize behavior.
164    pub fn fit(mut self, fit: ImageFit) -> Self {
165        self.fit = fit;
166        self
167    }
168
169    /// Set preferred image protocol.
170    pub fn protocol(mut self, protocol: ImageProtocol) -> Self {
171        self.protocol = protocol;
172        self
173    }
174
175    /// Set fallback text for decode/render failures.
176    pub fn alt(mut self, alt: impl Into<Arc<str>>) -> Self {
177        self.alt = Some(alt.into());
178        self
179    }
180
181    /// Set playback mode for animated formats.
182    pub fn playback(mut self, playback: ImagePlayback) -> Self {
183        self.playback = playback;
184        self
185    }
186
187    /// Convenience toggle for play/pause.
188    pub fn paused(mut self, paused: bool) -> Self {
189        self.playback = if paused {
190            ImagePlayback::Paused
191        } else {
192            ImagePlayback::Playing
193        };
194        self
195    }
196
197    /// Set loop mode for animated formats.
198    pub fn repeat(mut self, repeat: ImageRepeat) -> Self {
199        self.repeat = repeat;
200        self
201    }
202
203    /// Convenience toggle for loop mode.
204    pub fn looping(mut self, looping: bool) -> Self {
205        self.repeat = if looping {
206            ImageRepeat::Loop
207        } else {
208            ImageRepeat::Once
209        };
210        self
211    }
212
213    /// Set playback speed in percent (`100` = normal speed).
214    pub fn speed_percent(mut self, speed_percent: u16) -> Self {
215        self.speed_percent = speed_percent.max(1);
216        self
217    }
218}
219
220impl From<Image> for Element {
221    fn from(value: Image) -> Self {
222        Element::new(ElementKind::Image(value))
223    }
224}
225
226impl crate::layout::hash::LayoutHash for Image {
227    fn layout_hash(
228        &self,
229        hasher: &mut impl std::hash::Hasher,
230        _recurse: &dyn Fn(&crate::core::element::Element) -> Option<u64>,
231    ) -> Option<()> {
232        use std::hash::Hash;
233        self.width.hash(hasher);
234        self.height.hash(hasher);
235        self.fit.hash(hasher);
236        self.protocol.hash(hasher);
237        self.playback.hash(hasher);
238        self.repeat.hash(hasher);
239        self.speed_percent.hash(hasher);
240        self.source.hash(hasher);
241        self.alt.hash(hasher);
242        Some(())
243    }
244}