diff --git a/src/debugger/gui/DataGridWidget.cxx b/src/debugger/gui/DataGridWidget.cxx index f6898b6c8..d11375d29 100644 --- a/src/debugger/gui/DataGridWidget.cxx +++ b/src/debugger/gui/DataGridWidget.cxx @@ -571,12 +571,18 @@ void DataGridWidget::handleCommand(CommandSender* sender, int cmd, } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -string DataGridWidget::getToolTip(int x, int y) const +int DataGridWidget::getToolTipIndex(Common::Point pos) const { - const int col = (x - getAbsX()) / _colWidth; - const int row = (y - getAbsY()) / _rowHeight; - const int pos = row * _cols + col; - const Int32 val = _valueList[pos]; + const int col = (pos.x - getAbsX()) / _colWidth; + const int row = (pos.y - getAbsY()) / _rowHeight; + + return row * _cols + col; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string DataGridWidget::getToolTip(Common::Point pos) const +{ + const Int32 val = _valueList[getToolTipIndex(pos)]; const string hex = Common::Base::toString(val, Common::Base::Fmt::_16); const string dec = Common::Base::toString(val, Common::Base::Fmt::_10); const string bin = Common::Base::toString(val, Common::Base::Fmt::_2); @@ -587,6 +593,12 @@ string DataGridWidget::getToolTip(int x, int y) const return buf.str(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool DataGridWidget::changedToolTip(Common::Point oldPos, Common::Point newPos) const +{ + return getToolTipIndex(oldPos) != getToolTipIndex(newPos); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DataGridWidget::drawWidget(bool hilite) { diff --git a/src/debugger/gui/DataGridWidget.hxx b/src/debugger/gui/DataGridWidget.hxx index f50270f4f..1a402659d 100644 --- a/src/debugger/gui/DataGridWidget.hxx +++ b/src/debugger/gui/DataGridWidget.hxx @@ -84,8 +84,8 @@ class DataGridWidget : public EditableWidget void setCrossed(bool enable) { _crossGrid = enable; } - string getToolTip(int x = 0, int y = 0) const override; - bool hasToolTip() const override { return true; } + string getToolTip(Common::Point pos) const override; + bool changedToolTip(Common::Point oldPos, Common::Point newPos) const override; protected: void drawWidget(bool hilite) override; @@ -101,6 +101,8 @@ class DataGridWidget : public EditableWidget void receivedFocusWidget() override; void lostFocusWidget() override; + bool hasToolTip() const override { return true; } + void handleMouseDown(int x, int y, MouseButton b, int clickCount) override; void handleMouseUp(int x, int y, MouseButton b, int clickCount) override; void handleMouseWheel(int x, int y, int direction) override; @@ -148,6 +150,8 @@ class DataGridWidget : public EditableWidget void enableEditMode(bool state) { _editMode = state; } + int getToolTipIndex(Common::Point pos) const; + private: // Following constructors and assignment operators not supported DataGridWidget() = delete; diff --git a/src/debugger/gui/ToggleWidget.cxx b/src/debugger/gui/ToggleWidget.cxx index a73bc25dc..3d2fca262 100644 --- a/src/debugger/gui/ToggleWidget.cxx +++ b/src/debugger/gui/ToggleWidget.cxx @@ -212,16 +212,23 @@ void ToggleWidget::handleCommand(CommandSender* sender, int cmd, } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -string ToggleWidget::getToolTip(int, int y) const +int ToggleWidget::getToolTipIndex(Common::Point pos) const { - const int row = (y - getAbsY()) / _rowHeight; - const int pos = row * _cols;// +col; + const int row = (pos.y - getAbsY()) / _rowHeight; + + return row * _cols; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string ToggleWidget::getToolTip(Common::Point pos) const +{ + const int idx = getToolTipIndex(pos); Int32 val = 0; for(int col = 0; col < _cols; ++col) { val <<= 1; - val += _stateList[pos + col]; + val += _stateList[idx + col]; } const string hex = Common::Base::toString(val, Common::Base::Fmt::_16); @@ -234,4 +241,11 @@ string ToggleWidget::getToolTip(int, int y) const return buf.str(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool ToggleWidget::changedToolTip(Common::Point oldPos, Common::Point newPos) const +{ + return getToolTipIndex(oldPos) != getToolTipIndex(newPos); +} + + diff --git a/src/debugger/gui/ToggleWidget.hxx b/src/debugger/gui/ToggleWidget.hxx index a671f53e9..060a52e13 100644 --- a/src/debugger/gui/ToggleWidget.hxx +++ b/src/debugger/gui/ToggleWidget.hxx @@ -46,10 +46,11 @@ class ToggleWidget : public Widget, public CommandSender void setEditable(bool editable) { _editable = editable; } bool isEditable() const { return _editable; } - string getToolTip(int x = 0, int y = 0) const override; - bool hasToolTip() const override { return true; } + string getToolTip(Common::Point pos) const override; + bool changedToolTip(Common::Point oldPos, Common::Point newPos) const override; protected: + bool hasToolTip() const override { return true; } protected: int _rows; @@ -74,6 +75,8 @@ class ToggleWidget : public Widget, public CommandSender bool handleKeyDown(StellaKey key, StellaMod mod) override; void handleCommand(CommandSender* sender, int cmd, int data, int id) override; + int getToolTipIndex(Common::Point pos) const; + // Following constructors and assignment operators not supported ToggleWidget() = delete; ToggleWidget(const ToggleWidget&) = delete; diff --git a/src/gui/Dialog.cxx b/src/gui/Dialog.cxx index b9678182e..a33b4ffa1 100644 --- a/src/gui/Dialog.cxx +++ b/src/gui/Dialog.cxx @@ -593,9 +593,6 @@ void Dialog::handleMouseMoved(int x, int y) { Widget* w; - // Update mouse coordinates for tooltips - _toolTip->update(x, y); - if(_focusedWidget && !_dragWidget) { w = _focusedWidget; @@ -639,6 +636,9 @@ void Dialog::handleMouseMoved(int x, int y) if (w && (w->getFlags() & Widget::FLAG_TRACK_MOUSE)) w->handleMouseMoved(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y)); + + // Update mouse coordinates for tooltips + _toolTip->update(_mouseWidget, Common::Point(x, y)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/EditableWidget.cxx b/src/gui/EditableWidget.cxx index d6c35fa55..39a4b519f 100644 --- a/src/gui/EditableWidget.cxx +++ b/src/gui/EditableWidget.cxx @@ -22,6 +22,7 @@ #include "OSystem.hxx" #include "EventHandler.hxx" #include "UndoHandler.hxx" +#include "ToolTip.hxx" #include "EditableWidget.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -79,6 +80,12 @@ void EditableWidget::tick() Widget::tick(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool EditableWidget::wantsToolTip() const +{ + return !(_hasFocus && isEditable() && _editMode) && Widget::wantsToolTip(); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void EditableWidget::setEditable(bool editable, bool hiliteBG) { @@ -100,6 +107,7 @@ void EditableWidget::receivedFocusWidget() { _caretTimer = 0; _caretEnabled = true; + dialog().tooltip().release(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/EditableWidget.hxx b/src/gui/EditableWidget.hxx index 8ebf1791c..6e55cf541 100644 --- a/src/gui/EditableWidget.hxx +++ b/src/gui/EditableWidget.hxx @@ -68,6 +68,7 @@ class EditableWidget : public Widget, public CommandSender void receivedFocusWidget() override; void lostFocusWidget() override; void tick() override; + bool wantsToolTip() const override; virtual void startEditMode() { setFlags(Widget::FLAG_WANTS_RAWDATA); } virtual void endEditMode() { clearFlags(Widget::FLAG_WANTS_RAWDATA); } diff --git a/src/gui/LauncherDialog.cxx b/src/gui/LauncherDialog.cxx index 93361b9f5..9c140e15c 100644 --- a/src/gui/LauncherDialog.cxx +++ b/src/gui/LauncherDialog.cxx @@ -121,7 +121,7 @@ LauncherDialog::LauncherDialog(OSystem& osystem, DialogContainer& parent, // Show the filter input field xpos -= fwidth + LBL_GAP; myPattern = new EditTextWidget(this, font, xpos, ypos - 2, fwidth, lineHeight, ""); - myPattern->setToolTip("Enter a filter text to reduce file list."); + myPattern->setToolTip("Enter filter text to reduce file list."); // Show the "Filter" label xpos -= lwidth3 + LBL_GAP; new StaticTextWidget(this, font, xpos, ypos, lblFilter); diff --git a/src/gui/PopUpWidget.cxx b/src/gui/PopUpWidget.cxx index 06c726779..45f79e55b 100644 --- a/src/gui/PopUpWidget.cxx +++ b/src/gui/PopUpWidget.cxx @@ -20,6 +20,8 @@ #include "FBSurface.hxx" #include "Font.hxx" #include "ContextMenu.hxx" +#include "Dialog.hxx" +#include "ToolTip.hxx" #include "DialogContainer.hxx" #include "PopUpWidget.hxx" @@ -122,6 +124,7 @@ void PopUpWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount) { if(isEnabled() && !myMenu->isVisible()) { + dialog().tooltip().hide(); // Add menu just underneath parent widget myMenu->show(getAbsX() + _labelWidth, getAbsY() + getHeight(), dialog().surface().dstRect(), myMenu->getSelected()); diff --git a/src/gui/ToolTip.cxx b/src/gui/ToolTip.cxx index ece2f1ccc..36c8b1b20 100644 --- a/src/gui/ToolTip.cxx +++ b/src/gui/ToolTip.cxx @@ -24,8 +24,12 @@ #include "ToolTip.hxx" -// TODO: -// - disable when in edit mode +// TODOs: +// + keep enabled when mouse moves over same widget +// + static position and text for normal widgets +// + moving position and text over e.g. DataGridWidget +// + reenable tip faster +// + disable when in edit mode // - option to disable tips // - multi line tips // - nicer formating of DataDridWidget tip @@ -41,7 +45,7 @@ ToolTip::ToolTip(Dialog& dialog, const GUI::Font& font) const int fontWidth = font.getMaxCharWidth(), fontHeight = font.getFontHeight(); - myTextXOfs = fontHeight < 24 ? 5 : 8; // 3 : 5; + myTextXOfs = fontHeight < 24 ? 5 : 8; myTextYOfs = fontHeight < 24 ? 2 : 3; myWidth = fontWidth * MAX_LEN + myTextXOfs * 2; myHeight = fontHeight + myTextYOfs * 2; @@ -51,72 +55,103 @@ ToolTip::ToolTip(Dialog& dialog, const GUI::Font& font) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void ToolTip::request(const Widget* widget) +void ToolTip::update(const Widget* widget, Common::Point pos) { - if(myWidget != widget) - release(); - - if(myTimer == DELAY_TIME) + if(myTipWidget != widget) { - myWidget = widget; + myFocusWidget = widget; + release(); + } + if(myTipShown && myTipWidget->changedToolTip(myPos, pos)) + myPos = pos, show(); + else + myPos = pos; +} - const uInt32 VGAP = 1; - const uInt32 hCursor = 18; - string text = widget->getToolTip(myPos.x, myPos.y); - uInt32 width = std::min(myWidth, myFont.getStringWidth(text) + myTextXOfs * 2); - // Note: These include HiDPI scaling: - const Common::Rect imageRect = myDialog.instance().frameBuffer().imageRect(); - const Common::Rect dialogRect = myDialog.surface().dstRect(); - // Limit to app or screen size and adjust position - const Int32 xAbs = myPos.x + dialogRect.x() / myScale; - const uInt32 yAbs = myPos.y + dialogRect.y() / myScale; - Int32 x = std::min(xAbs, Int32(imageRect.w() / myScale - width)); - const uInt32 y = (yAbs + myHeight + hCursor > imageRect.h() / myScale) - ? yAbs - myHeight - VGAP - : yAbs + hCursor / myScale + VGAP; +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void ToolTip::hide() +{ + if(myTipShown) + { + myTimer = 0; + myTipWidget = myFocusWidget = nullptr; - if(x < 0) - { - x = 0; - width = imageRect.w() / myScale; - } - - mySurface->setSrcSize(width, myHeight); - mySurface->setDstSize(width * myScale, myHeight * myScale); - mySurface->setDstPos(x * myScale, y * myScale); - - mySurface->frameRect(0, 0, width, myHeight, kColor); - mySurface->fillRect(1, 1, width - 2, myHeight - 2, kWidColor); - mySurface->drawString(myFont, text, myTextXOfs, myTextYOfs, - width - myTextXOfs * 2, myHeight - myTextYOfs * 2, kTextColor); + myTipShown = false; myDialog.setDirtyChain(); } - myTimer++; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ToolTip::release() { - if(myWidget != nullptr) + if(myTipShown) { - myTimer = 0; - myWidget = nullptr; + myTimer = DELAY_TIME; + + myTipShown = false; myDialog.setDirtyChain(); } + + // After displaying a tip, slowly reset the timer to 0 + // until a new tip is requested + if(myTipWidget != myFocusWidget && myTimer) + myTimer--; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void ToolTip::request() +{ + myTipWidget = myFocusWidget; + + if(myTimer == DELAY_TIME) + show(); + + myTimer++; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void ToolTip::show() +{ + if(myTipWidget == nullptr) + return; + + const uInt32 V_GAP = 1; + const uInt32 H_CURSOR = 18; + string text = myTipWidget->getToolTip(myPos); + uInt32 width = std::min(myWidth, myFont.getStringWidth(text) + myTextXOfs * 2); + // Note: The rects include HiDPI scaling + const Common::Rect imageRect = myDialog.instance().frameBuffer().imageRect(); + const Common::Rect dialogRect = myDialog.surface().dstRect(); + // Limit position to app size and adjust accordingly + const Int32 xAbs = myPos.x + dialogRect.x() / myScale; + const uInt32 yAbs = myPos.y + dialogRect.y() / myScale; + Int32 x = std::min(xAbs, Int32(imageRect.w() / myScale - width)); + const uInt32 y = (yAbs + myHeight + H_CURSOR > imageRect.h() / myScale) + ? yAbs - myHeight - V_GAP + : yAbs + H_CURSOR / myScale + V_GAP; + + if(x < 0) + { + x = 0; + width = imageRect.w() / myScale; + } + + mySurface->setSrcSize(width, myHeight); + mySurface->setDstSize(width * myScale, myHeight * myScale); + mySurface->setDstPos(x * myScale, y * myScale); + + mySurface->frameRect(0, 0, width, myHeight, kColor); + mySurface->fillRect(1, 1, width - 2, myHeight - 2, kWidColor); + mySurface->drawString(myFont, text, myTextXOfs, myTextYOfs, + width - myTextXOfs * 2, myHeight - myTextYOfs * 2, kTextColor); + + myTipShown = true; + myDialog.setDirtyChain(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ToolTip::render() { - if(myWidget != nullptr) - mySurface->render(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void ToolTip::update(int x, int y) -{ - if(myWidget != nullptr && x != myPos.x || y != myPos.y) - release(); - myPos.x = x; - myPos.y = y; + if(myTipShown) + mySurface->render(), cerr << " render tooltip" << endl; } diff --git a/src/gui/ToolTip.hxx b/src/gui/ToolTip.hxx index efcf3bafe..b3bbf547a 100644 --- a/src/gui/ToolTip.hxx +++ b/src/gui/ToolTip.hxx @@ -33,50 +33,59 @@ class FBSurface; class ToolTip { -public: - // Maximum tooltip length - static constexpr uInt32 MAX_LEN = 80; + public: + // Maximum tooltip length + static constexpr uInt32 MAX_LEN = 80; - ToolTip(Dialog& dialog, const GUI::Font& font); - ~ToolTip() = default; + ToolTip(Dialog& dialog, const GUI::Font& font); + ~ToolTip() = default; - /** - Request a tooltip display - */ - void request(const Widget* widget); + /** + Request a tooltip display. + */ + void request(); + /** + Hide a displayed tooltip and reset the timer. + */ + void hide(); - /** - Hide an existing tooltip (if displayed) - */ - void release(); + /** + Hide a displayed tooltip and reset the timer slowly. + This allows faster tip display of the next tip. + */ + void release(); - /** - Update with current mouse position - */ - void update(int x, int y); + /** + Update focussed widget and current mouse position. + */ + void update(const Widget* widget, Common::Point pos); - /* - Render the tooltip - */ - void render(); + /* + Render the tooltip + */ + void render(); -private: - static constexpr uInt32 DELAY_TIME = 45; // display delay - //static constexpr int TEXT_Y_OFS = 2; + private: + void show(); - Dialog& myDialog; - const GUI::Font& myFont; + private: + static constexpr uInt32 DELAY_TIME = 45; // display delay - const Widget* myWidget{nullptr}; - uInt32 myTimer{0}; - Common::Point myPos; - uInt32 myWidth{0}; - uInt32 myHeight{0}; - uInt32 myScale{1}; - uInt32 myTextXOfs{0}; - uInt32 myTextYOfs{0}; - shared_ptr mySurface; + Dialog& myDialog; + const GUI::Font& myFont; + const Widget* myTipWidget{nullptr}; + const Widget* myFocusWidget{nullptr}; + + uInt32 myTimer{0}; + Common::Point myPos; + uInt32 myWidth{0}; + uInt32 myHeight{0}; + uInt32 myTextXOfs{0}; + uInt32 myTextYOfs{0}; + bool myTipShown{false}; + uInt32 myScale{1}; + shared_ptr mySurface; }; #endif diff --git a/src/gui/VideoAudioDialog.cxx b/src/gui/VideoAudioDialog.cxx index d34f0a330..def965aa6 100644 --- a/src/gui/VideoAudioDialog.cxx +++ b/src/gui/VideoAudioDialog.cxx @@ -129,7 +129,7 @@ void VideoAudioDialog::addDisplayTab() // TIA interpolation myTIAInterpolate = new CheckboxWidget(myTab, _font, xpos, ypos + 1, "Interpolation "); - myTIAInterpolate->setToolTip("Blur the emulated display."); + myTIAInterpolate->setToolTip("Blur emulated display."); wid.push_back(myTIAInterpolate); ypos += lineHeight + VGAP * 4; @@ -147,14 +147,14 @@ void VideoAudioDialog::addDisplayTab() // FS stretch myUseStretch = new CheckboxWidget(myTab, _font, xpos + INDENT, ypos + 1, "Stretch"); - myUseStretch->setToolTip("Stretch the emulated display to fill the whole screen."); + myUseStretch->setToolTip("Stretch emulated display to fill whole screen."); wid.push_back(myUseStretch); #ifdef ADAPTABLE_REFRESH_SUPPORT // Adapt refresh rate ypos += lineHeight + VGAP; myRefreshAdapt = new CheckboxWidget(myTab, _font, xpos + INDENT, ypos + 1, "Adapt display refresh rate"); - myRefreshAdapt->setToolTip("Select the optimal display refresh rate for each ROM."); + myRefreshAdapt->setToolTip("Select optimal display refresh rate for each ROM."); wid.push_back(myRefreshAdapt); #else myRefreshAdapt = nullptr; @@ -181,7 +181,7 @@ void VideoAudioDialog::addDisplayTab() "V-Size adjust", lwidth, kVSizeChanged, fontWidth * 7, "%", 0, true); myVSizeAdjust->setMinValue(-5); myVSizeAdjust->setMaxValue(5); myVSizeAdjust->setTickmarkIntervals(2); - myVSizeAdjust->setToolTip("Adapt vertical size to emulated TV display."); + myVSizeAdjust->setToolTip("Adjust vertical size to match emulated TV display."); wid.push_back(myVSizeAdjust); diff --git a/src/gui/Widget.cxx b/src/gui/Widget.cxx index a7fb55561..cfb933965 100644 --- a/src/gui/Widget.cxx +++ b/src/gui/Widget.cxx @@ -75,8 +75,8 @@ void Widget::tick() { if(isEnabled()) { - if(hasMouseFocus() && hasToolTip()) - dialog().tooltip().request(this); + if(wantsToolTip()) + dialog().tooltip().request(); // Recursively tick widget and all child dialogs and widgets Widget* w = _firstWidget; diff --git a/src/gui/Widget.hxx b/src/gui/Widget.hxx index 33c6a3e20..45c676fe2 100644 --- a/src/gui/Widget.hxx +++ b/src/gui/Widget.hxx @@ -26,6 +26,7 @@ class Dialog; #include #include "bspf.hxx" +#include "Rect.hxx" #include "Event.hxx" #include "EventHandlerConstants.hxx" #include "FrameBufferConstants.hxx" @@ -105,8 +106,8 @@ class Widget : public GuiObject void setShadowColor(ColorId color) { _shadowcolor = color; setDirty(); } void setToolTip(const string& text); - virtual string getToolTip(int x = 0, int y = 0) const { return _toolTipText; } - virtual bool hasToolTip() const { return !_toolTipText.empty(); } + virtual string getToolTip(Common::Point pos) const { return _toolTipText; } + virtual bool changedToolTip(Common::Point oldPos, Common::Point newPos) const { return false; } virtual void loadConfig() { } @@ -120,6 +121,9 @@ class Widget : public GuiObject void releaseFocus() override { assert(_boss); _boss->releaseFocus(); } + virtual bool wantsToolTip() const { return hasMouseFocus() && hasToolTip(); } + virtual bool hasToolTip() const { return !_toolTipText.empty(); } + // By default, delegate unhandled commands to the boss void handleCommand(CommandSender* sender, int cmd, int data, int id) override { assert(_boss); _boss->handleCommand(sender, cmd, data, id); }