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
use std::{
    fmt::Debug,
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
};

pub use ambient_cb as cb;
use ambient_core::{transform::*, window::window_ctl, window::WindowCtl};
use ambient_ecs::generated::messages;
pub use ambient_ecs::{EntityId, SystemGroup, World};
pub use ambient_editor_derive::ElementEditor;
pub use ambient_element as element;
use ambient_element::{element_component, Element, ElementComponentExt, Hooks};
use ambient_shared_types::ModifiersState;
pub use ambient_std::{cb, Cb};
pub use ambient_ui as ui;
use glam::*;
use parking_lot::Mutex;
use winit::window::CursorGrabMode;

// mod asset_url;

mod component_editor;
pub mod graph;
mod image;

pub use ambient_layout as layout;
pub use ambient_rect as rect;
pub use ambient_rect::{background_color, border_color, border_radius, border_thickness, Corners};
use ambient_text as text;
pub use ambient_text::*;
pub use ambient_ui::clickarea::*;
pub use ambient_ui::default_theme as style_constants;
pub use ambient_ui::*;
pub use ambient_ui::{button, dropdown, prompt, select, tabs, throbber};
pub use ambient_ui::{editor::*, layout::*, scroll_area::*, text::*};
// pub use asset_url::*;
pub use button::*;
pub use component_editor::*;
pub use dropdown::*;
pub use editor::*;
pub use layout::*;
pub use prompt::*;
pub use screens::*;
pub use select::*;
pub use style_constants::*;
pub use tabs::*;
pub use throbber::*;

pub use self::image::*;
use ambient_shared_types::MouseButton;

pub fn init_all_components() {
    layout::init_all_components();
    layout::init_gpu_components();
    text::init_components();
}

pub fn systems() -> SystemGroup {
    SystemGroup::new(
        "ui",
        vec![
            Box::new(rect::systems()),
            Box::new(text::systems(true)),
            Box::new(layout::layout_systems()),
        ],
    )
}

impl Default for HighjackMouse {
    fn default() -> Self {
        Self {
            on_mouse_move: cb(|_, _, _| {}),
            on_click: cb(|_| {}),
            hide_mouse: false,
        }
    }
}

#[element_component]
pub fn HighjackMouse(
    hooks: &mut Hooks,
    on_mouse_move: Cb<dyn Fn(&World, Vec2, Vec2) + Sync + Send>,
    on_click: Cb<dyn Fn(MouseButton) + Sync + Send>,
    hide_mouse: bool,
) -> Element {
    // let (window_focused, _) = hooks.use_state(Arc::new(AtomicBool::new(true)));
    // Assume window has focus
    let focused = Arc::new(AtomicBool::new(true));
    let position = hooks.use_ref_with(|_| Vec2::ZERO);
    hooks.use_spawn(move |world| {
        if hide_mouse {
            let ctl = world.resource(window_ctl());
            ctl.send(WindowCtl::GrabCursor(CursorGrabMode::Locked)).ok();
            ctl.send(WindowCtl::ShowCursor(false)).ok();
        }
        move |world| {
            if hide_mouse {
                let ctl = world.resource(window_ctl());
                ctl.send(WindowCtl::GrabCursor(CursorGrabMode::None)).ok();
                ctl.send(WindowCtl::ShowCursor(true)).ok();
            }
        }
    });

    hooks.use_runtime_message::<messages::WindowMouseMotion>({
        let focused = focused.clone();
        move |world, event| {
            let delta = event.delta;
            let pos = {
                let mut pos = position.lock();
                *pos += delta;
                *pos
            };

            if focused.load(Ordering::Relaxed) {
                on_mouse_move(world, pos, delta);
            }
        }
    });

    hooks.use_runtime_message::<messages::WindowFocusChange>(move |world, event| {
        let f = event.focused;
        let ctl = world.resource(window_ctl());
        ctl.send(WindowCtl::ShowCursor(!f)).ok();
        // window.set_cursor_visible(!f);
        // Fails on android/IOS
        ctl.send(WindowCtl::GrabCursor(if f {
            winit::window::CursorGrabMode::Locked
        } else {
            winit::window::CursorGrabMode::None
        }))
        .ok();

        focused.store(f, Ordering::Relaxed);
    });

    WindowSized(vec![])
        .el()
        .with_clickarea()
        .on_mouse_down(move |_, _, button| on_click(button))
        .el()
        .with(translation(), -Vec3::Z * 0.99)
}

/// Ctrl on windows, Command on osx
pub fn command_modifier() -> ModifiersState {
    #[cfg(target_os = "macos")]
    return ModifiersState::LOGO;
    #[cfg(not(target_os = "macos"))]
    return ModifiersState::CTRL;
}

#[derive(Clone)]
/// Helper for mutating UI state in multiple places.
pub struct WithChange<T: Clone>(Arc<Mutex<T>>, Cb<dyn Fn(T) + Sync + Send>);
impl<T: Clone> WithChange<T> {
    pub fn new(value: &T, changer: &Cb<dyn Fn(T) + Sync + Send>) -> Self {
        Self(Arc::new(Mutex::new(value.clone())), changer.clone())
    }

    pub fn change(&self, changer: impl FnOnce(&mut T)) {
        let mut lock = self.0.lock();
        changer(&mut lock);
        self.1(lock.clone());
    }

    pub fn query<R>(&self, extractor: impl FnOnce(&T) -> R) -> R {
        extractor(&self.0.lock())
    }
}

#[derive(Clone)]
/// Helper that takes an existing `WithChange<T>` and applies a projection from `T` to `U`.
///
/// That is, this allows you to mutate a field of the `T` in isolation.
pub struct WithChangePart<T: Clone, U: Clone, F: Fn(&mut T) -> &mut U>(WithChange<T>, F);
impl<T: Clone, U: Clone, F: Fn(&mut T) -> &mut U> WithChangePart<T, U, F> {
    pub fn new(with_change: WithChange<T>, projection: F) -> Self {
        Self(with_change, projection)
    }

    pub fn change(&self, changer: impl FnOnce(&mut U)) {
        self.0.change(|value| changer(self.1(value)));
    }

    pub fn query<R>(&self, extractor: impl FnOnce(&U) -> R) -> R {
        extractor(self.1(&mut self.0 .0.lock()))
    }

    pub fn get_cloned(&self) -> U {
        self.query(|value| value.clone())
    }

    pub fn set(&self, value: U) {
        self.change(|r| *r = value);
    }
}
impl<
        T: Clone + Send + Sync + 'static,
        U: Clone + Editor + Debug + Send + Sync + 'static,
        F: Fn(&mut T) -> &mut U + Clone + Send + Sync + 'static,
    > WithChangePart<T, U, F>
{
    /// Helper method that generates a callback that, when called, sets the current
    /// screen to an EditorPrompt that edits this value.
    pub fn to_editor_prompt_screen_callback(
        &self,
        title: impl Into<String>,
        set_screen: Cb<dyn Fn(Option<Element>) + Send + Sync>,
    ) -> impl Fn() + Sync + Send {
        let value = self.clone();
        let title = title.into();
        move || {
            set_screen(Some(
                EditorPrompt {
                    title: title.clone(),
                    value: value.get_cloned(),
                    set_screen: set_screen.clone(),
                    on_ok: cb({
                        let value = value.clone();
                        move |_, new_value| value.set(new_value)
                    }),
                    on_cancel: Some(cb(|_| {})),
                    validator: None,
                }
                .el(),
            ));
        }
    }
}