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
//! System for handling input related events.
//!
//! ### Weights
//! A weight can be assigned to each hook via the respective builder. Weight defines the order
//! in which hooks of the same class are called. A higher weight will be called first. Hooks
//! that have a weight specified can also block the execution of hooks in the same class by
//! a `NoPass` varient of `InputHookCtrl`.
//!
//! ##### Press/Hold/Release Weight Class
//! These hook types all share the same weighing. An important note with this class is that
//! window hooks will get called before bin hooks. A press hook with a higher weight than
//! a release hook that returns a `NoPass` varient of `InputHookCtrl` will prevent the release
//! from being called if the either share a key. Likewise with a hold, a press of a higher
//! weight can prevent it getting it called, but unlike release it will not prevent it from
//! getting released.
//!
//! ##### Enter/Leave
//! Window and Bins are seperate in the class of weights. Only hooks targeted for bins can
//! prevent hooks towards bins. Likewise with windows. A hook can effect multiple bins
//! depending of if `require_on_top` has been set to `false`. In this case hooks on different
//! bins can block the execution of one another.
//!
//! ##### Character
//! Window and Bins are treated the same. They are called in order of their weight. Calling
//! a `NoPass` varient of `InputHookCtrl` prevents the execution of all lesser weighed hooks.
//!
//! ##### Focus/FocusLost
//! Similar to Enter/Leave, but a hook can not effect multiple bins.
//!
//! ##### Scroll
//! Similar to Enter/Leave, but windows and bins are in the same class of weights.
//!
//! ##### Cursor
//! Same behavior as Scroll.
//!
//! ##### Motion
//! Similar to Character, but there are no targets.

pub mod builder;
mod inner;
pub mod key;
mod proc;
pub mod state;

use std::sync::atomic::{self, AtomicU64};
use std::sync::{Arc, Weak};

use crossbeam::channel::{self, Sender};

use self::inner::LoopEvent;
pub use self::key::{Char, Key, MouseButton, Qwerty};
use self::state::HookState;
use crate::input::builder::InputHookBuilder;
use crate::interface::bin::{Bin, BinID};
use crate::interface::Interface;
use crate::interval::Interval;
use crate::window::{BasaltWindow, BstWindowID};

const NO_HOOK_WEIGHT: i16 = i16::min_value();
const BIN_FOCUS_KEY: Key = Key::Mouse(MouseButton::Left);

/// An ID of a `Input` hook.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InputHookID(u64);

/// The target of a hook.
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum InputHookTarget {
    None,
    Window(Arc<dyn BasaltWindow>),
    Bin(Arc<Bin>),
}

impl InputHookTarget {
    fn id(&self) -> InputHookTargetID {
        match self {
            Self::None => InputHookTargetID::None,
            Self::Window(win) => InputHookTargetID::Window(win.id()),
            Self::Bin(bin) => InputHookTargetID::Bin(bin.id()),
        }
    }

    fn weak(&self) -> InputHookTargetWeak {
        match self {
            Self::None => InputHookTargetWeak::None,
            Self::Window(win) => InputHookTargetWeak::Window(Arc::downgrade(win)),
            Self::Bin(bin) => InputHookTargetWeak::Bin(Arc::downgrade(bin)),
        }
    }

    /// Try to convert target into a `Bin`.
    pub fn into_bin(self) -> Option<Arc<Bin>> {
        match self {
            InputHookTarget::Bin(bin) => Some(bin),
            _ => None,
        }
    }

    /// Try to convert target into a `<Arc<dyn BasaltWindow>`.
    pub fn into_window(self) -> Option<Arc<dyn BasaltWindow>> {
        match self {
            InputHookTarget::Window(win) => Some(win),
            _ => None,
        }
    }
}

impl PartialEq for InputHookTarget {
    fn eq(&self, other: &Self) -> bool {
        match self {
            Self::None => matches!(other, Self::None),
            Self::Window(window) => {
                match other {
                    Self::Window(other_window) => window.id() == other_window.id(),
                    _ => false,
                }
            },
            Self::Bin(bin) => {
                match other {
                    Self::Bin(other_bin) => bin == other_bin,
                    _ => false,
                }
            },
        }
    }
}

impl Eq for InputHookTarget {}

/// Controls what happens after the hook method is called.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InputHookCtrl {
    /// Retain the hook and pass then events.
    #[default]
    Retain,
    /// Same as `Retain`, but will not pass event onto next hook.
    ///
    /// # Notes
    /// - If this hook doesn't have a weight this is the same as `Retain`.
    RetainNoPass,
    /// Remove the hook
    Remove,
    /// Remove the hook and pass the event onto the next hook.
    ///
    /// # Notes
    /// - If this hook doesn't have a weight this is the same as `Remove`.
    RemoveNoPass,
}

