From 15faa626f7df7a3024dda5d11001347ed4573568 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Thu, 9 May 2019 20:52:43 +0200 Subject: Add tooltip buttons to frames. --- plugin/Makefile.mingw32.in | 3 + plugingui/Makefile.am | 3 + plugingui/eventhandler.cc | 23 ++++- plugingui/frame.cc | 20 +++- plugingui/frame.h | 7 +- plugingui/guievent.h | 24 ++++- plugingui/helpbutton.cc | 75 +++++++++++++++ plugingui/helpbutton.h | 58 ++++++++++++ plugingui/maintab.cc | 1 + plugingui/maintab.h | 2 +- plugingui/nativewindow_win32.cc | 35 ++++++- plugingui/nativewindow_win32.h | 2 + plugingui/nativewindow_x11.cc | 22 ++++- plugingui/resources/help_button.png | Bin 0 -> 2196 bytes plugingui/tooltip.cc | 184 ++++++++++++++++++++++++++++++++++++ plugingui/tooltip.h | 77 +++++++++++++++ plugingui/widget.h | 5 +- 17 files changed, 529 insertions(+), 12 deletions(-) create mode 100644 plugingui/helpbutton.cc create mode 100644 plugingui/helpbutton.h create mode 100644 plugingui/resources/help_button.png create mode 100644 plugingui/tooltip.cc create mode 100644 plugingui/tooltip.h diff --git a/plugin/Makefile.mingw32.in b/plugin/Makefile.mingw32.in index 3619fcb..eaf2e64 100644 --- a/plugin/Makefile.mingw32.in +++ b/plugin/Makefile.mingw32.in @@ -61,6 +61,7 @@ GUI_SRC = \ @top_srcdir@/plugingui/filebrowser.cc \ @top_srcdir@/plugingui/font.cc \ @top_srcdir@/plugingui/frame.cc \ + @top_srcdir@/plugingui/helpbutton.cc \ @top_srcdir@/plugingui/humanizerframecontent.cc \ @top_srcdir@/plugingui/humaniservisualiser.cc \ @top_srcdir@/plugingui/image.cc \ @@ -94,6 +95,7 @@ GUI_SRC = \ @top_srcdir@/plugingui/texturedbox.cc \ @top_srcdir@/plugingui/timingframecontent.cc \ @top_srcdir@/plugingui/toggle.cc \ + @top_srcdir@/plugingui/tooltip.cc \ @top_srcdir@/plugingui/utf8.cc \ @top_srcdir@/plugingui/verticalline.cc \ @top_srcdir@/plugingui/visualizerframecontent.cc \ @@ -135,6 +137,7 @@ RES = \ resources/bypass_button.png \ resources/font.png \ resources/fontemboss.png \ + resources/help_button.png \ resources/knob.png \ resources/logo.png \ resources/png_error \ diff --git a/plugingui/Makefile.am b/plugingui/Makefile.am index 8ae991a..09e5474 100644 --- a/plugingui/Makefile.am +++ b/plugingui/Makefile.am @@ -7,6 +7,7 @@ RES = \ resources/bypass_button.png \ resources/font.png \ resources/fontemboss.png \ + resources/help_button.png \ resources/knob.png \ resources/logo.png \ resources/png_error \ @@ -71,6 +72,7 @@ nodist_libdggui_la_SOURCES = \ filebrowser.cc \ font.cc \ frame.cc \ + helpbutton.cc \ humanizerframecontent.cc \ humaniservisualiser.cc \ image.cc \ @@ -104,6 +106,7 @@ nodist_libdggui_la_SOURCES = \ texturedbox.cc \ timingframecontent.cc \ toggle.cc \ + tooltip.cc \ utf8.cc \ verticalline.cc \ visualizerframecontent.cc \ diff --git a/plugingui/eventhandler.cc b/plugingui/eventhandler.cc index 696b242..fd333b8 100644 --- a/plugingui/eventhandler.cc +++ b/plugingui/eventhandler.cc @@ -254,10 +254,31 @@ void EventHandler::processEvents() closeNotifier(); break; + + case EventType::mouseEnter: + { + auto enterEvent = static_cast(event.get()); + auto widget = window.find(enterEvent->x, enterEvent->y); + if(widget) + { + widget->mouseEnterEvent(); + } + } + break; + + case EventType::mouseLeave: + { + auto widget = window.mouseFocus(); + if(widget) + { + widget->mouseLeaveEvent(); + } + } + break; } } - // Probe window and children to readrw as needed. + // Probe window and children to redraw as needed. // NOTE: This method will invoke native->redraw() if a redraw is needed. window.updateBuffer(); } diff --git a/plugingui/frame.cc b/plugingui/frame.cc index d552ff1..d375b6d 100644 --- a/plugingui/frame.cc +++ b/plugingui/frame.cc @@ -31,7 +31,7 @@ namespace GUI { -FrameWidget::FrameWidget(Widget* parent, bool has_switch) +FrameWidget::FrameWidget(Widget* parent, bool has_switch, bool has_help_text) : Widget(parent) , is_switched_on(!has_switch) , bar_height(24) @@ -44,10 +44,19 @@ FrameWidget::FrameWidget(Widget* parent, bool has_switch) power_button.setChecked(is_switched_on); CONNECT(&power_button, stateChangedNotifier, this, - &FrameWidget::powerButtonStateChanged); + &FrameWidget::powerButtonStateChanged); } power_button.setVisible(has_switch); + if(has_help_text) + { + // We only have to set this once as nothing happens on a resize + help_button.resize(16, 16); + help_button.move(width() - 4 - 16, 4); + help_button.setText("?"); + } + help_button.setVisible(has_help_text); + CONNECT(this, sizeChangeNotifier, this, &FrameWidget::sizeChanged); } @@ -93,6 +102,11 @@ void FrameWidget::setTitle(std::string const& title) label_width = font.textWidth(title.c_str()) / 2 + 1; } +void FrameWidget::setHelpText(const std::string& help_text) +{ + help_button.setHelpText(help_text); +} + void FrameWidget::setContent(Widget* content) { this->content = content; @@ -125,6 +139,8 @@ void FrameWidget::sizeChanged(int width, int height) content->move(content_start_x, content_start_y); content->resize(content_width, content_height); } + + help_button.move(width - 4 - 16, help_button.y()); } } // GUI:: diff --git a/plugingui/frame.h b/plugingui/frame.h index e29bd3d..db26ea9 100644 --- a/plugingui/frame.h +++ b/plugingui/frame.h @@ -30,6 +30,7 @@ #include "font.h" #include "powerbutton.h" +#include "helpbutton.h" #include "widget.h" namespace GUI @@ -39,7 +40,7 @@ class FrameWidget : public Widget { public: - FrameWidget(Widget* parent, bool has_switch = false); + FrameWidget(Widget* parent, bool has_switch = false, bool has_help_text = false); virtual ~FrameWidget() = default; // From Widget: @@ -48,7 +49,8 @@ public: bool isSwitchedOn() { return is_switched_on; } - void setTitle(std::string const& title); + void setTitle(const std::string& title); + void setHelpText(const std::string& help_text); void setContent(Widget* content); void setOnSwitch(bool on); @@ -81,6 +83,7 @@ private: // switch bool is_switched_on; PowerButton power_button{this}; + HelpButton help_button{this}; void powerButtonStateChanged(bool clicked); diff --git a/plugingui/guievent.h b/plugingui/guievent.h index 11a7b9b..fe51826 100644 --- a/plugingui/guievent.h +++ b/plugingui/guievent.h @@ -42,7 +42,9 @@ enum class EventType key, close, resize, - move + move, + mouseEnter, + mouseLeave, }; class Event @@ -172,6 +174,26 @@ public: int y; }; +class MouseEnterEvent + : public Event +{ +public: + EventType type() { return EventType::mouseEnter; } + + int x; + int y; +}; + +class MouseLeaveEvent + : public Event +{ +public: + EventType type() { return EventType::mouseLeave; } + + int x; + int y; +}; + using EventQueue = std::list>; struct Rect diff --git a/plugingui/helpbutton.cc b/plugingui/helpbutton.cc new file mode 100644 index 0000000..fa061a6 --- /dev/null +++ b/plugingui/helpbutton.cc @@ -0,0 +1,75 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * helpbutton.cc + * + * Wed May 8 17:10:08 CEST 2019 + * Copyright 2019 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "helpbutton.h" + +#include "painter.h" + +#include + +namespace GUI +{ + +HelpButton::HelpButton(Widget* parent) + : ButtonBase(parent) + , tip(this) +{ + CONNECT(this, clickNotifier, this, &HelpButton::showHelpText); + tip.hide(); +} + +void HelpButton::setHelpText(const std::string& help_text) +{ + tip.setText(help_text); +} + +void HelpButton::repaintEvent(RepaintEvent* repaintEvent) +{ + Painter p(*this); + + bool state = true; + + // enabled and on + if(state) + { + if(button_state == ButtonBase::State::Down) + { + p.drawImage(0, 0, pushed); + } + else + { + p.drawImage(0, 0, normal); + } + return; + } +} + +void HelpButton::showHelpText() +{ + tip.show(); +} + +} // GUI:: diff --git a/plugingui/helpbutton.h b/plugingui/helpbutton.h new file mode 100644 index 0000000..6e97eca --- /dev/null +++ b/plugingui/helpbutton.h @@ -0,0 +1,58 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * helpbutton.h + * + * Wed May 8 17:10:08 CEST 2019 + * Copyright 2019 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include "texture.h" +#include "button_base.h" +#include "tooltip.h" + +namespace GUI +{ + +class HelpButton + : public ButtonBase +{ +public: + HelpButton(Widget* parent); + virtual ~HelpButton() = default; + + void setHelpText(const std::string& help_text); + +protected: + // From Widget: + virtual void repaintEvent(RepaintEvent* repaintEvent) override; + +private: + void showHelpText(); + + Texture normal{getImageCache(), ":resources/help_button.png", 0, 0, 16, 16}; + Texture pushed{getImageCache(), ":resources/help_button.png", 16, 0, 16, 16}; + + Tooltip tip; +}; + +} // GUI:: diff --git a/plugingui/maintab.cc b/plugingui/maintab.cc index d98c6ea..1cf445a 100644 --- a/plugingui/maintab.cc +++ b/plugingui/maintab.cc @@ -53,6 +53,7 @@ MainTab::MainTab(Widget* parent, add("Resampling", resampling_frame, resamplingframe_content, 10, 0); add("Velocity Humanizer", humanizer_frame, humanizerframe_content, 10, 1); + humanizer_frame.setHelpText("Hello World\nThis is a nice World\n... I think"); add("Timing Humanizer", timing_frame, timingframe_content, 10, 1); add("Visualizer", visualizer_frame, visualizerframe_content, 10, 1); add("Bleed Control", bleedcontrol_frame, bleedcontrolframe_content, 9, 1); diff --git a/plugingui/maintab.h b/plugingui/maintab.h index efd83e9..7daa669 100644 --- a/plugingui/maintab.h +++ b/plugingui/maintab.h @@ -69,7 +69,7 @@ private: FrameWidget drumkit_frame{this, false}; FrameWidget status_frame{this, false}; - FrameWidget humanizer_frame{this, true}; + FrameWidget humanizer_frame{this, true, true}; FrameWidget diskstreaming_frame{this, false}; FrameWidget bleedcontrol_frame{this, true}; FrameWidget resampling_frame{this, true}; diff --git a/plugingui/nativewindow_win32.cc b/plugingui/nativewindow_win32.cc index 5cfb179..0dc30de 100644 --- a/plugingui/nativewindow_win32.cc +++ b/plugingui/nativewindow_win32.cc @@ -35,6 +35,16 @@ namespace GUI { +static BOOL trackMouse(HWND hwnd) +{ + TRACKMOUSEEVENT ev{}; + ev.cbSize = sizeof(ev); + ev.dwFlags = TME_HOVER | TME_LEAVE; + ev.hwndTrack = hwnd; + ev.dwHoverTime = 1; + return TrackMouseEvent(&ev); +} + LRESULT CALLBACK NativeWindowWin32::dialogProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { @@ -89,9 +99,20 @@ LRESULT CALLBACK NativeWindowWin32::dialogProc(HWND hwnd, UINT msg, // return 0; case WM_MOUSEMOVE: { + trackMouse(native->m_hwnd); auto mouseMoveEvent = std::make_shared(); mouseMoveEvent->x = (short)LOWORD(lp); mouseMoveEvent->y = (short)HIWORD(lp); + native->last_mouse_position = { mouseMoveEvent->x, mouseMoveEvent->y }; + + if(!native->mouse_in_window) + { + auto enterEvent = std::make_shared(); + enterEvent->x = native->last_mouse_position.first; + enterEvent->y = native->last_mouse_position.second; + native->event_queue.push_back(enterEvent); + native->mouse_in_window = true; + } native->event_queue.push_back(mouseMoveEvent); } break; @@ -295,8 +316,17 @@ LRESULT CALLBACK NativeWindowWin32::dialogProc(HWND hwnd, UINT msg, DeleteObject(ourbitmap); } } + break; - return DefWindowProc(hwnd, msg, wp, lp); + case WM_MOUSELEAVE: + { + auto leaveEvent = std::make_shared(); + leaveEvent->x = native->last_mouse_position.first; + leaveEvent->y = native->last_mouse_position.second; + native->event_queue.push_back(leaveEvent); + native->mouse_in_window = false; + } + break; } return DefWindowProc(hwnd, msg, wp, lp); @@ -391,6 +421,9 @@ NativeWindowWin32::NativeWindowWin32(void* native_window, Window& window) GetModuleHandle(nullptr), nullptr); SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)this); + + // Set up initial tracking of the mouse leave events + trackMouse(m_hwnd); } NativeWindowWin32::~NativeWindowWin32() diff --git a/plugingui/nativewindow_win32.h b/plugingui/nativewindow_win32.h index 2945be6..d547dc0 100644 --- a/plugingui/nativewindow_win32.h +++ b/plugingui/nativewindow_win32.h @@ -66,6 +66,8 @@ private: HWND parent_window; Window& window; WNDID m_hwnd = 0; + bool mouse_in_window{false}; + std::pair last_mouse_position{0, 0}; char* m_className = nullptr; EventQueue event_queue; }; diff --git a/plugingui/nativewindow_x11.cc b/plugingui/nativewindow_x11.cc index f77a772..e96f26a 100644 --- a/plugingui/nativewindow_x11.cc +++ b/plugingui/nativewindow_x11.cc @@ -92,7 +92,9 @@ NativeWindowX11::NativeWindowX11(void* native_window, Window& window) KeyReleaseMask| ExposureMask | StructureNotifyMask | - SubstructureNotifyMask); + SubstructureNotifyMask | + EnterWindowMask | + LeaveWindowMask); XSelectInput(display, xwindow, mask); // Register the delete window message: @@ -467,7 +469,25 @@ void NativeWindowX11::translateXMessage(XEvent& xevent) break; case EnterNotify: + //DEBUG(x11, "EnterNotify"); + { + auto enterEvent = std::make_shared(); + enterEvent->x = xevent.xcrossing.x; + enterEvent->y = xevent.xcrossing.y; + event_queue.push_back(enterEvent); + } + break; + case LeaveNotify: + //DEBUG(x11, "LeaveNotify"); + { + auto leaveEvent = std::make_shared(); + leaveEvent->x = xevent.xcrossing.x; + leaveEvent->y = xevent.xcrossing.y; + event_queue.push_back(leaveEvent); + } + break; + case MapNotify: case MappingNotify: //DEBUG(x11, "EnterNotify"); diff --git a/plugingui/resources/help_button.png b/plugingui/resources/help_button.png new file mode 100644 index 0000000..95790f5 Binary files /dev/null and b/plugingui/resources/help_button.png differ diff --git a/plugingui/tooltip.cc b/plugingui/tooltip.cc new file mode 100644 index 0000000..4b1998a --- /dev/null +++ b/plugingui/tooltip.cc @@ -0,0 +1,184 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * tooltip.cc + * + * Wed May 8 17:31:42 CEST 2019 + * Copyright 2019 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "tooltip.h" + +#include "painter.h" +#include "font.h" +#include "window.h" +#include + +namespace GUI +{ + +Tooltip::Tooltip(Widget *parent) + : Widget(parent->window()) + , activating_widget(parent) +{ + resize(32, 32); +} + +Tooltip::~Tooltip() +{ +} + +void Tooltip::setText(const std::string& text) +{ + this->text = text; + needs_preprocessing = true; + redraw(); +} + +void Tooltip::resize(std::size_t width, std::size_t height) +{ + Widget::resize(width, height); +} + +void Tooltip::repaintEvent(RepaintEvent* repaintEvent) +{ + if(needs_preprocessing) + { + preprocessText(); + } + + Painter p(*this); + + if((width() == 0) || (height() == 0)) + { + return; + } + + box.setSize(width(), height()); + p.drawImage(0, 0, box); + p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0f/255.0f, 1.0f)); + + int ypos = font.textHeight() + y_border; + + for(std::size_t i = 0; i < preprocessed_text.size(); ++i) + { + if(i * font.textHeight() >= (height() - y_border - font.textHeight())) + { + break; + } + + auto const& line = preprocessed_text[i]; + p.drawText(x_border, ypos, font, line); + ypos += font.textHeight(); + } +} + +void Tooltip::preprocessText() +{ + std::vector lines; + + preprocessed_text.clear(); + std::string text = this->text; + + // Handle tab characters + for(std::size_t i = 0; i < text.length(); ++i) + { + char ch = text.at(i); + if(ch == '\t') + { + text.erase(i, 1); + text.insert(i, 4, ' '); + } + } + + // Handle "\r" + for(std::size_t i = 0; i < text.length(); ++i) + { + char ch = text.at(i); + if(ch == '\r') + { + text.erase(i, 1); + } + } + + // Handle new line characters + std::size_t pos = 0; + do + { + pos = text.find("\n"); + lines.push_back(text.substr(0, pos)); + text = text.substr(pos + 1); + } while(pos != std::string::npos); + + max_text_width = 0; + total_text_height = 0; + for(auto const& line: lines) + { + std::string valid; + std::string current; + for(auto c: line) + { + current += c; + if(c == ' ') + { + valid.append(current.substr(valid.size())); + } + } + preprocessed_text.push_back(current); + + max_text_width = std::max(max_text_width, font.textWidth(line)); + total_text_height += font.textHeight(line); + } +} + +void Tooltip::mouseLeaveEvent() +{ + hide(); +} + +void Tooltip::show() +{ + if(needs_preprocessing) + { + preprocessText(); + } + + resize(max_text_width + 2*x_border, total_text_height + 2*y_border); + + int x = activating_widget->translateToWindowX(); + int y = activating_widget->translateToWindowY(); + + if(x + width() > window()->width()) + { + x -= width(); + x += activating_widget->width(); + } + + if(y + height() > window()->height()) + { + y -= height(); + y += activating_widget->height(); + } + + move(x, y); + Widget::show(); +} + +} // GUI:: diff --git a/plugingui/tooltip.h b/plugingui/tooltip.h new file mode 100644 index 0000000..b449512 --- /dev/null +++ b/plugingui/tooltip.h @@ -0,0 +1,77 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * tooltip.h + * + * Wed May 8 17:31:42 CEST 2019 + * Copyright 2019 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include +#include + +#include + +#include "widget.h" +#include "painter.h" +#include "texturedbox.h" +#include "font.h" + +namespace GUI +{ + +class Tooltip + : public Widget +{ +public: + Tooltip(Widget *parent); + virtual ~Tooltip(); + + void setText(const std::string& text); + + // From Widget: + virtual void repaintEvent(RepaintEvent* repaint_event) override; + virtual void resize(std::size_t height, std::size_t width) override; + virtual void mouseLeaveEvent() override; + virtual void show() override; + +private: + void preprocessText(); + + TexturedBox box{getImageCache(), ":resources/thinlistbox.png", + 0, 0, // atlas offset (x, y) + 1, 1, 1, // dx1, dx2, dx3 + 1, 1, 1}; // dy1, dy2, dy3 + Font font; + + static constexpr int x_border{10}; + static constexpr int y_border{8}; + + bool needs_preprocessing{false}; + std::string text; + std::vector preprocessed_text; + std::size_t max_text_width{0}; + std::size_t total_text_height{0}; + Widget* activating_widget; +}; + +} // GUI:: diff --git a/plugingui/widget.h b/plugingui/widget.h index 3b17f1f..8595ef7 100644 --- a/plugingui/widget.h +++ b/plugingui/widget.h @@ -97,15 +97,14 @@ public: Notifier sizeChangeNotifier; // (width, height) Notifier positionChangeNotifier; // (x, y) -protected: - friend class EventHandler; - //! Translate x-coordinate from parent-space to window-space. virtual std::size_t translateToWindowX(); //! Translate y-coordinate from parent-space to window-space. virtual std::size_t translateToWindowY(); +protected: + friend class EventHandler; PixelBufferAlpha pixbuf{0,0}; std::vector children; -- cgit v1.2.3