Skip to content

Commit

Permalink
Window Polish on Windows (#10097)
Browse files Browse the repository at this point in the history
* Update Windows UI integration and theme handling

- Added WindowsUtils.cpp and WindowsUtils.h to handle Windows-specific utilities.
- Implemented setTitleBarIcon and setDockIcon methods in WindowsUtils.cpp to set the title bar and dock icon images for the application.
- Updated Theme.cpp to handle Windows-specific theme settings.
- Added a new method getTitleBarIcon in Theme.cpp to return the title bar icon image.

* Update title bar color and icon handling for Windows

- Added a new function `updateTitleBarColor` to set the title bar color based on the current theme.
- Updated `setTitleBarIcon` to use the new `updateTitleBarColor` function.
- Added a new function `forceWindowRedraw` to force a window redraw.
- Updated `setDockIcon` to use the new `forceWindowRedraw` function.
-

* Force window redraw on theme change

- Added a call to `WindowsUtils::forceWindowRedraw(window)` in the `run()` method of `CommandUI` to ensure the window is redrawn when the theme changes.
- This ensures that the window decorations are updated to match the new theme, providing a more consistent and visually appealing user experience.

* link tests to qtsvg

* Link all the tests to Qt::Svg
  • Loading branch information
strseb authored Jan 23, 2025
1 parent 1e7a924 commit 971e551
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ qt_add_executable(mozillavpn MANUAL_FINALIZATION)

mz_target_handle_warnings(mozillavpn)

find_package(Qt6 REQUIRED COMPONENTS Svg)

target_link_libraries(mozillavpn PRIVATE
Qt6::Quick
Qt6::Test
Expand Down
17 changes: 17 additions & 0 deletions src/commands/commandui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QWindow>

#include "accessiblenotification.h"
#include "addons/manager/addonmanager.h"
Expand Down Expand Up @@ -64,6 +65,8 @@

# include "eventlistener.h"
# include "platforms/windows/windowsstartatbootwatcher.h"
# include "platforms/windows/windowsutils.h"
# include "theme.h"
#endif

#ifdef MZ_WASM
Expand Down Expand Up @@ -317,6 +320,20 @@ int CommandUI::run(QStringList& tokens) {
logger.error() << "Failed to load " << url.toString();
return -1;
}
#ifdef MZ_WINDOWS
auto const updateWindowDecoration = [&engineHolder]() {
auto const window = engineHolder.window();
WindowsUtils::updateTitleBarColor(window,
Theme::instance()->isThemeDark());
WindowsUtils::setDockIcon(window, QImage(":/ui/resources/logo-dock.png"));
WindowsUtils::setTitleBarIcon(window,
Theme::instance()->getTitleBarIcon());
WindowsUtils::forceWindowRedraw(window);
};
QObject::connect(Theme::instance(), &Theme::changed,
updateWindowDecoration);
updateWindowDecoration();
#endif

NotificationHandler* notificationHandler =
NotificationHandler::create(&engineHolder);
Expand Down
68 changes: 68 additions & 0 deletions src/platforms/windows/windowsutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
#include "windowsutils.h"

#include <Windows.h>
#include <dwmapi.h>
#include <errhandlingapi.h>
#include <qwindow.h>
#include <winsvc.h>

#include <QImage>
#include <QSettings>
#include <QSysInfo>
#include <QWindow>

#pragma comment(lib, "dwmapi.lib")

#include "logger.h"

Expand Down Expand Up @@ -79,3 +85,65 @@ bool WindowsUtils::getServiceStatus(const QString& name) {
}
return (status.dwCurrentState == SERVICE_RUNNING);
}

void WindowsUtils::setTitleBarIcon(QWindow* window, const QImage& icon) {
auto const windowHandle = (HWND)window->winId();
HICON hIcon = icon.toHICON();
SendMessage(windowHandle, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
}
void WindowsUtils::setDockIcon(QWindow* window, const QImage& icon) {
auto const windowHandle = (HWND)window->winId();
HICON hIcon = icon.toHICON();
SendMessage(windowHandle, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}

// constexpr to Help do color things in compile time.
//
namespace ColorUtils {
constexpr uint8_t hexToByte(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
throw std::invalid_argument("Invalid hex character");
}

constexpr uint8_t parseHexByte(const char* hex) {
return (hexToByte(hex[0]) << 4) | hexToByte(hex[1]);
}
/**
* @brief converts a hex RGBA to AGBR represented as DWORD
* Binary format is 0x00BBGGRR
*/
constexpr uint32_t toCOLORREF(const char* color) {
// Ensure it's a valid format
if (color[0] != '#' || color[7] != '\0') {
throw std::invalid_argument("Invalid color format. Expected '#RRGGBB'.");
}

// Extract and convert R, G, B
uint8_t r = parseHexByte(&color[1]);
uint8_t g = parseHexByte(&color[3]);
uint8_t b = parseHexByte(&color[5]);

// Construct and return the COLORREF value in 0x00BBGGRR format
return (b << 16) | (g << 8) | r;
}
} // namespace ColorUtils

void WindowsUtils::updateTitleBarColor(QWindow* window, bool darkMode) {
// TODO: Fetch that from the theme data.
const COLORREF defaultColor = darkMode ? ColorUtils::toCOLORREF("#0C0C0D")
: ColorUtils::toCOLORREF("#F9F9FA");

auto const windowHandle = (HWND)window->winId();
auto const ok = SUCCEEDED(DwmSetWindowAttribute(
windowHandle, DWMWINDOWATTRIBUTE::DWMWA_CAPTION_COLOR, &defaultColor,
sizeof(defaultColor)));
Q_ASSERT(ok);
}

void WindowsUtils::forceWindowRedraw(QWindow* w) {
auto const windowHandle = (HWND)w->winId();
ShowWindow(windowHandle, SW_MINIMIZE);
ShowWindow(windowHandle, SW_RESTORE);
}
14 changes: 14 additions & 0 deletions src/platforms/windows/windowsutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

#include <QString>

class QWindow;
class QImage;

class WindowsUtils final {
public:
static QString getErrorMessage();
Expand All @@ -20,6 +23,17 @@ class WindowsUtils final {

// Force an application crash for testing
static void forceCrash();

static void setTitleBarIcon(QWindow* window, const QImage& icon);
static void setDockIcon(QWindow* window, const QImage& icon);
static void forceWindowRedraw(QWindow* w);
/**
*
* @brief Set the Color for the titlebar, the color of the current theme
* will be used. This only has impact on windows.
*
*/
static void updateTitleBarColor(QWindow* window, bool darkMode);
};

#endif // WINDOWSUTILS_H
44 changes: 44 additions & 0 deletions src/theme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#endif

#include <QCoreApplication>
#include <QPainter>
#include <QQmlEngine>

namespace {
Expand Down Expand Up @@ -193,6 +194,12 @@ Qt::ColorScheme Theme::currentSystemTheme() {
return currentColorScheme;
}
#endif
#ifdef MZ_WINDOWS
# include <dwmapi.h>

# include <QWindow>
# pragma comment(lib, "dwmapi.lib")
#endif

void Theme::setStatusBarTextColor([[maybe_unused]] StatusBarTextColor color) {
#ifdef MZ_IOS
Expand All @@ -209,3 +216,40 @@ bool Theme::usesDarkModeAssets() const {
Q_ASSERT(false);
return true;
}

#include <QPainter>
#include <QSvgRenderer>

QImage Theme::getTitleBarIcon() {
bool isDarkmode = isThemeDark();
QString svgPath = ":/ui/resources/logo.svg";

QImage image(32, 32, QImage::Format_ARGB32);
image.fill(Qt::transparent); // Ensure transparency

// Load the SVG
QSvgRenderer svgRenderer(svgPath);
if (!svgRenderer.isValid()) {
qWarning() << "Failed to load SVG: " << svgPath;
return QImage();
}

// Paint the SVG onto the QImage
QPainter painter(&image);
svgRenderer.render(&painter, QRectF(0, 0, 32, 32));
painter.end();

// Invert the pixels if in dark mode
if (isDarkmode) {
for (int y = 0; y < image.height(); ++y) {
QRgb* scanLine = reinterpret_cast<QRgb*>(image.scanLine(y));
for (int x = 0; x < image.width(); ++x) {
QRgb pixel = scanLine[x];
// Invert RGB but keep the alpha channel
scanLine[x] = qRgba(255 - qRed(pixel), 255 - qGreen(pixel),
255 - qBlue(pixel), qAlpha(pixel));
}
}
}
return image;
}
8 changes: 8 additions & 0 deletions src/theme.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <QAbstractListModel>
#include <QHash>
#include <QIcon>
#include <QJSValue>

class QJSEngine;
Expand Down Expand Up @@ -34,6 +35,11 @@ class Theme final : public QAbstractListModel {
const QJSValue& readColors() const;

const QString& currentTheme() const { return m_currentTheme; }

// Todo: Add a thing for themes to define, if they are using dark or light
// resources.
bool isThemeDark() { return m_currentTheme != "main"; }

void setCurrentTheme(const QString& themeName);

void initialize(QJSEngine* engine);
Expand All @@ -58,6 +64,8 @@ class Theme final : public QAbstractListModel {
Q_INVOKABLE void setStatusBarTextColor(Theme::StatusBarTextColor color);

bool usesDarkModeAssets() const;
// Returns an Icon matching the current colorscheme
QImage getTitleBarIcon();

private:
void parseTheme(QJSEngine* engine, const QString& themeName);
Expand Down
1 change: 1 addition & 0 deletions tests/auth_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ target_link_libraries(app_auth_tests PRIVATE
Qt6::Test
Qt6::WebSockets
Qt6::Widgets
Qt6::Svg
)

if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten"
Expand Down
1 change: 1 addition & 0 deletions tests/qml/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ target_link_libraries(qml_tests PRIVATE
Qt6::Qml
Qt6::Quick
Qt6::QuickTest
Qt6::Svg
)

target_link_libraries(qml_tests PRIVATE qtglean lottie nebula translations)
Expand Down
1 change: 1 addition & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ target_link_libraries(unit_tests PRIVATE
Qt6::Gui
Qt6::Qml
Qt6::Quick
Qt6::Svg
)

target_compile_definitions(unit_tests PRIVATE UNIT_TEST)
Expand Down
1 change: 1 addition & 0 deletions tests/unit_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ target_link_libraries(app_unit_tests PRIVATE
Qt6::WebSockets
Qt6::Widgets
Qt6::Network
Qt6::Svg
)

if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten"
Expand Down

0 comments on commit 971e551

Please sign in to comment.