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 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 void ShowEditor(double x, double y, double fontHeight, double minWidth,
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <gtkmm/messagedialog.h>
|
||||
#include <gtkmm/scrollbar.h>
|
||||
#include <gtkmm/separatormenuitem.h>
|
||||
#include <gtkmm/tooltip.h>
|
||||
#include <gtkmm/window.h>
|
||||
|
||||
#include "config.h"
|
||||
|
@ -730,7 +731,10 @@ class GtkWindow : public Gtk::Window {
|
|||
Gtk::HBox _hbox;
|
||||
GtkEditorOverlay _editor_overlay;
|
||||
Gtk::VScrollbar _scrollbar;
|
||||
bool _is_under_cursor = false;
|
||||
bool _is_fullscreen = false;
|
||||
std::string _tooltip_text;
|
||||
Gdk::Rectangle _tooltip_area;
|
||||
|
||||
public:
|
||||
GtkWindow(Platform::Window *receiver) : _receiver(receiver), _editor_overlay(receiver) {
|
||||
|
@ -746,6 +750,10 @@ public:
|
|||
|
||||
_scrollbar.get_adjustment()->signal_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 {
|
||||
|
@ -779,7 +787,34 @@ public:
|
|||
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:
|
||||
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 {
|
||||
if(_receiver->onClose) {
|
||||
_receiver->onClose();
|
||||
|
@ -795,7 +830,7 @@ protected:
|
|||
_receiver->onFullScreen(_is_fullscreen);
|
||||
}
|
||||
|
||||
return Gtk::Window::on_window_state_event(gdk_event);
|
||||
return true;
|
||||
}
|
||||
|
||||
void on_scrollbar_value_changed() {
|
||||
|
@ -940,12 +975,9 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void SetTooltip(const std::string &text) override {
|
||||
if(text.empty()) {
|
||||
gtkWindow.get_gl_widget().set_has_tooltip(false);
|
||||
} else {
|
||||
gtkWindow.get_gl_widget().set_tooltip_text(text);
|
||||
}
|
||||
void SetTooltip(const std::string &text, double x, double y,
|
||||
double width, double height) override {
|
||||
gtkWindow.set_tooltip(text, { (int)x, (int)y, (int)width, (int)height });
|
||||
}
|
||||
|
||||
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);
|
||||
if(![[ssView toolTip] isEqualToString:nsNewText]) {
|
||||
[ssView setToolTip:nsNewText];
|
||||
if(![nsToolTip isEqualToString:nsNewText]) {
|
||||
nsToolTip = nsNewText;
|
||||
|
||||
NSToolTipManager *nsToolTipManager = [NSToolTipManager sharedToolTipManager];
|
||||
if(newText.empty()) {
|
||||
|
|
|
@ -572,9 +572,8 @@ public:
|
|||
|
||||
TOOLINFOW ti = {};
|
||||
ti.cbSize = sizeof(ti);
|
||||
ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS;
|
||||
ti.uFlags = TTF_SUBCLASS;
|
||||
ti.hwnd = hWindow;
|
||||
ti.uId = (UINT_PTR)hWindow;
|
||||
ti.lpszText = (LPWSTR)L"";
|
||||
sscheck(SendMessageW(hTooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti));
|
||||
sscheck(SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0));
|
||||
|
@ -1222,27 +1221,31 @@ public:
|
|||
sscheck(::SetCursor(hCursor));
|
||||
}
|
||||
|
||||
void SetTooltip(const std::string &newText) override {
|
||||
// The following SendMessage calls sometimes fail with ERROR_ACCESS_DENIED for
|
||||
// no discernible reason, but only on wine.
|
||||
if(newText.empty()) {
|
||||
SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0);
|
||||
SendMessageW(hTooltip, TTM_POP, 0, 0);
|
||||
} else if(newText != tooltipText) {
|
||||
void SetTooltip(const std::string &newText, double x, double y,
|
||||
double width, double height) override {
|
||||
if(newText == tooltipText) return;
|
||||
tooltipText = newText;
|
||||
|
||||
if(!newText.empty()) {
|
||||
int pixelRatio = GetDevicePixelRatio();
|
||||
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);
|
||||
TOOLINFOW ti = {};
|
||||
ti.cbSize = sizeof(ti);
|
||||
ti.uFlags = TTF_IDISHWND;
|
||||
ti.hwnd = hWindow;
|
||||
ti.uId = (UINT_PTR)hWindow;
|
||||
ti.rect = toolRect;
|
||||
ti.lpszText = &newTextW[0];
|
||||
SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti);
|
||||
|
||||
SendMessageW(hTooltip, TTM_ACTIVATE, TRUE, 0);
|
||||
SendMessageW(hTooltip, TTM_POPUP, 0, 0);
|
||||
sscheck(SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti));
|
||||
sscheck(SendMessageW(hTooltip, TTM_NEWTOOLRECTW, 0, (LPARAM)&ti));
|
||||
}
|
||||
// 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 {
|
||||
|
|
|
@ -597,12 +597,15 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow
|
|||
hoveredButton = NULL;
|
||||
}
|
||||
|
||||
double hoveredX, hoveredY;
|
||||
for(Button *button : buttons) {
|
||||
if(how == PAINT) {
|
||||
button->Draw(uiCanvas, x, y, (button == hoveredButton));
|
||||
} else if(mx > x - 2 && mx < x + 26 &&
|
||||
my < y + 2 && my > y - 26) {
|
||||
hoveredButton = button;
|
||||
hoveredX = x - 2;
|
||||
hoveredY = y - 26;
|
||||
if(how == CLICK) {
|
||||
button->Click();
|
||||
}
|
||||
|
@ -613,9 +616,9 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow
|
|||
|
||||
if(how != PAINT && hoveredButton != oldHovered) {
|
||||
if(hoveredButton == NULL) {
|
||||
window->SetTooltip("");
|
||||
window->SetTooltip("", 0, 0, 0, 0);
|
||||
} else {
|
||||
window->SetTooltip(hoveredButton->Tooltip());
|
||||
window->SetTooltip(hoveredButton->Tooltip(), hoveredX, hoveredY, 28, 28);
|
||||
}
|
||||
window->Invalidate();
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ static ToolIcon Toolbar[] = {
|
|||
};
|
||||
|
||||
void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) {
|
||||
ToolbarDrawOrHitTest(0, 0, canvas, NULL);
|
||||
ToolbarDrawOrHitTest(0, 0, canvas, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
|
||||
|
@ -97,11 +97,12 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
|
|||
x += ((int)width/2);
|
||||
y += ((int)height/2);
|
||||
|
||||
Command hit;
|
||||
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hit);
|
||||
Command hitCommand;
|
||||
int hitX, hitY;
|
||||
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, &hitX, &hitY);
|
||||
|
||||
if(hit != toolbarHovered) {
|
||||
toolbarHovered = hit;
|
||||
if(hitCommand != toolbarHovered) {
|
||||
toolbarHovered = hitCommand;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
|
@ -119,7 +120,9 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
|
|||
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;
|
||||
|
@ -132,16 +135,16 @@ bool GraphicsWindow::ToolbarMouseDown(int x, int y) {
|
|||
x += ((int)width/2);
|
||||
y += ((int)height/2);
|
||||
|
||||
Command hit;
|
||||
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hit);
|
||||
if(hit != Command::NONE) {
|
||||
SS.GW.ActivateCommand(hit);
|
||||
Command hitCommand;
|
||||
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, NULL, NULL);
|
||||
if(hitCommand != Command::NONE) {
|
||||
SS.GW.ActivateCommand(hitCommand);
|
||||
}
|
||||
return withinToolbar;
|
||||
}
|
||||
|
||||
bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
|
||||
UiCanvas *canvas, Command *menuHit)
|
||||
bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas,
|
||||
Command *hitCommand, int *hitX, int *hitY)
|
||||
{
|
||||
double width, height;
|
||||
window->GetContentSize(&width, &height);
|
||||
|
@ -155,8 +158,8 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
|
|||
bool withinToolbar =
|
||||
(mx >= aleft && mx <= aright && my <= atop && my >= abot);
|
||||
|
||||
// Initialize/clear menuHit.
|
||||
if(menuHit) *menuHit = Command::NONE;
|
||||
// Initialize/clear hitCommand.
|
||||
if(hitCommand) *hitCommand = Command::NONE;
|
||||
|
||||
if(!canvas && !withinToolbar) {
|
||||
// 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.command == icon.command)) {
|
||||
// Highlight the hovered or pending item.
|
||||
int boxhw = 15;
|
||||
const int boxhw = 15;
|
||||
canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw,
|
||||
/*fillColor=*/{ 255, 255, 0, 75 },
|
||||
/*outlineColor=*/{});
|
||||
}
|
||||
} else {
|
||||
int boxhw = 16;
|
||||
const int boxhw = 16;
|
||||
if(mx < (x+boxhw) && mx > (x - 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();
|
||||
|
||||
// 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);
|
||||
bool ToolbarMouseMoved(int x, int y);
|
||||
bool ToolbarMouseDown(int x, int y);
|
||||
|
|
Loading…
Reference in New Issue