keep tooltips visible while mouse moves in focus

show tooltips faster when moving from one to another
update tooltip when mouse moves over different widget items
disable tooltip when editing
This commit is contained in:
thrust26 2020-11-17 12:33:47 +01:00
parent f1f5938b79
commit d7fe5510bb
14 changed files with 205 additions and 112 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<FBSurface> 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<FBSurface> mySurface;
};
#endif

View File

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

View File

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

View File

@ -26,6 +26,7 @@ class Dialog;
#include <cassert>
#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); }