/// An event that `Input` should process.
///
/// # Notes
/// - This type should only be used externally when using a custom window implementation.
#[derive(Debug, Clone)]
pub enum InputEvent {
    Press { win: BstWindowID, key: Key },
    Release { win: BstWindowID, key: Key },
    Character { win: BstWindowID, c: char },
    Cursor { win: BstWindowID, x: f32, y: f32 },
    Scroll { win: BstWindowID, v: f32, h: f32 },
    Enter { win: BstWindowID },
    Leave { win: BstWindowID },
    Focus { win: BstWindowID },
    FocusLost { win: BstWindowID },
    Motion { x: f32, y: f32 },
    CursorCapture { win: BstWindowID, captured: bool },
}

/// An error that is returned by various `Input` related methods.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputError {
    NoKeys,
    NoMethod,
    NoTarget,
    NoTrigger,
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
enum InputHookTargetID {
    #[default]
    None,
    Window(BstWindowID),
    Bin(BinID),
}

enum InputHookTargetWeak {
    None,
    Window(Weak<dyn BasaltWindow>),
    Bin(Weak<Bin>),
}

impl InputHookTargetWeak {
    fn upgrade(&self) -> Option<InputHookTarget> {
        match self {
            Self::None => Some(InputHookTarget::None),
            Self::Window(wk) => wk.upgrade().map(InputHookTarget::Window),
            Self::Bin(wk) => wk.upgrade().map(InputHookTarget::Bin),
        }
    }
}

struct Hook {
    target_id: InputHookTargetID,
    target_wk: InputHookTargetWeak,
    state: HookState,
}

impl Hook {
    fn is_for_window_id(&self, win_id: BstWindowID) -> bool {
        match &self.target_id {
            InputHookTargetID::Window(self_win_id) => *self_win_id == win_id,
            _ => false,
        }
    }

    fn is_for_bin_id(&self, bin_id: BinID) -> bool {
        match &self.target_id {
            InputHookTargetID::Bin(self_bin_id) => *self_bin_id == bin_id,
            _ => false,
        }
    }

    fn bin_id(&self) -> Option<BinID> {
        match &self.target_id {
            InputHookTargetID::Bin(bin_id) => Some(*bin_id),
            _ => None,
        }
    }
}

/// The main struct for the input system.
///
/// Accessed via `basalt.input_ref()`.
pub struct Input {
    event_send: Sender<LoopEvent>,
    current_id: AtomicU64,
    interval: Arc<Interval>,
}

impl Input {
    pub(crate) fn new(interface: Arc<Interface>, interval: Arc<Interval>) -> Self {
        let (event_send, event_recv) = channel::unbounded();
        inner::begin_loop(interface, interval.clone(), event_send.clone(), event_recv);

        Self {
            event_send,
            interval,
            current_id: AtomicU64::new(0),
        }
    }

    pub(in crate::input) fn event_send(&self) -> Sender<LoopEvent> {
        self.event_send.clone()
    }

    pub(in crate::input) fn interval(&self) -> Arc<Interval> {
        self.interval.clone()
    }

    /// Returns a builder to add a hook.
    ///
    /// ```no_run
    /// let hook_id = basalt
    ///     .input_ref()
    ///     .hook()
    ///     .bin(&bin)
    ///     .on_press()
    ///     .keys(Qwerty::W)
    ///     .call(move |_target, _global, local| {
    ///         assert!(local.is_pressed(Qwerty::W));
    ///         println!("Pressed W on Bin");
    ///         Default::default()
    ///     })
    ///     .finish()
    ///     .unwrap();
    /// ```
    pub fn hook(&self) -> InputHookBuilder {
        InputHookBuilder::start(self)
    }

    /// Remove a hook from `Input`.
    ///
    /// # Notes
    /// - Hooks on a `Bin` or `Window` are automatically removed when they are dropped.
    pub fn remove_hook(&self, id: InputHookID) {
        self.event_send.send(LoopEvent::Remove(id)).unwrap();
    }

    /// Manually set the `Bin` that is focused.
    ///
    /// Useful for dialogs/forms that require text input.
    pub fn set_bin_focused(&self, bin: &Arc<Bin>) {
        // TODO: get window from Bin
        let win = BstWindowID(0);

        self.event_send
            .send(LoopEvent::FocusBin {
                win,
                bin: Some(bin.id()),
            })
            .unwrap();
    }

    /// Send an `InputEvent` to `Input`.
    ///
    /// # Notes
    /// - This method should only be used externally when using a custom window implementation.
    pub fn send_event(&self, event: InputEvent) {
        self.event_send.send(LoopEvent::Normal(event)).unwrap();
    }

    fn add_hook(&self, hook: Hook) -> InputHookID {
        let id = InputHookID(self.current_id.fetch_add(1, atomic::Ordering::SeqCst));
        self.event_send
            .send(LoopEvent::Add {
                id,
                hook,
            })
            .unwrap();
        id
    }

    pub(in crate::input) fn add_hook_with_id(&self, id: InputHookID, hook: Hook) {
        self.event_send
            .send(LoopEvent::Add {
                id,
                hook,
            })
            .unwrap();
    }

    pub(in crate::input) fn next_id(&self) -> InputHookID {
        InputHookID(self.current_id.fetch_add(1, atomic::Ordering::SeqCst))
    }
}