1 | //============================================================================ |
2 | // |
3 | // SSSS tt lll lll |
4 | // SS SS tt ll ll |
5 | // SS tttttt eeee ll ll aaaa |
6 | // SSSS tt ee ee ll ll aa |
7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
8 | // SS SS tt ee ll ll aa aa |
9 | // SSSS ttt eeeee llll llll aaaaa |
10 | // |
11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
12 | // and the Stella Team |
13 | // |
14 | // See the file "License.txt" for information on usage and redistribution of |
15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
16 | //============================================================================ |
17 | |
18 | #include "OSystem.hxx" |
19 | #include "Dialog.hxx" |
20 | #include "Stack.hxx" |
21 | #include "EventHandler.hxx" |
22 | #include "FrameBuffer.hxx" |
23 | #include "FBSurface.hxx" |
24 | #include "Joystick.hxx" |
25 | #include "bspf.hxx" |
26 | #include "DialogContainer.hxx" |
27 | |
28 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
29 | DialogContainer::DialogContainer(OSystem& osystem) |
30 | : myOSystem(osystem), |
31 | myTime(0), |
32 | myClickRepeatTime(0), |
33 | myButtonRepeatTime(0), |
34 | myButtonLongPressTime(0), |
35 | myAxisRepeatTime(0), |
36 | myHatRepeatTime(0) |
37 | { |
38 | _DOUBLE_CLICK_DELAY = osystem.settings().getInt("mdouble" ); |
39 | _REPEAT_INITIAL_DELAY = osystem.settings().getInt("ctrldelay" ); |
40 | setControllerRate(osystem.settings().getInt("ctrlrate" )); |
41 | reset(); |
42 | } |
43 | |
44 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
45 | void DialogContainer::updateTime(uInt64 time) |
46 | { |
47 | if(myDialogStack.empty()) |
48 | return; |
49 | |
50 | // We only need millisecond precision |
51 | myTime = time / 1000; |
52 | |
53 | // Check for pending continuous events and send them to the active dialog box |
54 | Dialog* activeDialog = myDialogStack.top(); |
55 | |
56 | // Mouse button still pressed |
57 | if(myCurrentMouseDown.b != MouseButton::NONE && myClickRepeatTime < myTime) |
58 | { |
59 | activeDialog->handleMouseDown(myCurrentMouseDown.x - activeDialog->_x, |
60 | myCurrentMouseDown.y - activeDialog->_y, |
61 | myCurrentMouseDown.b, 1); |
62 | myClickRepeatTime = myTime + _REPEAT_SUSTAIN_DELAY; |
63 | } |
64 | |
65 | // Joystick button still pressed |
66 | if(myCurrentButtonDown.stick != -1 && myButtonRepeatTime < myTime) |
67 | { |
68 | activeDialog->handleJoyDown(myCurrentButtonDown.stick, myCurrentButtonDown.button); |
69 | myButtonRepeatTime = myTime + _REPEAT_SUSTAIN_DELAY; |
70 | } |
71 | |
72 | // Joystick has been pressed long |
73 | if(myCurrentButtonDown.stick != -1 && myButtonLongPressTime < myTime) |
74 | { |
75 | activeDialog->handleJoyDown(myCurrentButtonDown.stick, myCurrentButtonDown.button, true); |
76 | myButtonLongPressTime = myButtonRepeatTime = myTime + _REPEAT_NONE; |
77 | } |
78 | |
79 | // Joystick axis still pressed |
80 | if(myCurrentAxisDown.stick != -1 && myAxisRepeatTime < myTime) |
81 | { |
82 | activeDialog->handleJoyAxis(myCurrentAxisDown.stick, myCurrentAxisDown.axis, |
83 | myCurrentAxisDown.adir); |
84 | myAxisRepeatTime = myTime + _REPEAT_SUSTAIN_DELAY; |
85 | } |
86 | |
87 | // Joystick hat still pressed |
88 | if(myCurrentHatDown.stick != -1 && myHatRepeatTime < myTime) |
89 | { |
90 | activeDialog->handleJoyHat(myCurrentHatDown.stick, myCurrentHatDown.hat, |
91 | myCurrentHatDown.hdir); |
92 | myHatRepeatTime = myTime + _REPEAT_SUSTAIN_DELAY; |
93 | } |
94 | } |
95 | |
96 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
97 | bool DialogContainer::draw(bool full) |
98 | { |
99 | if(myDialogStack.empty()) |
100 | return false; |
101 | |
102 | // Make the top dialog dirty if a full redraw is requested |
103 | if(full) |
104 | myDialogStack.top()->setDirty(); |
105 | |
106 | // If the top dialog is dirty, then all below it must be redrawn too |
107 | const bool dirty = needsRedraw(); |
108 | |
109 | myDialogStack.applyAll([&](Dialog*& d){ |
110 | if(dirty) |
111 | d->setDirty(); |
112 | full |= d->render(); |
113 | }); |
114 | |
115 | return full; |
116 | } |
117 | |
118 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
119 | bool DialogContainer::needsRedraw() const |
120 | { |
121 | return !myDialogStack.empty() ? myDialogStack.top()->isDirty() : false; |
122 | } |
123 | |
124 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
125 | bool DialogContainer::baseDialogIsActive() const |
126 | { |
127 | return myDialogStack.size() == 1; |
128 | } |
129 | |
130 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
131 | int DialogContainer::addDialog(Dialog* d) |
132 | { |
133 | const Common::Rect& r = myOSystem.frameBuffer().imageRect(); |
134 | const uInt32 scale = myOSystem.frameBuffer().hidpiScaleFactor(); |
135 | |
136 | if(uInt32(d->getWidth() * scale) > r.w() || uInt32(d->getHeight() * scale) > r.h()) |
137 | myOSystem.frameBuffer().showMessage( |
138 | "Unable to show dialog box; FIX THE CODE" ); |
139 | else |
140 | { |
141 | d->setDirty(); |
142 | myDialogStack.push(d); |
143 | } |
144 | return myDialogStack.size(); |
145 | } |
146 | |
147 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
148 | void DialogContainer::removeDialog() |
149 | { |
150 | if(!myDialogStack.empty()) |
151 | { |
152 | myDialogStack.pop(); |
153 | if(!myDialogStack.empty()) |
154 | myDialogStack.top()->setDirty(); |
155 | } |
156 | } |
157 | |
158 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
159 | void DialogContainer::reStack() |
160 | { |
161 | // Pop all items from the stack, and then add the base menu |
162 | while(!myDialogStack.empty()) |
163 | myDialogStack.top()->close(); |
164 | |
165 | baseDialog()->open(); |
166 | |
167 | // Reset all continuous events |
168 | reset(); |
169 | } |
170 | |
171 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
172 | void DialogContainer::handleTextEvent(char text) |
173 | { |
174 | if(myDialogStack.empty()) |
175 | return; |
176 | |
177 | // Send the event to the dialog box on the top of the stack |
178 | Dialog* activeDialog = myDialogStack.top(); |
179 | activeDialog->handleText(text); |
180 | } |
181 | |
182 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
183 | void DialogContainer::handleKeyEvent(StellaKey key, StellaMod mod, bool pressed, bool repeated) |
184 | { |
185 | if(myDialogStack.empty()) |
186 | return; |
187 | |
188 | // Send the event to the dialog box on the top of the stack |
189 | Dialog* activeDialog = myDialogStack.top(); |
190 | if(pressed) |
191 | activeDialog->handleKeyDown(key, mod, repeated); |
192 | else |
193 | activeDialog->handleKeyUp(key, mod); |
194 | } |
195 | |
196 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
197 | void DialogContainer::handleMouseMotionEvent(int x, int y) |
198 | { |
199 | if(myDialogStack.empty()) |
200 | return; |
201 | |
202 | // Send the event to the dialog box on the top of the stack |
203 | Dialog* activeDialog = myDialogStack.top(); |
204 | activeDialog->surface().translateCoords(x, y); |
205 | activeDialog->handleMouseMoved(x - activeDialog->_x, y - activeDialog->_y); |
206 | } |
207 | |
208 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
209 | void DialogContainer::handleMouseButtonEvent(MouseButton b, bool pressed, |
210 | int x, int y) |
211 | { |
212 | if(myDialogStack.empty()) |
213 | return; |
214 | |
215 | // Send the event to the dialog box on the top of the stack |
216 | Dialog* activeDialog = myDialogStack.top(); |
217 | activeDialog->surface().translateCoords(x, y); |
218 | |
219 | switch(b) |
220 | { |
221 | case MouseButton::LEFT: |
222 | case MouseButton::RIGHT: |
223 | if(pressed) |
224 | { |
225 | // If more than two clicks have been recorded, we start over |
226 | if(myLastClick.count == 2) |
227 | { |
228 | myLastClick.x = myLastClick.y = 0; |
229 | myLastClick.time = 0; |
230 | myLastClick.count = 0; |
231 | } |
232 | |
233 | if(myLastClick.count && (myTime < myLastClick.time + _DOUBLE_CLICK_DELAY) |
234 | && std::abs(myLastClick.x - x) < 3 |
235 | && std::abs(myLastClick.y - y) < 3) |
236 | { |
237 | myLastClick.count++; |
238 | } |
239 | else |
240 | { |
241 | myLastClick.x = x; |
242 | myLastClick.y = y; |
243 | myLastClick.count = 1; |
244 | } |
245 | myLastClick.time = myTime; |
246 | |
247 | // Now account for repeated mouse events (click and hold), but only |
248 | // if the dialog wants them |
249 | if(activeDialog->handleMouseClicks(x - activeDialog->_x, y - activeDialog->_y, b)) |
250 | { |
251 | myCurrentMouseDown.x = x; |
252 | myCurrentMouseDown.y = y; |
253 | myCurrentMouseDown.b = b; |
254 | myClickRepeatTime = myTime + _REPEAT_INITIAL_DELAY; |
255 | } |
256 | else |
257 | myCurrentMouseDown.b = MouseButton::NONE; |
258 | |
259 | activeDialog->handleMouseDown(x - activeDialog->_x, y - activeDialog->_y, |
260 | b, myLastClick.count); |
261 | } |
262 | else |
263 | { |
264 | activeDialog->handleMouseUp(x - activeDialog->_x, y - activeDialog->_y, |
265 | b, myLastClick.count); |
266 | |
267 | if(b == myCurrentMouseDown.b) |
268 | myCurrentMouseDown.b = MouseButton::NONE; |
269 | } |
270 | break; |
271 | |
272 | case MouseButton::WHEELUP: |
273 | activeDialog->handleMouseWheel(x - activeDialog->_x, y - activeDialog->_y, -1); |
274 | break; |
275 | |
276 | case MouseButton::WHEELDOWN: |
277 | activeDialog->handleMouseWheel(x - activeDialog->_x, y - activeDialog->_y, 1); |
278 | break; |
279 | |
280 | case MouseButton::NONE: // should never get here |
281 | break; |
282 | } |
283 | } |
284 | |
285 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
286 | void DialogContainer::handleJoyBtnEvent(int stick, int button, bool pressed) |
287 | { |
288 | if(myDialogStack.empty()) |
289 | return; |
290 | |
291 | // Send the event to the dialog box on the top of the stack |
292 | Dialog* activeDialog = myDialogStack.top(); |
293 | |
294 | if(pressed && myButtonRepeatTime < myTime) // prevent pending repeats after enabling repeat again |
295 | { |
296 | myCurrentButtonDown.stick = stick; |
297 | myCurrentButtonDown.button = button; |
298 | myButtonRepeatTime = myTime + (activeDialog->repeatEnabled() ? _REPEAT_INITIAL_DELAY : _REPEAT_NONE); |
299 | myButtonLongPressTime = myTime + _LONG_PRESS_DELAY; |
300 | |
301 | activeDialog->handleJoyDown(stick, button); |
302 | } |
303 | else |
304 | { |
305 | // Only stop firing events if it's the current button |
306 | if(stick == myCurrentButtonDown.stick) |
307 | { |
308 | myCurrentButtonDown.stick = myCurrentButtonDown.button = -1; |
309 | myButtonRepeatTime = myButtonLongPressTime = 0; |
310 | } |
311 | activeDialog->handleJoyUp(stick, button); |
312 | } |
313 | } |
314 | |
315 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
316 | void DialogContainer::handleJoyAxisEvent(int stick, JoyAxis axis, JoyDir adir, int button) |
317 | { |
318 | if(myDialogStack.empty()) |
319 | return; |
320 | |
321 | // Send the event to the dialog box on the top of the stack |
322 | Dialog* activeDialog = myDialogStack.top(); |
323 | |
324 | // Prevent long button press in button/axis combinations |
325 | myButtonLongPressTime = myTime + _REPEAT_NONE; |
326 | |
327 | // Only stop firing events if it's the current stick |
328 | if(myCurrentAxisDown.stick == stick && adir == JoyDir::NONE) |
329 | { |
330 | myCurrentAxisDown.stick = -1; |
331 | myCurrentAxisDown.axis = JoyAxis::NONE; |
332 | myAxisRepeatTime = 0; |
333 | } |
334 | else if(adir != JoyDir::NONE && myAxisRepeatTime < myTime) // never repeat the 'off' event; prevent pending repeats after enabling repeat again |
335 | { |
336 | // Now account for repeated axis events (press and hold) |
337 | myCurrentAxisDown.stick = stick; |
338 | myCurrentAxisDown.axis = axis; |
339 | myCurrentAxisDown.adir = adir; |
340 | myAxisRepeatTime = myTime + (activeDialog->repeatEnabled() ? _REPEAT_INITIAL_DELAY : _REPEAT_NONE); |
341 | } |
342 | activeDialog->handleJoyAxis(stick, axis, adir, button); |
343 | } |
344 | |
345 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
346 | void DialogContainer::handleJoyHatEvent(int stick, int hat, JoyHatDir hdir, int button) |
347 | { |
348 | if(myDialogStack.empty()) |
349 | return; |
350 | |
351 | // Send the event to the dialog box on the top of the stack |
352 | Dialog* activeDialog = myDialogStack.top(); |
353 | |
354 | // Prevent long button press in button/hat combinations |
355 | myButtonLongPressTime = myTime + _REPEAT_NONE; |
356 | |
357 | // Only stop firing events if it's the current stick |
358 | if(myCurrentHatDown.stick == stick && hdir == JoyHatDir::CENTER) |
359 | { |
360 | myCurrentHatDown.stick = myCurrentHatDown.hat = -1; |
361 | myHatRepeatTime = 0; |
362 | } |
363 | else if(hdir != JoyHatDir::CENTER && myHatRepeatTime < myTime) // never repeat the 'center' direction; prevent pending repeats after enabling repeat again |
364 | { |
365 | // Now account for repeated hat events (press and hold) |
366 | myCurrentHatDown.stick = stick; |
367 | myCurrentHatDown.hat = hat; |
368 | myCurrentHatDown.hdir = hdir; |
369 | myHatRepeatTime = myTime + (activeDialog->repeatEnabled() ? _REPEAT_INITIAL_DELAY : _REPEAT_NONE); |
370 | } |
371 | activeDialog->handleJoyHat(stick, hat, hdir, button); |
372 | } |
373 | |
374 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
375 | void DialogContainer::reset() |
376 | { |
377 | myCurrentMouseDown.b = MouseButton::NONE; |
378 | myLastClick.x = myLastClick.y = 0; |
379 | myLastClick.time = 0; |
380 | myLastClick.count = 0; |
381 | |
382 | myCurrentButtonDown.stick = myCurrentButtonDown.button = -1; |
383 | myCurrentAxisDown.stick = -1; |
384 | myCurrentAxisDown.axis = JoyAxis::NONE; |
385 | myCurrentHatDown.stick = myCurrentHatDown.hat = -1; |
386 | } |
387 | |
388 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
389 | uInt64 DialogContainer::_DOUBLE_CLICK_DELAY = 500; |
390 | uInt64 DialogContainer::_REPEAT_INITIAL_DELAY = 400; |
391 | uInt64 DialogContainer::_REPEAT_SUSTAIN_DELAY = 50; |
392 | |
393 | |