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 "bspf.hxx" |
19 | #include "Logger.hxx" |
20 | |
21 | #include "Console.hxx" |
22 | #include "EventHandler.hxx" |
23 | #include "Event.hxx" |
24 | #include "OSystem.hxx" |
25 | #include "Settings.hxx" |
26 | #include "TIA.hxx" |
27 | #include "Sound.hxx" |
28 | |
29 | #include "FBSurface.hxx" |
30 | #include "TIASurface.hxx" |
31 | #include "FrameBuffer.hxx" |
32 | |
33 | #ifdef DEBUGGER_SUPPORT |
34 | #include "Debugger.hxx" |
35 | #endif |
36 | #ifdef GUI_SUPPORT |
37 | #include "Font.hxx" |
38 | #include "StellaFont.hxx" |
39 | #include "StellaMediumFont.hxx" |
40 | #include "StellaLargeFont.hxx" |
41 | #include "ConsoleFont.hxx" |
42 | #include "Launcher.hxx" |
43 | #include "Menu.hxx" |
44 | #include "CommandMenu.hxx" |
45 | #include "TimeMachine.hxx" |
46 | #endif |
47 | |
48 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
49 | FrameBuffer::FrameBuffer(OSystem& osystem) |
50 | : myOSystem(osystem), |
51 | myNumDisplays(1), |
52 | myInitializedCount(0), |
53 | myPausedCount(0), |
54 | myStatsEnabled(false), |
55 | myLastScanlines(0), |
56 | myGrabMouse(false), |
57 | myHiDPIAllowed(false), |
58 | myHiDPIEnabled(false), |
59 | myCurrentModeList(nullptr) |
60 | { |
61 | } |
62 | |
63 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
64 | FrameBuffer::~FrameBuffer() |
65 | { |
66 | } |
67 | |
68 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
69 | bool FrameBuffer::initialize() |
70 | { |
71 | // Get desktop resolution and supported renderers |
72 | vector<Common::Size> windowedDisplays; |
73 | queryHardware(myFullscreenDisplays, windowedDisplays, myRenderers); |
74 | uInt32 query_w = windowedDisplays[0].w, query_h = windowedDisplays[0].h; |
75 | |
76 | // Check the 'maxres' setting, which is an undocumented developer feature |
77 | // that specifies the desktop size (not normally set) |
78 | const Common::Size& s = myOSystem.settings().getSize("maxres" ); |
79 | if(s.valid()) |
80 | { |
81 | query_w = s.w; |
82 | query_h = s.h; |
83 | } |
84 | // Various parts of the codebase assume a minimum screen size |
85 | myAbsDesktopSize.w = std::max(query_w, FBMinimum::Width); |
86 | myAbsDesktopSize.h = std::max(query_h, FBMinimum::Height); |
87 | myDesktopSize = myAbsDesktopSize; |
88 | |
89 | // Check for HiDPI mode (is it activated, and can we use it?) |
90 | myHiDPIAllowed = ((myAbsDesktopSize.w / 2) >= FBMinimum::Width) && |
91 | ((myAbsDesktopSize.h / 2) >= FBMinimum::Height); |
92 | myHiDPIEnabled = myHiDPIAllowed && myOSystem.settings().getBool("hidpi" ); |
93 | |
94 | // In HiDPI mode, the desktop resolution is essentially halved |
95 | // Later, the output is scaled and rendered in 2x mode |
96 | if(hidpiEnabled()) |
97 | { |
98 | myDesktopSize.w = myAbsDesktopSize.w / hidpiScaleFactor(); |
99 | myDesktopSize.h = myAbsDesktopSize.h / hidpiScaleFactor(); |
100 | } |
101 | |
102 | #ifdef GUI_SUPPORT |
103 | //////////////////////////////////////////////////////////////////// |
104 | // Create fonts to draw text |
105 | // NOTE: the logic determining appropriate font sizes is done here, |
106 | // so that the UI classes can just use the font they expect, |
107 | // and not worry about it |
108 | // This logic should also take into account the size of the |
109 | // framebuffer, and try to be intelligent about font sizes |
110 | // We can probably add ifdefs to take care of corner cases, |
111 | // but that means we've failed to abstract it enough ... |
112 | //////////////////////////////////////////////////////////////////// |
113 | |
114 | // This font is used in a variety of situations when a really small |
115 | // font is needed; we let the specific widget/dialog decide when to |
116 | // use it |
117 | mySmallFont = make_unique<GUI::Font>(GUI::stellaDesc); |
118 | |
119 | // The general font used in all UI elements |
120 | // This is determined by the size of the framebuffer |
121 | if(myOSystem.settings().getBool("minimal_ui" )) |
122 | myFont = make_unique<GUI::Font>(GUI::stellaLargeDesc); |
123 | else |
124 | myFont = make_unique<GUI::Font>(GUI::stellaMediumDesc); |
125 | |
126 | // The info font used in all UI elements |
127 | // This is determined by the size of the framebuffer |
128 | myInfoFont = make_unique<GUI::Font>(GUI::consoleDesc); |
129 | |
130 | // The font used by the ROM launcher |
131 | const string& lf = myOSystem.settings().getString("launcherfont" ); |
132 | if(lf == "small" ) |
133 | myLauncherFont = make_unique<GUI::Font>(GUI::consoleDesc); |
134 | else if(lf == "medium" ) |
135 | myLauncherFont = make_unique<GUI::Font>(GUI::stellaMediumDesc); |
136 | else |
137 | myLauncherFont = make_unique<GUI::Font>(GUI::stellaLargeDesc); |
138 | #endif |
139 | |
140 | // Determine possible TIA windowed zoom levels |
141 | myTIAMaxZoom = maxZoomForScreen( |
142 | TIAConstants::viewableWidth, TIAConstants::viewableHeight, |
143 | myAbsDesktopSize.w, myAbsDesktopSize.h); |
144 | |
145 | setUIPalette(); |
146 | |
147 | myGrabMouse = myOSystem.settings().getBool("grabmouse" ); |
148 | |
149 | // Create a TIA surface; we need it for rendering TIA images |
150 | myTIASurface = make_unique<TIASurface>(myOSystem); |
151 | |
152 | return true; |
153 | } |
154 | |
155 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
156 | void FrameBuffer::setUIPalette() |
157 | { |
158 | // Set palette for GUI (upper area of array) |
159 | int palID = 0; |
160 | if(myOSystem.settings().getString("uipalette" ) == "classic" ) |
161 | palID = 1; |
162 | else if(myOSystem.settings().getString("uipalette" ) == "light" ) |
163 | palID = 2; |
164 | |
165 | for(uInt32 i = 0, j = 256; i < kNumColors - 256; ++i, ++j) |
166 | { |
167 | uInt8 r = (ourGUIColors[palID][i] >> 16) & 0xff; |
168 | uInt8 g = (ourGUIColors[palID][i] >> 8) & 0xff; |
169 | uInt8 b = ourGUIColors[palID][i] & 0xff; |
170 | |
171 | myPalette[j] = mapRGB(r, g, b); |
172 | } |
173 | FBSurface::setPalette(myPalette); |
174 | } |
175 | |
176 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
177 | FBInitStatus FrameBuffer::createDisplay(const string& title, |
178 | uInt32 width, uInt32 height, |
179 | bool honourHiDPI) |
180 | { |
181 | ++myInitializedCount; |
182 | myScreenTitle = title; |
183 | |
184 | // In HiDPI mode, all created displays must be scaled by 2x |
185 | if(honourHiDPI && hidpiEnabled()) |
186 | { |
187 | width *= hidpiScaleFactor(); |
188 | height *= hidpiScaleFactor(); |
189 | } |
190 | |
191 | // A 'windowed' system is defined as one where the window size can be |
192 | // larger than the screen size, as there's some sort of window manager |
193 | // that takes care of it (all current desktop systems fall in this category) |
194 | // However, some systems have no concept of windowing, and have hard limits |
195 | // on how large a window can be (ie, the size of the 'desktop' is the |
196 | // absolute upper limit on window size) |
197 | // |
198 | // If the WINDOWED_SUPPORT macro is defined, we treat the system as the |
199 | // former type; if not, as the latter type |
200 | |
201 | bool useFullscreen = false; |
202 | #ifdef WINDOWED_SUPPORT |
203 | // We assume that a desktop of at least minimum acceptable size means that |
204 | // we're running on a 'large' system, and the window size requirements |
205 | // can be relaxed |
206 | // Otherwise, we treat the system as if WINDOWED_SUPPORT is not defined |
207 | if(myDesktopSize.w < FBMinimum::Width && myDesktopSize.h < FBMinimum::Height && |
208 | (myDesktopSize.w < width || myDesktopSize.h < height)) |
209 | return FBInitStatus::FailTooLarge; |
210 | |
211 | useFullscreen = myOSystem.settings().getBool("fullscreen" ); |
212 | #else |
213 | // Make sure this mode is even possible |
214 | // We only really need to worry about it in non-windowed environments, |
215 | // where requesting a window that's too large will probably cause a crash |
216 | if(myDesktopSize.w < width || myDesktopSize.h < height) |
217 | return FBInitStatus::FailTooLarge; |
218 | |
219 | useFullscreen = true; |
220 | #endif |
221 | |
222 | // Set the available video modes for this framebuffer |
223 | setAvailableVidModes(width, height); |
224 | |
225 | // Initialize video subsystem (make sure we get a valid mode) |
226 | string pre_about = about(); |
227 | const FrameBuffer::VideoMode& mode = getSavedVidMode(useFullscreen); |
228 | if(width <= mode.screen.w && height <= mode.screen.h) |
229 | { |
230 | // Changing the video mode can take some time, during which the last |
231 | // sound played may get 'stuck' |
232 | // So we mute the sound until the operation completes |
233 | bool oldMuteState = myOSystem.sound().mute(true); |
234 | if(setVideoMode(myScreenTitle, mode)) |
235 | { |
236 | myImageRect = mode.image; |
237 | myScreenSize = mode.screen; |
238 | myScreenRect = Common::Rect(mode.screen); |
239 | |
240 | // Inform TIA surface about new mode |
241 | if(myOSystem.eventHandler().state() != EventHandlerState::LAUNCHER && |
242 | myOSystem.eventHandler().state() != EventHandlerState::DEBUGGER) |
243 | myTIASurface->initialize(myOSystem.console(), mode); |
244 | |
245 | // Did we get the requested fullscreen state? |
246 | myOSystem.settings().setValue("fullscreen" , fullScreen()); |
247 | resetSurfaces(); |
248 | setCursorState(); |
249 | |
250 | myOSystem.sound().mute(oldMuteState); |
251 | } |
252 | else |
253 | { |
254 | Logger::error("ERROR: Couldn't initialize video subsystem" ); |
255 | return FBInitStatus::FailNotSupported; |
256 | } |
257 | } |
258 | else |
259 | return FBInitStatus::FailTooLarge; |
260 | |
261 | #ifdef GUI_SUPPORT |
262 | // Erase any messages from a previous run |
263 | myMsg.counter = 0; |
264 | |
265 | // Create surfaces for TIA statistics and general messages |
266 | const GUI::Font& f = hidpiEnabled() ? infoFont() : font(); |
267 | myStatsMsg.color = kColorInfo; |
268 | myStatsMsg.w = f.getMaxCharWidth() * 40 + 3; |
269 | myStatsMsg.h = (f.getFontHeight() + 2) * 3; |
270 | |
271 | if(!myStatsMsg.surface) |
272 | { |
273 | myStatsMsg.surface = allocateSurface(myStatsMsg.w, myStatsMsg.h); |
274 | myStatsMsg.surface->attributes().blending = true; |
275 | myStatsMsg.surface->attributes().blendalpha = 92; //aligned with TimeMachineDialog |
276 | myStatsMsg.surface->applyAttributes(); |
277 | } |
278 | |
279 | if(!myMsg.surface) |
280 | myMsg.surface = allocateSurface(FBMinimum::Width, font().getFontHeight()+10); |
281 | #endif |
282 | |
283 | // Print initial usage message, but only print it later if the status has changed |
284 | if(myInitializedCount == 1) |
285 | { |
286 | Logger::info(about()); |
287 | } |
288 | else |
289 | { |
290 | string post_about = about(); |
291 | if(post_about != pre_about) |
292 | Logger::info(post_about); |
293 | } |
294 | |
295 | return FBInitStatus::Success; |
296 | } |
297 | |
298 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
299 | void FrameBuffer::update(bool force) |
300 | { |
301 | // Onscreen messages are a special case and require different handling than |
302 | // other objects; they aren't UI dialogs in the normal sense nor are they |
303 | // TIA images, and they need to be rendered on top of everything |
304 | // The logic is split in two pieces: |
305 | // - at the top of ::update(), to determine whether underlying dialogs |
306 | // need to be force-redrawn |
307 | // - at the bottom of ::update(), to actually draw them (this must come |
308 | // last, since they are always drawn on top of everything else). |
309 | |
310 | // Full rendering is required when messages are enabled |
311 | force |= myMsg.counter >= 0; |
312 | |
313 | // Detect when a message has been turned off; one last redraw is required |
314 | // in this case, to draw over the area that the message occupied |
315 | if(myMsg.counter == 0) |
316 | myMsg.counter = -1; |
317 | |
318 | switch(myOSystem.eventHandler().state()) |
319 | { |
320 | case EventHandlerState::NONE: |
321 | case EventHandlerState::EMULATION: |
322 | // Do nothing; emulation mode is handled separately (see below) |
323 | return; |
324 | |
325 | case EventHandlerState::PAUSE: |
326 | { |
327 | // Show a pause message immediately and then every 7 seconds |
328 | if(myPausedCount-- <= 0) |
329 | { |
330 | myPausedCount = uInt32(7 * myOSystem.frameRate()); |
331 | showMessage("Paused" , MessagePosition::MiddleCenter); |
332 | } |
333 | if(force) |
334 | myTIASurface->render(); |
335 | |
336 | break; // EventHandlerState::PAUSE |
337 | } |
338 | |
339 | #ifdef GUI_SUPPORT |
340 | case EventHandlerState::OPTIONSMENU: |
341 | { |
342 | force |= myOSystem.menu().needsRedraw(); |
343 | if(force) |
344 | { |
345 | clear(); |
346 | myTIASurface->render(); |
347 | myOSystem.menu().draw(force); |
348 | } |
349 | break; // EventHandlerState::OPTIONSMENU |
350 | } |
351 | |
352 | case EventHandlerState::CMDMENU: |
353 | { |
354 | force |= myOSystem.commandMenu().needsRedraw(); |
355 | if(force) |
356 | { |
357 | clear(); |
358 | myTIASurface->render(); |
359 | myOSystem.commandMenu().draw(force); |
360 | } |
361 | break; // EventHandlerState::CMDMENU |
362 | } |
363 | |
364 | case EventHandlerState::TIMEMACHINE: |
365 | { |
366 | force |= myOSystem.timeMachine().needsRedraw(); |
367 | if(force) |
368 | { |
369 | clear(); |
370 | myTIASurface->render(); |
371 | myOSystem.timeMachine().draw(force); |
372 | } |
373 | break; // EventHandlerState::TIMEMACHINE |
374 | } |
375 | |
376 | case EventHandlerState::LAUNCHER: |
377 | { |
378 | force |= myOSystem.launcher().needsRedraw(); |
379 | if(force) |
380 | { |
381 | clear(); |
382 | myOSystem.launcher().draw(force); |
383 | } |
384 | break; // EventHandlerState::LAUNCHER |
385 | } |
386 | #endif |
387 | |
388 | #ifdef DEBUGGER_SUPPORT |
389 | case EventHandlerState::DEBUGGER: |
390 | { |
391 | force |= myOSystem.debugger().needsRedraw(); |
392 | if(force) |
393 | { |
394 | clear(); |
395 | myOSystem.debugger().draw(force); |
396 | } |
397 | break; // EventHandlerState::DEBUGGER |
398 | } |
399 | #endif |
400 | default: |
401 | break; |
402 | } |
403 | |
404 | // Draw any pending messages |
405 | // The logic here determines whether to draw the message |
406 | // If the message is to be disabled, logic inside the draw method |
407 | // indicates that, and then the code at the top of this method sees |
408 | // the change and redraws everything |
409 | if(myMsg.enabled) |
410 | force |= drawMessage(); |
411 | |
412 | // Push buffers to screen only when necessary |
413 | if(force) |
414 | renderToScreen(); |
415 | } |
416 | |
417 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
418 | void FrameBuffer::updateInEmulationMode(float framesPerSecond) |
419 | { |
420 | // Update method that is specifically tailored to emulation mode |
421 | // Typically called from a thread, so it needs to be separate from |
422 | // the normal update() method |
423 | // |
424 | // We don't worry about selective rendering here; the rendering |
425 | // always happens at the full framerate |
426 | |
427 | clear(); // TODO - test this: it may cause slowdowns on older systems |
428 | myTIASurface->render(); |
429 | |
430 | // Show frame statistics |
431 | if(myStatsMsg.enabled) |
432 | drawFrameStats(framesPerSecond); |
433 | |
434 | myLastScanlines = myOSystem.console().tia().frameBufferScanlinesLastFrame(); |
435 | myPausedCount = 0; |
436 | |
437 | // Draw any pending messages |
438 | if(myMsg.enabled) |
439 | drawMessage(); |
440 | |
441 | // Push buffers to screen |
442 | renderToScreen(); |
443 | } |
444 | |
445 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
446 | void FrameBuffer::showMessage(const string& message, MessagePosition position, |
447 | bool force) |
448 | { |
449 | #ifdef GUI_SUPPORT |
450 | // Only show messages if they've been enabled |
451 | if(myMsg.surface == nullptr || !(force || myOSystem.settings().getBool("uimessages" ))) |
452 | return; |
453 | |
454 | // Precompute the message coordinates |
455 | myMsg.text = message; |
456 | myMsg.counter = uInt32(myOSystem.frameRate()) << 1; // Show message for 2 seconds |
457 | if(myMsg.counter == 0) myMsg.counter = 60; |
458 | myMsg.color = kBtnTextColor; |
459 | |
460 | myMsg.w = font().getStringWidth(myMsg.text) + 10; |
461 | myMsg.h = font().getFontHeight() + 8; |
462 | myMsg.surface->setSrcSize(myMsg.w, myMsg.h); |
463 | myMsg.surface->setDstSize(myMsg.w * hidpiScaleFactor(), myMsg.h * hidpiScaleFactor()); |
464 | myMsg.position = position; |
465 | myMsg.enabled = true; |
466 | #endif |
467 | } |
468 | |
469 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
470 | void FrameBuffer::drawFrameStats(float framesPerSecond) |
471 | { |
472 | #ifdef GUI_SUPPORT |
473 | const ConsoleInfo& info = myOSystem.console().about(); |
474 | int xPos = 2, yPos = 0; |
475 | const GUI::Font& f = hidpiEnabled() ? infoFont() : font(); |
476 | const int dy = f.getFontHeight() + 2; |
477 | |
478 | ostringstream ss; |
479 | |
480 | myStatsMsg.surface->invalidate(); |
481 | |
482 | // draw scanlines |
483 | ColorId color = myOSystem.console().tia().frameBufferScanlinesLastFrame() != myLastScanlines ? |
484 | kDbgColorRed : myStatsMsg.color; |
485 | |
486 | ss |
487 | << myOSystem.console().tia().frameBufferScanlinesLastFrame() |
488 | << " / " |
489 | << std::fixed << std::setprecision(1) << myOSystem.console().getFramerate() |
490 | << "Hz => " |
491 | << info.DisplayFormat; |
492 | |
493 | myStatsMsg.surface->drawString(f, ss.str(), xPos, yPos, |
494 | myStatsMsg.w, color, TextAlign::Left, 0, true, kBGColor); |
495 | |
496 | yPos += dy; |
497 | ss.str("" ); |
498 | |
499 | ss |
500 | << std::fixed << std::setprecision(1) << framesPerSecond |
501 | << "fps @ " |
502 | << std::fixed << std::setprecision(0) << 100 * myOSystem.settings().getFloat("speed" ) |
503 | << "% speed" ; |
504 | |
505 | myStatsMsg.surface->drawString(f, ss.str(), xPos, yPos, |
506 | myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor); |
507 | |
508 | yPos += dy; |
509 | ss.str("" ); |
510 | |
511 | ss << info.BankSwitch; |
512 | if (myOSystem.settings().getBool("dev.settings" )) ss << "| Developer" ; |
513 | |
514 | myStatsMsg.surface->drawString(f, ss.str(), xPos, yPos, |
515 | myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor); |
516 | |
517 | myStatsMsg.surface->setDstPos(myImageRect.x() + 10, myImageRect.y() + 8); |
518 | myStatsMsg.surface->setDstSize(myStatsMsg.w * hidpiScaleFactor(), |
519 | myStatsMsg.h * hidpiScaleFactor()); |
520 | myStatsMsg.surface->render(); |
521 | #endif |
522 | } |
523 | |
524 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
525 | void FrameBuffer::toggleFrameStats() |
526 | { |
527 | showFrameStats(!myStatsEnabled); |
528 | myOSystem.settings().setValue( |
529 | myOSystem.settings().getBool("dev.settings" ) ? "dev.stats" : "plr.stats" , myStatsEnabled); |
530 | } |
531 | |
532 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
533 | void FrameBuffer::showFrameStats(bool enable) |
534 | { |
535 | myStatsEnabled = myStatsMsg.enabled = enable; |
536 | } |
537 | |
538 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
539 | void FrameBuffer::enableMessages(bool enable) |
540 | { |
541 | if(enable) |
542 | { |
543 | // Only re-enable frame stats if they were already enabled before |
544 | myStatsMsg.enabled = myStatsEnabled; |
545 | } |
546 | else |
547 | { |
548 | // Temporarily disable frame stats |
549 | myStatsMsg.enabled = false; |
550 | |
551 | // Erase old messages on the screen |
552 | myMsg.enabled = false; |
553 | myMsg.counter = 0; |
554 | update(true); // Force update immediately |
555 | } |
556 | } |
557 | |
558 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
559 | inline bool FrameBuffer::drawMessage() |
560 | { |
561 | #ifdef GUI_SUPPORT |
562 | // Either erase the entire message (when time is reached), |
563 | // or show again this frame |
564 | if(myMsg.counter == 0) |
565 | { |
566 | myMsg.enabled = false; |
567 | return true; |
568 | } |
569 | else if(myMsg.counter < 0) |
570 | { |
571 | myMsg.enabled = false; |
572 | return false; |
573 | } |
574 | |
575 | // Draw the bounded box and text |
576 | const Common::Rect& dst = myMsg.surface->dstRect(); |
577 | |
578 | switch(myMsg.position) |
579 | { |
580 | case MessagePosition::TopLeft: |
581 | myMsg.x = 5; |
582 | myMsg.y = 5; |
583 | break; |
584 | |
585 | case MessagePosition::TopCenter: |
586 | myMsg.x = (myImageRect.w() - dst.w()) >> 1; |
587 | myMsg.y = 5; |
588 | break; |
589 | |
590 | case MessagePosition::TopRight: |
591 | myMsg.x = myImageRect.w() - dst.w() - 5; |
592 | myMsg.y = 5; |
593 | break; |
594 | |
595 | case MessagePosition::MiddleLeft: |
596 | myMsg.x = 5; |
597 | myMsg.y = (myImageRect.h() - dst.h()) >> 1; |
598 | break; |
599 | |
600 | case MessagePosition::MiddleCenter: |
601 | myMsg.x = (myImageRect.w() - dst.w()) >> 1; |
602 | myMsg.y = (myImageRect.h() - dst.h()) >> 1; |
603 | break; |
604 | |
605 | case MessagePosition::MiddleRight: |
606 | myMsg.x = myImageRect.w() - dst.w() - 5; |
607 | myMsg.y = (myImageRect.h() - dst.h()) >> 1; |
608 | break; |
609 | |
610 | case MessagePosition::BottomLeft: |
611 | myMsg.x = 5; |
612 | myMsg.y = myImageRect.h() - dst.h() - 5; |
613 | break; |
614 | |
615 | case MessagePosition::BottomCenter: |
616 | myMsg.x = (myImageRect.w() - dst.w()) >> 1; |
617 | myMsg.y = myImageRect.h() - dst.h() - 5; |
618 | break; |
619 | |
620 | case MessagePosition::BottomRight: |
621 | myMsg.x = myImageRect.w() - dst.w() - 5; |
622 | myMsg.y = myImageRect.h() - dst.h() - 5; |
623 | break; |
624 | } |
625 | |
626 | myMsg.surface->setDstPos(myMsg.x + myImageRect.x(), myMsg.y + myImageRect.y()); |
627 | myMsg.surface->fillRect(1, 1, myMsg.w-2, myMsg.h-2, kBtnColor); |
628 | myMsg.surface->frameRect(0, 0, myMsg.w, myMsg.h, kColor); |
629 | myMsg.surface->drawString(font(), myMsg.text, 5, 4, |
630 | myMsg.w, myMsg.color, TextAlign::Left); |
631 | myMsg.surface->render(); |
632 | myMsg.counter--; |
633 | #endif |
634 | |
635 | return true; |
636 | } |
637 | |
638 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
639 | void FrameBuffer::setPauseDelay() |
640 | { |
641 | myPausedCount = uInt32(2 * myOSystem.frameRate()); |
642 | } |
643 | |
644 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
645 | shared_ptr<FBSurface> FrameBuffer::allocateSurface(int w, int h, const uInt32* data) |
646 | { |
647 | // Add new surface to the list |
648 | mySurfaceList.push_back(createSurface(w, h, data)); |
649 | |
650 | // And return a pointer to it (pointer should be treated read-only) |
651 | return mySurfaceList.at(mySurfaceList.size() - 1); |
652 | } |
653 | |
654 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
655 | void FrameBuffer::freeSurfaces() |
656 | { |
657 | for(auto& s: mySurfaceList) |
658 | s->free(); |
659 | } |
660 | |
661 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
662 | void FrameBuffer::reloadSurfaces() |
663 | { |
664 | for(auto& s: mySurfaceList) |
665 | s->reload(); |
666 | } |
667 | |
668 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
669 | void FrameBuffer::resetSurfaces() |
670 | { |
671 | // Free all resources for each surface, then reload them |
672 | // Due to possible timing and/or synchronization issues, all free()'s |
673 | // are done first, then all reload()'s |
674 | // Any derived FrameBuffer classes that call this method should be |
675 | // aware of these restrictions, and act accordingly |
676 | |
677 | freeSurfaces(); |
678 | reloadSurfaces(); |
679 | |
680 | update(true); // force full update |
681 | } |
682 | |
683 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
684 | void FrameBuffer::setPalette(const uInt32* raw_palette) |
685 | { |
686 | // Set palette for normal fill |
687 | for(int i = 0; i < 256; ++i) |
688 | { |
689 | uInt8 r = (raw_palette[i] >> 16) & 0xff; |
690 | uInt8 g = (raw_palette[i] >> 8) & 0xff; |
691 | uInt8 b = raw_palette[i] & 0xff; |
692 | |
693 | myPalette[i] = mapRGB(r, g, b); |
694 | } |
695 | |
696 | // Let the TIA surface know about the new palette |
697 | myTIASurface->setPalette(myPalette, raw_palette); |
698 | } |
699 | |
700 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
701 | void FrameBuffer::stateChanged(EventHandlerState state) |
702 | { |
703 | // Make sure any onscreen messages are removed |
704 | myMsg.enabled = false; |
705 | myMsg.counter = 0; |
706 | |
707 | update(true); // force full update |
708 | } |
709 | |
710 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
711 | void FrameBuffer::setFullscreen(bool enable) |
712 | { |
713 | #ifdef WINDOWED_SUPPORT |
714 | // Switching between fullscreen and windowed modes will invariably mean |
715 | // that the 'window' resolution changes. Currently, dialogs are not |
716 | // able to resize themselves when they are actively being shown |
717 | // (they would have to be closed and then re-opened, etc). |
718 | // For now, we simply disallow screen switches in such modes |
719 | switch(myOSystem.eventHandler().state()) |
720 | { |
721 | case EventHandlerState::EMULATION: |
722 | case EventHandlerState::PAUSE: |
723 | break; // continue with processing (aka, allow a mode switch) |
724 | case EventHandlerState::DEBUGGER: |
725 | case EventHandlerState::LAUNCHER: |
726 | if(myOSystem.eventHandler().overlay().baseDialogIsActive()) |
727 | break; // allow a mode switch when there is only one dialog |
728 | [[fallthrough]]; |
729 | default: |
730 | return; |
731 | } |
732 | |
733 | // Changing the video mode can take some time, during which the last |
734 | // sound played may get 'stuck' |
735 | // So we mute the sound until the operation completes |
736 | bool oldMuteState = myOSystem.sound().mute(true); |
737 | |
738 | const VideoMode& mode = getSavedVidMode(enable); |
739 | if(setVideoMode(myScreenTitle, mode)) |
740 | { |
741 | myImageRect = mode.image; |
742 | myScreenSize = mode.screen; |
743 | myScreenRect = Common::Rect(mode.screen); |
744 | |
745 | // Inform TIA surface about new mode |
746 | if(myOSystem.eventHandler().state() != EventHandlerState::LAUNCHER && |
747 | myOSystem.eventHandler().state() != EventHandlerState::DEBUGGER) |
748 | myTIASurface->initialize(myOSystem.console(), mode); |
749 | |
750 | // Did we get the requested fullscreen state? |
751 | myOSystem.settings().setValue("fullscreen" , fullScreen()); |
752 | resetSurfaces(); |
753 | setCursorState(); |
754 | } |
755 | myOSystem.sound().mute(oldMuteState); |
756 | #endif |
757 | } |
758 | |
759 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
760 | void FrameBuffer::toggleFullscreen() |
761 | { |
762 | setFullscreen(!fullScreen()); |
763 | } |
764 | |
765 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
766 | void FrameBuffer::changeOverscan(int direction) |
767 | { |
768 | if (fullScreen()) |
769 | { |
770 | int oldOverscan = myOSystem.settings().getInt("tia.fs_overscan" ); |
771 | int overscan = BSPF::clamp(oldOverscan + direction, 0, 10); |
772 | |
773 | if (overscan != oldOverscan) |
774 | { |
775 | myOSystem.settings().setValue("tia.fs_overscan" , overscan); |
776 | |
777 | // issue a complete framebuffer re-initialization |
778 | myOSystem.createFrameBuffer(); |
779 | } |
780 | ostringstream msg; |
781 | msg << "Overscan at " << overscan << "%" ; |
782 | showMessage(msg.str()); |
783 | } |
784 | } |
785 | |
786 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
787 | bool FrameBuffer::changeVidMode(int direction) |
788 | { |
789 | EventHandlerState state = myOSystem.eventHandler().state(); |
790 | bool tiaMode = (state != EventHandlerState::DEBUGGER && |
791 | state != EventHandlerState::LAUNCHER); |
792 | |
793 | // Only applicable when in TIA/emulation mode |
794 | if(!tiaMode) |
795 | return false; |
796 | |
797 | if(direction == +1) |
798 | myCurrentModeList->next(); |
799 | else if(direction == -1) |
800 | myCurrentModeList->previous(); |
801 | else |
802 | return false; |
803 | |
804 | // Changing the video mode can take some time, during which the last |
805 | // sound played may get 'stuck' |
806 | // So we mute the sound until the operation completes |
807 | bool oldMuteState = myOSystem.sound().mute(true); |
808 | |
809 | const VideoMode& mode = myCurrentModeList->current(); |
810 | if(setVideoMode(myScreenTitle, mode)) |
811 | { |
812 | myImageRect = mode.image; |
813 | myScreenSize = mode.screen; |
814 | myScreenRect = Common::Rect(mode.screen); |
815 | |
816 | // Inform TIA surface about new mode |
817 | myTIASurface->initialize(myOSystem.console(), mode); |
818 | |
819 | resetSurfaces(); |
820 | showMessage(mode.description); |
821 | myOSystem.sound().mute(oldMuteState); |
822 | |
823 | if(fullScreen()) |
824 | myOSystem.settings().setValue("tia.fs_stretch" , |
825 | mode.stretch == VideoMode::Stretch::Fill); |
826 | else |
827 | myOSystem.settings().setValue("tia.zoom" , mode.zoom); |
828 | |
829 | return true; |
830 | } |
831 | myOSystem.sound().mute(oldMuteState); |
832 | |
833 | return false; |
834 | } |
835 | |
836 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
837 | void FrameBuffer::setCursorState() |
838 | { |
839 | // Always grab mouse in emulation (if enabled) and emulating a controller |
840 | // that always uses the mouse |
841 | bool emulation = |
842 | myOSystem.eventHandler().state() == EventHandlerState::EMULATION; |
843 | bool analog = myOSystem.hasConsole() ? |
844 | (myOSystem.console().leftController().isAnalog() || |
845 | myOSystem.console().rightController().isAnalog()) : false; |
846 | bool alwaysUseMouse = BSPF::equalsIgnoreCase("always" , myOSystem.settings().getString("usemouse" )); |
847 | |
848 | grabMouse(emulation && (analog || alwaysUseMouse) && myGrabMouse); |
849 | |
850 | // Show/hide cursor in UI/emulation mode based on 'cursor' setting |
851 | switch(myOSystem.settings().getInt("cursor" )) |
852 | { |
853 | case 0: showCursor(false); break; |
854 | case 1: showCursor(emulation); break; |
855 | case 2: showCursor(!emulation); break; |
856 | case 3: showCursor(true); break; |
857 | } |
858 | } |
859 | |
860 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
861 | void FrameBuffer::enableGrabMouse(bool enable) |
862 | { |
863 | myGrabMouse = enable; |
864 | setCursorState(); |
865 | } |
866 | |
867 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
868 | void FrameBuffer::toggleGrabMouse() |
869 | { |
870 | myGrabMouse = !myGrabMouse; |
871 | setCursorState(); |
872 | myOSystem.settings().setValue("grabmouse" , myGrabMouse); |
873 | } |
874 | |
875 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
876 | float FrameBuffer::maxZoomForScreen(uInt32 baseWidth, uInt32 baseHeight, |
877 | uInt32 screenWidth, uInt32 screenHeight) const |
878 | { |
879 | float multiplier = 1; |
880 | for(;;) |
881 | { |
882 | // Figure out the zoomed size of the window |
883 | uInt32 width = baseWidth * multiplier; |
884 | uInt32 height = baseHeight * multiplier; |
885 | |
886 | if((width > screenWidth) || (height > screenHeight)) |
887 | break; |
888 | |
889 | multiplier += ZOOM_STEPS; |
890 | } |
891 | return multiplier > 1 ? multiplier - ZOOM_STEPS : 1; |
892 | } |
893 | |
894 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
895 | void FrameBuffer::setAvailableVidModes(uInt32 baseWidth, uInt32 baseHeight) |
896 | { |
897 | myWindowedModeList.clear(); |
898 | |
899 | for(auto& mode: myFullscreenModeLists) |
900 | mode.clear(); |
901 | for(size_t i = myFullscreenModeLists.size(); i < myFullscreenDisplays.size(); ++i) |
902 | myFullscreenModeLists.push_back(VideoModeList()); |
903 | |
904 | // Check if zooming is allowed for this state (currently only allowed |
905 | // for TIA screens) |
906 | EventHandlerState state = myOSystem.eventHandler().state(); |
907 | bool tiaMode = (state != EventHandlerState::DEBUGGER && |
908 | state != EventHandlerState::LAUNCHER); |
909 | float overscan = 1 - myOSystem.settings().getInt("tia.fs_overscan" ) / 100.0; |
910 | |
911 | // TIA mode allows zooming at integral factors in windowed modes, |
912 | // and also non-integral factors in fullscreen mode |
913 | if(tiaMode) |
914 | { |
915 | // TIA windowed modes |
916 | uInt32 minZoom = supportedTIAMinZoom(); |
917 | myTIAMaxZoom = maxZoomForScreen(baseWidth, baseHeight, |
918 | myAbsDesktopSize.w, myAbsDesktopSize.h); |
919 | |
920 | #if 0 // FIXME - does this apply any longer?? |
921 | // Aspect ratio |
922 | uInt32 aspect = myOSystem.settings().getInt( |
923 | myOSystem.console().tia().frameLayout() == FrameLayout::ntsc ? |
924 | "tia.aspectn" : "tia.aspectp" ); |
925 | #endif |
926 | |
927 | // Determine all zoom levels |
928 | for(float zoom = minZoom; zoom <= myTIAMaxZoom; zoom += ZOOM_STEPS) |
929 | { |
930 | ostringstream desc; |
931 | desc << "Zoom " << zoom << "x" ; |
932 | |
933 | VideoMode mode(baseWidth*zoom, baseHeight*zoom, baseWidth*zoom, baseHeight*zoom, |
934 | VideoMode::Stretch::Fill, 1.0, desc.str(), zoom); |
935 | myWindowedModeList.add(mode); |
936 | } |
937 | |
938 | // TIA fullscreen mode |
939 | for(uInt32 i = 0; i < myFullscreenDisplays.size(); ++i) |
940 | { |
941 | myTIAMaxZoom = maxZoomForScreen(baseWidth, baseHeight, |
942 | myFullscreenDisplays[i].w * overscan, |
943 | myFullscreenDisplays[i].h * overscan); |
944 | |
945 | // Add both normal aspect and filled modes |
946 | // It's easier to define them both now, and simply switch between |
947 | // them when necessary |
948 | VideoMode mode1(baseWidth * myTIAMaxZoom, baseHeight * myTIAMaxZoom, |
949 | myFullscreenDisplays[i].w, myFullscreenDisplays[i].h, |
950 | VideoMode::Stretch::Preserve, overscan, |
951 | "Preserve aspect, no stretch" , myTIAMaxZoom, i); |
952 | myFullscreenModeLists[i].add(mode1); |
953 | VideoMode mode2(baseWidth * myTIAMaxZoom, baseHeight * myTIAMaxZoom, |
954 | myFullscreenDisplays[i].w, myFullscreenDisplays[i].h, |
955 | VideoMode::Stretch::Fill, overscan, |
956 | "Ignore aspect, full stretch" , myTIAMaxZoom, i); |
957 | myFullscreenModeLists[i].add(mode2); |
958 | } |
959 | } |
960 | else // UI mode |
961 | { |
962 | // Windowed and fullscreen mode differ only in screen size |
963 | myWindowedModeList.add( |
964 | VideoMode(baseWidth, baseHeight, baseWidth, baseHeight, |
965 | VideoMode::Stretch::None) |
966 | ); |
967 | for(uInt32 i = 0; i < myFullscreenDisplays.size(); ++i) |
968 | { |
969 | myFullscreenModeLists[i].add( |
970 | VideoMode(baseWidth, baseHeight, |
971 | myFullscreenDisplays[i].w, myFullscreenDisplays[i].h, |
972 | VideoMode::Stretch::None, 1.0, "" , 1, i) |
973 | ); |
974 | } |
975 | } |
976 | } |
977 | |
978 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
979 | const FrameBuffer::VideoMode& FrameBuffer::getSavedVidMode(bool fullscreen) |
980 | { |
981 | if(fullscreen) |
982 | { |
983 | Int32 i = getCurrentDisplayIndex(); |
984 | if(i < 0) |
985 | { |
986 | // default to the first display |
987 | i = 0; |
988 | } |
989 | myCurrentModeList = &myFullscreenModeLists[i]; |
990 | } |
991 | else |
992 | myCurrentModeList = &myWindowedModeList; |
993 | |
994 | // Now select the best resolution depending on the state |
995 | // UI modes (launcher and debugger) have only one supported resolution |
996 | // so the 'current' one is the only valid one |
997 | EventHandlerState state = myOSystem.eventHandler().state(); |
998 | if(state == EventHandlerState::DEBUGGER || state == EventHandlerState::LAUNCHER) |
999 | myCurrentModeList->setByZoom(1); |
1000 | else // TIA mode |
1001 | { |
1002 | if(fullscreen) |
1003 | myCurrentModeList->setByStretch(myOSystem.settings().getBool("tia.fs_stretch" ) |
1004 | ? VideoMode::Stretch::Fill : VideoMode::Stretch::Preserve); |
1005 | else |
1006 | myCurrentModeList->setByZoom(myOSystem.settings().getFloat("tia.zoom" )); |
1007 | } |
1008 | |
1009 | return myCurrentModeList->current(); |
1010 | } |
1011 | |
1012 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1013 | // |
1014 | // VideoMode implementation |
1015 | // |
1016 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1017 | FrameBuffer::VideoMode::VideoMode() |
1018 | : stretch(VideoMode::Stretch::None), |
1019 | description("" ), |
1020 | zoom(1), |
1021 | fsIndex(-1) |
1022 | { |
1023 | } |
1024 | |
1025 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1026 | FrameBuffer::VideoMode::VideoMode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, |
1027 | Stretch smode, float overscan, const string& desc, |
1028 | float zoomLevel, Int32 fsindex) |
1029 | : stretch(smode), |
1030 | description(desc), |
1031 | zoom(zoomLevel), |
1032 | fsIndex(fsindex) |
1033 | { |
1034 | // First set default size and positioning |
1035 | sw = std::max(sw, TIAConstants::viewableWidth); |
1036 | sh = std::max(sh, TIAConstants::viewableHeight); |
1037 | iw = std::min(iw, sw); |
1038 | ih = std::min(ih, sh); |
1039 | int ix = (sw - iw) >> 1; |
1040 | int iy = (sh - ih) >> 1; |
1041 | image = Common::Rect(ix, iy, ix+iw, iy+ih); |
1042 | screen = Common::Size(sw, sh); |
1043 | |
1044 | // Now resize based on windowed/fullscreen mode and stretch factor |
1045 | iw = image.w(); |
1046 | ih = image.h(); |
1047 | |
1048 | if(fsIndex != -1) |
1049 | { |
1050 | switch(stretch) |
1051 | { |
1052 | case Stretch::Preserve: |
1053 | { |
1054 | float stretchFactor = 1.0; |
1055 | float scaleX = float(iw) / screen.w; |
1056 | float scaleY = float(ih) / screen.h; |
1057 | |
1058 | // Scale to all available space, keep aspect correct |
1059 | if(scaleX > scaleY) |
1060 | stretchFactor = float(screen.w) / iw; |
1061 | else |
1062 | stretchFactor = float(screen.h) / ih; |
1063 | |
1064 | iw = uInt32(stretchFactor * iw) * overscan; |
1065 | ih = uInt32(stretchFactor * ih) * overscan; |
1066 | break; |
1067 | } |
1068 | |
1069 | case Stretch::Fill: |
1070 | // Scale to all available space |
1071 | iw = screen.w * overscan; |
1072 | ih = screen.h * overscan; |
1073 | break; |
1074 | |
1075 | case Stretch::None: |
1076 | // Don't do any scaling at all, but obey overscan |
1077 | iw = std::min(iw, screen.w) * overscan; |
1078 | ih = std::min(ih, screen.h) * overscan; |
1079 | break; |
1080 | } |
1081 | } |
1082 | else |
1083 | { |
1084 | // In windowed mode, currently the size is scaled to the screen |
1085 | // TODO - this may be updated if/when we allow variable-sized windows |
1086 | switch(stretch) |
1087 | { |
1088 | case Stretch::Preserve: |
1089 | case Stretch::Fill: |
1090 | screen.w = iw; |
1091 | screen.h = ih; |
1092 | break; |
1093 | case Stretch::None: |
1094 | break; // Do not change image or screen rects whatsoever |
1095 | } |
1096 | } |
1097 | |
1098 | // Now re-calculate the dimensions |
1099 | iw = std::min(iw, screen.w); |
1100 | ih = std::min(ih, screen.h); |
1101 | |
1102 | image.moveTo((screen.w - iw) >> 1, (screen.h - ih) >> 1); |
1103 | image.setWidth(iw); |
1104 | image.setHeight(ih); |
1105 | } |
1106 | |
1107 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1108 | // |
1109 | // VideoModeList implementation |
1110 | // |
1111 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1112 | FrameBuffer::VideoModeList::VideoModeList() |
1113 | : myIdx(-1) |
1114 | { |
1115 | } |
1116 | |
1117 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1118 | void FrameBuffer::VideoModeList::add(const VideoMode& mode) |
1119 | { |
1120 | myModeList.emplace_back(mode); |
1121 | } |
1122 | |
1123 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1124 | void FrameBuffer::VideoModeList::clear() |
1125 | { |
1126 | myModeList.clear(); |
1127 | } |
1128 | |
1129 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1130 | bool FrameBuffer::VideoModeList::empty() const |
1131 | { |
1132 | return myModeList.empty(); |
1133 | } |
1134 | |
1135 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1136 | uInt32 FrameBuffer::VideoModeList::size() const |
1137 | { |
1138 | return uInt32(myModeList.size()); |
1139 | } |
1140 | |
1141 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1142 | void FrameBuffer::VideoModeList::previous() |
1143 | { |
1144 | --myIdx; |
1145 | if(myIdx < 0) myIdx = int(myModeList.size()) - 1; |
1146 | } |
1147 | |
1148 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1149 | const FrameBuffer::VideoMode& FrameBuffer::VideoModeList::current() const |
1150 | { |
1151 | return myModeList[myIdx]; |
1152 | } |
1153 | |
1154 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1155 | void FrameBuffer::VideoModeList::next() |
1156 | { |
1157 | myIdx = (myIdx + 1) % myModeList.size(); |
1158 | } |
1159 | |
1160 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1161 | void FrameBuffer::VideoModeList::setByZoom(float zoom) |
1162 | { |
1163 | for(uInt32 i = 0; i < myModeList.size(); ++i) |
1164 | { |
1165 | if(myModeList[i].zoom == zoom) |
1166 | { |
1167 | myIdx = i; |
1168 | return; |
1169 | } |
1170 | } |
1171 | myIdx = 0; |
1172 | } |
1173 | |
1174 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1175 | void FrameBuffer::VideoModeList::setByStretch(FrameBuffer::VideoMode::Stretch stretch) |
1176 | { |
1177 | for(uInt32 i = 0; i < myModeList.size(); ++i) |
1178 | { |
1179 | if(myModeList[i].stretch == stretch) |
1180 | { |
1181 | myIdx = i; |
1182 | return; |
1183 | } |
1184 | } |
1185 | myIdx = 0; |
1186 | } |
1187 | |
1188 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1189 | /* |
1190 | Palette is defined as follows: |
1191 | *** Base colors *** |
1192 | kColor Normal foreground color (non-text) |
1193 | kBGColor Normal background color (non-text) |
1194 | kBGColorLo Disabled background color dark (non-text) |
1195 | kBGColorHi Disabled background color light (non-text) |
1196 | kShadowColor Item is disabled |
1197 | *** Text colors *** |
1198 | kTextColor Normal text color |
1199 | kTextColorHi Highlighted text color |
1200 | kTextColorEm Emphasized text color |
1201 | kTextColorInv Color for selected text |
1202 | *** UI elements (dialog and widgets) *** |
1203 | kDlgColor Dialog background |
1204 | kWidColor Widget background |
1205 | kWidColorHi Widget highlight color |
1206 | kWidFrameColor Border for currently selected widget |
1207 | *** Button colors *** |
1208 | kBtnColor Normal button background |
1209 | kBtnColorHi Highlighted button background |
1210 | kBtnBorderColor, |
1211 | kBtnBorderColorHi, |
1212 | kBtnTextColor Normal button font color |
1213 | kBtnTextColorHi Highlighted button font color |
1214 | *** Checkbox colors *** |
1215 | kCheckColor Color of 'X' in checkbox |
1216 | *** Scrollbar colors *** |
1217 | kScrollColor Normal scrollbar color |
1218 | kScrollColorHi Highlighted scrollbar color |
1219 | *** Debugger colors *** |
1220 | kDbgChangedColor Background color for changed cells |
1221 | kDbgChangedTextColor Text color for changed cells |
1222 | kDbgColorHi Highlighted color in debugger data cells |
1223 | kDbgColorRed Red color in debugger |
1224 | *** Slider colors *** |
1225 | kSliderColor Enabled slider |
1226 | kSliderColorHi Focussed slider |
1227 | kSliderBGColor Enabled slider background |
1228 | kSliderBGColorHi Focussed slider background |
1229 | kSliderBGColorLo Disabled slider background |
1230 | *** Other colors *** |
1231 | kColorInfo TIA output position color |
1232 | kColorTitleBar Title bar color |
1233 | kColorTitleText Title text color |
1234 | kColorTitleBarLo Disabled title bar color |
1235 | kColorTitleTextLo Disabled title text color |
1236 | */ |
1237 | uInt32 FrameBuffer::ourGUIColors[3][kNumColors-256] = { |
1238 | // Standard |
1239 | { 0x686868, 0x000000, 0xa38c61, 0xdccfa5, 0x404040, // base |
1240 | 0x000000, 0xac3410, 0x9f0000, 0xf0f0cf, // text |
1241 | 0xc9af7c, 0xf0f0cf, 0xd55941, 0xc80000, // UI elements |
1242 | 0xac3410, 0xd55941, 0x686868, 0xdccfa5, 0xf0f0cf, 0xf0f0cf, // buttons |
1243 | 0xac3410, // checkbox |
1244 | 0xac3410, 0xd55941, // scrollbar |
1245 | 0xc80000, 0xffff80, 0xc8c8ff, 0xc80000, // debugger |
1246 | 0xac3410, 0xd55941, 0xdccfa5, 0xf0f0cf, 0xa38c61, // slider |
1247 | 0xffffff, 0xac3410, 0xf0f0cf, 0x686868, 0xdccfa5 // other |
1248 | }, |
1249 | // Classic |
1250 | { 0x686868, 0x000000, 0x404040, 0x404040, 0x404040, // base |
1251 | 0x20a020, 0x00ff00, 0xc80000, 0x000000, // text |
1252 | 0x000000, 0x000000, 0x00ff00, 0xc80000, // UI elements |
1253 | 0x000000, 0x000000, 0x686868, 0x00ff00, 0x20a020, 0x00ff00, // buttons |
1254 | 0x20a020, // checkbox |
1255 | 0x20a020, 0x00ff00, // scrollbar |
1256 | 0xc80000, 0x00ff00, 0xc8c8ff, 0xc80000, // debugger |
1257 | 0x20a020, 0x00ff00, 0x404040, 0x686868, 0x404040, // slider |
1258 | 0x00ff00, 0x20a020, 0x000000, 0x686868, 0x404040 // other |
1259 | }, |
1260 | // Light |
1261 | { 0x808080, 0x000000, 0xc0c0c0, 0xe1e1e1, 0x333333, // base |
1262 | 0x000000, 0xBDDEF9, 0x0078d7, 0x000000, // text |
1263 | 0xf0f0f0, 0xffffff, 0x0078d7, 0x0f0f0f, // UI elements |
1264 | 0xe1e1e1, 0xe5f1fb, 0x808080, 0x0078d7, 0x000000, 0x000000, // buttons |
1265 | 0x333333, // checkbox |
1266 | 0xc0c0c0, 0x808080, // scrollbar |
1267 | 0xffc0c0, 0x000000, 0xe00000, 0xc00000, // debugger |
1268 | 0x333333, 0x0078d7, 0xc0c0c0, 0xffffff, 0xc0c0c0, // slider 0xBDDEF9| 0xe1e1e1 | 0xffffff |
1269 | 0xffffff, 0x333333, 0xf0f0f0, 0x808080, 0xc0c0c0 // other |
1270 | } |
1271 | }; |
1272 | |