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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
use std::collections::{HashSet, VecDeque};
use std::hash::Hash;

use ggez::audio::{SoundSource, Source};
use ggez::winit::event::VirtualKeyCode;
use ggez::{
    glam::Vec2,
    graphics::{Canvas, Rect},
    Context, GameResult,
};

/// The main struct and traits [UiElement], [UiContent], [UiContainer].
mod ui_element;
pub use ui_element::UiContainer;
pub use ui_element::UiContent;

/// Contains basic UI contents such as text and images.
/// There is nothing actually here, because the basic elements Text, Image and Empty
/// are created by simply implementing UiContent on ggez's Text and Image as well as the basic ().
pub mod basic;
/// Contains UI contents that contain other UI elements, such as vertical boxes and stack boxes.
pub mod containers;

/// Structs and functions to manage how a UI element positions and sizes itself.
mod layout;
pub use layout::Alignment;
pub use layout::Layout;
pub use layout::Size;

/// The [Visuals] structs as well as associated functions that control how an element looks.
mod visuals;
use tinyvec::TinyVec;
pub use visuals::Visuals;

/// The [Transition] struct and associated functions to control an element dynamically changing layout, visuals, content, etc.
mod transition;
pub use transition::Transition;

/// The [DrawCache] struct to remember where an element was drawn in the last frame and (if possible) simply redraw it without recalculating its position.
mod draw_cache;
use draw_cache::DrawCache;

/// The [UiMessage] struct to facilitate communcation between elements and between elements an the game state.
mod message;
pub use message::UiMessage;

/// The [UiElementBuilder] struct for simple construction of UiElements using a basic builder pattern.
mod ui_element_builder;
pub use ui_element_builder::UiElementBuilder;

/// The [UiDrawParam] struct is an extension of the [ggez::graphics::DrawParam] struct and contains some additonal information specific to UiElements.
mod ui_draw_param;
pub use ui_draw_param::UiDrawParam;

/// A UI element. The entire UI tree of mooeye is built out of these elements.
/// This wrapper struct contains all information about look, layout, tooltip, message handling, etc. of the element, while also containing one [UiContent] field that contains the actual content.
pub struct UiElement<T: Copy + Eq + Hash> {
    /// The elements layout.
    layout: Layout,
    /// The elements visuals.
    visuals: Visuals,
    /// The alternative visuals of this element, displayed while the user hovers the mouse cursor above it.
    hover_visuals: Option<Visuals>,
    /// The sound that is played whenever the element is triggered via mouse or key press.
    trigger_sound: Option<Source>,

    /// The elements ID. Not neccessarily guaranteed to be unique.
    id: u32,

    /// This elements draw cache.
    draw_cache: DrawCache,

    /// The conent managed & displayed by this element
    pub content: Box<dyn UiContent<T>>,

    /// The tooltip managed by this element, if it has one.
    tooltip: Option<Box<UiElement<T>>>,

    /// The transition queue
    transitions: VecDeque<Transition<T>>,

    /// The keyboard key triggering events on this element.
    keys: TinyVec<[Option<VirtualKeyCode>; 2]>,

    /// The message handler. This function is called on every frame to handle received message.
    /// 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.
    message_handler: MessageHandler<T>,
}

/// The functional type of a UiElements MessageHandler.
type MessageHandler<T> = Box<dyn Fn(&HashSet<UiMessage<T>>, Layout, &mut VecDeque<Transition<T>>)>;

impl<T: Copy + Eq + Hash> std::fmt::Debug for UiElement<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UiElement")
            .field("layout", &self.layout)
            .field("visuals", &self.visuals)
            .field("hover_visuals", &self.hover_visuals)
            .field("trigger_sound", &self.trigger_sound)
            .field("id", &self.id)
            .field("draw_cache", &self.draw_cache)
            .field("tooltip", &self.tooltip)
            .field("keys", &self.keys)
            .finish()
    }
}

