diff --git a/src/platform/gui.h b/src/platform/gui.h index c710868b..cef74f6d 100644 --- a/src/platform/gui.h +++ b/src/platform/gui.h @@ -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, diff --git a/src/platform/guigtk.cpp b/src/platform/guigtk.cpp index a4dfb587..fbcc701a 100644 --- a/src/platform/guigtk.cpp +++ b/src/platform/guigtk.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #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 &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 { diff --git a/src/platform/guimac.mm b/src/platform/guimac.mm index f6859639..6ed9d4c1 100644 --- a/src/platform/guimac.mm +++ b/src/platform/guimac.mm @@ -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()) { diff --git a/src/platform/guiwin.cpp b/src/platform/guiwin.cpp index ba0a3a1e..6914a8de 100644 --- a/src/platform/guiwin.cpp +++ b/src/platform/guiwin.cpp @@ -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) { - tooltipText = newText; + 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 { diff --git a/src/textwin.cpp b/src/textwin.cpp index 2a1eeb8a..4ae71d66 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -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(); } diff --git a/src/toolbar.cpp b/src/toolbar.cpp index ca88d750..4ca478c2 100644 --- a/src/toolbar.cpp +++ b/src/toolbar.cpp @@ -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); @@ -154,9 +157,9 @@ 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); } } diff --git a/src/ui.h b/src/ui.h index cb6f06a6..4fef169e 100644 --- a/src/ui.h +++ b/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);