improved tooltip handling (better delays, rerender instead of redraw)

added tooltip to StringListWidget for shortened texts (e.g. ROM names in launcher)
added code for StaticTextWidget tooltip (without setting widget dirty)
This commit is contained in:
thrust26 2020-11-18 17:48:19 +01:00
parent 1e4f3563b6
commit 3690d83c7f
11 changed files with 130 additions and 73 deletions

View File

@ -40,6 +40,7 @@ RomListWidget::RomListWidget(GuiObject* boss, const GUI::Font& lfont,
_textcolorhi = kTextColor; _textcolorhi = kTextColor;
_editMode = false; _editMode = false;
_dyCaret = 1;
_cols = w / _fontWidth; _cols = w / _fontWidth;
_rows = h / _lineHeight; _rows = h / _lineHeight;
@ -485,7 +486,6 @@ string RomListWidget::getToolTip(Common::Point pos) const
const string valStr = bytes.substr((idx.x / 3) * 3, 2); const string valStr = bytes.substr((idx.x / 3) * 3, 2);
val = static_cast<Int32>(stol(valStr, nullptr, 16)); val = static_cast<Int32>(stol(valStr, nullptr, 16));
} }
ostringstream buf; ostringstream buf;

View File

@ -432,9 +432,9 @@ void Dialog::drawDialog()
FBSurface& s = surface(); FBSurface& s = surface();
cerr << endl << "d";
if(isDirty()) if(isDirty())
{ {
cerr << endl << "d";
//cerr << "*** draw dialog " << typeid(*this).name() << " ***" << endl; //cerr << "*** draw dialog " << typeid(*this).name() << " ***" << endl;
if(clearsBackground()) if(clearsBackground())
@ -464,6 +464,8 @@ void Dialog::drawDialog()
clearDirty(); clearDirty();
} }
else
cerr << endl;
// Draw all children // Draw all children
drawChain(); drawChain();

View File

@ -107,7 +107,7 @@ void EditableWidget::receivedFocusWidget()
{ {
_caretTimer = 0; _caretTimer = 0;
_caretEnabled = true; _caretEnabled = true;
dialog().tooltip().release(); dialog().tooltip().hide();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -379,7 +379,7 @@ void EditableWidget::drawCaretSelection()
x += _x; x += _x;
y += _y; y += _y;
s.fillRect(x - 1, y + 1, w + 1, h - 3, kTextColorHi); s.fillRect(x - 1, y + 1 + _dyCaret, w + 1, h - 3, kTextColorHi);
s.drawString(_font, text, x, y + 1, w, h, s.drawString(_font, text, x, y + 1, w, h,
kTextColorInv, TextAlign::Left, 0, false); kTextColorInv, TextAlign::Left, 0, false);
} }
@ -397,8 +397,8 @@ void EditableWidget::drawCaretSelection()
x += _x; x += _x;
y += _y; y += _y;
s.vLine(x, y + 1, y + editRect.h() - 3, color); s.vLine(x, y + 1 + _dyCaret, y + editRect.h() - 3, color);
s.vLine(x - 1, y + 1, y + editRect.h() - 3, color); s.vLine(x - 1, y + 1 + _dyCaret, y + editRect.h() - 3, color);
clearDirty(); clearDirty();
} }
} }

View File

@ -125,6 +125,7 @@ class EditableWidget : public Widget, public CommandSender
protected: protected:
int _editScrollOffset{0}; int _editScrollOffset{0};
bool _editMode{true}; bool _editMode{true};
int _dyCaret{0};
private: private:
TextFilter _filter; TextFilter _filter;

View File