impl<T: Copy + Eq + Hash> UiElement<T> {
    /// Creates a new UiElement containig the specified content and the specified ID.
    /// The element will be treated as a leaf node, even if its implements [UiContainer].
    /// ID should be as unique as you require it.
    /// Layout and visuals will be set to default values, hover_visuals is initialized as None.
    pub fn new<E: UiContent<T> + 'static>(id: u32, content: E) -> Self {
        Self {
            layout: Layout::default(),
            visuals: Visuals::default(),
            hover_visuals: None,
            trigger_sound: None,
            id,
            draw_cache: DrawCache::default(),
            content: Box::new(content),
            tooltip: None,
            transitions: VecDeque::new(),
            keys: TinyVec::new(),
            message_handler: Box::new(|_messages, _layout, _transition_queue| {}),
        }
    }

    /// Adds an element to this element (or its children), recursively searching until an element with a fitting ID is found.
    /// The element is discarded there is no container child with fitting ID.
    pub fn add_element(&mut self, id: u32, element: UiElement<T>) -> Option<UiElement<T>> {
        match self.content.container_mut() {
            Some(cont) => {
                if self.id == id {
                    cont.add(element);
                    return None;
                };

                let mut element_option = Some(element);

                for child in cont.get_children_mut().iter_mut() {
                    // check if element is still there
                    if let Some(element) = element_option {
                        // yes: try children
                        element_option = child.add_element(id, element);
                    } else {
                        // no: an element with correct id has been found in this child, propagate none up the chain
                        break;
                    }
                }

                element_option
            }
            None => Some(element),
        }
    }

    /// Removes all elements with the given ID from this element and (recursively) all its children.
    pub fn remove_elements(&mut self, id: u32) {
        if let Some(cont) = self.content.container_mut() {
            cont.remove_id(id);
            for child in cont.get_children_mut() {
                child.remove_elements(id);
            }
        }
    }

    /// Returns this elements (not neccessarily unique) ID within this UI. This ID is used to indentify the source of intern messages.
    pub fn get_id(&self) -> u32 {
        self.id
    }

    /// Returns this elements (current) layout.
    pub fn get_layout(&self) -> Layout {
        self.layout
    }

    /// Receives a data structure containing all messages triggered by your game_state this frame (or None if there were no messages).
    /// It then collects all messages sent by this element and its children and redistributes all of those messages to this element and all children.
    /// Returns all internal messages to act on them.
    /// In addition, if this element has children, all children whose [UiContent::expired] function returns true are removed from the container.
    pub fn update(
        &mut self,
        ctx: &ggez::Context,
        extern_messages: impl Into<Option<HashSet<UiMessage<T>>>>,
    ) -> HashSet<UiMessage<T>> {
        // Message handling

        let intern_messages = self.collect_messages(ctx);

        let all_messages = match extern_messages.into() {
            None => intern_messages.clone(),
            Some(extern_messages) => intern_messages.union(&extern_messages).copied().collect(),
        };

        self.distribute_messages(&all_messages).expect("Something went wrong delivering or executing messages. Probably you wrote a bad handler function.");

        intern_messages
    }

    /// Returns wether this element should be removed by its parents.
    pub(crate) fn expired(&self) -> bool {
        self.content.expired()
    }

    /// Deprecated version of [UiElement::update].
    pub fn manage_messages(
        &mut self,
        ctx: &ggez::Context,
        extern_messages: impl Into<Option<HashSet<UiMessage<T>>>>,
    ) -> HashSet<UiMessage<T>> {
        self.update(ctx, extern_messages)
    }

    /// Iterates over this element and all successors and collects all internal messages (clicks) sent during the last frame.
    fn collect_messages(&self, ctx: &Context) -> HashSet<UiMessage<T>> {
        let mut res: HashSet<UiMessage<T>> = HashSet::new();

        if self.id != 0
            && match self.draw_cache {
                DrawCache::Invalid => false,
                DrawCache::Valid {
                    outer,
                    inner: _,
                    target: _,
                } => outer.contains(ctx.mouse.position()),
            }
        {
            if ctx
                .mouse
                .button_just_pressed(ggez::event::MouseButton::Left)
            {
                res.insert(UiMessage::Clicked(self.id));
                res.insert(UiMessage::Triggered(self.id));
                if let Some(sound) = &self.trigger_sound {
                    if sound.play_later().is_err() && cfg!(debug_assertions) {
                        println!("[ERROR] Failed to play sound.");
                    }
                }
            }

            if ctx
                .mouse
                .button_just_pressed(ggez::event::MouseButton::Right)
            {
                res.insert(UiMessage::ClickedRight(self.id));
            }
        }

        if self.id != 0
            && self.keys.iter().any(|key_opt| {
                if let Some(key) = key_opt {
                    ctx.keyboard.is_key_just_pressed(*key)
                } else {
                    false
                }
            })
        {
            res.insert(UiMessage::PressedKey(self.id));
            res.insert(UiMessage::Triggered(self.id));
        }

        if let Some(cont) = self.content.container() {
            for child in cont.get_children() {
                res.extend(child.collect_messages(ctx));
            }
        }

        res
    }

    /// Distributes the passed set of [UiMessage]s to this element and all its successors, letting their message handlers react to the messages.
    fn distribute_messages(&mut self, messages: &HashSet<UiMessage<T>>) -> GameResult {
        (self.message_handler)(messages, self.layout, &mut self.transitions);

        if let Some(cont) = self.content.container_mut() {
            // actual distribution
            for child in cont.get_children_mut() {
                child.distribute_messages(messages)?;
            }
            // remove expired children
            cont.remove_expired();
        }

        Ok(())
    }

    /// Adds a transition to the end of the transition queue. It will be executed as soon as all transitions added beforehand have run their course.
    pub fn add_transition(&mut self, transition: Transition<T>) {
        self.transitions.push_back(transition);
    }

    /// Progresses the currently active transition by the time of the last frame.
    /// If this ends the current transition, the values of this element are updated to the values given by the transition and it is removed from the queue.
    fn progress_transitions(&mut self, ctx: &Context) {
        if !self.transitions.is_empty() && self.transitions[0].progress(ctx.time.delta()) {
            let trans = self.transitions.pop_front().expect(
                "Transitions did not contain a first element despite being not empty 2 lines ago.",
            );

            if let Some(layout) = trans.new_layout {
                self.layout = layout;
                self.draw_cache = DrawCache::Invalid;
            }
            if let Some(visuals) = trans.new_visuals {
                self.visuals = visuals;
            }
            if let Some(hover_visuals) = trans.new_hover_visuals {
                self.hover_visuals = hover_visuals;
            }
            if let Some(content) = trans.new_content {
                self.content = content;
            }
            if let Some(tooltip) = trans.new_tooltip {
                self.tooltip = tooltip;
            }
        }
    }

    /// First checks wether the user is currently hovering this element or not and chooses to return visuals or hover visuals accordingly.
    /// Then checks if the transition queue contains a (hover-)visual-changing element and returns an average visuals if needed.
    fn get_current_visual(&self, ctx: &Context, param: UiDrawParam) -> Visuals {
        // check if this element is being hovered

        if param.mouse_listen
            && match self.draw_cache {
                DrawCache::Invalid => false,
                DrawCache::Valid {
                    outer,
                    inner: _,
                    target: _,
                } => outer.contains(ctx.mouse.position()),
            }
        {
            // yes: get what this element, diregarding transitions, would display on hover
            let own_vis = if let Some(hover_visuals) = self.hover_visuals {
                hover_visuals
            } else {
                self.visuals
            };

            // check wether there are transitions in the queue
            if self.transitions.is_empty() {
                //no: just return own visuals
                own_vis
            } else {
                // yes: check wether the top transition wants to change hover_visuals
                let trans = &self.transitions[0];
                match trans.new_hover_visuals {
                    // yes: find out what it wants to display on hover and take the average
                    Some(vis) => {
                        let trans_vis = if let Some(hover_visuals) = vis {
                            hover_visuals
                        } else {
                            self.visuals
                        };
                        own_vis.average(trans_vis, trans.get_progress_ratio())
                    }
                    // no: just return own visuals
                    None => own_vis,
                }
            }
        } else {
            // not hovered: check wether there are transitons in the queue
            if self.transitions.is_empty() {
                // no transitions: just return own visuals
                self.visuals
            } else {
                // transitions: check wether the top transition wants to change visuals
                let trans = &self.transitions[0];
                match trans.new_visuals {
                    // yes: find average between the two visuals
                    Some(vis) => self.visuals.average(vis, trans.get_progress_ratio()),
                    // no: just return own visuals
                    None => self.visuals,
                }
            }
        }
    }

    /// Updates this element's draw cache by checking for validity.
    /// If the draw cache is still valid (see [UiElement::cache_valid]), nothing happens.
    /// Otherwise, the function uses ```content_min```, the ```layout``` and the currently active ```Transition``` to generate a valid draw cache
    /// If no valid draw chache can be generated, the draw_cache wil be reset to default value.
    /// The function will only change ```draw_cache::valid``` to ```true``` if the generated rectangles fit within the target ```rect```.
    fn update_draw_cache(&mut self, _ctx: &Context, target: Rect) {
        // check wether draw cache needs to be updated at all (or a transition is going on)
        if !self.cache_valid(target) {
            // first calculate the target of this element if it were on its own
            let (own_outer, own_inner) = self
                .layout
                .get_outer_inner_bounds_in_target(&target, self.content_min());
            // check if there is a transition going on
            let (outer, inner) = if !self.transitions.is_empty() {
                // the transitions are not empty: check if the top transitions wants to change the layout
                if let Some(new_layout) = self.transitions[0].new_layout {
                    let (trans_outer, trans_inner) =
                        new_layout.get_outer_inner_bounds_in_target(&target, self.content_min());
                    (
                        transition::average_rect(
                            &own_outer,
                            &trans_outer,
                            self.transitions[0].get_progress_ratio(),
                        ),
                        transition::average_rect(
                            &own_inner,
                            &trans_inner,
                            self.transitions[0].get_progress_ratio(),
                        ),
                    )
                } else {
                    (own_outer, own_inner)
                }
            } else {
                // draw cache was invalidated by some other means (e.g. by sub element having a transition, the element not being initalized, etc.) -> calculate target
                (own_outer, own_inner)
            };

            // checking bounds, adding 0.01 to deal with problems stemming from imprecise multiplication
            if outer.w > target.w + 0.01
                || outer.h > target.h + 0.01
                || outer.x < 0.
                || outer.y < 0.
            //|| outer.x + outer.w > ctx.gfx.window().inner_size().width as f32 + 0.01
            //|| outer.y + outer.h > ctx.gfx.window().inner_size().height as f32 + 0.01
            {
                if cfg!(test) {
                    println!(
                        "Skipped Element due to bounds violation. Outer: {:?}, Target: {:?}",
                        outer, target
                    );
                }
                self.draw_cache = DrawCache::Invalid;
            } else {
                self.draw_cache = DrawCache::Valid {
                    outer,
                    inner,
                    target,
                };
            }
        }
    }

    /// Returns wether this elements cache is still valid. The cache may be invalidated manually or because the target_rect has changed.
    /// Any chache is considered invalid if there is currently an active transition that is actively changing the layout
    /// In the case of containers, the cache may also be invalidated because the cache of a child element has turned invalid. The default implementation for this case can e.g. be found in the code for [VerticalBox].
    fn cache_valid(&self, target: Rect) -> bool {
        let init = match self.draw_cache {
            DrawCache::Invalid => false,
            DrawCache::Valid {
                outer: _,
                inner: _,
                target: cache_target,
            } => cache_target == target,
        } && (self.transitions.is_empty()
            || matches!(self.transitions[0].new_layout, None));
        match self.content.container() {
            Some(cont) => cont
                .get_children()
                .iter()
                .fold(init, |valid, child| valid && child.cache_valid(target)),
            None => init,
        }
    }

    /// Returns the minimum and maximum width this element this element can have. Calculated from adding left and right padding to the size-data.
    pub fn width_range(&self) -> (f32, f32) {
        let layout = self.layout;
        (
            // get min width by taking minimum of inner min width, clamping it within the bounds given by the layout and adding padding
            self.content
                .container()
                .map(|cont| cont.content_width_range())
                .unwrap_or((0., f32::INFINITY))
                .0
                .clamp(layout.x_size.min(), layout.x_size.max())
                + layout.padding.1
                + layout.padding.3,
            // get max width by adding padding, overruling inner max width
            layout.x_size.max() + layout.padding.1 + layout.padding.3,
        )
    }

    /// Returns the minimum and maximum height this element this element can have. Calculated from adding top and bottom padding to the size-data.
    pub fn height_range(&self) -> (f32, f32) {
        let layout = self.layout;
        (
            // get min width by taking minimum of inner min width, clamping it within the bounds given by the layout and adding padding
            self.content
                .container()
                .map(|cont| cont.content_height_range())
                .unwrap_or((0., f32::INFINITY))
                .0
                .clamp(layout.y_size.min(), layout.y_size.max())
                + layout.padding.0
                + layout.padding.2,
            // get max width by adding padding, overruling inner max width
            layout.y_size.max() + layout.padding.0 + layout.padding.2,
        )
    }

    /// Returns the minimum size required by the content of this element.
    fn content_min(&self) -> Vec2 {
        Vec2 {
            x: self
                .content
                .container()
                .map(|cont| cont.content_width_range().0)
                .unwrap_or_default(),
            y: self
                .content
                .container()
                .map(|cont| cont.content_height_range().0)
                .unwrap_or_default(),
        }
    }

    /// Takes in a rectangle target, a canvas, a context and draws the UiElement to that rectangle within that canvas using that context.
    /// The element will either completely fit within the rectangle (including its padding) or not be drawn at all.
    /// The element will align and offset itself within the rectangle.
    pub(crate) fn draw_to_rectangle(
        &mut self,
        ctx: &mut Context,
        canvas: &mut Canvas,
        param: UiDrawParam,
    ) {
        self.progress_transitions(ctx);

        // update draw_cache
        self.update_draw_cache(ctx, param.target);

        // if draw chache is still invalid, early return and try again next frame

        let (outer, inner) = match self.draw_cache {
            DrawCache::Invalid => return,
            DrawCache::Valid {
                outer,
                inner,
                target: _,
            } => (outer, inner),
        };

        // draw visuals
        self.get_current_visual(ctx, param)
            .draw(ctx, canvas, param.target(outer));

        // draw content

        self.content.draw_content(ctx, canvas, param.target(inner));

        // draw tooltip
        if param.mouse_listen && outer.contains(ctx.mouse.position()) {
            if let Some(tt) = &mut self.tooltip {
                // get relevant positions
                let mouse_pos = ctx.mouse.position();
                let screen_size = ctx.gfx.window().inner_size();
                let tt_size = (tt.width_range().0, tt.height_range().0);

                // check if element center is left or right on the screen
                let x = if 2. * inner.x + inner.w > screen_size.width as f32 {
                    mouse_pos.x - tt_size.0 - 10.
                } else {
                    mouse_pos.x + 10.
                }
                .clamp(0., screen_size.width as f32 - tt_size.0);

                // check if element is on the top or bottom of the screen
                let y = (if 2. * inner.y + inner.h > screen_size.height as f32 {
                    mouse_pos.y - tt_size.1
                } else {
                    mouse_pos.y
                } - 10.)
                    .max(0.);

                // draw the tooltip
                tt.draw_to_rectangle(
                    ctx,
                    canvas,
                    param
                        .target(Rect::new(x, y, tt_size.0, tt_size.1))
                        .z_level(param.param.z + 1),
                );
            }
        }
    }

    /// Draws this UiElement to the current screen. Call this on your root element every frame.
    pub fn draw_to_screen(&mut self, ctx: &mut Context, canvas: &mut Canvas, mouse_listen: bool) {
        self.draw_to_rectangle(
            ctx,
            canvas,
            UiDrawParam::default()
                .target(Rect::new(
                    0.,
                    0.,
                    ctx.gfx.window().inner_size().width as f32,
                    ctx.gfx.window().inner_size().height as f32,
                ))
                .mouse_listen(mouse_listen),
        );
    }
}