kas_widgets/
image.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! 2D pixmap widget
7
8use kas::draw::{DrawShared, ImageHandle};
9use kas::layout::PixmapScaling;
10use kas::prelude::*;
11
12/// Image loading errors
13#[cfg(feature = "image")]
14#[derive(thiserror::Error, Debug)]
15pub enum ImageError {
16    #[error("IO error")]
17    IOError(#[from] std::io::Error),
18    #[error(transparent)]
19    Image(#[from] image::ImageError),
20    #[error("failed to allocate texture space for image")]
21    Allocation,
22}
23
24#[cfg(feature = "image")]
25impl From<kas::draw::AllocError> for ImageError {
26    fn from(_: kas::draw::AllocError) -> ImageError {
27        ImageError::Allocation
28    }
29}
30
31/// Image `Result` type
32#[cfg(feature = "image")]
33pub type Result<T> = std::result::Result<T, ImageError>;
34
35#[impl_self]
36mod Image {
37    /// An image with margins
38    ///
39    /// May be default constructed (result is empty).
40    #[derive(Clone, Debug, Default)]
41    #[widget]
42    pub struct Image {
43        core: widget_core!(),
44        scaling: PixmapScaling,
45        handle: Option<ImageHandle>,
46    }
47
48    impl Self {
49        /// Construct from a pre-allocated image
50        ///
51        /// The image may be allocated through the [`DrawShared`] interface.
52        #[inline]
53        pub fn new(handle: ImageHandle, draw: &mut dyn DrawShared) -> Option<Self> {
54            draw.image_size(&handle).map(|size| {
55                let mut sprite = Self::default();
56                sprite.scaling.size = size.cast();
57                sprite.handle = Some(handle);
58                sprite
59            })
60        }
61
62        /// Construct from a path
63        #[cfg(feature = "image")]
64        #[inline]
65        pub fn new_path<P: AsRef<std::path::Path>>(
66            path: P,
67            draw: &mut dyn DrawShared,
68        ) -> Result<Self> {
69            let mut sprite = Self::default();
70            sprite._load_path(path, draw)?;
71            Ok(sprite)
72        }
73
74        /// Assign a pre-allocated image
75        ///
76        /// Returns `true` on success. On error, `self` is unchanged.
77        pub fn set(
78            &mut self,
79            cx: &mut EventState,
80            handle: ImageHandle,
81            draw: &mut dyn DrawShared,
82        ) -> bool {
83            if let Some(size) = draw.image_size(&handle) {
84                self.scaling.size = size.cast();
85                self.handle = Some(handle);
86                cx.resize(self);
87                true
88            } else {
89                false
90            }
91        }
92
93        /// Load from a path
94        ///
95        /// On error, `self` is unchanged.
96        #[cfg(feature = "image")]
97        pub fn load_path<P: AsRef<std::path::Path>>(
98            &mut self,
99            cx: &mut EventState,
100            path: P,
101            draw: &mut dyn DrawShared,
102        ) -> Result<()> {
103            self._load_path(path, draw).map(|_| {
104                cx.resize(self);
105            })
106        }
107
108        #[cfg(feature = "image")]
109        fn _load_path<P: AsRef<std::path::Path>>(
110            &mut self,
111            path: P,
112            draw: &mut dyn DrawShared,
113        ) -> Result<()> {
114            let image = image::ImageReader::open(path)?
115                .with_guessed_format()?
116                .decode()?;
117
118            // TODO(opt): we convert to RGBA8 since this is the only format common
119            // to both the image and wgpu crates. It may not be optimal however.
120            // It also assumes that the image colour space is sRGB.
121            let image = image.into_rgba8();
122            let size = image.dimensions();
123
124            let handle = draw.image_alloc(size)?;
125            draw.image_upload(&handle, &image, kas::draw::ImageFormat::Rgba8);
126
127            if let Some(old_handle) = self.handle.take() {
128                draw.image_free(old_handle);
129            }
130
131            self.scaling.size = size.cast();
132            self.handle = Some(handle);
133
134            Ok(())
135        }
136
137        /// Remove image (set empty)
138        pub fn clear(&mut self, cx: &mut EventState, draw: &mut dyn DrawShared) {
139            if let Some(handle) = self.handle.take() {
140                draw.image_free(handle);
141                cx.resize(self);
142            }
143        }
144
145        /// Adjust scaling
146        ///
147        /// By default, this is [`PixmapScaling::default`] except with
148        /// `fix_aspect: true`.
149        #[inline]
150        #[must_use]
151        pub fn with_scaling(mut self, f: impl FnOnce(&mut PixmapScaling)) -> Self {
152            f(&mut self.scaling);
153            self
154        }
155
156        /// Adjust scaling
157        ///
158        /// By default, this is [`PixmapScaling::default`] except with
159        /// `fix_aspect: true`.
160        #[inline]
161        pub fn set_scaling(&mut self, cx: &mut EventState, f: impl FnOnce(&mut PixmapScaling)) {
162            f(&mut self.scaling);
163            // NOTE: if only `aspect` is changed, REDRAW is enough
164            cx.resize(self);
165        }
166    }
167
168    impl Layout for Image {
169        fn rect(&self) -> Rect {
170            self.scaling.rect
171        }
172
173        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
174            self.scaling.size_rules(sizer, axis)
175        }
176
177        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
178            let align = hints.complete_default();
179            let scale_factor = cx.size_cx().scale_factor();
180            self.scaling.set_rect(rect, align, scale_factor);
181        }
182
183        fn draw(&self, mut draw: DrawCx) {
184            if let Some(id) = self.handle.as_ref().map(|h| h.id()) {
185                draw.image(self.rect(), id);
186            }
187        }
188    }
189
190    impl Tile for Self {
191        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
192            Role::Image
193        }
194    }
195}