veloren_voxygen/ui/ice/widget/
aspect_ratio_container.rs

1use iced::{
2    Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, layout,
3};
4use std::hash::Hash;
5
6// Note: it might be more efficient to make this generic over the content type?
7
8enum AspectRatio<I> {
9    /// Image Id
10    Image(I),
11    /// width / height
12    Ratio(f32),
13}
14
15impl<I: Hash> Hash for AspectRatio<I> {
16    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
17        match self {
18            Self::Image(i) => i.hash(state),
19            Self::Ratio(r) => r.to_bits().hash(state),
20        }
21    }
22}
23
24/// Provides a container that takes on a fixed aspect ratio
25/// Thus, can be used to fix the aspect ratio of content if it is set to
26/// Length::Fill The aspect ratio may be based on that of an image, in which
27/// case the ratio is obtained from the renderer
28pub struct AspectRatioContainer<'a, M, R: Renderer> {
29    max_width: u32,
30    max_height: u32,
31    aspect_ratio: AspectRatio<R::ImageHandle>,
32    content: Element<'a, M, R>,
33}
34
35impl<'a, M, R> AspectRatioContainer<'a, M, R>
36where
37    R: Renderer,
38{
39    pub fn new(content: impl Into<Element<'a, M, R>>) -> Self {
40        Self {
41            max_width: u32::MAX,
42            max_height: u32::MAX,
43            aspect_ratio: AspectRatio::Ratio(1.0),
44            content: content.into(),
45        }
46    }
47
48    /// Set the ratio (width/height)
49    #[must_use]
50    pub fn ratio(mut self, ratio: f32) -> Self {
51        self.aspect_ratio = AspectRatio::Ratio(ratio);
52        self
53    }
54
55    /// Use the ratio of the provided image
56    #[must_use]
57    pub fn ratio_of_image(mut self, handle: R::ImageHandle) -> Self {
58        self.aspect_ratio = AspectRatio::Image(handle);
59        self
60    }
61
62    #[must_use]
63    pub fn max_width(mut self, max_width: u32) -> Self {
64        self.max_width = max_width;
65        self
66    }
67
68    #[must_use]
69    pub fn max_height(mut self, max_height: u32) -> Self {
70        self.max_height = max_height;
71        self
72    }
73}
74
75impl<M, R> Widget<M, R> for AspectRatioContainer<'_, M, R>
76where
77    R: Renderer,
78{
79    fn width(&self) -> Length { Length::Shrink }
80
81    fn height(&self) -> Length { Length::Shrink }
82
83    fn layout(&self, renderer: &R, limits: &layout::Limits) -> layout::Node {
84        let limits = limits
85            .loose()
86            .max_width(self.max_width)
87            .max_height(self.max_height);
88
89        let aspect_ratio = match &self.aspect_ratio {
90            AspectRatio::Image(handle) => {
91                let (pixel_w, pixel_h) = renderer.dimensions(handle);
92
93                // Just in case
94                // could convert to gracefully handling
95                debug_assert!(pixel_w != 0);
96                debug_assert!(pixel_h != 0);
97
98                pixel_w as f32 / pixel_h as f32
99            },
100            AspectRatio::Ratio(ratio) => *ratio,
101        };
102
103        // We need to figure out the max width/height of the limits
104        // and then adjust one down to meet the aspect ratio
105        let max_size = limits.max();
106        let (max_width, max_height) = (max_size.width, max_size.height);
107        let max_aspect_ratio = max_width / max_height;
108        let limits = if max_aspect_ratio > aspect_ratio {
109            limits.max_width((max_height * aspect_ratio) as u32)
110        } else {
111            limits.max_height((max_width / aspect_ratio) as u32)
112        };
113
114        // Remove fill limits in case one of the parents was Shrink
115        let limits = layout::Limits::new(Size::ZERO, limits.max());
116        let content = self.content.layout(renderer, &limits);
117
118        layout::Node::with_children(limits.max(), vec![content])
119    }
120
121    fn draw(
122        &self,
123        renderer: &mut R,
124        defaults: &R::Defaults,
125        layout: Layout<'_>,
126        cursor_position: Point,
127        viewport: &Rectangle,
128    ) -> R::Output {
129        renderer.draw(
130            defaults,
131            layout.bounds(),
132            cursor_position,
133            viewport,
134            &self.content,
135            layout.children().next().unwrap(),
136        )
137    }
138
139    fn hash_layout(&self, state: &mut Hasher) {
140        struct Marker;
141        std::any::TypeId::of::<Marker>().hash(state);
142
143        self.max_width.hash(state);
144        self.max_height.hash(state);
145        self.aspect_ratio.hash(state);
146        // TODO: add pixel dims (need renderer)
147
148        self.content.hash_layout(state);
149    }
150
151    fn on_event(
152        &mut self,
153        event: Event,
154        layout: Layout<'_>,
155        cursor_position: Point,
156        renderer: &R,
157        clipboard: &mut dyn Clipboard,
158        messages: &mut Vec<M>,
159    ) -> iced::event::Status {
160        self.content.on_event(
161            event,
162            layout.children().next().unwrap(),
163            cursor_position,
164            renderer,
165            clipboard,
166            messages,
167        )
168    }
169
170    fn overlay(&mut self, layout: Layout<'_>) -> Option<iced::overlay::Element<'_, M, R>> {
171        self.content.overlay(layout.children().next().unwrap())
172    }
173}
174
175pub trait Renderer: iced::Renderer {
176    /// The handle used by this renderer for images.
177    type ImageHandle: Hash;
178
179    fn dimensions(&self, handle: &Self::ImageHandle) -> (u32, u32);
180
181    fn draw<M>(
182        &mut self,
183        defaults: &Self::Defaults,
184        bounds: Rectangle,
185        cursor_position: Point,
186        viewport: &Rectangle,
187        content: &Element<'_, M, Self>,
188        content_layout: Layout<'_>,
189    ) -> Self::Output;
190}
191
192// They got to live ¯\_(ツ)_/¯
193impl<'a, M, R> From<AspectRatioContainer<'a, M, R>> for Element<'a, M, R>
194where
195    R: 'a + Renderer,
196    M: 'a,
197{
198    fn from(widget: AspectRatioContainer<'a, M, R>) -> Element<'a, M, R> { Element::new(widget) }
199}