mainwindow.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. // Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
  2. // Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
  3. // SPDX-License-Identifier: Apache-2.0
  4. #include "mainwindow.h"
  5. #include <QtCore/QDebug>
  6. #include <QtCore/QFile>
  7. #include <QtCore/QTime>
  8. #include <QtCore/QTimer>
  9. #include <QtGui/QPainter>
  10. #include <QtGui/QWindow>
  11. #include <QtWidgets/QApplication>
  12. #include <QtWidgets/QStyle>
  13. #include <QtWidgets/QPushButton>
  14. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  15. # include <QtGui/QActionGroup>
  16. #else
  17. # include <QtWidgets/QActionGroup>
  18. #endif
  19. // #include <QtWebEngineWidgets/QWebEngineView>
  20. #include <QWKWidgets/widgetwindowagent.h>
  21. #include <widgetframe/windowbar.h>
  22. #include <widgetframe/windowbutton.h>
  23. class ClockWidget : public QLabel {
  24. public:
  25. explicit ClockWidget(QWidget *parent = nullptr) : QLabel(parent) {
  26. startTimer(100);
  27. setAlignment(Qt::AlignCenter);
  28. }
  29. ~ClockWidget() override = default;
  30. protected:
  31. void timerEvent(QTimerEvent *event) override {
  32. QLabel::timerEvent(event);
  33. setText(QTime::currentTime().toString(QStringLiteral("hh:mm:ss")));
  34. }
  35. };
  36. MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
  37. setAttribute(Qt::WA_DontCreateNativeAncestors);
  38. installWindowAgent();
  39. #if 1
  40. auto clockWidget = new ClockWidget();
  41. clockWidget->setObjectName(QStringLiteral("clock-widget"));
  42. clockWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  43. setCentralWidget(clockWidget);
  44. #else
  45. auto webView = new QWebEngineView();
  46. webView->load(QUrl("https://www.baidu.com"));
  47. setCentralWidget(webView);
  48. #endif
  49. loadStyleSheet(Dark);
  50. setWindowTitle(tr("Example MainWindow"));
  51. resize(800, 600);
  52. // setFixedHeight(600);
  53. // windowAgent->centralize();
  54. }
  55. static inline void emulateLeaveEvent(QWidget *widget) {
  56. Q_ASSERT(widget);
  57. if (!widget) {
  58. return;
  59. }
  60. QTimer::singleShot(0, widget, [widget]() {
  61. #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
  62. const QScreen *screen = widget->screen();
  63. #else
  64. const QScreen *screen = widget->windowHandle()->screen();
  65. #endif
  66. const QPoint globalPos = QCursor::pos(screen);
  67. if (!QRect(widget->mapToGlobal(QPoint{0, 0}), widget->size()).contains(globalPos)) {
  68. QCoreApplication::postEvent(widget, new QEvent(QEvent::Leave));
  69. if (widget->testAttribute(Qt::WA_Hover)) {
  70. const QPoint localPos = widget->mapFromGlobal(globalPos);
  71. const QPoint scenePos = widget->window()->mapFromGlobal(globalPos);
  72. static constexpr const auto oldPos = QPoint{};
  73. const Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  74. #if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
  75. const auto event =
  76. new QHoverEvent(QEvent::HoverLeave, scenePos, globalPos, oldPos, modifiers);
  77. Q_UNUSED(localPos);
  78. #elif (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
  79. const auto event = new QHoverEvent(QEvent::HoverLeave, localPos, globalPos, oldPos, modifiers);
  80. Q_UNUSED(scenePos);
  81. #else
  82. const auto event = new QHoverEvent(QEvent::HoverLeave, localPos, oldPos, modifiers);
  83. Q_UNUSED(scenePos);
  84. #endif
  85. QCoreApplication::postEvent(widget, event);
  86. }
  87. }
  88. });
  89. }
  90. MainWindow::~MainWindow() = default;
  91. bool MainWindow::event(QEvent *event) {
  92. switch (event->type()) {
  93. case QEvent::WindowActivate: {
  94. auto menu = menuWidget();
  95. if (menu) {
  96. menu->setProperty("bar-active", true);
  97. style()->polish(menu);
  98. }
  99. break;
  100. }
  101. case QEvent::WindowDeactivate: {
  102. auto menu = menuWidget();
  103. if (menu) {
  104. menu->setProperty("bar-active", false);
  105. style()->polish(menu);
  106. }
  107. break;
  108. }
  109. default:
  110. break;
  111. }
  112. return QMainWindow::event(event);
  113. }
  114. void MainWindow::installWindowAgent() {
  115. // 1. Setup window agent
  116. windowAgent = new QWK::WidgetWindowAgent(this);
  117. windowAgent->setup(this);
  118. // 2. Construct your title bar
  119. auto menuBar = [this]() {
  120. auto menuBar = new QMenuBar(this);
  121. // Virtual menu
  122. auto file = new QMenu(tr("File(&F)"), menuBar);
  123. file->addAction(new QAction(tr("New(&N)"), menuBar));
  124. file->addAction(new QAction(tr("Open(&O)"), menuBar));
  125. file->addSeparator();
  126. auto edit = new QMenu(tr("Edit(&E)"), menuBar);
  127. edit->addAction(new QAction(tr("Undo(&U)"), menuBar));
  128. edit->addAction(new QAction(tr("Redo(&R)"), menuBar));
  129. // Theme action
  130. auto darkAction = new QAction(tr("Enable dark theme"), menuBar);
  131. darkAction->setCheckable(true);
  132. connect(darkAction, &QAction::triggered, this, [this](bool checked) {
  133. loadStyleSheet(checked ? Dark : Light); //
  134. });
  135. connect(this, &MainWindow::themeChanged, darkAction, [this, darkAction]() {
  136. darkAction->setChecked(currentTheme == Dark); //
  137. });
  138. #ifdef Q_OS_WIN
  139. auto noneAction = new QAction(tr("None"), menuBar);
  140. noneAction->setData(QStringLiteral("none"));
  141. noneAction->setCheckable(true);
  142. noneAction->setChecked(true);
  143. auto dwmBlurAction = new QAction(tr("Enable DWM blur"), menuBar);
  144. dwmBlurAction->setData(QStringLiteral("dwm-blur"));
  145. dwmBlurAction->setCheckable(true);
  146. auto acrylicAction = new QAction(tr("Enable acrylic material"), menuBar);
  147. acrylicAction->setData(QStringLiteral("acrylic-material"));
  148. acrylicAction->setCheckable(true);
  149. auto micaAction = new QAction(tr("Enable mica"), menuBar);
  150. micaAction->setData(QStringLiteral("mica"));
  151. micaAction->setCheckable(true);
  152. auto micaAltAction = new QAction(tr("Enable mica alt"), menuBar);
  153. micaAltAction->setData(QStringLiteral("mica-alt"));
  154. micaAltAction->setCheckable(true);
  155. auto winStyleGroup = new QActionGroup(menuBar);
  156. winStyleGroup->addAction(noneAction);
  157. winStyleGroup->addAction(dwmBlurAction);
  158. winStyleGroup->addAction(acrylicAction);
  159. winStyleGroup->addAction(micaAction);
  160. winStyleGroup->addAction(micaAltAction);
  161. connect(winStyleGroup, &QActionGroup::triggered, this,
  162. [this, winStyleGroup](QAction *action) {
  163. // Unset all custom style attributes first, otherwise the style will not display
  164. // correctly
  165. for (const QAction *_act : winStyleGroup->actions()) {
  166. const QString data = _act->data().toString();
  167. if (data.isEmpty() || data == QStringLiteral("none")) {
  168. continue;
  169. }
  170. windowAgent->setWindowAttribute(data, false);
  171. }
  172. const QString data = action->data().toString();
  173. if (data == QStringLiteral("none")) {
  174. setProperty("custom-style", false);
  175. } else if (!data.isEmpty()) {
  176. windowAgent->setWindowAttribute(data, true);
  177. setProperty("custom-style", true);
  178. }
  179. style()->polish(this);
  180. });
  181. #elif defined(Q_OS_MAC)
  182. // Set whether to use system buttons (close/minimize/zoom)
  183. // - true: Hide system buttons (use custom UI controls)
  184. // - false: Show native system buttons (default behavior)
  185. windowAgent->setWindowAttribute(QStringLiteral("no-system-buttons"), false);
  186. auto darkBlurAction = new QAction(tr("Dark blur"), menuBar);
  187. darkBlurAction->setCheckable(true);
  188. connect(darkBlurAction, &QAction::toggled, this, [this](bool checked) {
  189. if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "dark")) {
  190. return;
  191. }
  192. if (checked) {
  193. setProperty("custom-style", true);
  194. style()->polish(this);
  195. }
  196. });
  197. auto lightBlurAction = new QAction(tr("Light blur"), menuBar);
  198. lightBlurAction->setCheckable(true);
  199. connect(lightBlurAction, &QAction::toggled, this, [this](bool checked) {
  200. if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "light")) {
  201. return;
  202. }
  203. if (checked) {
  204. setProperty("custom-style", true);
  205. style()->polish(this);
  206. }
  207. });
  208. auto noBlurAction = new QAction(tr("No blur"), menuBar);
  209. noBlurAction->setCheckable(true);
  210. connect(noBlurAction, &QAction::toggled, this, [this](bool checked) {
  211. if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), "none")) {
  212. return;
  213. }
  214. if (checked) {
  215. setProperty("custom-style", false);
  216. style()->polish(this);
  217. }
  218. });
  219. auto macStyleGroup = new QActionGroup(menuBar);
  220. macStyleGroup->addAction(darkBlurAction);
  221. macStyleGroup->addAction(lightBlurAction);
  222. macStyleGroup->addAction(noBlurAction);
  223. #endif
  224. // Real menu
  225. auto settings = new QMenu(tr("Settings(&S)"), menuBar);
  226. settings->addAction(darkAction);
  227. #ifdef Q_OS_WIN
  228. settings->addSeparator();
  229. settings->addAction(noneAction);
  230. settings->addAction(dwmBlurAction);
  231. settings->addAction(acrylicAction);
  232. settings->addAction(micaAction);
  233. settings->addAction(micaAltAction);
  234. #elif defined(Q_OS_MAC)
  235. settings->addAction(darkBlurAction);
  236. settings->addAction(lightBlurAction);
  237. settings->addAction(noBlurAction);
  238. #endif
  239. menuBar->addMenu(file);
  240. menuBar->addMenu(edit);
  241. menuBar->addMenu(settings);
  242. return menuBar;
  243. }();
  244. menuBar->setObjectName(QStringLiteral("win-menu-bar"));
  245. auto titleLabel = new QLabel();
  246. titleLabel->setAlignment(Qt::AlignCenter);
  247. titleLabel->setObjectName(QStringLiteral("win-title-label"));
  248. #ifndef Q_OS_MAC
  249. auto iconButton = new QWK::WindowButton();
  250. iconButton->setObjectName(QStringLiteral("icon-button"));
  251. iconButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  252. auto pinButton = new QWK::WindowButton();
  253. pinButton->setCheckable(true);
  254. pinButton->setObjectName(QStringLiteral("pin-button"));
  255. pinButton->setProperty("system-button", true);
  256. pinButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  257. auto minButton = new QWK::WindowButton();
  258. minButton->setObjectName(QStringLiteral("min-button"));
  259. minButton->setProperty("system-button", true);
  260. minButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  261. auto maxButton = new QWK::WindowButton();
  262. maxButton->setCheckable(true);
  263. maxButton->setObjectName(QStringLiteral("max-button"));
  264. maxButton->setProperty("system-button", true);
  265. maxButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  266. auto closeButton = new QWK::WindowButton();
  267. closeButton->setObjectName(QStringLiteral("close-button"));
  268. closeButton->setProperty("system-button", true);
  269. closeButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  270. #endif
  271. auto windowBar = new QWK::WindowBar();
  272. #ifndef Q_OS_MAC
  273. windowBar->setIconButton(iconButton);
  274. windowBar->setPinButton(pinButton);
  275. windowBar->setMinButton(minButton);
  276. windowBar->setMaxButton(maxButton);
  277. windowBar->setCloseButton(closeButton);
  278. #endif
  279. windowBar->setMenuBar(menuBar);
  280. windowBar->setTitleLabel(titleLabel);
  281. windowBar->setHostWidget(this);
  282. windowAgent->setTitleBar(windowBar);
  283. #ifndef Q_OS_MAC
  284. windowAgent->setHitTestVisible(pinButton, true);
  285. windowAgent->setSystemButton(QWK::WindowAgentBase::WindowIcon, iconButton);
  286. windowAgent->setSystemButton(QWK::WindowAgentBase::Minimize, minButton);
  287. windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton);
  288. windowAgent->setSystemButton(QWK::WindowAgentBase::Close, closeButton);
  289. #endif
  290. windowAgent->setHitTestVisible(menuBar, true);
  291. #ifdef Q_OS_MAC
  292. windowAgent->setSystemButtonAreaCallback([](const QSize &size) {
  293. static constexpr const int width = 75;
  294. return QRect(QPoint(size.width() - width, 0), QSize(width, size.height())); //
  295. });
  296. #endif
  297. setMenuWidget(windowBar);
  298. #ifndef Q_OS_MAC
  299. connect(windowBar, &QWK::WindowBar::pinRequested, this, [this, pinButton](bool pin){
  300. if (isHidden() || isMinimized() || isMaximized() || isFullScreen()) {
  301. return;
  302. }
  303. setWindowFlag(Qt::WindowStaysOnTopHint, pin);
  304. show();
  305. pinButton->setChecked(pin);
  306. });
  307. connect(windowBar, &QWK::WindowBar::minimizeRequested, this, &QWidget::showMinimized);
  308. connect(windowBar, &QWK::WindowBar::maximizeRequested, this, [this, maxButton](bool max) {
  309. if (max) {
  310. showMaximized();
  311. } else {
  312. showNormal();
  313. }
  314. // It's a Qt issue that if a QAbstractButton::clicked triggers a window's maximization,
  315. // the button remains to be hovered until the mouse move. As a result, we need to
  316. // manually send leave events to the button.
  317. emulateLeaveEvent(maxButton);
  318. });
  319. connect(windowBar, &QWK::WindowBar::closeRequested, this, &QWidget::close);
  320. #endif
  321. }
  322. void MainWindow::loadStyleSheet(Theme theme) {
  323. if (!styleSheet().isEmpty() && theme == currentTheme)
  324. return;
  325. currentTheme = theme;
  326. if (QFile qss(theme == Dark ? QStringLiteral(":/dark-style.qss")
  327. : QStringLiteral(":/light-style.qss"));
  328. qss.open(QIODevice::ReadOnly | QIODevice::Text)) {
  329. setStyleSheet(QString::fromUtf8(qss.readAll()));
  330. Q_EMIT themeChanged();
  331. }
  332. }