@ -106,20 +106,20 @@ class GuiObject : public CommandReceiver
virtual void tick() = 0; virtual void tick() = 0;
void setFlags(uInt32 flags) void setFlags(uInt32 flags, bool updateDirty = true)
{ {
uInt32 oldFlags = _flags; uInt32 oldFlags = _flags;
_flags |= flags; _flags |= flags;
if(oldFlags != _flags) if(updateDirty && oldFlags != _flags)
setDirty(); setDirty();
} }
void clearFlags(uInt32 flags) void clearFlags(uInt32 flags, bool updateDirty = true)
{ {
uInt32 oldFlags = _flags; uInt32 oldFlags = _flags;
_flags &= ~flags; _flags &= ~flags;
if(oldFlags != _flags) if(updateDirty && oldFlags != _flags)
setDirty(); setDirty();
} }
uInt32 getFlags() const { return _flags; } uInt32 getFlags() const { return _flags; }

View File

@ -50,6 +50,36 @@ void StringListWidget::setList(const StringList& list)
ListWidget::recalc(); ListWidget::recalc();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int StringListWidget::getToolTipIndex(Common::Point pos) const
{
return (pos.y - getAbsY()) / _lineHeight + _currentPos;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string StringListWidget::getToolTip(Common::Point pos) const
{
Common::Rect rect = getEditRect();
const string value = _list[getToolTipIndex(pos)];
if(uInt32(_font.getStringWidth(value)) > rect.w())
return _toolTipText + value;
else
return _toolTipText;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool StringListWidget::changedToolTip(Common::Point oldPos, Common::Point newPos) const
{
bool ch = getToolTipIndex(oldPos) != getToolTipIndex(newPos)
&& getToolTip(oldPos) != getToolTip(newPos);
if(ch)
cerr << "changed" << endl;
return ch;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StringListWidget::drawWidget(bool hilite) void StringListWidget::drawWidget(bool hilite)
{ {

View File

@ -32,10 +32,16 @@ class StringListWidget : public ListWidget
void setList(const StringList& list); void setList(const StringList& list);
bool wantsFocus() const override { return true; } bool wantsFocus() const override { return true; }
string getToolTip(Common::Point pos) const override;
bool changedToolTip(Common::Point oldPos, Common::Point newPos) const override;
protected: protected:
// display depends on _hasFocus so we have to redraw when focus changes // display depends on _hasFocus so we have to redraw when focus changes
void receivedFocusWidget() override { setDirty(); } void receivedFocusWidget() override { setDirty(); }
void lostFocusWidget() override { setDirty(); } void lostFocusWidget() override { setDirty(); }
bool hasToolTip() const override { return true; }
void drawWidget(bool hilite) override; void drawWidget(bool hilite) override;
Common::Rect getEditRect() const override; Common::Rect getEditRect() const override;
@ -43,6 +49,9 @@ class StringListWidget : public ListWidget
bool _hilite{false}; bool _hilite{false};
int _textOfs{0}; int _textOfs{0};
private:
int getToolTipIndex(Common::Point pos) const;
private: private:
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
StringListWidget() = delete; StringListWidget() = delete;

View File

@ -17,6 +17,7 @@
#include "OSystem.hxx" #include "OSystem.hxx"
#include "Dialog.hxx" #include "Dialog.hxx"
#include "DialogContainer.hxx"
#include "Font.hxx" #include "Font.hxx"
#include "FrameBuffer.hxx" #include "FrameBuffer.hxx"
#include "FBSurface.hxx" #include "FBSurface.hxx"
@ -25,17 +26,8 @@
#include "ToolTip.hxx" #include "ToolTip.hxx"
// TODOs: // 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 // - option to disable tips
// - multi line tips // - multi line tips
// + nicer formating of DataDridWidget tip
// + allow reversing ToogleWidget (TooglePixelWidget)
// + shift checkbox bits
// - RomListWidget (hex codes, maybe disassembly operands)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ToolTip::ToolTip(Dialog& dialog, const GUI::Font& font) ToolTip::ToolTip(Dialog& dialog, const GUI::Font& font)
@ -61,21 +53,48 @@ void ToolTip::setFont(const GUI::Font& font)
mySurface = myDialog.instance().frameBuffer().allocateSurface(myWidth, myHeight); mySurface = myDialog.instance().frameBuffer().allocateSurface(myWidth, myHeight);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ToolTip::request()
{
// Called each frame when a tooltip is wanted
if(myFocusWidget && myTimer < DELAY_TIME * RELEASE_SPEED)
{
const string tip = myFocusWidget->getToolTip(myMousePos);
if(!tip.empty())
{
myTipWidget = myFocusWidget;
myTimer += RELEASE_SPEED;
if(myTimer >= DELAY_TIME * RELEASE_SPEED)
show(tip);
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ToolTip::update(const Widget* widget, const Common::Point& pos) void ToolTip::update(const Widget* widget, const Common::Point& pos)
{ {
// Called each mouse move
myMousePos = pos;
myFocusWidget = widget;
if(myTipWidget != widget) if(myTipWidget != widget)
{ release(false);
myFocusWidget = widget;
release(); if(!myTipShown)
} release(true);
if(myTipShown && myTipWidget->changedToolTip(myPos, pos))
{
myPos = pos;
show();
}
else else
myPos = pos; {
if(myTipWidget->changedToolTip(myTipPos, myMousePos))
{
const string tip = myTipWidget->getToolTip(myMousePos);
if(!tip.empty())
show(tip);
else
release(true);
}
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -85,65 +104,40 @@ void ToolTip::hide()
{ {
myTimer = 0; myTimer = 0;
myTipWidget = myFocusWidget = nullptr; myTipWidget = myFocusWidget = nullptr;
myTipShown = false; myTipShown = false;
myDialog.setDirtyChain(); myDialog.instance().frameBuffer().setPendingRender();
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ToolTip::release() void ToolTip::release(bool emptyTip)
{ {
if(myTipShown) if(myTipShown)
{ {
myTimer = DELAY_TIME - 1;
myTipShown = false; myTipShown = false;
myDialog.setDirtyChain(); myDialog.instance().frameBuffer().setPendingRender();
} }
// After displaying a tip, slowly reset the timer to 0 // After displaying a tip, slowly reset the timer to 0
// until a new tip is requested // until a new tip is requested
if(myTipWidget != myFocusWidget && myTimer) if((emptyTip || myTipWidget != myFocusWidget) && myTimer)
myTimer--; myTimer--;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ToolTip::request() void ToolTip::show(const string& tip)
{ {
myTipWidget = myFocusWidget; myTipPos = myMousePos;
if(myTimer == DELAY_TIME)
show();
myTimer++;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ToolTip::show()
{
if(myTipWidget == nullptr)
return;
const uInt32 V_GAP = 1; const uInt32 V_GAP = 1;
const uInt32 H_CURSOR = 18; const uInt32 H_CURSOR = 18;
string text = myTipWidget->getToolTip(myPos); uInt32 width = std::min(myWidth, myFont->getStringWidth(tip) + myTextXOfs * 2);
myTipShown = true;
if(text.empty())
{
release();
return;
}
uInt32 width = std::min(myWidth, myFont->getStringWidth(text) + myTextXOfs * 2);
//uInt32 height = std::min(myHeight, font.getFontHeight() + myTextYOfs * 2);
// Note: The rects include HiDPI scaling // Note: The rects include HiDPI scaling
const Common::Rect imageRect = myDialog.instance().frameBuffer().imageRect(); const Common::Rect imageRect = myDialog.instance().frameBuffer().imageRect();
const Common::Rect dialogRect = myDialog.surface().dstRect(); const Common::Rect dialogRect = myDialog.surface().dstRect();
// Limit position to app size and adjust accordingly // Limit position to app size and adjust accordingly
const Int32 xAbs = myPos.x + dialogRect.x() / myScale; const Int32 xAbs = myTipPos.x + dialogRect.x() / myScale;
const uInt32 yAbs = myPos.y + dialogRect.y() / myScale; const uInt32 yAbs = myTipPos.y + dialogRect.y() / myScale;
Int32 x = std::min(xAbs, Int32(imageRect.w() / myScale - width)); Int32 x = std::min(xAbs, Int32(imageRect.w() / myScale - width));
const uInt32 y = (yAbs + myHeight + H_CURSOR > imageRect.h() / myScale) const uInt32 y = (yAbs + myHeight + H_CURSOR > imageRect.h() / myScale)
? yAbs - myHeight - V_GAP ? yAbs - myHeight - V_GAP
@ -161,11 +155,11 @@ void ToolTip::show()
mySurface->frameRect(0, 0, width, myHeight, kColor); mySurface->frameRect(0, 0, width, myHeight, kColor);
mySurface->fillRect(1, 1, width - 2, myHeight - 2, kWidColor); mySurface->fillRect(1, 1, width - 2, myHeight - 2, kWidColor);
mySurface->drawString(*myFont, text, myTextXOfs, myTextYOfs, mySurface->drawString(*myFont, tip, myTextXOfs, myTextYOfs,
width - myTextXOfs * 2, myHeight - myTextYOfs * 2, kTextColor); width - myTextXOfs * 2, myHeight - myTextYOfs * 2, kTextColor);
myTipShown = true; myTipShown = true;
myDialog.setDirtyChain(); myDialog.instance().frameBuffer().setPendingRender();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -56,7 +56,7 @@ class ToolTip
Hide a displayed tooltip and reset the timer slowly. Hide a displayed tooltip and reset the timer slowly.
This allows faster tip display of the next tip. This allows faster tip display of the next tip.
*/ */
void release(); void release(bool emptyTip);
/** /**
Update focused widget and current mouse position. Update focused widget and current mouse position.
@ -69,10 +69,13 @@ class ToolTip
void render(); void render();
private: private:
void show(); void show(const string& tip);
private: private:
static constexpr uInt32 DELAY_TIME = 45; // display delay static constexpr uInt32 DELAY_TIME = 45; // display delay [frames]
// Tips are slower released than requested, so that repeated tips are shown
// faster. This constant defines how much faster.
static constexpr uInt32 RELEASE_SPEED = 2;
Dialog& myDialog; Dialog& myDialog;
const GUI::Font* myFont{nullptr}; const GUI::Font* myFont{nullptr};
@ -80,7 +83,8 @@ class ToolTip
const Widget* myFocusWidget{nullptr}; const Widget* myFocusWidget{nullptr};
uInt32 myTimer{0}; uInt32 myTimer{0};
Common::Point myPos; Common::Point myMousePos;
Common::Point myTipPos;
uInt32 myWidth{0}; uInt32 myWidth{0};
uInt32 myHeight{0}; uInt32 myHeight{0};
uInt32 myTextXOfs{0}; uInt32 myTextXOfs{0};

View File

@ -97,8 +97,8 @@ void Widget::draw()
if(isDirty()) if(isDirty())
{ {
//cerr << " *** draw widget " << typeid(*this).name() << " ***" << endl; cerr << " *** draw widget " << typeid(*this).name() << " ***" << endl;
cerr << "w"; //cerr << "w";
FBSurface& s = _boss->dialog().surface(); FBSurface& s = _boss->dialog().surface();
@ -418,6 +418,23 @@ void StaticTextWidget::setLabel(const string& label)
setDirty(); setDirty();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StaticTextWidget::handleMouseEntered()
{
if(isEnabled())
// Mouse focus for tooltips must not change dirty status
setFlags(Widget::FLAG_MOUSE_FOCUS, false);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StaticTextWidget::handleMouseLeft()
{
if(isEnabled())
// Mouse focus for tooltips must not change dirty status
clearFlags(Widget::FLAG_MOUSE_FOCUS, false);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StaticTextWidget::drawWidget(bool hilite) void StaticTextWidget::drawWidget(bool hilite)
{ {

View File

@ -186,8 +186,8 @@ class StaticTextWidget : public Widget
ColorId shadowColor = kNone); ColorId shadowColor = kNone);
~StaticTextWidget() override = default; ~StaticTextWidget() override = default;
void handleMouseEntered() override {} void handleMouseEntered() override;
void handleMouseLeft() override {} void handleMouseLeft() override;
void setValue(int value); void setValue(int value);
void setLabel(const string& label); void setLabel(const string& label);