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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
29DialogContainer::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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
45void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
97bool 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119bool DialogContainer::needsRedraw() const
120{
121 return !myDialogStack.empty() ? myDialogStack.top()->isDirty() : false;
122}
123
124// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
125bool DialogContainer::baseDialogIsActive() const
126{
127 return myDialogStack.size() == 1;
128}
129
130// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
131int 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
148void DialogContainer::removeDialog()
149{
150 if(!myDialogStack.empty())
151 {
152 myDialogStack.pop();
153 if(!myDialogStack.empty())
154 myDialogStack.top()->setDirty();
155 }
156}
157
158// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
159void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
172void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
183void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
197void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
209void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
286void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
316void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
346void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
375void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
389uInt64 DialogContainer::_DOUBLE_CLICK_DELAY = 500;
390uInt64 DialogContainer::_REPEAT_INITIAL_DELAY = 400;
391uInt64 DialogContainer::_REPEAT_SUSTAIN_DELAY = 50;
392
393