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
use std::{any::TypeId, collections::HashMap, marker::PhantomData, sync::LazyLock};

pub use global::*;
use parking_lot::RwLock;

use crate::ui::{FileBuilder, Ui, WindowBuilder};

mod global {
    use super::{Hookable, Hooks};

    static HOOKS: Hooks = Hooks::new();

    pub fn add<H: Hookable>(
        f: impl for<'a, 'b> FnMut(&'b mut H::Args<'a>) + Send + Sync + 'static,
    ) {
        HOOKS.add_hook::<H>("", f)
    }

    pub fn add_grouped<H: Hookable>(
        group: &'static str,
        f: impl for<'a, 'b> FnMut(&'b mut H::Args<'a>) + Send + Sync + 'static,
    ) {
        HOOKS.add_hook::<H>(group, f)
    }

    pub fn remove_group(group: &'static str) {
        HOOKS.remove(group)
    }

    pub fn trigger<H: Hookable>(args: &mut H::Args<'_>) {
        HOOKS.activate::<H>(args)
    }
}

pub trait Hookable: Sized + 'static {
    type Args<'args>;
}

trait HookHolder: Send + Sync {
    fn remove_group(&mut self, group: &'static str);
}

struct HooksOf<H>(RwLock<Vec<Hook<H>>>)
where
    H: Hookable;

impl<H: Hookable> HookHolder for HooksOf<H> {
    fn remove_group(&mut self, group: &'static str) {
        self.0.write().extract_if(|(cmp, _)| *cmp == group).last();
    }
}

struct Hooks(LazyLock<RwLock<HashMap<TypeId, Box<dyn HookHolder>>>>);

impl Hooks {
    const fn new() -> Self {
        Hooks(LazyLock::new(|| RwLock::new(HashMap::new())))
    }

    fn add_hook<H: Hookable>(
        &self,
        group: &'static str,
        f: impl for<'a, 'b> FnMut(&'b mut H::Args<'a>) + Send + Sync + 'static,
    ) {
        let mut map = self.0.write();

        if let Some(holder) = map.get_mut(&TypeId::of::<H>()) {
            let hooks_of = unsafe {
                let ptr = (&mut **holder as *mut dyn HookHolder).cast::<HooksOf<H>>();
                ptr.as_mut().unwrap()
            };

            hooks_of.0.write().push((group, Box::new(f)))
        } else {
            let hooks_of = HooksOf::<H>(RwLock::new(vec![(group, Box::new(f))]));

            map.insert(TypeId::of::<H>(), Box::new(hooks_of));
        }
    }

    fn remove(&self, group: &'static str) {
        let mut map = self.0.write();

        for holder in map.iter_mut() {
            holder.1.remove_group(group)
        }
    }

    fn activate<H: Hookable>(&self, args: &mut H::Args<'_>) {
        let map = self.0.read();

        if let Some(holder) = map.get(&TypeId::of::<H>()) {
            let hooks_of = unsafe {
                let ptr = (&**holder as *const dyn HookHolder).cast::<HooksOf<H>>();
                ptr.as_ref().unwrap()
            };

            for (_, f) in &mut *hooks_of.0.write() {
                f(args)
            }
        }
    }
}

pub struct OnFileOpen<U>(PhantomData<U>)
where
    U: Ui;

impl<U> Hookable for OnFileOpen<U>
where
    U: Ui,
{
    type Args<'a> = FileBuilder<'a, U>;
}

pub struct OnWindowOpen<U>(PhantomData<U>)
where
    U: Ui;

impl<U> Hookable for OnWindowOpen<U>
where
    U: Ui,
{
    type Args<'a> = WindowBuilder<'a, U>;
}

type Hook<H> = (
    &'static str,
    Box<dyn for<'a, 'b> FnMut(&'b mut <H as Hookable>::Args<'a>) + Send + Sync>,
);