From e691853f0e05e830abc267376cbf7280233173c2 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Wed, 25 Jul 2018 08:48:21 -0230 Subject: [PATCH] First pass at fixing superfluous re-draws in the UI. - This addresses issue 158, and reduces CPU usage to near 0% when no changes are happening - This returns the code to the same performance levels as version 3.x. --- src/common/FBSurfaceSDL2.cxx | 11 +-- src/common/FrameBufferSDL2.cxx | 16 ++-- src/common/FrameBufferSDL2.hxx | 8 +- src/debugger/Debugger.cxx | 2 +- src/debugger/gui/DebuggerDialog.cxx | 12 +++ src/debugger/gui/DebuggerDialog.hxx | 2 - src/emucore/EventHandler.cxx | 3 +- src/emucore/FBSurface.hxx | 13 +-- src/emucore/FrameBuffer.cxx | 124 +++++++++++++++++++--------- src/emucore/FrameBuffer.hxx | 16 ++-- src/emucore/OSystem.cxx | 4 +- src/emucore/TIASurface.cxx | 14 ---- src/gui/AboutDialog.cxx | 2 +- src/gui/AudioDialog.cxx | 5 +- src/gui/ComboDialog.cxx | 2 +- src/gui/ContextMenu.cxx | 69 +++++++--------- src/gui/Dialog.cxx | 117 ++++++++++++++------------ src/gui/Dialog.hxx | 11 ++- src/gui/DialogContainer.cxx | 49 +++++++---- src/gui/DialogContainer.hxx | 9 +- src/gui/GlobalPropsDialog.cxx | 2 - src/gui/GuiObject.hxx | 5 +- src/gui/HelpDialog.cxx | 2 - src/gui/InputDialog.cxx | 2 - src/gui/LauncherFilterDialog.cxx | 2 - src/gui/ListWidget.cxx | 7 +- src/gui/UIDialog.cxx | 2 - src/gui/VideoDialog.cxx | 4 - src/gui/Widget.cxx | 40 ++++++--- src/gui/Widget.hxx | 17 ++-- 30 files changed, 318 insertions(+), 254 deletions(-) diff --git a/src/common/FBSurfaceSDL2.cxx b/src/common/FBSurfaceSDL2.cxx index 385f4aff5..f0ff4b4da 100644 --- a/src/common/FBSurfaceSDL2.cxx +++ b/src/common/FBSurfaceSDL2.cxx @@ -125,20 +125,13 @@ void FBSurfaceSDL2::translateCoords(Int32& x, Int32& y) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool FBSurfaceSDL2::render() { - if(mySurfaceIsDirty && myIsVisible) + if(myIsVisible) { -//cerr << "src: x=" << mySrcR.x << ", y=" << mySrcR.y << ", w=" << mySrcR.w << ", h=" << mySrcR.h << endl; -//cerr << "dst: x=" << myDstR.x << ", y=" << myDstR.y << ", w=" << myDstR.w << ", h=" << myDstR.h << endl; - -//cerr << "render()\n"; if(myTexAccess == SDL_TEXTUREACCESS_STREAMING) SDL_UpdateTexture(myTexture, &mySrcR, mySurface->pixels, mySurface->pitch); SDL_RenderCopy(myFB.myRenderer, myTexture, &mySrcR, &myDstR); - mySurfaceIsDirty = false; - - // Let postFrameUpdate() know that a change has been made - return myFB.myDirtyFlag = true; + return true; } return false; } diff --git a/src/common/FrameBufferSDL2.cxx b/src/common/FrameBufferSDL2.cxx index ab1ee1f14..3e4c4840b 100644 --- a/src/common/FrameBufferSDL2.cxx +++ b/src/common/FrameBufferSDL2.cxx @@ -32,8 +32,7 @@ FrameBufferSDL2::FrameBufferSDL2(OSystem& osystem) : FrameBuffer(osystem), myWindow(nullptr), - myRenderer(nullptr), - myDirtyFlag(true) + myRenderer(nullptr) { // Initialize SDL2 context if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_JOYSTICK) < 0) @@ -275,7 +274,6 @@ string FrameBufferSDL2::about() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void FrameBufferSDL2::invalidate() { - myDirtyFlag = true; SDL_RenderClear(myRenderer); } @@ -302,14 +300,10 @@ bool FrameBufferSDL2::fullScreen() const } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBufferSDL2::postFrameUpdate() +void FrameBufferSDL2::renderToScreen() { - if(myDirtyFlag) - { - // Now show all changes made to the renderer - SDL_RenderPresent(myRenderer); - myDirtyFlag = false; - } + // Show all changes made to the renderer + SDL_RenderPresent(myRenderer); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -347,5 +341,5 @@ void FrameBufferSDL2::readPixels(uInt8* pixels, uInt32 pitch, void FrameBufferSDL2::clear() { invalidate(); - postFrameUpdate(); + renderToScreen(); } diff --git a/src/common/FrameBufferSDL2.hxx b/src/common/FrameBufferSDL2.hxx index 31c6871f1..c6f12d92b 100644 --- a/src/common/FrameBufferSDL2.hxx +++ b/src/common/FrameBufferSDL2.hxx @@ -164,9 +164,10 @@ class FrameBufferSDL2 : public FrameBuffer string about() const override; /** - This method is called after any drawing is done (per-frame). + This method must be called after all drawing is done, and indicates + that the buffers should be pushed to the physical screen. */ - void postFrameUpdate() override; + void renderToScreen() override; private: // The SDL video buffer @@ -176,9 +177,6 @@ class FrameBufferSDL2 : public FrameBuffer // Used by mapRGB (when palettes are created) SDL_PixelFormat* myPixelFormat; - // Indicates that the renderer has been modified, and should be redrawn - bool myDirtyFlag; - private: // Following constructors and assignment operators not supported FrameBufferSDL2() = delete; diff --git a/src/debugger/Debugger.cxx b/src/debugger/Debugger.cxx index 2ff9130ce..5606bf9f2 100644 --- a/src/debugger/Debugger.cxx +++ b/src/debugger/Debugger.cxx @@ -793,7 +793,7 @@ void Debugger::unlockSystem() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool Debugger::canExit() const { - return myDialogStack.top() == baseDialog(); + return !myDialogStack.empty() && myDialogStack.top() == baseDialog(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/debugger/gui/DebuggerDialog.cxx b/src/debugger/gui/DebuggerDialog.cxx index 861deff3f..28c14458e 100644 --- a/src/debugger/gui/DebuggerDialog.cxx +++ b/src/debugger/gui/DebuggerDialog.cxx @@ -221,72 +221,84 @@ void DebuggerDialog::handleCommand(CommandSender* sender, int cmd, void DebuggerDialog::doStep() { instance().debugger().parser().run("step"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doTrace() { instance().debugger().parser().run("trace"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doAdvance() { instance().debugger().parser().run("frame #1"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doScanlineAdvance() { instance().debugger().parser().run("scanline #1"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doRewind() { instance().debugger().parser().run("rewind"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doUnwind() { instance().debugger().parser().run("unwind"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doRewind10() { instance().debugger().parser().run("rewind #10"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doUnwind10() { instance().debugger().parser().run("unwind #10"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doRewindAll() { instance().debugger().parser().run("rewind #1000"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doUnwindAll() { instance().debugger().parser().run("unwind #1000"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doExitDebugger() { instance().debugger().parser().run("run"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doExitRom() { instance().debugger().parser().run("exitrom"); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/debugger/gui/DebuggerDialog.hxx b/src/debugger/gui/DebuggerDialog.hxx index a010203de..2f1a4c9f9 100644 --- a/src/debugger/gui/DebuggerDialog.hxx +++ b/src/debugger/gui/DebuggerDialog.hxx @@ -130,8 +130,6 @@ class DebuggerDialog : public Dialog ButtonWidget* myRewindButton; ButtonWidget* myUnwindButton; - //ButtonWidget* myOptionsButton; - unique_ptr myFatalError; unique_ptr myOptions; diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx index 4b8c5388a..673573d7c 100644 --- a/src/emucore/EventHandler.cxx +++ b/src/emucore/EventHandler.cxx @@ -283,7 +283,8 @@ void EventHandler::handleSystemEvent(SystemEvent e, int, int) switch(e) { case SystemEvent::WINDOW_EXPOSED: - myOSystem.frameBuffer().update(); + case SystemEvent::WINDOW_RESIZED: + myOSystem.frameBuffer().update(true); // force full update break; case SystemEvent::WINDOW_FOCUS_GAINED: diff --git a/src/emucore/FBSurface.hxx b/src/emucore/FBSurface.hxx index 21d06306f..73999cf25 100644 --- a/src/emucore/FBSurface.hxx +++ b/src/emucore/FBSurface.hxx @@ -219,12 +219,6 @@ class FBSurface uInt32 color, TextAlign align = TextAlign::Left, int deltax = 0, bool useEllipsis = true, uInt32 shadowColor = 0); - /** - This method should be called to indicate that the surface has been - modified, and should be redrawn at the next interval. - */ - virtual void setDirty() { } - ////////////////////////////////////////////////////////////////////////// // Note: The following methods are FBSurface-specific, and must be // implemented in child classes. @@ -331,6 +325,13 @@ class FBSurface static void setPalette(const uInt32* palette) { myPalette = palette; } + protected: + /** + This method must be called to indicate that the surface has been + modified, and should be redrawn at the next interval. + */ + virtual void setDirty() = 0; + protected: static const uInt32* myPalette; uInt32* myPixels; diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 16017487d..7d842d59a 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -50,7 +50,8 @@ FrameBuffer::FrameBuffer(OSystem& osystem) myLastScanlines(0), myGrabMouse(false), myCurrentModeList(nullptr) -{} +{ +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FrameBuffer::~FrameBuffer() @@ -258,85 +259,116 @@ FBInitStatus FrameBuffer::createDisplay(const string& title, } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::update() +void FrameBuffer::update(bool force) { - // Determine which mode we are in (from the EventHandler) - // Take care of S_EMULATE mode here, otherwise let the GUI - // figure out what to draw + // Onscreen messages are a special case and require different handling than + // other objects; they aren't UI dialogs in the normal sense nor are they + // TIA images, and they need to be rendered on top of everything + // The logic is split in two pieces: + // - at the top of ::update(), to determine whether underlying dialogs + // need to be force-redrawn + // - at the bottom of ::update(), to actually draw them (this must come + // last, since they are always drawn on top of everything else). + + // Full rendering is required when messages are enabled + force |= myMsg.counter >= 0; + + // Detect when a message has been turned off; one last redraw is required + // in this case, to draw over the area that the message occupied + if(myMsg.counter == 0) + myMsg.counter = -1; - invalidate(); switch(myOSystem.eventHandler().state()) { + case EventHandlerState::NONE: case EventHandlerState::EMULATION: // Do nothing; emulation mode is handled separately (see below) - break; + return; case EventHandlerState::PAUSE: { - myTIASurface->render(); - // Show a pause message immediately and then every 7 seconds - if (myPausedCount-- <= 0) + if(myPausedCount-- <= 0) { myPausedCount = uInt32(7 * myOSystem.frameRate()); showMessage("Paused", MessagePosition::MiddleCenter); } + if(force) + myTIASurface->render(); + break; // EventHandlerState::PAUSE } case EventHandlerState::OPTIONSMENU: { - myTIASurface->render(); - myOSystem.menu().draw(true); + force |= myOSystem.menu().needsRedraw(); + if(force) + { + myTIASurface->render(); + myOSystem.menu().draw(force); + } break; // EventHandlerState::OPTIONSMENU } case EventHandlerState::CMDMENU: { - myTIASurface->render(); - myOSystem.commandMenu().draw(true); + force |= myOSystem.commandMenu().needsRedraw(); + if(force) + { + myTIASurface->render(); + myOSystem.commandMenu().draw(force); + } break; // EventHandlerState::CMDMENU } case EventHandlerState::TIMEMACHINE: { - myTIASurface->render(); - myOSystem.timeMachine().draw(true); + force |= myOSystem.timeMachine().needsRedraw(); + if(force) + { + myTIASurface->render(); + myOSystem.timeMachine().draw(force); + } break; // EventHandlerState::TIMEMACHINE } case EventHandlerState::LAUNCHER: { - myOSystem.launcher().draw(true); + force |= myOSystem.launcher().draw(force); break; // EventHandlerState::LAUNCHER } case EventHandlerState::DEBUGGER: { #ifdef DEBUGGER_SUPPORT - myOSystem.debugger().draw(true); + force |= myOSystem.debugger().draw(force); #endif break; // EventHandlerState::DEBUGGER } - - case EventHandlerState::NONE: - return; } // Draw any pending messages + // The logic here determines whether to draw the message + // If the message is to be disabled, logic inside the draw method + // indicates that, and then the code at the top of this method sees + // the change and redraws everything if(myMsg.enabled) - drawMessage(); + force |= drawMessage(); - // Do any post-frame stuff - postFrameUpdate(); + // Push buffers to screen only when necessary + if(force) + renderToScreen(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void FrameBuffer::updateInEmulationMode() { - // Determine which mode we are in (from the EventHandler) - // Take care of S_EMULATE mode here, otherwise let the GUI - // figure out what to draw + // Update method that is specifically tailored to emulation mode + // Typically called from a thread, so it needs to be separate from + // the normal update() method + // + // We don't worry about selective rendering here; the rendering + // always happens at the full framerate myTIASurface->render(); @@ -351,8 +383,8 @@ void FrameBuffer::updateInEmulationMode() if(myMsg.enabled) drawMessage(); - // Do any post-frame stuff - postFrameUpdate(); + // Push buffers to screen + renderToScreen(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -413,7 +445,6 @@ void FrameBuffer::drawFrameStats() myStatsMsg.surface->drawString(font(), bsinfo, XPOS, YPOS + font().getFontHeight(), myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor); - myStatsMsg.surface->setDirty(); myStatsMsg.surface->setDstPos(myImageRect.x() + 10, myImageRect.y() + 8); myStatsMsg.surface->render(); } @@ -448,13 +479,26 @@ void FrameBuffer::enableMessages(bool enable) // Erase old messages on the screen myMsg.enabled = false; myMsg.counter = 0; - update(); // Force update immediately + update(true); // Force update immediately } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -inline void FrameBuffer::drawMessage() +inline bool FrameBuffer::drawMessage() { + // Either erase the entire message (when time is reached), + // or show again this frame + if(myMsg.counter == 0) + { + myMsg.enabled = false; + return true; + } + else if(myMsg.counter < 0) + { + myMsg.enabled = false; + return false; + } + // Draw the bounded box and text const GUI::Rect& dst = myMsg.surface->dstRect(); @@ -511,16 +555,10 @@ inline void FrameBuffer::drawMessage() myMsg.surface->frameRect(0, 0, myMsg.w, myMsg.h, kColor); myMsg.surface->drawString(font(), myMsg.text, 5, 4, myMsg.w, myMsg.color, TextAlign::Left); + myMsg.surface->render(); + myMsg.counter--; - // Either erase the entire message (when time is reached), - // or show again this frame - if(myMsg.counter-- > 0) - { - myMsg.surface->setDirty(); - myMsg.surface->render(); - } - else - myMsg.enabled = false; + return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -564,6 +602,8 @@ void FrameBuffer::resetSurfaces() freeSurfaces(); reloadSurfaces(); + + update(true); // force full update } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -589,6 +629,8 @@ void FrameBuffer::stateChanged(EventHandlerState state) // Make sure any onscreen messages are removed myMsg.enabled = false; myMsg.counter = 0; + + update(true); // force full update } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index 7a7e0a487..c656437cd 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -113,10 +113,9 @@ class FrameBuffer /** Updates the display, which depending on the current mode could mean - drawing the TIA, any pending menus, etc. Returns the numbers of CPU cycles - spent during emulation, or -1 if not applicable. + drawing the TIA, any pending menus, etc. */ - void update(); + void update(bool force = false); /** There is a dedicated update method for emulation mode. @@ -379,9 +378,10 @@ class FrameBuffer virtual void setWindowIcon() = 0; /** - This method is called after any drawing is done (per-frame). + This method must be called after all drawing is done, and indicates + that the buffers should be pushed to the physical screen. */ - virtual void postFrameUpdate() = 0; + virtual void renderToScreen() = 0; /** This method is called to provide information about the FrameBuffer. @@ -398,8 +398,10 @@ class FrameBuffer private: /** Draw pending messages. + + @return Indicates whether any changes actually occurred. */ - void drawMessage(); + bool drawMessage(); /** Frees and reloads all surfaces that the framebuffer knows about. @@ -521,7 +523,7 @@ class FrameBuffer bool enabled; Message() - : counter(0), x(0), y(0), w(0), h(0), position(MessagePosition::BottomCenter), + : counter(-1), x(0), y(0), w(0), h(0), position(MessagePosition::BottomCenter), color(0), enabled(false) { } }; Message myMsg; diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 469ec738e..a92347bd2 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -704,8 +704,8 @@ void OSystem::mainLoop() // Dispatch emulation and render frame (if applicable) timesliceSeconds = dispatchEmulation(emulationWorker); else { - // Render the GUI with 30 Hz in all other modes - timesliceSeconds = 1. / 30.; + // Render the GUI with 60 Hz in all other modes + timesliceSeconds = 1. / 60.; myFrameBuffer->update(); } diff --git a/src/emucore/TIASurface.cxx b/src/emucore/TIASurface.cxx index 783acbf73..b080d0f5a 100644 --- a/src/emucore/TIASurface.cxx +++ b/src/emucore/TIASurface.cxx @@ -206,7 +206,6 @@ uInt32 TIASurface::enableScanlines(int relative, int absolute) attr.blendalpha = std::min(100u, attr.blendalpha); mySLineSurface->applyAttributes(); - mySLineSurface->setDirty(); return attr.blendalpha; } @@ -217,7 +216,6 @@ void TIASurface::enableScanlineInterpolation(bool enable) FBSurface::Attributes& attr = mySLineSurface->attributes(); attr.smoothing = enable; mySLineSurface->applyAttributes(); - mySLineSurface->setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -231,8 +229,6 @@ void TIASurface::enablePhosphor(bool enable, int blend) myPhosphorPercent = blend / 100.0; myFilter = Filter(enable ? uInt8(myFilter) | 0x01 : uInt8(myFilter) & 0x10); - myTiaSurface->setDirty(); - mySLineSurface->setDirty(); memset(myRGBFramebuffer, 0, sizeof(myRGBFramebuffer)); // Precalculate the average colors for the 'phosphor' effect @@ -283,8 +279,6 @@ void TIASurface::enableNTSC(bool enable) sl_attr.blendalpha = myOSystem.settings().getInt("tv.scanlines"); mySLineSurface->applyAttributes(); - myTiaSurface->setDirty(); - mySLineSurface->setDirty(); memset(myRGBFramebuffer, 0, sizeof(myRGBFramebuffer)); } @@ -379,15 +373,11 @@ void TIASurface::render() } // Draw TIA image - myTiaSurface->setDirty(); myTiaSurface->render(); // Draw overlaying scanlines if(myScanlinesEnabled) - { - mySLineSurface->setDirty(); mySLineSurface->render(); - } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -424,14 +414,10 @@ void TIASurface::reRender() if (myUsePhosphor) { // Draw TIA image - myTiaSurface->setDirty(); myTiaSurface->render(); // Draw overlaying scanlines if (myScanlinesEnabled) - { - mySLineSurface->setDirty(); mySLineSurface->render(); - } } } diff --git a/src/gui/AboutDialog.cxx b/src/gui/AboutDialog.cxx index 76410d349..5b9e722e1 100644 --- a/src/gui/AboutDialog.cxx +++ b/src/gui/AboutDialog.cxx @@ -226,7 +226,7 @@ void AboutDialog::displayInfo() } // Redraw entire dialog - _dirty = true; + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/AudioDialog.cxx b/src/gui/AudioDialog.cxx index 076ba666c..945a4de65 100644 --- a/src/gui/AudioDialog.cxx +++ b/src/gui/AudioDialog.cxx @@ -47,7 +47,7 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, int xpos, ypos; int lwidth = font.getStringWidth("Resampling quality "), pwidth = font.getStringWidth("512 bytes"); - + WidgetArray wid; VariantList items; @@ -186,7 +186,6 @@ void AudioDialog::updateSettingsWithPreset(AudioSettings& audioSettings) myResamplingPopup->setSelected(static_cast(audioSettings.resamplingQuality())); } - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioDialog::saveConfig() { @@ -235,8 +234,6 @@ void AudioDialog::setDefaults() else updatePreset(); updateEnabledState(); - - _dirty = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/ComboDialog.cxx b/src/gui/ComboDialog.cxx index 35b76ddb6..9a3df6087 100644 --- a/src/gui/ComboDialog.cxx +++ b/src/gui/ComboDialog.cxx @@ -118,7 +118,7 @@ void ComboDialog::setDefaults() for(int i = 0; i < 8; ++i) myEvents[i]->setSelected("None", "-1"); - _dirty = true; + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/ContextMenu.cxx b/src/gui/ContextMenu.cxx index 1085d0c32..1548575f3 100644 --- a/src/gui/ContextMenu.cxx +++ b/src/gui/ContextMenu.cxx @@ -546,45 +546,38 @@ void ContextMenu::drawDialog() // by the ScummVM guys, so I'm not going to mess with it. FBSurface& s = surface(); - if(_dirty) + // Draw menu border and background + s.fillRect(_x+1, _y+1, _w-2, _h-2, kWidColor); + s.frameRect(_x, _y, _w, _h, kTextColor); + + // Draw the entries, taking scroll buttons into account + int x = _x + 1, y = _y + 1, w = _w - 2; + + // Show top scroll area + int offset = _selectedOffset; + if(_showScroll) { - // Draw menu border and background - s.fillRect(_x+1, _y+1, _w-2, _h-2, kWidColor); - s.frameRect(_x, _y, _w, _h, kTextColor); - - // Draw the entries, taking scroll buttons into account - int x = _x + 1, y = _y + 1, w = _w - 2; - - // Show top scroll area - int offset = _selectedOffset; - if(_showScroll) - { - s.hLine(x, y+_rowHeight-1, w+2, kColor); - s.drawBitmap(up_arrow, ((_w-_x)>>1)-4, (_rowHeight>>1)+y-4, _scrollUpColor, 8); - y += _rowHeight; - offset--; - } - - for(int i = _firstEntry, current = 0; i < _firstEntry + _numEntries; ++i, ++current) - { - bool hilite = offset == current; - if(hilite) s.fillRect(x, y, w, _rowHeight, kTextColorHi); - s.drawString(_font, _entries[i].first, x + 1, y + 2, w, - !hilite ? kTextColor : kTextColorInv); - y += _rowHeight; - } - - // Show bottom scroll area - if(_showScroll) - { - s.hLine(x, y, w+2, kColor); - s.drawBitmap(down_arrow, ((_w-_x)>>1)-4, (_rowHeight>>1)+y-4, _scrollDnColor, 8); - } - - s.setDirty(); - _dirty = false; + s.hLine(x, y+_rowHeight-1, w+2, kColor); + s.drawBitmap(up_arrow, ((_w-_x)>>1)-4, (_rowHeight>>1)+y-4, _scrollUpColor, 8); + y += _rowHeight; + offset--; } - // Commit surface changes to screen - s.render(); + for(int i = _firstEntry, current = 0; i < _firstEntry + _numEntries; ++i, ++current) + { + bool hilite = offset == current; + if(hilite) s.fillRect(x, y, w, _rowHeight, kTextColorHi); + s.drawString(_font, _entries[i].first, x + 1, y + 2, w, + !hilite ? kTextColor : kTextColorInv); + y += _rowHeight; + } + + // Show bottom scroll area + if(_showScroll) + { + s.hLine(x, y, w+2, kColor); + s.drawBitmap(down_arrow, ((_w-_x)>>1)-4, (_rowHeight>>1)+y-4, _scrollDnColor, 8); + } + + setDirty(); } diff --git a/src/gui/Dialog.cxx b/src/gui/Dialog.cxx index 638f43579..3d9a76b0a 100644 --- a/src/gui/Dialog.cxx +++ b/src/gui/Dialog.cxx @@ -64,6 +64,7 @@ Dialog::Dialog(OSystem& instance, DialogContainer& parent, const GUI::Font& font _flags(WIDGET_ENABLED | WIDGET_BORDER | WIDGET_CLEARBG) { setTitle(title); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -86,7 +87,7 @@ Dialog::~Dialog() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Dialog::open(bool refresh) +void Dialog::open() { // Make sure we have a valid surface to draw into // Technically, this shouldn't be needed until drawDialog(), but some @@ -103,10 +104,12 @@ void Dialog::open(bool refresh) buildCurrentFocusList(); _visible = true; + + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Dialog::close(bool refresh) +void Dialog::close() { if (_mouseWidget) { @@ -119,6 +122,7 @@ void Dialog::close(bool refresh) _visible = false; parent().removeDialog(); + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -131,6 +135,8 @@ void Dialog::setTitle(const string& title) else _th = _font.getLineHeight() + 4; _h += _th; + + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -144,6 +150,29 @@ void Dialog::center() } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Dialog::render() +{ + if(!_dirty) + return false; + + // Draw this dialog + center(); + drawDialog(); + + // Update dialog surface; also render any extra surfaces + // Extra surfaces must be rendered afterwards, so they are drawn on top + if(_surface->render()) + { + mySurfaceStack.applyAll([](shared_ptr& surface){ + surface->render(); + }); + } + _dirty = false; + + return true; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Dialog::releaseFocus() { @@ -291,6 +320,7 @@ void Dialog::addSurface(shared_ptr surface) mySurfaceStack.push(surface); } +static int COUNT = 1; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Dialog::drawDialog() { @@ -299,63 +329,46 @@ void Dialog::drawDialog() FBSurface& s = surface(); - if(_dirty) +cerr << COUNT++ << " Dialog::drawDialog()\n"; + // Dialog is still on top if e.g a ContextMenu is opened + bool onTop = parent().myDialogStack.top() == this + || (parent().myDialogStack.get(parent().myDialogStack.size() - 2) == this + && !parent().myDialogStack.top()->hasTitle()); + + if(_flags & WIDGET_CLEARBG) { - // dialog is still on top if e.g a ContextMenu is opened - bool onTop = parent().myDialogStack.top() == this - || (parent().myDialogStack.get(parent().myDialogStack.size() - 2) == this - && !parent().myDialogStack.top()->hasTitle()); - - if(_flags & WIDGET_CLEARBG) + // cerr << "Dialog::drawDialog(): w = " << _w << ", h = " << _h << " @ " << &s << endl << endl; + s.fillRect(_x, _y + _th, _w, _h - _th, onTop ? kDlgColor : kBGColorLo); + if(_th) { - // cerr << "Dialog::drawDialog(): w = " << _w << ", h = " << _h << " @ " << &s << endl << endl; - s.fillRect(_x, _y + _th, _w, _h - _th, onTop ? kDlgColor : kBGColorLo); - if(_th) - { - s.fillRect(_x, _y, _w, _th, onTop ? kColorTitleBar : kColorTitleBarLo); - s.drawString(_font, _title, _x + 10, _y + 2 + 1, _font.getStringWidth(_title), - onTop ? kColorTitleText : kColorTitleTextLo); - } + s.fillRect(_x, _y, _w, _th, onTop ? kColorTitleBar : kColorTitleBarLo); + s.drawString(_font, _title, _x + 10, _y + 2 + 1, _font.getStringWidth(_title), + onTop ? kColorTitleText : kColorTitleTextLo); } - else - s.invalidate(); - if(_flags & WIDGET_BORDER) - s.frameRect(_x, _y, _w, _h, onTop ? kColor : kShadowColor); + } + else + s.invalidate(); + if(_flags & WIDGET_BORDER) + s.frameRect(_x, _y, _w, _h, onTop ? kColor : kShadowColor); - // Make all child widget dirty - Widget* w = _firstWidget; - Widget::setDirtyInChain(w); + // Make all child widget dirty + Widget* w = _firstWidget; + Widget::setDirtyInChain(w); - // Draw all children - w = _firstWidget; - while(w) - { - w->draw(); - w = w->_next; - } - - // Draw outlines for focused widgets - // Don't change focus, since this will trigger lost and received - // focus events - if(_focusedWidget) - _focusedWidget = Widget::setFocusForChain(this, getFocusList(), - _focusedWidget, 0, false); - - // Tell the surface(s) this area is dirty - s.setDirty(); - - _dirty = false; + // Draw all children + w = _firstWidget; + while(w) + { + w->draw(); + w = w->_next; } - // Commit surface changes to screen; also render any extra surfaces - // Extra surfaces must be rendered afterwards, so they are drawn on top - if(s.render()) - { - mySurfaceStack.applyAll([](shared_ptr& surface){ - surface->setDirty(); - surface->render(); - }); - } + // Draw outlines for focused widgets + // Don't change focus, since this will trigger lost and received + // focus events + if(_focusedWidget) + _focusedWidget = Widget::setFocusForChain(this, getFocusList(), + _focusedWidget, 0, false); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/Dialog.hxx b/src/gui/Dialog.hxx index 1701bc66e..71df684a3 100644 --- a/src/gui/Dialog.hxx +++ b/src/gui/Dialog.hxx @@ -51,8 +51,8 @@ class Dialog : public GuiObject virtual ~Dialog(); - void open(bool refresh = true); - void close(bool refresh = true); + void open(); + void close(); bool isVisible() const override { return _visible; } @@ -62,6 +62,12 @@ class Dialog : public GuiObject virtual void saveConfig() { } virtual void setDefaults() { } + // A dialog being dirty indicates that its underlying surface needs to be + // redrawn and then re-rendered; this is taken care of in ::render() + void setDirty() override { _dirty = true; } + bool isDirty() const { return _dirty; } + bool render(); + void addFocusWidget(Widget* w) override; void addToFocusList(WidgetArray& list) override; void addToFocusList(WidgetArray& list, TabWidget* w, int tabId); @@ -190,6 +196,7 @@ class Dialog : public GuiObject int _tabID; int _flags; + bool _dirty; private: // Following constructors and assignment operators not supported diff --git a/src/gui/DialogContainer.cxx b/src/gui/DialogContainer.cxx index a25a8fc4e..1d3aab8a2 100644 --- a/src/gui/DialogContainer.cxx +++ b/src/gui/DialogContainer.cxx @@ -48,12 +48,12 @@ DialogContainer::~DialogContainer() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DialogContainer::updateTime(uInt64 time) { - // We only need millisecond precision - myTime = time / 1000; - if(myDialogStack.empty()) return; + // We only need millisecond precision + myTime = time / 1000; + // Check for pending continuous events and send them to the active dialog box Dialog* activeDialog = myDialogStack.top(); @@ -98,19 +98,31 @@ void DialogContainer::updateTime(uInt64 time) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void DialogContainer::draw(bool full) +bool DialogContainer::draw(bool full) { - // Draw all the dialogs on the stack when we want a full refresh + if(myDialogStack.empty()) + return false; + + // Make the top dialog dirty if a full redraw is requested if(full) - { - myDialogStack.applyAll([](Dialog*& d){ - d->center(); + myDialogStack.top()->setDirty(); + + // If the top dialog is dirty, then all below it must be redrawn too + bool dirty = needsRedraw(); + + myDialogStack.applyAll([&](Dialog*& d){ + if(dirty) d->setDirty(); - d->drawDialog(); - }); - } - else if(!myDialogStack.empty()) - myDialogStack.top()->drawDialog(); + full |= d->render(); + }); + + return full; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool DialogContainer::needsRedraw() const +{ + return !myDialogStack.empty() ? myDialogStack.top()->isDirty() : false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -121,14 +133,21 @@ void DialogContainer::addDialog(Dialog* d) myOSystem.frameBuffer().showMessage( "Unable to show dialog box; FIX THE CODE"); else + { + d->setDirty(); myDialogStack.push(d); + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DialogContainer::removeDialog() { if(!myDialogStack.empty()) + { myDialogStack.pop(); + if(!myDialogStack.empty()) + myDialogStack.top()->setDirty(); + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -136,9 +155,9 @@ void DialogContainer::reStack() { // Pop all items from the stack, and then add the base menu while(!myDialogStack.empty()) - myDialogStack.top()->close(false); // don't force a refresh + myDialogStack.top()->close(); - myBaseDialog->open(false); // don't force a refresh + myBaseDialog->open(); // Reset all continuous events reset(); diff --git a/src/gui/DialogContainer.hxx b/src/gui/DialogContainer.hxx index 776f917bb..6905e3c86 100644 --- a/src/gui/DialogContainer.hxx +++ b/src/gui/DialogContainer.hxx @@ -121,8 +121,15 @@ class DialogContainer /** Draw the stack of menus (full indicates to redraw all items). + + @return Answers whether any drawing actually occurred. */ - void draw(bool full = false); + bool draw(bool full = false); + + /** + Answers whether a full redraw is required. + */ + bool needsRedraw() const; /** Reset dialog stack to the main configuration menu. diff --git a/src/gui/GlobalPropsDialog.cxx b/src/gui/GlobalPropsDialog.cxx index b6ba86764..1ead71b93 100644 --- a/src/gui/GlobalPropsDialog.cxx +++ b/src/gui/GlobalPropsDialog.cxx @@ -274,8 +274,6 @@ void GlobalPropsDialog::setDefaults() myHoldSelect->setState(false); myHoldReset->setState(false); - - _dirty = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/GuiObject.hxx b/src/gui/GuiObject.hxx index a9967fc62..27e4bd7a5 100644 --- a/src/gui/GuiObject.hxx +++ b/src/gui/GuiObject.hxx @@ -60,7 +60,6 @@ class GuiObject : public CommandReceiver myParent(parent), myDialog(dialog), _x(x), _y(y), _w(w), _h(h), - _dirty(false), _firstWidget(nullptr) { } virtual ~GuiObject() = default; @@ -69,8 +68,6 @@ class GuiObject : public CommandReceiver DialogContainer& parent() const { return myParent; } Dialog& dialog() const { return myDialog; } - void setDirty() { _dirty = true; } - virtual int getAbsX() const { return _x; } virtual int getAbsY() const { return _y; } virtual int getChildX() const { return getAbsX(); } @@ -82,6 +79,7 @@ class GuiObject : public CommandReceiver virtual void setHeight(int h) { _h = h; } virtual bool isVisible() const = 0; + virtual void setDirty() = 0; /** Add given widget(s) to the focus list */ virtual void addFocusWidget(Widget* w) = 0; @@ -107,7 +105,6 @@ class GuiObject : public CommandReceiver protected: int _x, _y, _w, _h; - bool _dirty; Widget* _firstWidget; WidgetArray _focusList; diff --git a/src/gui/HelpDialog.cxx b/src/gui/HelpDialog.cxx index 5235c09d8..c645df45c 100644 --- a/src/gui/HelpDialog.cxx +++ b/src/gui/HelpDialog.cxx @@ -185,8 +185,6 @@ void HelpDialog::displayInfo() myKey[i]->setLabel(myKeyStr[i]); myDesc[i]->setLabel(myDescStr[i]); } - - _dirty = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/InputDialog.cxx b/src/gui/InputDialog.cxx index f6cd9599b..614abedb7 100644 --- a/src/gui/InputDialog.cxx +++ b/src/gui/InputDialog.cxx @@ -399,8 +399,6 @@ void InputDialog::setDefaults() default: break; } - - _dirty = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/LauncherFilterDialog.cxx b/src/gui/LauncherFilterDialog.cxx index 704c9f5e2..7467a5e6e 100644 --- a/src/gui/LauncherFilterDialog.cxx +++ b/src/gui/LauncherFilterDialog.cxx @@ -178,8 +178,6 @@ void LauncherFilterDialog::saveConfig() void LauncherFilterDialog::setDefaults() { handleFileTypeChange("allroms"); - - _dirty = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/ListWidget.cxx b/src/gui/ListWidget.cxx index 2093b86e1..4d503f671 100644 --- a/src/gui/ListWidget.cxx +++ b/src/gui/ListWidget.cxx @@ -62,11 +62,10 @@ ListWidget::ListWidget(GuiObject* boss, const GUI::Font& font, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ListWidget::setSelected(int item) { + setDirty(); + if(item < 0 || item >= int(_list.size())) - { - setDirty(); // Simply redraw and exit return; - } if(isEnabled()) { @@ -180,6 +179,8 @@ void ListWidget::recalc() // Reset to normal data entry abortEditMode(); + + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/UIDialog.cxx b/src/gui/UIDialog.cxx index 029db08ad..263e37235 100644 --- a/src/gui/UIDialog.cxx +++ b/src/gui/UIDialog.cxx @@ -279,8 +279,6 @@ void UIDialog::setDefaults() default: break; } - - _dirty = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/VideoDialog.cxx b/src/gui/VideoDialog.cxx index cce8ca846..4217f54bd 100644 --- a/src/gui/VideoDialog.cxx +++ b/src/gui/VideoDialog.cxx @@ -551,8 +551,6 @@ void VideoDialog::setDefaults() break; } } - - _dirty = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -580,8 +578,6 @@ void VideoDialog::handleTVModeChange(NTSCFilter::Preset preset) myTVScanLabel->setEnabled(scanenable); myTVScanIntense->setEnabled(scanenable); myTVScanInterpolate->setEnabled(scanenable); - - _dirty = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/Widget.cxx b/src/gui/Widget.cxx index ff276a95c..20f4c1680 100644 --- a/src/gui/Widget.cxx +++ b/src/gui/Widget.cxx @@ -48,6 +48,8 @@ Widget::Widget(GuiObject* boss, const GUI::Font& font, _fontWidth = _font.getMaxCharWidth(); _fontHeight = _font.getLineHeight(); + + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -59,14 +61,20 @@ Widget::~Widget() _focusList.clear(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Widget::setDirty() +{ + // A widget being dirty indicates that its parent dialog is dirty + // So we inform the parent about it + _boss->dialog().setDirty(); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Widget::draw() { - if(!_dirty || !isVisible() || !_boss->isVisible()) + if(!isVisible() || !_boss->isVisible()) return; - _dirty = false; - FBSurface& s = _boss->dialog().surface(); bool hasBorder = _flags & WIDGET_BORDER; @@ -119,9 +127,6 @@ void Widget::draw() w->draw(); w = w->_next; } - - // Tell the framebuffer this area is dirty - s.setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -224,7 +229,6 @@ Widget* Widget::setFocusForChain(GuiObject* boss, WidgetArray& arr, s.frameRect(x, y, w, h, kDlgColor); tmp->setDirty(); - s.setDirty(); } } @@ -278,7 +282,6 @@ Widget* Widget::setFocusForChain(GuiObject* boss, WidgetArray& arr, s.frameRect(x, y, w, h, kWidFrameColor, FrameStyle::Dashed); tmp->setDirty(); - s.setDirty(); return tmp; } @@ -346,6 +349,8 @@ void StaticTextWidget::drawWidget(bool hilite) FBSurface& s = _boss->dialog().surface(); s.drawString(_font, _label, _x, _y, _w, isEnabled() ? _textcolor : uInt32(kColor), _align, 0, true, _shadowcolor); + + _boss->dialog().setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -402,14 +407,12 @@ ButtonWidget::ButtonWidget(GuiObject* boss, const GUI::Font& font, void ButtonWidget::handleMouseEntered() { setFlags(WIDGET_HILITED); - setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ButtonWidget::handleMouseLeft() { clearFlags(WIDGET_HILITED); - setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -446,6 +449,8 @@ void ButtonWidget::setBitmap(uInt32* bitmap, int bmw, int bmh) _bmh = bmh; _bmw = bmw; _useBitmap = true; + + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -464,6 +469,8 @@ void ButtonWidget::drawWidget(bool hilite) !isEnabled() ? /*hilite ? uInt32(kColor) :*/ uInt32(kBGColorLo) : hilite ? _textcolorhi : _textcolor, _bmw, _bmh); + + _boss->dialog().setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -584,6 +591,7 @@ void CheckboxWidget::setEditable(bool editable) _bgcolor = kBGColorHi; setFill(CheckboxWidget::Inactive); } + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -604,6 +612,7 @@ void CheckboxWidget::setFill(FillType type) _drawBox = false; break; } + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -634,6 +643,8 @@ void CheckboxWidget::drawWidget(bool hilite) // Finally draw the label s.drawString(_font, _label, _x + 20, _y + _textY, _w, isEnabled() ? kTextColor : kColor); + + _boss->dialog().setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -697,18 +708,21 @@ void SliderWidget::setValue(int value) void SliderWidget::setMinValue(int value) { _valueMin = value; + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SliderWidget::setMaxValue(int value) { _valueMax = value; + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SliderWidget::setStepValue(int value) { _stepValue = value; + setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -868,10 +882,12 @@ void SliderWidget::drawWidget(bool hilite) if(_valueLabelWidth > 0) s.drawString(_font, _valueLabel + _valueUnit, _x + _w - _valueLabelWidth, _y + 2, _valueLabelWidth, isEnabled() ? kTextColor : kColor); + + _boss->dialog().setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -int SliderWidget::valueToPos(int value) +int SliderWidget::valueToPos(int value) const { if(value < _valueMin) value = _valueMin; else if(value > _valueMax) value = _valueMax; @@ -881,7 +897,7 @@ int SliderWidget::valueToPos(int value) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -int SliderWidget::posToValue(int pos) +int SliderWidget::posToValue(int pos) const { int value = (pos) * (_valueMax - _valueMin) / (_w - _labelWidth - _valueLabelGap - _valueLabelWidth - 4) + _valueMin; diff --git a/src/gui/Widget.hxx b/src/gui/Widget.hxx index 0e10ea2a9..a550073cd 100644 --- a/src/gui/Widget.hxx +++ b/src/gui/Widget.hxx @@ -82,6 +82,7 @@ class Widget : public GuiObject virtual bool handleJoyHat(int stick, int hat, JoyHat value) { return false; } virtual bool handleEvent(Event::Type event) { return false; } + void setDirty() override; void draw() override; void receivedFocus(); void lostFocus(); @@ -108,11 +109,11 @@ class Widget : public GuiObject virtual const GUI::Font& font() const { return _font; } - void setTextColor(uInt32 color) { _textcolor = color; } - void setTextColorHi(uInt32 color) { _textcolorhi = color; } - void setBGColor(uInt32 color) { _bgcolor = color; } - void setBGColorHi(uInt32 color) { _bgcolorhi = color; } - void setShadowColor(uInt32 color) { _shadowcolor = color; } + void setTextColor(uInt32 color) { _textcolor = color; setDirty(); } + void setTextColorHi(uInt32 color) { _textcolorhi = color; setDirty(); } + void setBGColor(uInt32 color) { _bgcolor = color; setDirty(); } + void setBGColorHi(uInt32 color) { _bgcolorhi = color; setDirty(); } + void setShadowColor(uInt32 color) { _shadowcolor = color; setDirty(); } virtual void loadConfig() { } @@ -187,7 +188,7 @@ class StaticTextWidget : public Widget uInt32 shadowColor = 0); void setValue(int value); void setLabel(const string& label); - void setAlign(TextAlign align) { _align = align; } + void setAlign(TextAlign align) { _align = align; setDirty(); } const string& getLabel() const { return _label; } bool isEditable() const { return _editable; } @@ -341,8 +342,8 @@ class SliderWidget : public ButtonWidget void drawWidget(bool hilite) override; - int valueToPos(int value); - int posToValue(int pos); + int valueToPos(int value) const; + int posToValue(int pos) const; protected: int _value, _stepValue;