1 module sbylib.event.mouseevent;
2 
3 import std;
4 import sbylib.math;
5 import sbylib.wrapper.glfw;
6 import sbylib.event;
7 
8 private alias MouseEnterCallback = void delegate(bool);
9 private alias MousePosCallback = void delegate(double[2]);
10 private alias MouseScrollCallback = void delegate(double[2]);
11 private alias MouseButtonCallback = void delegate(MouseButton, ButtonState, BitFlags!ModKeyButton);
12 
13 private struct MouseEnterNotification { Window window; }
14 private struct MouseLeaveNotification { Window window; }
15 private struct MousePosNotification { Window window; }
16 private struct MouseScrollNotification { Window window; }
17 private struct MouseButtonNotification {
18     Window window;
19     MouseButton button;
20     ButtonState state;
21 }
22 private struct MousePos {}
23 
24 private struct Mouse {
25     MouseEnterNotification entered() { return MouseEnterNotification(); }
26     MouseLeaveNotification left() { return MouseLeaveNotification(); }
27     MousePosNotification moved() { return MousePosNotification(); }
28     MouseScrollNotification scrolled() { return MouseScrollNotification(); }
29     MousePos pos() { return MousePos(); }
30 }
31 
32 static foreach (T; AliasSeq!(MouseEnterNotification, MouseLeaveNotification, MousePosNotification, MouseScrollNotification)) {
33     T on(T t, Window window) {
34         t.window = window;
35         return t;
36     }
37 }
38 
39 vec2 on(MousePos, Window window) {
40     return vec2(window.mousePos());
41 }
42 
43 Mouse mouse() { return Mouse(); }
44 
45 MouseButtonNotification pressed(MouseButton mouse) {
46     return MouseButtonNotification(null, mouse, ButtonState.Press);
47 }
48 
49 MouseButtonNotification released(MouseButton mouse) {
50     return MouseButtonNotification(null, mouse, ButtonState.Release);
51 }
52 
53 struct MousePressing {
54     MouseButton button;
55 }
56 
57 MousePressing pressing(MouseButton button) {
58     return MousePressing(button);
59 }
60 
61 MouseButtonNotification on(MouseButtonNotification notification, Window window) {
62     notification.window = window;
63     return notification;
64 }
65 
66 auto on(MousePressing m, Window window) {
67     return FrameNotification(() => window.getMouse(m.button) == ButtonState.Press);
68 }
69 
70 auto when(MouseEnterNotification notification) {
71     import sbylib.event : when;
72 
73     auto event = new Event!();
74     auto cb = MouseEventWatcher.add(notification.window, (bool entered) {
75         if (!entered) return;
76         event.fire();
77     });
78     when(event.finish).then({
79         MouseEventWatcher.remove(notification.window, cb);
80     });
81     return event;
82 }
83 
84 auto when(MouseLeaveNotification notification) {
85     import sbylib.event : when;
86 
87     auto event = new Event!();
88     auto cb = MouseEventWatcher.add(notification.window, (bool entered) {
89         if (entered) return;
90         event.fire();
91     });
92     when(event.finish).then({
93         MouseEventWatcher.remove(notification.window, cb);
94     });
95     return event;
96 }
97 
98 auto when(MousePosNotification notification) {
99     import sbylib.event : when;
100 
101     auto event = new Event!(vec2);
102     auto cb = MouseEventWatcher.add(notification.window, (double[2] pos) {
103         event.fire(vec2(pos));
104     });
105     when(event.finish).then({
106         MouseEventWatcher.remove(notification.window, cb);
107     });
108     return event;
109 }
110 
111 auto when(MouseScrollNotification notification) {
112     import sbylib.event : when;
113 
114     auto event = new Event!(vec2);
115     auto cb = MouseEventWatcher.addScroll(notification.window, (double[2] scroll) {
116         event.fire(vec2(scroll));
117     });
118     when(event.finish).then({
119         MouseEventWatcher.removeScroll(notification.window, cb);
120     });
121     return event;
122 }
123 
124 auto when(MouseButtonNotification notification) {
125     import sbylib.event : when;
126 
127     auto event = new Event!(BitFlags!ModKeyButton);
128     auto cb = MouseEventWatcher.add(notification.window,
129             (MouseButton button, ButtonState state, BitFlags!ModKeyButton mods) {
130         if (button != notification.button) return;
131         if (state != notification.state) return;
132         event.fire(mods);
133     });
134     when(event.finish).then({
135         MouseEventWatcher.remove(notification.window, cb);
136     });
137     return event;
138 }
139 
140 private class MouseEventWatcher {
141 static:
142     private Array!MouseEnterCallback[Window] enterCallbackList;
143     private Array!MousePosCallback[Window] posCallbackList;
144     private Array!MouseScrollCallback[Window] scrollCallbackList;
145     private Array!MouseButtonCallback[Window] buttonCallbackList;
146     private Array!Window registered;
147 
148     private void use(Window window) {
149         foreach (r; registered) {
150             if (r == window) return;
151         }
152         registered ~= window;
153 
154         alias errorHandler = (Exception e) { assert(false, e.toString()); };
155         window.setMouseEnterCallback!(enterCallback, errorHandler);
156         window.setMousePosCallback!(posCallback, errorHandler);
157         window.setScrollCallback!(scrollCallback, errorHandler);
158         window.setMouseButtonCallback!(buttonCallback, errorHandler);
159 
160         enterCallbackList[window] = Array!MouseEnterCallback();
161         posCallbackList[window] = Array!MousePosCallback();
162         scrollCallbackList[window] = Array!MouseScrollCallback();
163         buttonCallbackList[window] = Array!MouseButtonCallback();
164     }
165 
166     private MouseEnterCallback add(Window window, MouseEnterCallback callback) {
167         use(window);
168         enterCallbackList[window] ~= callback;
169         return callback;
170     }
171 
172     private void remove(Window window, MouseEnterCallback callback) {
173         auto target = enterCallbackList[window];
174         target.linearRemove(target[].find(callback).take(1));
175     }
176 
177     void enterCallback(Window window, bool enter) {
178         foreach (cb; enterCallbackList[window]) {
179             cb(enter);
180         }
181     }
182 
183     private MousePosCallback add(Window window, MousePosCallback callback) {
184         use(window);
185         posCallbackList[window] ~= callback;
186         return callback;
187     }
188 
189     private void remove(Window window, MousePosCallback callback) {
190         auto target = posCallbackList[window];
191         target.linearRemove(target[].find(callback).take(1));
192     }
193 
194     void posCallback(Window window, double[2] pos) {
195         foreach (cb; posCallbackList[window]) {
196             cb(pos);
197         }
198     }
199 
200     private MouseScrollCallback addScroll(Window window, MouseScrollCallback callback) {
201         use(window);
202         scrollCallbackList[window] ~= callback;
203         return callback;
204     }
205 
206     private void removeScroll(Window window, MouseScrollCallback callback) {
207         auto target = scrollCallbackList[window];
208         target.linearRemove(target[].find(callback).take(1));
209     }
210 
211     void scrollCallback(Window window, double[2] scroll) {
212         foreach (cb; scrollCallbackList[window]) {
213             cb(scroll);
214         }
215     }
216 
217     private MouseButtonCallback add(Window window, MouseButtonCallback callback) {
218         use(window);
219         buttonCallbackList[window] ~= callback;
220         return callback;
221     }
222 
223     private void remove(Window window, MouseButtonCallback callback) {
224         auto target = buttonCallbackList[window];
225         target.linearRemove(target[].find(callback).take(1));
226     }
227 
228     void buttonCallback(Window window, MouseButton button, ButtonState state, BitFlags!ModKeyButton mods) {
229         foreach (cb; buttonCallbackList[window]) {
230             cb(button, state, mods);
231         }
232     }
233 }