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.
This commit is contained in:
Stephen Anthony 2018-07-25 08:48:21 -02:30
parent a20bb6e95d
commit e691853f0e
30 changed files with 318 additions and 254 deletions

View File

@ -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;
}

View File

@ -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
// Show all changes made to the renderer
SDL_RenderPresent(myRenderer);
myDirtyFlag = false;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -347,5 +341,5 @@ void FrameBufferSDL2::readPixels(uInt8* pixels, uInt32 pitch,
void FrameBufferSDL2::clear()
{
invalidate();
postFrameUpdate();
renderToScreen();
}

View File

@ -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;

View File

@ -793,7 +793,7 @@ void Debugger::unlockSystem()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Debugger::canExit() const
{
return myDialogStack.top() == baseDialog();
return !myDialogStack.empty() && myDialogStack.top() == baseDialog();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -130,8 +130,6 @@ class DebuggerDialog : public Dialog
ButtonWidget* myRewindButton;
ButtonWidget* myUnwindButton;
//ButtonWidget* myOptionsButton;
unique_ptr<GUI::MessageBox> myFatalError;
unique_ptr<OptionsDialog> myOptions;

View File

@ -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:

View File

@ -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;

View File

@ -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)
{
myPausedCount = uInt32(7 * myOSystem.frameRate());
showMessage("Paused", MessagePosition::MiddleCenter);
}
if(force)
myTIASurface->render();
break; // EventHandlerState::PAUSE
}
case EventHandlerState::OPTIONSMENU:
{
force |= myOSystem.menu().needsRedraw();
if(force)
{
myTIASurface->render();
myOSystem.menu().draw(true);
myOSystem.menu().draw(force);
}
break; // EventHandlerState::OPTIONSMENU
}
case EventHandlerState::CMDMENU:
{
force |= myOSystem.commandMenu().needsRedraw();
if(force)
{
myTIASurface->render();
myOSystem.commandMenu().draw(true);
myOSystem.commandMenu().draw(force);
}
break; // EventHandlerState::CMDMENU
}
case EventHandlerState::TIMEMACHINE:
{
force |= myOSystem.timeMachine().needsRedraw();
if(force)
{
myTIASurface->render();
myOSystem.timeMachine().draw(true);
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);
// 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;
myMsg.counter--;
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
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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;

View File

@ -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();
}

View File

@ -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,16 +373,12 @@ void TIASurface::render()
}
// Draw TIA image
myTiaSurface->setDirty();
myTiaSurface->render();
// Draw overlaying scanlines
if(myScanlinesEnabled)
{
mySLineSurface->setDirty();
mySLineSurface->render();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASurface::reRender()
@ -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();
}
}
}

View File

@ -226,7 +226,7 @@ void AboutDialog::displayInfo()
}
// Redraw entire dialog
_dirty = true;
setDirty();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -186,7 +186,6 @@ void AudioDialog::updateSettingsWithPreset(AudioSettings& audioSettings)
myResamplingPopup->setSelected(static_cast<int>(audioSettings.resamplingQuality()));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioDialog::saveConfig()
{
@ -235,8 +234,6 @@ void AudioDialog::setDefaults()
else updatePreset();
updateEnabledState();
_dirty = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -118,7 +118,7 @@ void ComboDialog::setDefaults()
for(int i = 0; i < 8; ++i)
myEvents[i]->setSelected("None", "-1");
_dirty = true;
setDirty();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -546,8 +546,6 @@ 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);
@ -581,10 +579,5 @@ void ContextMenu::drawDialog()
s.drawBitmap(down_arrow, ((_w-_x)>>1)-4, (_rowHeight>>1)+y-4, _scrollDnColor, 8);
}
s.setDirty();
_dirty = false;
}
// Commit surface changes to screen
s.render();
setDirty();
}

View File

@ -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<FBSurface>& surface){
surface->render();
});
}
_dirty = false;
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::releaseFocus()
{
@ -291,6 +320,7 @@ void Dialog::addSurface(shared_ptr<FBSurface> surface)
mySurfaceStack.push(surface);
}
static int COUNT = 1;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::drawDialog()
{
@ -299,9 +329,8 @@ void Dialog::drawDialog()
FBSurface& s = surface();
if(_dirty)
{
// dialog is still on top if e.g a ContextMenu is opened
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());
@ -340,22 +369,6 @@ void Dialog::drawDialog()
if(_focusedWidget)
_focusedWidget = Widget::setFocusForChain(this, getFocusList(),
_focusedWidget, 0, false);
// Tell the surface(s) this area is dirty
s.setDirty();
_dirty = false;
}
// 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<FBSurface>& surface){
surface->setDirty();
surface->render();
});
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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

View File

@ -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();
full |= d->render();
});
return full;
}
else if(!myDialogStack.empty())
myDialogStack.top()->drawDialog();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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();

View File

@ -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.

View File

@ -274,8 +274,6 @@ void GlobalPropsDialog::setDefaults()
myHoldSelect->setState(false);
myHoldReset->setState(false);
_dirty = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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;

View File

@ -185,8 +185,6 @@ void HelpDialog::displayInfo()
myKey[i]->setLabel(myKeyStr[i]);
myDesc[i]->setLabel(myDescStr[i]);
}
_dirty = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -399,8 +399,6 @@ void InputDialog::setDefaults()
default:
break;
}
_dirty = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -178,8 +178,6 @@ void LauncherFilterDialog::saveConfig()
void LauncherFilterDialog::setDefaults()
{
handleFileTypeChange("allroms");
_dirty = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -279,8 +279,6 @@ void UIDialog::setDefaults()
default:
break;
}
_dirty = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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;

View File

@ -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;