1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
use ggez::{audio::Source, winit::event::VirtualKeyCode};

use crate::ui::{UiContent, UiElement};
use std::hash::Hash;

use super::Layout;

/// A builder struct for UiElements. Allows changing of all relevant fields of the built element, and contains shorthand function for changing the components of the elements layout.
/// Also contains shorthand functions for some very frequently used combination of layout settings.
#[derive(Debug)]
pub struct UiElementBuilder<T: Copy + Eq + Hash> {
    /// The element currenty being built.
    element: UiElement<T>,
}

impl<T: Copy + Eq + Hash> UiElementBuilder<T> {
    /// Creates a new builder building an element containing the specified content with the specified id.
    /// If built directly, all fields except content will be set to their default values or the values set in the to_element function of the passed con.
    pub fn new(id: u32, content: impl UiContent<T> + 'static) -> Self {
        Self {
            element: UiElement::new(id, content),
        }
    }

    /// If the elements content is a container, the passed element is added to it.
    /// Otherwise, the passed element is discarded.
    pub fn with_child(mut self, element: UiElement<T>) -> Self {
        if let Some(cont) = self.element.content.container_mut() {
            cont.add(element);
        }
        self
    }

    /// If the elements content is a container and the passed conditional evaluates to ```true```, the passed element is added to it.
    /// Otherwise, the passed element is discarded.
    pub fn with_conditional_child(mut self, condition: bool, element: UiElement<T>) -> Self {
        if condition {
            if let Some(cont) = self.element.content.container_mut() {
                cont.add(element);
            }
        }
        self
    }

    /// Sets the elements visuals.
    pub fn with_visuals(mut self, visuals: super::Visuals) -> Self {
        self.element.visuals = visuals;
        self
    }

    /// Sets the elements hover_visuals. Pass in None to delete any existing hover_visuals.
    pub fn with_hover_visuals(mut self, hover_visuals: impl Into<Option<super::Visuals>>) -> Self {
        self.element.hover_visuals = hover_visuals.into();
        self
    }

    /// Sets a sound to be played whenever this element is triggered via key press or mouse click.
    pub fn with_trigger_sound(mut self, trigger_sound: impl Into<Option<Source>>) -> Self {
        self.element.trigger_sound = trigger_sound.into();
        self
    }

    /// Sets the elements tooltip to the specified UiContent (or disables any tooltip by passing None).
    /// Tooltips are displayed when hovering over an element with the mouse cursor.
    /// Most specific alignment and sizes of tooltips will be ignored. Tooltips will always be shrink in both dimensions and align as close to the cursor as possible.
    pub fn with_tooltip(mut self, tooltip: impl Into<Option<UiElement<T>>>) -> Self {
        match tooltip.into() {
            None => self.element.tooltip = None,
            Some(mut tt) => {
                tt.layout.x_size = tt.layout.x_size.to_shrink();
                tt.layout.y_size = tt.layout.y_size.to_shrink();
                self.element.tooltip = Some(Box::new(tt))
            }
        }
        self
    }

    /// Sets the elements message handler.
    /// The message handler lambda receives each frame a hash set consisting of all internal and external messages received by this element.
    /// It also receives a function pointer. Calling this pointer with a transition pushes that transition to this elements transition queue.
    /// Lastly, it receives the current layout of the element. This allows any transitions to re-use that layout and only change the variables the transition wants to change.
    pub fn with_message_handler(
        mut self,
        handler: impl Fn(
                &std::collections::HashSet<crate::ui::UiMessage<T>>,
                Layout,
                &mut std::collections::VecDeque<super::Transition<T>>,
            ) + 'static,
    ) -> Self {
        self.element.message_handler = Box::new(handler);
        self
    }

    /// Sets the elements entire layout.
    pub fn with_layout(mut self, layout: super::Layout) -> Self {
        self.element.layout = layout;
        self
    }

    /// Sets only the offset values of the elements layout. Pass None in any argument to leave that offset as-is.
    pub fn with_offset(
        mut self,
        x_offset: impl Into<Option<f32>>,
        y_offset: impl Into<Option<f32>>,
    ) -> Self {
        match x_offset.into() {
            None => {}
            Some(offset) => self.element.layout.x_offset = offset,
        };

        match y_offset.into() {
            None => {}
            Some(offset) => self.element.layout.y_offset = offset,
        };

        self
    }

