From e7b7bfa3cdc6e81bb92c167b804478526ee91378 Mon Sep 17 00:00:00 2001 From: thrust26 Date: Tue, 10 Nov 2020 19:53:36 +0100 Subject: [PATCH] initial commit for #719 --- src/emucore/FrameBuffer.cxx | 2 +- src/gui/ContextMenu.cxx | 2 +- src/gui/Dialog.cxx | 93 +++++++++++++++++------- src/gui/Dialog.hxx | 7 +- src/gui/DialogContainer.cxx | 42 ++++++++--- src/gui/GuiObject.hxx | 5 ++ src/gui/ScrollBarWidget.cxx | 1 + src/gui/TabWidget.cxx | 60 +++++++++------- src/gui/Widget.cxx | 139 ++++++++++++++++++++++-------------- src/gui/Widget.hxx | 2 + 10 files changed, 235 insertions(+), 118 deletions(-) diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 97bbb2153..e922ff390 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -439,7 +439,7 @@ void FrameBuffer::update(bool force) force = force || myOSystem.launcher().needsRedraw(); if(force) { - clear(); + //clear(); myOSystem.launcher().draw(force); } break; // EventHandlerState::LAUNCHER diff --git a/src/gui/ContextMenu.cxx b/src/gui/ContextMenu.cxx index 41f049ba2..9cf626657 100644 --- a/src/gui/ContextMenu.cxx +++ b/src/gui/ContextMenu.cxx @@ -621,5 +621,5 @@ void ContextMenu::drawDialog() s.drawBitmap(_downImg, ((_w-_x)>>1)-4, (_rowHeight>>1)+y-4, _scrollDnColor, _arrowSize); } - setDirty(); + clearDirty(); } diff --git a/src/gui/Dialog.cxx b/src/gui/Dialog.cxx index 85c95349f..6e81535c2 100644 --- a/src/gui/Dialog.cxx +++ b/src/gui/Dialog.cxx @@ -149,6 +149,35 @@ void Dialog::center() positionAt(instance().settings().getInt("dialogpos")); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Dialog::setDirty() +{ + _dirty = true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Dialog::isDirty() const +{ + return _dirty; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Dialog::isChainDirty() const +{ + bool dirty = false; + + // Check if widget or any subwidgets are dirty + Widget* w = _firstWidget; + + while(!dirty && w) + { + dirty |= w->needsRedraw(); + w = w->_next; + } + + return dirty; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Dialog::positionAt(uInt32 pos) { @@ -192,7 +221,9 @@ void Dialog::positionAt(uInt32 pos) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool Dialog::render() { - if(!_dirty || !isVisible()) + //assert(_dirty); + + if(!isVisible() || !needsRedraw()) return false; // Draw this dialog @@ -207,7 +238,7 @@ bool Dialog::render() surface->render(); }); } - _dirty = false; + //_dirty = false; return true; } @@ -371,37 +402,49 @@ void Dialog::drawDialog() FBSurface& s = surface(); - // Dialog is still on top if e.g a ContextMenu is opened - _onTop = parent().myDialogStack.top() == this - || (parent().myDialogStack.get(parent().myDialogStack.size() - 2) == this - && !parent().myDialogStack.top()->hasTitle()); - - if(_flags & Widget::FLAG_CLEARBG) + if(isDirty()) { - // 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 + _font.getMaxCharWidth() * 1.25, _y + _font.getFontHeight() / 6, - _font.getStringWidth(_title), - _onTop ? kColorTitleText : kColorTitleTextLo); - } - } - else - s.invalidate(); - if(_flags & Widget::FLAG_BORDER) // currently only used by Dialog itself - s.frameRect(_x, _y, _w, _h, _onTop ? kColor : kShadowColor); + //cerr << "*** draw dialog " << typeid(*this).name() << " ***" << endl; + + // Dialog is still on top if e.g a ContextMenu is opened + _onTop = parent().myDialogStack.top() == this + || (parent().myDialogStack.get(parent().myDialogStack.size() - 2) == this + && !parent().myDialogStack.top()->hasTitle()); + + if(_flags & Widget::FLAG_CLEARBG) + { + // 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 + _font.getMaxCharWidth() * 1.25, _y + _font.getFontHeight() / 6, + _font.getStringWidth(_title), + _onTop ? kColorTitleText : kColorTitleTextLo); + } + } + else { + s.invalidate(); + cerr << "invalidate " << typeid(*this).name() << endl; + } + if(_flags & Widget::FLAG_BORDER) // currently only used by Dialog itself + s.frameRect(_x, _y, _w, _h, _onTop ? kColor : kShadowColor); + + // Make all child widgets dirty + Widget::setDirtyInChain(_firstWidget); + + clearDirty(); + } - // Make all child widget dirty Widget* w = _firstWidget; - Widget::setDirtyInChain(w); // Draw all children w = _firstWidget; while(w) { - w->draw(); + // only redraw changed widgets + if(w->needsRedraw()) + w->draw(); w = w->_next; } diff --git a/src/gui/Dialog.hxx b/src/gui/Dialog.hxx index 1f6bbec60..805522c42 100644 --- a/src/gui/Dialog.hxx +++ b/src/gui/Dialog.hxx @@ -64,8 +64,9 @@ class Dialog : public GuiObject // 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; } + void setDirty() override; + bool isDirty() const override; + bool isChainDirty() const override; bool render(); void addFocusWidget(Widget* w) override; @@ -235,7 +236,7 @@ class Dialog : public GuiObject int _tabID{0}; int _flags{0}; - bool _dirty{false}; + //bool _dirty{false}; uInt32 _max_w{0}; // maximum wanted width uInt32 _max_h{0}; // maximum wanted height diff --git a/src/gui/DialogContainer.cxx b/src/gui/DialogContainer.cxx index 950d71c06..af353f452 100644 --- a/src/gui/DialogContainer.cxx +++ b/src/gui/DialogContainer.cxx @@ -91,21 +91,32 @@ void DialogContainer::updateTime(uInt64 time) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool DialogContainer::draw(bool full) { + cerr << "draw " << full << endl; if(myDialogStack.empty()) return false; // Make the top dialog dirty if a full redraw is requested - if(full) - myDialogStack.top()->setDirty(); + //if(full) + // myDialogStack.top()->setDirty(); // If the top dialog is dirty, then all below it must be redrawn too const bool dirty = needsRedraw(); + //if(dirty) + // myDialogStack.top()->setDirty(); - myDialogStack.applyAll([&](Dialog*& d){ - if(dirty) - d->setDirty(); - full |= d->render(); - }); + //myDialogStack.applyAll([&](Dialog*& d){ + // if(dirty) + // d->setDirty(); + // full |= d->render(); + //}); + //if(dirty) + { + myDialogStack.applyAll([&](Dialog*& d) { + if(d->needsRedraw()) + //d->setDirty(); + full |= d->render(); + }); + } return full; } @@ -113,7 +124,9 @@ bool DialogContainer::draw(bool full) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool DialogContainer::needsRedraw() const { - return !myDialogStack.empty() ? myDialogStack.top()->isDirty() : false; + return !myDialogStack.empty() + ? myDialogStack.top()->needsRedraw() + : false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -133,6 +146,9 @@ int DialogContainer::addDialog(Dialog* d) "Unable to show dialog box; FIX THE CODE"); else { + // fade out current top dialog + if(!myDialogStack.empty()) + myDialogStack.top()->setDirty(); d->setDirty(); myDialogStack.push(d); } @@ -145,8 +161,16 @@ void DialogContainer::removeDialog() if(!myDialogStack.empty()) { myDialogStack.pop(); + // necessary as long as all dialogs share the same surface if(!myDialogStack.empty()) - myDialogStack.top()->setDirty(); + { + //myDialogStack.top()->setDirty(); + + // Mark all dialogs for redraw + myDialogStack.applyAll([&](Dialog*& d){ + d->setDirty(); + }); + } } } diff --git a/src/gui/GuiObject.hxx b/src/gui/GuiObject.hxx index 77392209c..2f4a5c1ca 100644 --- a/src/gui/GuiObject.hxx +++ b/src/gui/GuiObject.hxx @@ -78,6 +78,10 @@ class GuiObject : public CommandReceiver virtual bool isVisible() const = 0; virtual void setDirty() = 0; + virtual void clearDirty() { _dirty = false; } + virtual bool isDirty() const { return _dirty; } + virtual bool isChainDirty() const = 0; + virtual bool needsRedraw() const { return isDirty() || isChainDirty(); }; /** Add given widget(s) to the focus list */ virtual void addFocusWidget(Widget* w) = 0; @@ -104,6 +108,7 @@ class GuiObject : public CommandReceiver protected: int _x{0}, _y{0}, _w{0}, _h{0}; + bool _dirty{false}; Widget* _firstWidget{nullptr}; WidgetArray _focusList; diff --git a/src/gui/ScrollBarWidget.cxx b/src/gui/ScrollBarWidget.cxx index 335b716de..451f4a3ee 100644 --- a/src/gui/ScrollBarWidget.cxx +++ b/src/gui/ScrollBarWidget.cxx @@ -315,6 +315,7 @@ void ScrollBarWidget::drawWidget(bool hilite) s.fillRect(_x + 1, _y + _sliderPos - 1, _w - 2, _sliderHeight + 2, onTop ? (hilite && _part == Part::Slider) ? kScrollColorHi : kScrollColor : kColor); } + clearDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/TabWidget.cxx b/src/gui/TabWidget.cxx index f1b92b876..61f42b350 100644 --- a/src/gui/TabWidget.cxx +++ b/src/gui/TabWidget.cxx @@ -275,39 +275,45 @@ void TabWidget::drawWidget(bool hilite) // The tab widget is strange in that it acts as both a widget (obviously) // and a dialog (it contains other widgets). Because of the latter, // it must assume responsibility for refreshing all its children. - Widget::setDirtyInChain(_tabs[_activeTab].firstWidget); - FBSurface& s = dialog().surface(); - bool onTop = _boss->dialog().isOnTop(); - - // Iterate over all tabs and draw them - int i, x = _x + kTabLeftOffset; - for (i = 0; i < int(_tabs.size()); ++i) + if(isDirty()) { - int tabWidth = _tabs[i].tabWidth ? _tabs[i].tabWidth : _tabWidth; - ColorId fontcolor = _tabs[i].enabled && onTop? kTextColor : kColor; - int yOffset = (i == _activeTab) ? 0 : 1; - s.fillRect(x, _y + 1, tabWidth, _tabHeight - 1, - (i == _activeTab) - ? onTop ? kDlgColor : kBGColorLo - : onTop ? kBGColorHi : kDlgColor); // ? kWidColor : kDlgColor - s.drawString(_font, _tabs[i].title, x + kTabPadding + yOffset, - _y + yOffset + (_tabHeight - _lineHeight - 1), - tabWidth - 2 * kTabPadding, fontcolor, TextAlign::Center); - if(i == _activeTab) + FBSurface& s = dialog().surface(); + bool onTop = _boss->dialog().isOnTop(); + + // Iterate over all tabs and draw them + int i, x = _x + kTabLeftOffset; + for(i = 0; i < int(_tabs.size()); ++i) { - s.hLine(x, _y, x + tabWidth - 1, onTop ? kWidColor : kDlgColor); - s.vLine(x + tabWidth, _y + 1, _y + _tabHeight - 1, onTop ? kBGColorLo : kColor); + int tabWidth = _tabs[i].tabWidth ? _tabs[i].tabWidth : _tabWidth; + ColorId fontcolor = _tabs[i].enabled && onTop ? kTextColor : kColor; + int yOffset = (i == _activeTab) ? 0 : 1; + s.fillRect(x, _y + 1, tabWidth, _tabHeight - 1, + (i == _activeTab) + ? onTop ? kDlgColor : kBGColorLo + : onTop ? kBGColorHi : kDlgColor); // ? kWidColor : kDlgColor + s.drawString(_font, _tabs[i].title, x + kTabPadding + yOffset, + _y + yOffset + (_tabHeight - _lineHeight - 1), + tabWidth - 2 * kTabPadding, fontcolor, TextAlign::Center); + if(i == _activeTab) + { + s.hLine(x, _y, x + tabWidth - 1, onTop ? kWidColor : kDlgColor); + s.vLine(x + tabWidth, _y + 1, _y + _tabHeight - 1, onTop ? kBGColorLo : kColor); + } + else + s.hLine(x, _y + _tabHeight, x + tabWidth, onTop ? kWidColor : kDlgColor); + + x += tabWidth + kTabSpacing; } - else - s.hLine(x, _y + _tabHeight, x + tabWidth, onTop ? kWidColor : kDlgColor); - x += tabWidth + kTabSpacing; + // fill empty right space + s.hLine(x - kTabSpacing + 1, _y + _tabHeight, _x + _w - 1, onTop ? kWidColor : kDlgColor); + s.hLine(_x, _y + _h - 1, _x + _w - 1, onTop ? kBGColorLo : kColor); + + clearDirty(); + // Make all child widgets of currently active tab dirty + Widget::setDirtyInChain(_tabs[_activeTab].firstWidget); } - - // fill empty right space - s.hLine(x - kTabSpacing + 1, _y + _tabHeight, _x + _w - 1, onTop ? kWidColor : kDlgColor); - s.hLine(_x, _y + _h - 1, _x + _w - 1, onTop ? kBGColorLo : kColor); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/Widget.cxx b/src/gui/Widget.cxx index a54d9e45c..33f6b632d 100644 --- a/src/gui/Widget.cxx +++ b/src/gui/Widget.cxx @@ -56,7 +56,41 @@ void Widget::setDirty() { // A widget being dirty indicates that its parent dialog is dirty // So we inform the parent about it - _boss->dialog().setDirty(); + //_boss->dialog().setDirty(); + //cerr << "set dirty " << typeid(*this).name() << endl; + + _dirty = true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Widget::isDirty() const +{ + string name = typeid(*this).name(); + if(_dirty && name == "class TabWidget") + cerr << "is dirty " << typeid(*this).name() << endl; + + return _dirty; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Widget::isChainDirty() const +{ + string name = typeid(*this).name(); + if(_dirty && name == "class TabWidget") + cerr << "is chain dirty " << typeid(*this).name() << endl; + + bool dirty = false; + + // Check if widget or any subwidgets are dirty + Widget* w = _firstWidget; + + while(!dirty && w) + { + dirty |= w->isDirty(); + w = w->_next; + } + + return dirty; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -65,60 +99,67 @@ void Widget::draw() if(!isVisible() || !_boss->isVisible()) return; - FBSurface& s = _boss->dialog().surface(); - - bool onTop = _boss->dialog().isOnTop(); - - bool hasBorder = _flags & Widget::FLAG_BORDER; // currently only used by Dialog widget - int oldX = _x, oldY = _y; - - // Account for our relative position in the dialog - _x = getAbsX(); - _y = getAbsY(); - - // Clear background (unless alpha blending is enabled) - if(_flags & Widget::FLAG_CLEARBG) + if(isDirty()) { - int x = _x, y = _y, w = _w, h = _h; + //cerr << " *** draw widget " << typeid(*this).name() << " ***" << endl; + + FBSurface& s = _boss->dialog().surface(); + + bool onTop = _boss->dialog().isOnTop(); + + bool hasBorder = _flags & Widget::FLAG_BORDER; // currently only used by Dialog widget + int oldX = _x, oldY = _y; + + // Account for our relative position in the dialog + _x = getAbsX(); + _y = getAbsY(); + + // Clear background (unless alpha blending is enabled) + if(_flags & Widget::FLAG_CLEARBG) + { + int x = _x, y = _y, w = _w, h = _h; + if(hasBorder) + { + x++; y++; w -= 2; h -= 2; + } + s.fillRect(x, y, w, h, !onTop ? _bgcolorlo : (_flags & Widget::FLAG_HILITED) && isEnabled() ? _bgcolorhi : _bgcolor); + } + + // Draw border if(hasBorder) { - x++; y++; w-=2; h-=2; + s.frameRect(_x, _y, _w, _h, !onTop ? kColor : (_flags & Widget::FLAG_HILITED) && isEnabled() ? kWidColorHi : kColor); + _x += 4; + _y += 4; + _w -= 8; + _h -= 8; } - s.fillRect(x, y, w, h, !onTop ? _bgcolorlo : (_flags & Widget::FLAG_HILITED) && isEnabled() ? _bgcolorhi : _bgcolor); + + // Now perform the actual widget draw + drawWidget((_flags & Widget::FLAG_HILITED) ? true : false); + + // Restore x/y + if(hasBorder) + { + _x -= 4; + _y -= 4; + _w += 8; + _h += 8; + } + + _x = oldX; + _y = oldY; } - // Draw border - if(hasBorder) - { - s.frameRect(_x, _y, _w, _h, !onTop ? kColor : (_flags & Widget::FLAG_HILITED) && isEnabled() ? kWidColorHi : kColor); - _x += 4; - _y += 4; - _w -= 8; - _h -= 8; - } - - // Now perform the actual widget draw - drawWidget((_flags & Widget::FLAG_HILITED) ? true : false); - - // Restore x/y - if (hasBorder) - { - _x -= 4; - _y -= 4; - _w += 8; - _h += 8; - } - - _x = oldX; - _y = oldY; - // Draw all children Widget* w = _firstWidget; while(w) { - w->draw(); + if(w->needsRedraw()) + w->draw(); w = w->_next; } + clearDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -290,6 +331,7 @@ void Widget::setDirtyInChain(Widget* start) { while(start) { + //cerr << "setDirtyInChain " << typeid(*start).name() << endl; start->setDirty(); start = start->_next; } @@ -345,8 +387,6 @@ void StaticTextWidget::drawWidget(bool hilite) bool onTop = _boss->dialog().isOnTop(); s.drawString(_font, _label, _x, _y, _w, isEnabled() && onTop ? _textcolor : kColor, _align, 0, true, _shadowcolor); - - setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -454,6 +494,7 @@ void ButtonWidget::setBitmap(const uInt32* bitmap, int bmw, int bmh) _bmh = bmh; _bmw = bmw; + cerr << "setBitmap" << endl; setDirty(); } @@ -474,8 +515,6 @@ void ButtonWidget::drawWidget(bool hilite) !(isEnabled() && onTop) ? _textcolorlo : hilite ? _textcolorhi : _textcolor, _bmw, _bmh); - - setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -635,8 +674,6 @@ void CheckboxWidget::drawWidget(bool hilite) // Finally draw the label s.drawString(_font, _label, _x + prefixSize(_font), _y + _textY, _w, onTop && isEnabled() ? kTextColor : kColor); - - setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -652,7 +689,7 @@ SliderWidget::SliderWidget(GuiObject* boss, const GUI::Font& font, _valueLabelWidth(valueLabelWidth), _forceLabelSign(forceLabelSign) { - _flags = Widget::FLAG_ENABLED | Widget::FLAG_TRACK_MOUSE; + _flags = Widget::FLAG_ENABLED | Widget::FLAG_TRACK_MOUSE | Widget::FLAG_CLEARBG;; _bgcolor = kDlgColor; _bgcolorhi = kDlgColor; @@ -870,8 +907,6 @@ void SliderWidget::drawWidget(bool hilite) if(_valueLabelWidth > 0) s.drawString(_font, _valueLabel + _valueUnit, _x + _w - _valueLabelWidth, _y + 2, _valueLabelWidth, isEnabled() ? kTextColor : kColor); - - setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/Widget.hxx b/src/gui/Widget.hxx index 50e71681e..c5fd8dced 100644 --- a/src/gui/Widget.hxx +++ b/src/gui/Widget.hxx @@ -83,6 +83,8 @@ class Widget : public GuiObject virtual bool handleEvent(Event::Type event) { return false; } void setDirty() override; + bool isDirty() const override; + bool isChainDirty() const override; void draw() override; void receivedFocus(); void lostFocus();