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;
_editMode = false;
_dyCaret = 1;
_cols = w / _fontWidth;
_rows = h / _lineHeight;
@ -485,7 +486,6 @@ string RomListWidget::getToolTip(Common::Point pos) const
const string valStr = bytes.substr((idx.x / 3) * 3, 2);
val = static_cast<Int32>(stol(valStr, nullptr, 16));
}
ostringstream buf;

View File

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

View File

@ -107,7 +107,7 @@ void EditableWidget::receivedFocusWidget()
{
_caretTimer = 0;
_caretEnabled = true;
dialog().tooltip().release();
dialog().tooltip().hide();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -379,7 +379,7 @@ void EditableWidget::drawCaretSelection()
x += _x;
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,
kTextColorInv, TextAlign::Left, 0, false);
}
@ -397,8 +397,8 @@ void EditableWidget::drawCaretSelection()
x += _x;
y += _y;
s.vLine(x, y + 1, y + editRect.h() - 3, color);
s.vLine(x - 1, y + 1, y + editRect.h() - 3, color);
s.vLine(x, y + 1 + _dyCaret, y + editRect.h() - 3, color);
s.vLine(x - 1, y + 1 + _dyCaret, y + editRect.h() - 3, color);
clearDirty();
}
}

View File

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

View File

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

View File

@ -50,6 +50,36 @@ void StringListWidget::setList(const StringList& list)
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)
{

View File

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

View File

@ -17,6 +17,7 @@
#include "OSystem.hxx"
#include "Dialog.hxx"
#include "DialogContainer.hxx"
#include "Font.hxx"
#include "FrameBuffer.hxx"
#include "FBSurface.hxx"
@ -25,17 +26,8 @@
#include "ToolTip.hxx"
// 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
// + allow reversing ToogleWidget (TooglePixelWidget)
// + shift checkbox bits
// - RomListWidget (hex codes, maybe disassembly operands)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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)
{
// Called each mouse move
myMousePos = pos;
myFocusWidget = widget;
if(myTipWidget != widget)
{
myFocusWidget = widget;
release();
}
if(myTipShown && myTipWidget->changedToolTip(myPos, pos))
{
myPos = pos;
show();
}
release(false);
if(!myTipShown)
release(true);
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;
myTipWidget = myFocusWidget = nullptr;
myTipShown = false;
myDialog.setDirtyChain();
myDialog.instance().frameBuffer().setPendingRender();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ToolTip::release()
void ToolTip::release(bool emptyTip)
{
if(myTipShown)
{
myTimer = DELAY_TIME - 1;
myTipShown = false;
myDialog.setDirtyChain();
myDialog.instance().frameBuffer().setPendingRender();
}
// After displaying a tip, slowly reset the timer to 0
// until a new tip is requested
if(myTipWidget != myFocusWidget && myTimer)
if((emptyTip || myTipWidget != myFocusWidget) && myTimer)
myTimer--;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ToolTip::request()
void ToolTip::show(const string& tip)
{
myTipWidget = myFocusWidget;
if(myTimer == DELAY_TIME)
show();
myTimer++;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ToolTip::show()
{
if(myTipWidget == nullptr)
return;
myTipPos = myMousePos;
const uInt32 V_GAP = 1;
const uInt32 H_CURSOR = 18;
string text = myTipWidget->getToolTip(myPos);
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);
uInt32 width = std::min(myWidth, myFont->getStringWidth(tip) + 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;
const Int32 xAbs = myTipPos.x + dialogRect.x() / myScale;
const uInt32 yAbs = myTipPos.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
@ -161,11 +155,11 @@ void ToolTip::show()
mySurface->frameRect(0, 0, width, myHeight, kColor);
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);
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.
This allows faster tip display of the next tip.
*/
void release();
void release(bool emptyTip);
/**
Update focused widget and current mouse position.
@ -69,10 +69,13 @@ class ToolTip
void render();
private:
void show();
void show(const string& tip);
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;
const GUI::Font* myFont{nullptr};
@ -80,7 +83,8 @@ class ToolTip
const Widget* myFocusWidget{nullptr};
uInt32 myTimer{0};
Common::Point myPos;
Common::Point myMousePos;
Common::Point myTipPos;
uInt32 myWidth{0};
uInt32 myHeight{0};
uInt32 myTextXOfs{0};

View File

@ -97,8 +97,8 @@ void Widget::draw()
if(isDirty())
{
//cerr << " *** draw widget " << typeid(*this).name() << " ***" << endl;
cerr << "w";
cerr << " *** draw widget " << typeid(*this).name() << " ***" << endl;
//cerr << "w";
FBSurface& s = _boss->dialog().surface();
@ -418,6 +418,23 @@ void StaticTextWidget::setLabel(const string& label)
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)
{

View File

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