aseprite/src/ui/tooltips.cpp
2020-04-08 17:48:06 -03:00

277 lines
6.3 KiB
C++

// Aseprite UI Library
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ui/tooltips.h"
#include "base/clamp.h"
#include "gfx/size.h"
#include "ui/graphics.h"
#include "ui/intern.h"
#include "ui/manager.h"
#include "ui/message.h"
#include "ui/paint_event.h"
#include "ui/scale.h"
#include "ui/size_hint_event.h"
#include "ui/system.h"
#include "ui/textbox.h"
#include "ui/theme.h"
#include <string>
static const int kTooltipDelayMsecs = 300;
namespace ui {
using namespace gfx;
TooltipManager::TooltipManager()
: Widget(kGenericWidget)
{
Manager* manager = Manager::getDefault();
manager->addMessageFilter(kMouseEnterMessage, this);
manager->addMessageFilter(kKeyDownMessage, this);
manager->addMessageFilter(kMouseDownMessage, this);
manager->addMessageFilter(kMouseLeaveMessage, this);
setVisible(false);
}
TooltipManager::~TooltipManager()
{
Manager* manager = Manager::getDefault();
manager->removeMessageFilterFor(this);
}
void TooltipManager::addTooltipFor(Widget* widget, const std::string& text, int arrowAlign)
{
ASSERT(!widget->hasFlags(IGNORE_MOUSE));
m_tips[widget] = TipInfo(text, arrowAlign);
}
void TooltipManager::removeTooltipFor(Widget* widget)
{
auto it = m_tips.find(widget);
if (it != m_tips.end())
m_tips.erase(it);
}
bool TooltipManager::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kMouseEnterMessage: {
// Tooltips are only for widgets that can directly get the mouse
// (get the kMouseEnterMessage directly).
if (Widget* widget = msg->recipient()) {
Tips::iterator it = m_tips.find(widget);
if (it != m_tips.end()) {
m_target.widget = it->first;
m_target.tipInfo = it->second;
if (m_timer == nullptr) {
m_timer.reset(new Timer(kTooltipDelayMsecs, this));
m_timer->Tick.connect(&TooltipManager::onTick, this);
}
m_timer->start();
}
}
break;
}
case kKeyDownMessage:
case kMouseDownMessage:
case kMouseLeaveMessage:
if (m_tipWindow) {
m_tipWindow->closeWindow(nullptr);
m_tipWindow.reset();
}
if (m_timer)
m_timer->stop();
return false;
}
return Widget::onProcessMessage(msg);
}
void TooltipManager::onInitTheme(InitThemeEvent& ev)
{
Widget::onInitTheme(ev);
if (m_tipWindow)
m_tipWindow->initTheme();
}
void TooltipManager::onTick()
{
if (!m_tipWindow) {
m_tipWindow.reset(new TipWindow(m_target.tipInfo.text));
int arrowAlign = m_target.tipInfo.arrowAlign;
gfx::Rect target = m_target.widget->bounds();
if (!arrowAlign)
target.setOrigin(ui::get_mouse_position()+12*guiscale());
if (m_tipWindow->pointAt(arrowAlign, target)) {
m_tipWindow->openWindow();
}
else {
// No enough room for the tooltip
m_tipWindow.reset();
m_timer->stop();
}
}
m_timer->stop();
}
// TipWindow
TipWindow::TipWindow(const std::string& text)
// Put an empty string in the ctor so the window label isn't build
: PopupWindow("", ClickBehavior::CloseOnClickInOtherWindow)
, m_arrowStyle(nullptr)
, m_arrowAlign(0)
, m_closeOnKeyDown(true)
, m_textBox(new TextBox("", LEFT | TOP))
{
setTransparent(true);
// Here we build our own custimized label for the window
// (a text box).
m_textBox->setVisible(false);
addChild(m_textBox);
setText(text);
makeFixed();
initTheme();
}
void TipWindow::setCloseOnKeyDown(bool state)
{
m_closeOnKeyDown = state;
}
bool TipWindow::pointAt(int arrowAlign, const gfx::Rect& target)
{
// TODO merge this code with the new ui::fit_bounds() algorithm
m_target = target;
m_arrowAlign = arrowAlign;
remapWindow();
int x = target.x;
int y = target.y;
int w = bounds().w;
int h = bounds().h;
int trycount = 0;
for (; trycount < 4; ++trycount) {
switch (arrowAlign) {
case TOP | LEFT:
x = m_target.x + m_target.w;
y = m_target.y + m_target.h;
break;
case TOP | RIGHT:
x = m_target.x - w;
y = m_target.y + m_target.h;
break;
case BOTTOM | LEFT:
x = m_target.x + m_target.w;
y = m_target.y - h;
break;
case BOTTOM | RIGHT:
x = m_target.x - w;
y = m_target.y - h;
break;
case TOP:
x = m_target.x + m_target.w/2 - w/2;
y = m_target.y + m_target.h;
break;
case BOTTOM:
x = m_target.x + m_target.w/2 - w/2;
y = m_target.y - h;
break;
case LEFT:
x = m_target.x + m_target.w;
y = m_target.y + m_target.h/2 - h/2;
break;
case RIGHT:
x = m_target.x - w;
y = m_target.y + m_target.h/2 - h/2;
break;
}
x = base::clamp(x, 0, ui::display_w()-w);
y = base::clamp(y, 0, ui::display_h()-h);
if (m_target.intersects(gfx::Rect(x, y, w, h))) {
switch (trycount) {
case 0:
case 2:
// Switch position
if (arrowAlign & (TOP | BOTTOM)) arrowAlign ^= TOP | BOTTOM;
if (arrowAlign & (LEFT | RIGHT)) arrowAlign ^= LEFT | RIGHT;
break;
case 1:
// Rotate positions
if (arrowAlign & (TOP | LEFT)) arrowAlign ^= TOP | LEFT;
if (arrowAlign & (BOTTOM | RIGHT)) arrowAlign ^= BOTTOM | RIGHT;
break;
}
}
else {
m_arrowAlign = arrowAlign;
positionWindow(x, y);
break;
}
}
return (trycount < 4);
}
bool TipWindow::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kKeyDownMessage:
if (m_closeOnKeyDown &&
static_cast<KeyMessage*>(msg)->scancode() < kKeyFirstModifierScancode)
closeWindow(nullptr);
break;
}
return PopupWindow::onProcessMessage(msg);
}
void TipWindow::onPaint(PaintEvent& ev)
{
theme()->paintTooltip(
ev.graphics(), this, style(), arrowStyle(),
clientBounds(), arrowAlign(),
gfx::Rect(target()).offset(-bounds().origin()));
}
void TipWindow::onBuildTitleLabel()
{
if (!text().empty()) {
m_textBox->setVisible(true);
m_textBox->setText(text());
}
else
m_textBox->setVisible(false);
}
} // namespace ui