Rework tooltip implementation to track tip area.
This fixes an elusive GTK issue where tooltips would be spuriously displayed, and makes tooltips behave nicer on Windows. Unfortunately the macOS code is unchanged as the macOS tooltip implementation seems seriously broken in ways I do not understand.pull/434/head
parent
f6484c78e7
commit
3296474c15
|
@ -253,7 +253,8 @@ public:
|
||||||
virtual void ThawPosition(SettingsRef settings, const std::string &key) = 0;
|
virtual void ThawPosition(SettingsRef settings, const std::string &key) = 0;
|
||||||
|
|
||||||
virtual void SetCursor(Cursor cursor) = 0;
|
virtual void SetCursor(Cursor cursor) = 0;
|
||||||
virtual void SetTooltip(const std::string &text) = 0;
|
virtual void SetTooltip(const std::string &text, double x, double y,
|
||||||
|
double width, double height) = 0;
|
||||||
|
|
||||||
virtual bool IsEditorVisible() = 0;
|
virtual bool IsEditorVisible() = 0;
|
||||||
virtual void ShowEditor(double x, double y, double fontHeight, double minWidth,
|
virtual void ShowEditor(double x, double y, double fontHeight, double minWidth,
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <gtkmm/messagedialog.h>
|
#include <gtkmm/messagedialog.h>
|
||||||
#include <gtkmm/scrollbar.h>
|
#include <gtkmm/scrollbar.h>
|
||||||
#include <gtkmm/separatormenuitem.h>
|
#include <gtkmm/separatormenuitem.h>
|
||||||
|
#include <gtkmm/tooltip.h>
|
||||||
#include <gtkmm/window.h>
|
#include <gtkmm/window.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
@ -730,7 +731,10 @@ class GtkWindow : public Gtk::Window {
|
||||||
Gtk::HBox _hbox;
|
Gtk::HBox _hbox;
|
||||||
GtkEditorOverlay _editor_overlay;
|
GtkEditorOverlay _editor_overlay;
|
||||||
Gtk::VScrollbar _scrollbar;
|
Gtk::VScrollbar _scrollbar;
|
||||||
|
bool _is_under_cursor = false;
|
||||||
bool _is_fullscreen = false;
|
bool _is_fullscreen = false;
|
||||||
|
std::string _tooltip_text;
|
||||||
|
Gdk::Rectangle _tooltip_area;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GtkWindow(Platform::Window *receiver) : _receiver(receiver), _editor_overlay(receiver) {
|
GtkWindow(Platform::Window *receiver) : _receiver(receiver), _editor_overlay(receiver) {
|
||||||
|
@ -746,6 +750,10 @@ public:
|
||||||
|
|
||||||
_scrollbar.get_adjustment()->signal_value_changed().
|
_scrollbar.get_adjustment()->signal_value_changed().
|
||||||
connect(sigc::mem_fun(this, &GtkWindow::on_scrollbar_value_changed));
|
connect(sigc::mem_fun(this, &GtkWindow::on_scrollbar_value_changed));
|
||||||
|
|
||||||
|
get_gl_widget().set_has_tooltip(true);
|
||||||
|
get_gl_widget().signal_query_tooltip().
|
||||||
|
connect(sigc::mem_fun(this, &GtkWindow::on_query_tooltip));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_full_screen() const {
|
bool is_full_screen() const {
|
||||||
|
@ -779,7 +787,34 @@ public:
|
||||||
return _scrollbar;
|
return _scrollbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_tooltip(const std::string &text, const Gdk::Rectangle &rect) {
|
||||||
|
if(_tooltip_text != text) {
|
||||||
|
_tooltip_text = text;
|
||||||
|
_tooltip_area = rect;
|
||||||
|
get_gl_widget().trigger_tooltip_query();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
bool on_query_tooltip(int x, int y, bool keyboard_tooltip,
|
||||||
|
const Glib::RefPtr<Gtk::Tooltip> &tooltip) {
|
||||||
|
tooltip->set_text(_tooltip_text);
|
||||||
|
tooltip->set_tip_area(_tooltip_area);
|
||||||
|
return !_tooltip_text.empty() && (keyboard_tooltip || _is_under_cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool on_enter_notify_event(GdkEventCrossing* gdk_event) override {
|
||||||
|
_is_under_cursor = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool on_leave_notify_event(GdkEventCrossing* gdk_event) override {
|
||||||
|
_is_under_cursor = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool on_delete_event(GdkEventAny* gdk_event) override {
|
bool on_delete_event(GdkEventAny* gdk_event) override {
|
||||||
if(_receiver->onClose) {
|
if(_receiver->onClose) {
|
||||||
_receiver->onClose();
|
_receiver->onClose();
|
||||||
|
@ -795,7 +830,7 @@ protected:
|
||||||
_receiver->onFullScreen(_is_fullscreen);
|
_receiver->onFullScreen(_is_fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Gtk::Window::on_window_state_event(gdk_event);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_scrollbar_value_changed() {
|
void on_scrollbar_value_changed() {
|
||||||
|
@ -940,12 +975,9 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetTooltip(const std::string &text) override {
|
void SetTooltip(const std::string &text, double x, double y,
|
||||||
if(text.empty()) {
|
double width, double height) override {
|
||||||
gtkWindow.get_gl_widget().set_has_tooltip(false);
|
gtkWindow.set_tooltip(text, { (int)x, (int)y, (int)width, (int)height });
|
||||||
} else {
|
|
||||||
gtkWindow.get_gl_widget().set_tooltip_text(text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsEditorVisible() override {
|
bool IsEditorVisible() override {
|
||||||
|
|
|
@ -925,10 +925,10 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetTooltip(const std::string &newText) override {
|
void SetTooltip(const std::string &newText, double x, double y, double w, double h) override {
|
||||||
NSString *nsNewText = Wrap(newText);
|
NSString *nsNewText = Wrap(newText);
|
||||||
if(![[ssView toolTip] isEqualToString:nsNewText]) {
|
if(![nsToolTip isEqualToString:nsNewText]) {
|
||||||
[ssView setToolTip:nsNewText];
|
nsToolTip = nsNewText;
|
||||||
|
|
||||||
NSToolTipManager *nsToolTipManager = [NSToolTipManager sharedToolTipManager];
|
NSToolTipManager *nsToolTipManager = [NSToolTipManager sharedToolTipManager];
|
||||||
if(newText.empty()) {
|
if(newText.empty()) {
|
||||||
|
|
|
@ -572,9 +572,8 @@ public:
|
||||||
|
|
||||||
TOOLINFOW ti = {};
|
TOOLINFOW ti = {};
|
||||||
ti.cbSize = sizeof(ti);
|
ti.cbSize = sizeof(ti);
|
||||||
ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS;
|
ti.uFlags = TTF_SUBCLASS;
|
||||||
ti.hwnd = hWindow;
|
ti.hwnd = hWindow;
|
||||||
ti.uId = (UINT_PTR)hWindow;
|
|
||||||
ti.lpszText = (LPWSTR)L"";
|
ti.lpszText = (LPWSTR)L"";
|
||||||
sscheck(SendMessageW(hTooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti));
|
sscheck(SendMessageW(hTooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti));
|
||||||
sscheck(SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0));
|
sscheck(SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0));
|
||||||
|
@ -1222,27 +1221,31 @@ public:
|
||||||
sscheck(::SetCursor(hCursor));
|
sscheck(::SetCursor(hCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetTooltip(const std::string &newText) override {
|
void SetTooltip(const std::string &newText, double x, double y,
|
||||||
// The following SendMessage calls sometimes fail with ERROR_ACCESS_DENIED for
|
double width, double height) override {
|
||||||
// no discernible reason, but only on wine.
|
if(newText == tooltipText) return;
|
||||||
if(newText.empty()) {
|
tooltipText = newText;
|
||||||
SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0);
|
|
||||||
SendMessageW(hTooltip, TTM_POP, 0, 0);
|
if(!newText.empty()) {
|
||||||
} else if(newText != tooltipText) {
|
int pixelRatio = GetDevicePixelRatio();
|
||||||
tooltipText = newText;
|
RECT toolRect;
|
||||||
|
toolRect.left = (int)(x * pixelRatio);
|
||||||
|
toolRect.top = (int)(y * pixelRatio);
|
||||||
|
toolRect.right = toolRect.left + (int)(width * pixelRatio);
|
||||||
|
toolRect.bottom = toolRect.top + (int)(height * pixelRatio);
|
||||||
|
|
||||||
std::wstring newTextW = Widen(newText);
|
std::wstring newTextW = Widen(newText);
|
||||||
TOOLINFOW ti = {};
|
TOOLINFOW ti = {};
|
||||||
ti.cbSize = sizeof(ti);
|
ti.cbSize = sizeof(ti);
|
||||||
ti.uFlags = TTF_IDISHWND;
|
|
||||||
ti.hwnd = hWindow;
|
ti.hwnd = hWindow;
|
||||||
ti.uId = (UINT_PTR)hWindow;
|
ti.rect = toolRect;
|
||||||
ti.lpszText = &newTextW[0];
|
ti.lpszText = &newTextW[0];
|
||||||
SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti);
|
sscheck(SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti));
|
||||||
|
sscheck(SendMessageW(hTooltip, TTM_NEWTOOLRECTW, 0, (LPARAM)&ti));
|
||||||
SendMessageW(hTooltip, TTM_ACTIVATE, TRUE, 0);
|
|
||||||
SendMessageW(hTooltip, TTM_POPUP, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
// The following SendMessage call sometimes fails with ERROR_ACCESS_DENIED for
|
||||||
|
// no discernible reason, but only on wine.
|
||||||
|
SendMessageW(hTooltip, TTM_ACTIVATE, !newText.empty(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsEditorVisible() override {
|
bool IsEditorVisible() override {
|
||||||
|
|
|
@ -597,12 +597,15 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow
|
||||||
hoveredButton = NULL;
|
hoveredButton = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double hoveredX, hoveredY;
|
||||||
for(Button *button : buttons) {
|
for(Button *button : buttons) {
|
||||||
if(how == PAINT) {
|
if(how == PAINT) {
|
||||||
button->Draw(uiCanvas, x, y, (button == hoveredButton));
|
button->Draw(uiCanvas, x, y, (button == hoveredButton));
|
||||||
} else if(mx > x - 2 && mx < x + 26 &&
|
} else if(mx > x - 2 && mx < x + 26 &&
|
||||||
my < y + 2 && my > y - 26) {
|
my < y + 2 && my > y - 26) {
|
||||||
hoveredButton = button;
|
hoveredButton = button;
|
||||||
|
hoveredX = x - 2;
|
||||||
|
hoveredY = y - 26;
|
||||||
if(how == CLICK) {
|
if(how == CLICK) {
|
||||||
button->Click();
|
button->Click();
|
||||||
}
|
}
|
||||||
|
@ -613,9 +616,9 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow
|
||||||
|
|
||||||
if(how != PAINT && hoveredButton != oldHovered) {
|
if(how != PAINT && hoveredButton != oldHovered) {
|
||||||
if(hoveredButton == NULL) {
|
if(hoveredButton == NULL) {
|
||||||
window->SetTooltip("");
|
window->SetTooltip("", 0, 0, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
window->SetTooltip(hoveredButton->Tooltip());
|
window->SetTooltip(hoveredButton->Tooltip(), hoveredX, hoveredY, 28, 28);
|
||||||
}
|
}
|
||||||
window->Invalidate();
|
window->Invalidate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ static ToolIcon Toolbar[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) {
|
void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) {
|
||||||
ToolbarDrawOrHitTest(0, 0, canvas, NULL);
|
ToolbarDrawOrHitTest(0, 0, canvas, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
|
bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
|
||||||
|
@ -97,11 +97,12 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
|
||||||
x += ((int)width/2);
|
x += ((int)width/2);
|
||||||
y += ((int)height/2);
|
y += ((int)height/2);
|
||||||
|
|
||||||
Command hit;
|
Command hitCommand;
|
||||||
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hit);
|
int hitX, hitY;
|
||||||
|
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, &hitX, &hitY);
|
||||||
|
|
||||||
if(hit != toolbarHovered) {
|
if(hitCommand != toolbarHovered) {
|
||||||
toolbarHovered = hit;
|
toolbarHovered = hitCommand;
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +120,9 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
|
||||||
tooltip += ssprintf(" (%s)", accelDesc.c_str());
|
tooltip += ssprintf(" (%s)", accelDesc.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
window->SetTooltip(tooltip);
|
window->SetTooltip(tooltip, hitX, hitY, 32, 32);
|
||||||
|
} else {
|
||||||
|
window->SetTooltip("", 0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return withinToolbar;
|
return withinToolbar;
|
||||||
|
@ -132,16 +135,16 @@ bool GraphicsWindow::ToolbarMouseDown(int x, int y) {
|
||||||
x += ((int)width/2);
|
x += ((int)width/2);
|
||||||
y += ((int)height/2);
|
y += ((int)height/2);
|
||||||
|
|
||||||
Command hit;
|
Command hitCommand;
|
||||||
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hit);
|
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, NULL, NULL);
|
||||||
if(hit != Command::NONE) {
|
if(hitCommand != Command::NONE) {
|
||||||
SS.GW.ActivateCommand(hit);
|
SS.GW.ActivateCommand(hitCommand);
|
||||||
}
|
}
|
||||||
return withinToolbar;
|
return withinToolbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
|
bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas,
|
||||||
UiCanvas *canvas, Command *menuHit)
|
Command *hitCommand, int *hitX, int *hitY)
|
||||||
{
|
{
|
||||||
double width, height;
|
double width, height;
|
||||||
window->GetContentSize(&width, &height);
|
window->GetContentSize(&width, &height);
|
||||||
|
@ -154,9 +157,9 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
|
||||||
|
|
||||||
bool withinToolbar =
|
bool withinToolbar =
|
||||||
(mx >= aleft && mx <= aright && my <= atop && my >= abot);
|
(mx >= aleft && mx <= aright && my <= atop && my >= abot);
|
||||||
|
|
||||||
// Initialize/clear menuHit.
|
// Initialize/clear hitCommand.
|
||||||
if(menuHit) *menuHit = Command::NONE;
|
if(hitCommand) *hitCommand = Command::NONE;
|
||||||
|
|
||||||
if(!canvas && !withinToolbar) {
|
if(!canvas && !withinToolbar) {
|
||||||
// This gets called every MouseMove event, so return quickly.
|
// This gets called every MouseMove event, so return quickly.
|
||||||
|
@ -203,17 +206,19 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
|
||||||
(pending.operation == Pending::COMMAND &&
|
(pending.operation == Pending::COMMAND &&
|
||||||
pending.command == icon.command)) {
|
pending.command == icon.command)) {
|
||||||
// Highlight the hovered or pending item.
|
// Highlight the hovered or pending item.
|
||||||
int boxhw = 15;
|
const int boxhw = 15;
|
||||||
canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw,
|
canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw,
|
||||||
/*fillColor=*/{ 255, 255, 0, 75 },
|
/*fillColor=*/{ 255, 255, 0, 75 },
|
||||||
/*outlineColor=*/{});
|
/*outlineColor=*/{});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int boxhw = 16;
|
const int boxhw = 16;
|
||||||
if(mx < (x+boxhw) && mx > (x - boxhw) &&
|
if(mx < (x+boxhw) && mx > (x - boxhw) &&
|
||||||
my < (y+boxhw) && my > (y - boxhw))
|
my < (y+boxhw) && my > (y - boxhw))
|
||||||
{
|
{
|
||||||
if(menuHit) *menuHit = icon.command;
|
if(hitCommand) *hitCommand = icon.command;
|
||||||
|
if(hitX) *hitX = x - boxhw;
|
||||||
|
if(hitY) *hitY = height - (y + boxhw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
src/ui.h
3
src/ui.h
|
@ -756,7 +756,8 @@ public:
|
||||||
void ClearSuper();
|
void ClearSuper();
|
||||||
|
|
||||||
// The toolbar, in toolbar.cpp
|
// The toolbar, in toolbar.cpp
|
||||||
bool ToolbarDrawOrHitTest(int x, int y, UiCanvas *canvas, Command *menuHit);
|
bool ToolbarDrawOrHitTest(int x, int y, UiCanvas *canvas,
|
||||||
|
Command *hitCommand, int *hitX, int *hitY);
|
||||||
void ToolbarDraw(UiCanvas *canvas);
|
void ToolbarDraw(UiCanvas *canvas);
|
||||||
bool ToolbarMouseMoved(int x, int y);
|
bool ToolbarMouseMoved(int x, int y);
|
||||||
bool ToolbarMouseDown(int x, int y);
|
bool ToolbarMouseDown(int x, int y);
|
||||||
|
|
Loading…
Reference in New Issue