Skip to main content

kas_image/
sprite.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 super::Scaling;
9use kas::draw::ImageHandle;
10use kas::layout::LogicalSize;
11use kas::prelude::*;
12use kas::theme::MarginStyle;
13
14#[impl_self]
15mod Sprite {
16    /// A raster image widget, loaded from a handle
17    ///
18    /// Size is inferred from the loaded image. By default, scaling is limited
19    /// to integer multiples of the source image size.
20    ///
21    /// May be default constructed (result is empty).
22    #[derive(Debug, Default)]
23    #[widget]
24    pub struct Sprite {
25        core: widget_core!(),
26        scaling: Scaling,
27        image_size: Size,
28        handle: Option<ImageHandle>,
29    }
30
31    impl Self {
32        /// Construct an empty (unallocated) image
33        #[inline]
34        pub fn new() -> Self {
35            Self::default()
36        }
37
38        /// Assign a pre-allocated image
39        ///
40        /// Returns `true` on success. On error, `self` is unchanged.
41        pub fn set(&mut self, cx: &mut EventCx, handle: ImageHandle) -> bool {
42            let draw = cx.draw_shared();
43            if let Some(old_handle) = self.handle.take() {
44                draw.image_free(old_handle);
45            }
46
47            if let Some(size) = draw.image_size(&handle) {
48                if self.scaling.size == LogicalSize::default() && self.image_size != size {
49                    cx.resize();
50                }
51                self.image_size = size;
52                self.handle = Some(handle);
53                true
54            } else {
55                self.image_size = Size::ZERO;
56                false
57            }
58        }
59
60        /// Access the current [`ImageHandle`], if any
61        ///
62        /// This handle may be used with [`DrawShared`](kas::draw::DrawShared)
63        /// methods.
64        #[inline]
65        pub fn handle(&self) -> Option<&ImageHandle> {
66            self.handle.as_ref()
67        }
68
69        /// Get the image buffer size
70        ///
71        /// This is the size of the image last assigned using [`Sprite::set`].
72        /// Initially it is [`Size::ZERO`].
73        #[inline]
74        pub fn image_size(&self) -> Size {
75            self.image_size
76        }
77
78        /// Remove image (set empty)
79        pub fn clear(&mut self, cx: &mut EventCx) {
80            if let Some(handle) = self.handle.take() {
81                cx.draw_shared().image_free(handle);
82                if self.scaling.size == LogicalSize::default() {
83                    cx.resize();
84                }
85            }
86        }
87
88        /// Set size in logical pixels
89        ///
90        /// This enables fractional scaling of the image with a fixed aspect ratio.
91        pub fn set_logical_size(&mut self, size: impl Into<LogicalSize>) {
92            self.scaling.size = size.into();
93        }
94
95        /// Set size in logical pixels (inline)
96        ///
97        /// This enables fractional scaling of the image with a fixed aspect ratio.
98        #[must_use]
99        pub fn with_logical_size(mut self, size: impl Into<LogicalSize>) -> Self {
100            self.scaling.size = size.into();
101            self
102        }
103
104        /// Set the margin style (inline)
105        ///
106        /// By default, this is [`MarginStyle::Large`].
107        #[must_use]
108        #[inline]
109        pub fn with_margin_style(mut self, style: MarginStyle) -> Self {
110            self.scaling.margins = style;
111            self
112        }
113
114        /// Control whether the aspect ratio is fixed (inline)
115        ///
116        /// This is only applicable when using fractional scaling (see
117        /// [`Self::set_logical_size`]) since integer scaling always uses a
118        /// fixed aspect ratio. By default this is enabled.
119        #[must_use]
120        #[inline]
121        pub fn with_fixed_aspect_ratio(mut self, fixed: bool) -> Self {
122            self.scaling.fix_aspect = fixed;
123            self
124        }
125
126        /// Set the stretch factor (inline)
127        ///
128        /// By default this is [`Stretch::None`]. Particular to this widget,
129        /// [`Stretch::None`] will avoid stretching of content, aligning instead.
130        #[must_use]
131        #[inline]
132        pub fn with_stretch(mut self, stretch: Stretch) -> Self {
133            self.scaling.stretch = stretch;
134            self
135        }
136    }
137
138    impl Layout for Self {
139        fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
140            if self.scaling.size == LogicalSize::default() {
141                let scale: i32 = (cx.scale_factor() * 0.9).cast_nearest();
142                debug_assert!(scale >= 1);
143                SizeRules::fixed(self.image_size.extract(axis) * scale)
144                    .with_margins(cx.margins(self.scaling.margins).extract(axis))
145            } else {
146                self.scaling.size_rules(cx, axis)
147            }
148        }
149
150        fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, hints: AlignHints) {
151            let align = hints.complete_default();
152            let rect = if self.scaling.size == LogicalSize::default() {
153                // Avoid divide-by-zero
154                let image_size = self.image_size.max(Size::splat(1));
155                let scale = (rect.size.0 / image_size.0)
156                    .min(rect.size.1 / image_size.1)
157                    .max(1);
158                let size = self.image_size * scale;
159                align.aligned_rect(size, rect)
160            } else {
161                let scale_factor = cx.scale_factor();
162                self.scaling.align(rect, align, scale_factor)
163            };
164            self.core.set_rect(rect);
165        }
166
167        fn draw(&self, mut draw: DrawCx) {
168            if let Some(id) = self.handle.as_ref().map(|h| h.id()) {
169                draw.image(self.rect(), id);
170            }
171        }
172    }
173
174    impl Tile for Self {
175        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
176            Role::Image
177        }
178    }
179}