    /// Sets only the alingment of the elements layout. Pass None in any argument to leave that alignment as-is.
    pub fn with_alignment(
        mut self,
        x_alignment: impl Into<Option<super::Alignment>>,
        y_alignment: impl Into<Option<super::Alignment>>,
    ) -> Self {
        match x_alignment.into() {
            None => {}
            Some(alignment) => self.element.layout.x_alignment = alignment,
        };

        match y_alignment.into() {
            None => {}
            Some(alignment) => self.element.layout.y_alignment = alignment,
        };

        self
    }

    /// Attaches a key code to this element. Pressing this key will send the same trigger event as clicking the element.
    pub fn with_trigger_key(mut self, key: VirtualKeyCode) -> Self {
        self.element.keys.push(Some(key));
        self
    }

    /// Sets only the padding of the elements layout.
    pub fn with_padding(mut self, padding: (f32, f32, f32, f32)) -> Self {
        self.element.layout.padding = padding;
        self
    }

    /// Sets only the presever_ratio parameter of the elements layout.
    pub fn with_preserve_ratio(mut self, preserve_ratio: bool) -> Self {
        self.element.layout.preserve_ratio = preserve_ratio;
        self
    }

    /// Sets only the size of the elements layout. Pass None in any argument to leave that size as-is.
    pub fn with_size(
        mut self,
        x_size: impl Into<Option<super::Size>>,
        y_size: impl Into<Option<super::Size>>,
    ) -> Self {
        match x_size.into() {
            None => {}
            Some(size) => self.element.layout.x_size = size,
        };

        match y_size.into() {
            None => {}
            Some(size) => self.element.layout.y_size = size,
        };

        self
    }

    /// Changes both sizes of the element to SHRINK, taking any boundaries from previous size.
    pub fn as_shrink(mut self) -> Self {
        self.element.layout.x_size = self.element.layout.x_size.to_shrink();
        self.element.layout.y_size = self.element.layout.y_size.to_shrink();
        self
    }

    /// Changes both sizes of the element to FILL, taking any boundaries from previous size.
    pub fn as_fill(mut self) -> Self {
        self.element.layout.x_size = self.element.layout.x_size.to_fill();
        self.element.layout.y_size = self.element.layout.y_size.to_fill();
        self
    }

    /// Scales any boundaries of the sizes of this element by the respective factor. Pass in None or 1. to not scale any dimension.
    pub fn scaled(
        mut self,
        x_scale: impl Into<Option<f32>>,
        y_scale: impl Into<Option<f32>>,
    ) -> Self {
        match x_scale.into() {
            None => {}
            Some(scale) => self.element.layout.x_size = self.element.layout.x_size.scale(scale),
        };

        match y_scale.into() {
            None => {}
            Some(scale) => self.element.layout.y_size = self.element.layout.y_size.scale(scale),
        };

        self
    }

    /// Takes in a layout and sets the elements layout to be as you would want for a container wrapping the passed layout.
    /// Sets size to fill, taking boundaries from the passed layout + padding, and own padding to 0.
    pub fn with_wrapper_layout(self, wrapped_layout: Layout) -> Self {
        self.with_size(
            super::Size::Fill(
                wrapped_layout.x_size.min() + wrapped_layout.padding.1 + wrapped_layout.padding.3,
                f32::INFINITY,
            ),
            super::Size::Fill(
                wrapped_layout.y_size.min() + wrapped_layout.padding.0 + wrapped_layout.padding.2,
                f32::INFINITY,
            ),
        )
        .with_padding((0., 0., 0., 0.))
    }

    /// Returns the underlying built element.
    pub fn build(self) -> UiElement<T> {
        self.element
    }
}

impl<T: Copy + Eq + Hash> From<UiElement<T>> for UiElementBuilder<T> {
    fn from(value: UiElement<T>) -> Self {
        Self { element: value }
    }
}

impl<T: Copy + Eq + Hash> From<UiElementBuilder<T>> for UiElement<T> {
    fn from(value: UiElementBuilder<T>) -> Self {
        value.element
    }
}