From 5b36e097c21770a687afc3c1bd978cce26769f7c Mon Sep 17 00:00:00 2001 From: Jiri Hruska Date: Sun, 9 Jun 2024 04:22:50 +0200 Subject: [PATCH] Fix status bar and tab bar flicker when updated Add double buffering for status bar and tab bar to avoid flickering. Fix #15260, close #15296 --- PowerEditor/src/WinControls/DoubleBuffer.h | 101 ++++++++++++++++++ .../src/WinControls/StatusBar/StatusBar.cpp | 36 +++---- PowerEditor/src/WinControls/TabBar/TabBar.cpp | 32 +++--- PowerEditor/src/WinControls/TabBar/TabBar.h | 2 + PowerEditor/visual.net/notepadPlus.vcxproj | 1 + 5 files changed, 134 insertions(+), 38 deletions(-) create mode 100644 PowerEditor/src/WinControls/DoubleBuffer.h diff --git a/PowerEditor/src/WinControls/DoubleBuffer.h b/PowerEditor/src/WinControls/DoubleBuffer.h new file mode 100644 index 000000000..386010c6d --- /dev/null +++ b/PowerEditor/src/WinControls/DoubleBuffer.h @@ -0,0 +1,101 @@ +// This file is part of Notepad++ project +// Copyright (C) 2024 Jiri Hruska + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once +#include +#include + +class DoubleBuffer final +{ +private: + SIZE _size{}; + HDC _hMemoryDC = nullptr; + HBITMAP _hDefaultBitmap = nullptr; + HBITMAP _hBufferBitmap = nullptr; + +public: + DoubleBuffer() {} + DoubleBuffer(const DoubleBuffer&) = delete; + DoubleBuffer& operator=(const DoubleBuffer&) = delete; + + ~DoubleBuffer() + { + if (_hBufferBitmap) + { + ::SelectObject(_hMemoryDC, _hDefaultBitmap); + ::DeleteObject(_hBufferBitmap); + } + + if (_hMemoryDC) + { + ::DeleteDC(_hMemoryDC); + } + } + + HDC beginPaint(HWND hWnd, PAINTSTRUCT* ps) + { + if (!::BeginPaint(hWnd, ps)) + { + return nullptr; + } + + if (!_hMemoryDC) + { + _hMemoryDC = ::CreateCompatibleDC(ps->hdc); + } + + RECT clientRc{}; + ::GetClientRect(hWnd, &clientRc); + if (clientRc.right != _size.cx || clientRc.bottom != _size.cy || !_hBufferBitmap) + { + _size = { clientRc.right, clientRc.bottom }; + + if (_hBufferBitmap) + { + ::SelectObject(_hMemoryDC, _hDefaultBitmap); + ::DeleteObject(_hBufferBitmap); + } + + _hBufferBitmap = ::CreateCompatibleBitmap(ps->hdc, _size.cx, _size.cy); + _hDefaultBitmap = static_cast(::SelectObject(_hMemoryDC, _hBufferBitmap)); + } + + assert(ps->rcPaint.left < _size.cx && ps->rcPaint.top < _size.cy); + assert(ps->rcPaint.right <= _size.cx && ps->rcPaint.bottom <= _size.cy); + + return _hMemoryDC; + } + + void endPaint(HWND hWnd, PAINTSTRUCT* ps) + { + ::BitBlt( + ps->hdc, ps->rcPaint.left, ps->rcPaint.top, ps->rcPaint.right - ps->rcPaint.left, ps->rcPaint.bottom - ps->rcPaint.top, + _hMemoryDC, ps->rcPaint.left, ps->rcPaint.top, + SRCCOPY); + + ::EndPaint(hWnd, ps); + } + + int getWidth() const + { + return _size.cx; + } + + int getHeight() const + { + return _size.cy; + } +}; diff --git a/PowerEditor/src/WinControls/StatusBar/StatusBar.cpp b/PowerEditor/src/WinControls/StatusBar/StatusBar.cpp index d30d7cfd6..4485d9b9b 100644 --- a/PowerEditor/src/WinControls/StatusBar/StatusBar.cpp +++ b/PowerEditor/src/WinControls/StatusBar/StatusBar.cpp @@ -24,6 +24,7 @@ #include "NppDarkMode.h" #include #include +#include "DoubleBuffer.h" //#define IDC_STATUSBAR 789 @@ -50,6 +51,7 @@ struct StatusBarSubclassInfo { HTHEME hTheme = nullptr; HFONT _hFont = nullptr; + DoubleBuffer _dblBuf; StatusBarSubclassInfo() = default; StatusBarSubclassInfo(const HFONT& hFont) @@ -107,22 +109,24 @@ static LRESULT CALLBACK StatusBarSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, L { case WM_ERASEBKGND: { - if (!NppDarkMode::isEnabled()) - { - break; - } - - RECT rc{}; - GetClientRect(hWnd, &rc); - FillRect((HDC)wParam, &rc, NppDarkMode::getBackgroundBrush()); - return TRUE; + // Skip background erasing and set fErase in PAINTSTRUCT instead, + // WM_PAINT does all the painting in all cases + return FALSE; } case WM_PAINT: { + PAINTSTRUCT ps{}; + HDC hdc = pStatusBarInfo->_dblBuf.beginPaint(hWnd, &ps); + if (!NppDarkMode::isEnabled()) { - break; + // Even if the standard status bar common control is used directly in non-dark mode, + // it suffers from flickering during updates, so let it paint into a back buffer + DefSubclassProc(hWnd, WM_ERASEBKGND, reinterpret_cast(hdc), 0); + DefSubclassProc(hWnd, WM_PRINTCLIENT, reinterpret_cast(hdc), PRF_NONCLIENT | PRF_CLIENT); + pStatusBarInfo->_dblBuf.endPaint(hWnd, &ps); + return 0; } struct { @@ -136,16 +140,10 @@ static LRESULT CALLBACK StatusBarSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, L const auto style = ::GetWindowLongPtr(hWnd, GWL_STYLE); bool isSizeGrip = style & SBARS_SIZEGRIP; - PAINTSTRUCT ps{}; - HDC hdc = BeginPaint(hWnd, &ps); - auto holdPen = static_cast(::SelectObject(hdc, NppDarkMode::getEdgePen())); auto holdFont = static_cast(::SelectObject(hdc, pStatusBarInfo->_hFont)); - RECT rcClient{}; - GetClientRect(hWnd, &rcClient); - FillRect(hdc, &ps.rcPaint, NppDarkMode::getBackgroundBrush()); int nParts = static_cast(SendMessage(hWnd, SB_GETPARTS, 0, 0)); @@ -220,8 +218,8 @@ static LRESULT CALLBACK StatusBarSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, L { pStatusBarInfo->ensureTheme(hWnd); SIZE gripSize{}; - GetThemePartSize(pStatusBarInfo->hTheme, hdc, SP_GRIPPER, 0, &rcClient, TS_DRAW, &gripSize); - RECT rc = rcClient; + RECT rc = { 0, 0, pStatusBarInfo->_dblBuf.getWidth(), pStatusBarInfo->_dblBuf.getHeight() }; + GetThemePartSize(pStatusBarInfo->hTheme, hdc, SP_GRIPPER, 0, &rc, TS_DRAW, &gripSize); rc.left = rc.right - gripSize.cx; rc.top = rc.bottom - gripSize.cy; DrawThemeBackground(pStatusBarInfo->hTheme, hdc, SP_GRIPPER, 0, &rc, nullptr); @@ -230,7 +228,7 @@ static LRESULT CALLBACK StatusBarSubclass(HWND hWnd, UINT uMsg, WPARAM wParam, L ::SelectObject(hdc, holdFont); ::SelectObject(hdc, holdPen); - EndPaint(hWnd, &ps); + pStatusBarInfo->_dblBuf.endPaint(hWnd, &ps); return 0; } diff --git a/PowerEditor/src/WinControls/TabBar/TabBar.cpp b/PowerEditor/src/WinControls/TabBar/TabBar.cpp index b01ebf4aa..73f6fcbcc 100644 --- a/PowerEditor/src/WinControls/TabBar/TabBar.cpp +++ b/PowerEditor/src/WinControls/TabBar/TabBar.cpp @@ -915,35 +915,29 @@ LRESULT TabBarPlus::runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lPara case WM_ERASEBKGND: { - if (!NppDarkMode::isEnabled()) - { - break; - } - - RECT rc{}; - GetClientRect(hwnd, &rc); - FillRect((HDC)wParam, &rc, NppDarkMode::getDarkerBackgroundBrush()); - - return 1; + // Skip background erasing and set fErase in PAINTSTRUCT instead, + // WM_PAINT does all the painting in all cases + return FALSE; } case WM_PAINT: { - if (!NppDarkMode::isEnabled()) - { - break; - } + PAINTSTRUCT ps{}; + HDC hdc = _dblBuf.beginPaint(hwnd, &ps); LONG_PTR dwStyle = GetWindowLongPtr(hwnd, GWL_STYLE); - if (!(dwStyle & TCS_OWNERDRAWFIXED)) + if (!NppDarkMode::isEnabled() || !(dwStyle & TCS_OWNERDRAWFIXED)) { - break; + // Even if the tab bar common control is used directly, e.g. in non-dark mode, + // it suffers from flickering during updates, so let it paint into a back buffer + ::CallWindowProc(_tabBarDefaultProc, hwnd, WM_ERASEBKGND, reinterpret_cast(hdc), 0); + ::CallWindowProc(_tabBarDefaultProc, hwnd, WM_PRINTCLIENT, reinterpret_cast(hdc), PRF_NONCLIENT | PRF_CLIENT); + _dblBuf.endPaint(hwnd, &ps); + return 0; } const bool hasMultipleLines = ((dwStyle & TCS_BUTTONS) == TCS_BUTTONS); - PAINTSTRUCT ps{}; - HDC hdc = BeginPaint(hwnd, &ps); FillRect(hdc, &ps.rcPaint, NppDarkMode::getDarkerBackgroundBrush()); UINT id = ::GetDlgCtrlID(hwnd); @@ -1072,7 +1066,7 @@ LRESULT TabBarPlus::runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lPara SelectObject(hdc, holdPen); - EndPaint(hwnd, &ps); + _dblBuf.endPaint(hwnd, &ps); return 0; } diff --git a/PowerEditor/src/WinControls/TabBar/TabBar.h b/PowerEditor/src/WinControls/TabBar/TabBar.h index 171760a77..2e15b32cd 100644 --- a/PowerEditor/src/WinControls/TabBar/TabBar.h +++ b/PowerEditor/src/WinControls/TabBar/TabBar.h @@ -28,6 +28,7 @@ #include #include "Window.h" #include "dpiManagerV2.h" +#include "DoubleBuffer.h" //Notification message #define TCN_TABDROPPED (TCN_FIRST - 10) @@ -241,6 +242,7 @@ protected: int _previousTabSwapped = -1; POINT _draggingPoint{}; // coordinate of Screen WNDPROC _tabBarDefaultProc = nullptr; + DoubleBuffer _dblBuf; RECT _currentHoverTabRect{}; int _currentHoverTabItem = -1; // -1 : no mouse on any tab diff --git a/PowerEditor/visual.net/notepadPlus.vcxproj b/PowerEditor/visual.net/notepadPlus.vcxproj index e18a3409c..b32907bd6 100755 --- a/PowerEditor/visual.net/notepadPlus.vcxproj +++ b/PowerEditor/visual.net/notepadPlus.vcxproj @@ -298,6 +298,7 @@ +