veloren_voxygen/ui/ice/widget/
aspect_ratio_container.rs1use iced::{
2 Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, layout,
3};
4use std::hash::Hash;
5
6enum AspectRatio<I> {
9 Image(I),
11 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
24pub 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 #[must_use]
50 pub fn ratio(mut self, ratio: f32) -> Self {
51 self.aspect_ratio = AspectRatio::Ratio(ratio);
52 self
53 }
54
55 #[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 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 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 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 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 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
192impl<'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}