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
//! Implements a window with a title bar and a child element. Can be moved around.

use ambient_cb::{cb, Cb};
use ambient_element::{
    element_component, use_runtime_message, use_state, Element, ElementComponentExt, Hooks,
};
use ambient_guest_bridge::core::{
    layout::{components::fit_horizontal, types::Fit},
    messages::{WindowMouseInput, WindowMouseMotion},
    text::{
        components::{font_size, font_style},
        types::FontStyle,
    },
    transform::components::translation,
    ui::components::focusable,
};
use ambient_shared_types::MouseButton;
use glam::{vec3, vec4, Vec2};

use crate::{
    button::{Button, ButtonStyle},
    clickarea::MouseInput,
    layout::{FlowColumn, FlowRow},
    text::Text,
    with_rect, UIExt,
};

/// A chance to style the window
#[derive(Debug, Clone)]
pub struct WindowStyle {
    /// Body style
    pub body: Cb<dyn Fn(Element) -> Element + Send + Sync>,
    /// Title bar style
    pub title_bar: Cb<dyn Fn(String, Option<Cb<dyn Fn() + Send + Sync>>) -> Element + Send + Sync>,
}
impl Default for WindowStyle {
    fn default() -> Self {
        Self {
            body: cb(|e| e),
            title_bar: cb(|title, close| {
                with_rect(FlowRow::el([
                    close
                        .map(|close| {
                            Button::new(" X ", move |_| close())
                                .style(ButtonStyle::Card)
                                .el()
                        })
                        .unwrap_or_default(),
                    Text::el(title)
                        .with_margin_even(4.0)
                        .with(font_style(), FontStyle::Bold)
                        .with(font_size(), 14.),
                ]))
                .with_background(vec4(0.0, 0.0, 0.0, 0.5))
                .with(fit_horizontal(), Fit::Parent)
            }),
        }
    }
}

#[element_component]
/// A window with a title bar and a child element. Can be moved around.
pub fn Window(
    hooks: &mut Hooks,
    /// The title of the window.
    title: String,
    /// A callback to be called when the window requests to be closed.
    /// If this is `None`, the window will not have a close button.
    /// This callback should update `visible` to `false`.
    close: Option<Cb<dyn Fn() + Send + Sync>>,
    /// Whether the window is visible.
    visible: bool,
    /// An optional chance to style the body of the window.
    style: Option<WindowStyle>,
    /// The child element.
    child: Element,
) -> Element {
    let (dragging, set_dragging) = use_state(hooks, false);
    let (position, set_position) = use_state(hooks, Vec2::ONE * 100.0);

    use_runtime_message::<WindowMouseInput>(hooks, {
        let set_dragging = set_dragging.clone();
        move |_world, event| {
            if event.button == u32::from(MouseButton::Left) && !event.pressed {
                set_dragging(false);
            }
        }
    });

    use_runtime_message::<WindowMouseMotion>(hooks, move |_world, event| {
        if dragging {
            set_position(position + event.delta);
        }
    });

    if !visible {
        return Element::new();
    }

    let style = style.unwrap_or_default();

    let title = (style.title_bar)(title, close)
        .with(focusable(), hooks.instance_id().to_string())
        .with_clickarea()
        .on_mouse_input(move |_world, _, input, button| {
            if button == MouseButton::Left {
                set_dragging(input == MouseInput::Pressed);
            }
        })
        .el();

    (style.body)(
        with_rect(FlowColumn::el([title, child])).with_background(vec4(0.0, 0.0, 0.0, 0.5)),
    )
    .with(translation(), vec3(position.x, position.y, -0.001))
}