veloren_voxygen/ui/ice/widget/
compound_graphic.rs

1use super::image::Handle;
2use iced::{Element, Hasher, Layout, Length, Point, Rectangle, Widget, layout};
3use std::hash::Hash;
4use vek::{Aabr, Rgba, Vec2};
5
6// TODO: this widget combines multiple images in precise ways, they may or may
7// nor overlap and it would be helpful for optimising the renderer by telling it
8// if there is no overlap (i.e. draw calls can be reordered freely), we don't
9// need to do this yet since the renderer isn't that advanced
10
11// TODO: design trait to interface with background container
12#[derive(Copy, Clone)]
13pub enum GraphicKind {
14    Image(Handle, Rgba<u8>),
15    Color(Rgba<u8>),
16    /// Vertical gradient
17    Gradient(Rgba<u8>, Rgba<u8>),
18}
19
20// TODO: consider faculties for composing compound graphics (if a use case pops
21// up)
22pub struct Graphic {
23    aabr: Aabr<u16>,
24    kind: GraphicKind,
25}
26
27impl Graphic {
28    fn new(kind: GraphicKind, size: [u16; 2], offset: [u16; 2]) -> Self {
29        let size = Vec2::from(size);
30        let offset = Vec2::from(offset);
31        Self {
32            aabr: Aabr {
33                min: offset,
34                max: offset + size,
35            },
36            kind,
37        }
38    }
39
40    pub fn image(handle: Handle, size: [u16; 2], offset: [u16; 2]) -> Self {
41        Self::new(
42            GraphicKind::Image(handle, Rgba::broadcast(255)),
43            size,
44            offset,
45        )
46    }
47
48    #[must_use]
49    pub fn color(mut self, color: Rgba<u8>) -> Self {
50        match &mut self.kind {
51            GraphicKind::Image(_, c) => *c = color,
52            GraphicKind::Color(c) => *c = color,
53            // Not relevant here
54            GraphicKind::Gradient(_, _) => (),
55        }
56        self
57    }
58
59    pub fn gradient(
60        top_color: Rgba<u8>,
61        bottom_color: Rgba<u8>,
62        size: [u16; 2],
63        offset: [u16; 2],
64    ) -> Self {
65        Self::new(GraphicKind::Gradient(top_color, bottom_color), size, offset)
66    }
67
68    pub fn rect(color: Rgba<u8>, size: [u16; 2], offset: [u16; 2]) -> Self {
69        Self::new(GraphicKind::Color(color), size, offset)
70    }
71}
72
73pub struct CompoundGraphic {
74    graphics: Vec<Graphic>,
75    // move into option inside fix_aspect_ratio?
76    graphics_size: [u16; 2],
77    width: Length,
78    height: Length,
79    fix_aspect_ratio: bool,
80    /* TODO: allow coloring the widget as a whole (if there is a use case)
81     *color: Rgba<u8>, */
82}
83
84impl CompoundGraphic {
85    pub fn from_graphics(graphics: Vec<Graphic>) -> Self {
86        let width = Length::Fill;
87        let height = Length::Fill;
88        let graphics_size = graphics
89            .iter()
90            .fold(Vec2::zero(), |size, graphic| {
91                Vec2::max(size, graphic.aabr.max)
92            })
93            .into_array();
94        Self {
95            graphics,
96            graphics_size,
97            width,
98            height,
99            fix_aspect_ratio: false,
100            //color: Rgba::broadcast(255),
101        }
102    }
103
104    pub fn padded_image(image: Handle, size: [u16; 2], pad: [u16; 4]) -> Self {
105        let image = Graphic::image(image, size, [pad[0], pad[1]]);
106        let mut this = Self::from_graphics(vec![image]);
107        this.graphics_size[0] += pad[2];
108        this.graphics_size[1] += pad[3];
109        this
110    }
111
112    #[must_use]
113    pub fn width(mut self, width: Length) -> Self {
114        self.width = width;
115        self
116    }
117
118    #[must_use]
119    pub fn height(mut self, height: Length) -> Self {
120        self.height = height;
121        self
122    }
123
124    #[must_use]
125    pub fn fix_aspect_ratio(mut self) -> Self {
126        self.fix_aspect_ratio = true;
127        self
128    }
129
130    //pub fn color(mut self, color: Rgba<u8>) -> Self {
131    //    self.color = color;
132    //    self
133    //}
134
135    fn draw<R: Renderer>(&self, renderer: &mut R, layout: Layout<'_>) -> R::Output {
136        let [pixel_w, pixel_h] = self.graphics_size;
137        let bounds = layout.bounds();
138        let scale = Vec2::new(
139            bounds.width / pixel_w as f32,
140            bounds.height / pixel_h as f32,
141        );
142        let graphics = self.graphics.iter().map(|graphic| {
143            let bounds = {
144                let Aabr { min, max } = graphic.aabr.map(|e| e as f32);
145                let min = min * scale;
146                let size = max * scale - min;
147                Rectangle {
148                    x: min.x + bounds.x,
149                    y: min.y + bounds.y,
150                    width: size.x,
151                    height: size.y,
152                }
153            };
154            (bounds, graphic.kind)
155        });
156
157        renderer.draw(graphics)
158    }
159}
160
161impl<M, R> Widget<M, R> for CompoundGraphic
162where
163    R: Renderer,
164{
165    fn width(&self) -> Length { self.width }
166
167    fn height(&self) -> Length { self.height }
168
169    fn layout(&self, _renderer: &R, limits: &layout::Limits) -> layout::Node {
170        let mut size = limits.width(self.width).height(self.height).max();
171
172        if self.fix_aspect_ratio {
173            let aspect_ratio = {
174                let [w, h] = self.graphics_size;
175                w as f32 / h as f32
176            };
177
178            let max_aspect_ratio = size.width / size.height;
179
180            if max_aspect_ratio > aspect_ratio {
181                size.width = size.height * aspect_ratio;
182            } else {
183                size.height = size.width / aspect_ratio;
184            }
185        }
186
187        layout::Node::new(size)
188    }
189
190    fn draw(
191        &self,
192        renderer: &mut R,
193        _defaults: &R::Defaults,
194        layout: Layout<'_>,
195        _cursor_position: Point,
196        // Note: could use to skip elements outside the viewport
197        _viewport: &Rectangle,
198    ) -> R::Output {
199        Self::draw(self, renderer, layout)
200    }
201
202    fn hash_layout(&self, state: &mut Hasher) {
203        struct Marker;
204        std::any::TypeId::of::<Marker>().hash(state);
205
206        self.width.hash(state);
207        self.height.hash(state);
208        if self.fix_aspect_ratio {
209            self.graphics_size.hash(state);
210        }
211    }
212}
213
214pub trait Renderer: iced::Renderer {
215    fn draw<I>(&mut self, graphics: I) -> Self::Output
216    where
217        I: Iterator<Item = (Rectangle, GraphicKind)>;
218}
219
220impl<'a, M, R> From<CompoundGraphic> for Element<'a, M, R>
221where
222    R: Renderer,
223{
224    fn from(compound_graphic: CompoundGraphic) -> Element<'a, M, R> {
225        Element::new(compound_graphic)
226    }
227}
228
229impl<R> super::background_container::Background<R> for CompoundGraphic
230where
231    R: Renderer,
232{
233    fn width(&self) -> Length { self.width }
234
235    fn height(&self) -> Length { self.height }
236
237    fn aspect_ratio_fixed(&self) -> bool { self.fix_aspect_ratio }
238
239    fn pixel_dims(&self, _renderer: &R) -> (u16, u16) {
240        (self.graphics_size[0], self.graphics_size[1])
241    }
242
243    fn draw(
244        &self,
245        renderer: &mut R,
246        _defaults: &R::Defaults,
247        layout: Layout<'_>,
248        _cursor_position: Point,
249        _viewport: &Rectangle,
250    ) -> R::Output {
251        Self::draw(self, renderer, layout)
252    }
253}