tool-tip.cpp 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. namespace hiro {
  2. static auto CALLBACK ToolTip_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
  3. if(auto toolTip = (pToolTip*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
  4. if(auto result = toolTip->windowProc(hwnd, msg, wparam, lparam)) {
  5. return result();
  6. }
  7. }
  8. return DefWindowProc(hwnd, msg, wparam, lparam);
  9. }
  10. pToolTip::pToolTip(const string& toolTipText) {
  11. text = toolTipText;
  12. htheme = OpenThemeData(hwnd, L"TOOLTIP");
  13. hwnd = CreateWindowEx(
  14. WS_EX_TOOLWINDOW | WS_EX_TOPMOST | (htheme ? WS_EX_LAYERED : 0), L"hiroToolTip", L"",
  15. WS_POPUP, 0, 0, 0, 0,
  16. 0, 0, GetModuleHandle(0), 0
  17. );
  18. SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
  19. tracking.x = -1;
  20. tracking.y = -1;
  21. timeout.setInterval(Timeout);
  22. timeout.onActivate([&] { hide(); });
  23. }
  24. pToolTip::~pToolTip() {
  25. hide();
  26. if(htheme) { CloseThemeData(htheme); htheme = nullptr; }
  27. if(hwnd) { DestroyWindow(hwnd); hwnd = nullptr; }
  28. }
  29. auto pToolTip::drawLayered() -> void {
  30. auto hdcOutput = GetDC(nullptr);
  31. uint32_t* below = nullptr;
  32. auto hdcBelow = CreateCompatibleDC(hdcOutput);
  33. auto hbmBelow = CreateBitmap(hdcBelow, size.cx, size.cy, below);
  34. SelectObject(hdcBelow, hbmBelow);
  35. RECT rc{};
  36. rc.left = 0, rc.top = 0, rc.right = size.cx, rc.bottom = size.cy;
  37. DrawThemeBackground(htheme, hdcBelow, TTP_STANDARD, TTSS_NORMAL, &rc, nullptr);
  38. uint32_t* above = nullptr;
  39. auto hdcAbove = CreateCompatibleDC(hdcOutput);
  40. auto hbmAbove = CreateBitmap(hdcAbove, size.cx, size.cy, above);
  41. SelectObject(hdcAbove, hbmAbove);
  42. memory::copy<uint32_t>(above, below, size.cx * size.cy);
  43. auto hfont = pFont::create(Font());
  44. SelectObject(hdcAbove, hfont);
  45. SetBkMode(hdcAbove, TRANSPARENT);
  46. SetTextColor(hdcAbove, RGB(0, 0, 0));
  47. utf16_t drawText(text);
  48. rc.left += 6, rc.top += 6, rc.right -= 6, rc.bottom -= 6;
  49. DrawText(hdcAbove, drawText, -1, &rc, DT_LEFT | DT_TOP);
  50. DeleteObject(hfont);
  51. for(uint n : range(size.cx * size.cy)) {
  52. below[n] = (below[n] & 0xff000000) | (above[n] & 0x00ffffff);
  53. }
  54. BLENDFUNCTION blend{};
  55. blend.BlendOp = AC_SRC_OVER;
  56. blend.SourceConstantAlpha = 255;
  57. blend.AlphaFormat = AC_SRC_ALPHA;
  58. POINT zero{};
  59. zero.x = 0, zero.y = 0;
  60. UpdateLayeredWindow(hwnd, hdcOutput, &position, &size, hdcBelow, &zero, RGB(0, 0, 0), &blend, ULW_ALPHA);
  61. DeleteObject(hbmAbove);
  62. DeleteObject(hbmBelow);
  63. DeleteDC(hdcAbove);
  64. DeleteDC(hdcBelow);
  65. ReleaseDC(nullptr, hdcOutput);
  66. }
  67. auto pToolTip::drawOpaque() -> void {
  68. PAINTSTRUCT ps;
  69. BeginPaint(hwnd, &ps);
  70. RECT rc{};
  71. GetClientRect(hwnd, &rc);
  72. auto brush = CreateSolidBrush(RGB(0, 0, 0));
  73. FillRect(ps.hdc, &rc, brush);
  74. DeleteObject(brush);
  75. rc.left += 1, rc.top += 1, rc.right -= 1, rc.bottom -= 1;
  76. brush = CreateSolidBrush(RGB(255, 255, 225));
  77. FillRect(ps.hdc, &rc, brush);
  78. DeleteObject(brush);
  79. rc.left += 5, rc.top += 5, rc.right -= 5, rc.bottom -= 5;
  80. SetBkMode(ps.hdc, TRANSPARENT);
  81. auto font = pFont::create(Font());
  82. SelectObject(ps.hdc, font);
  83. SetTextColor(ps.hdc, RGB(0, 0, 0));
  84. DrawText(ps.hdc, utf16_t(text), -1, &rc, DT_LEFT | DT_TOP);
  85. DeleteObject(font);
  86. EndPaint(hwnd, &ps);
  87. }
  88. auto pToolTip::show() -> void {
  89. if(auto toolTip = pApplication::state().toolTip) {
  90. if(toolTip != this) toolTip->hide();
  91. }
  92. pApplication::state().toolTip = this;
  93. GetCursorPos(&position);
  94. if(position.x == tracking.x && position.y == tracking.y) return;
  95. tracking.x = position.x, tracking.y = position.y;
  96. position.y += 18;
  97. auto textSize = pFont::size(Font(), text ? text : " ");
  98. size.cx = 12 + textSize.width();
  99. size.cy = 12 + textSize.height();
  100. //try to keep the tool-tip onscreen
  101. auto desktop = pDesktop::size();
  102. if(position.x + size.cx >= desktop.width ()) position.x = desktop.width () - size.cx;
  103. if(position.y + size.cy >= desktop.height()) position.y = desktop.height() - size.cy;
  104. if(position.x < 0) position.x = 0;
  105. if(position.y < 0) position.y = 0;
  106. SetWindowPos(hwnd, HWND_TOP, position.x, position.y, size.cx, size.cy, SWP_NOACTIVATE | SWP_SHOWWINDOW);
  107. if(htheme) drawLayered();
  108. timeout.setEnabled(true);
  109. }
  110. auto pToolTip::hide() -> void {
  111. pApplication::state().toolTip = nullptr;
  112. timeout.setEnabled(false);
  113. ShowWindow(hwnd, SW_HIDE);
  114. GetCursorPos(&tracking);
  115. }
  116. auto pToolTip::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
  117. switch(msg) {
  118. case WM_ERASEBKGND:
  119. case WM_PAINT:
  120. if(htheme) break;
  121. drawOpaque();
  122. return msg == WM_ERASEBKGND;
  123. case WM_MOUSEMOVE:
  124. case WM_MOUSELEAVE: {
  125. POINT point{};
  126. GetCursorPos(&point);
  127. if(point.x != tracking.x || point.y != tracking.y) hide();
  128. } break;
  129. case WM_LBUTTONDOWN: case WM_LBUTTONUP:
  130. case WM_MBUTTONDOWN: case WM_MBUTTONUP:
  131. case WM_RBUTTONDOWN: case WM_RBUTTONUP:
  132. hide();
  133. break;
  134. }
  135. return {};
  136. }
  137. }