From 341a03be815aa5a45d73329008eafd5b220a5899 Mon Sep 17 00:00:00 2001 From: wuyize Date: Sun, 23 Oct 2022 19:25:43 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=86=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ArchitectureColoredPainting.sln | 9 + .../ArchitectureColoredPainting.vcxproj | 21 +- ...rchitectureColoredPainting.vcxproj.filters | 57 +- ArchitectureColoredPainting/EditorWidget.ui | 3 + .../FramelessWindow.ui | 208 + ArchitectureColoredPainting/MainWindow.qrc | 10 + ArchitectureColoredPainting/MainWindow.ui | 42 +- .../NavigationBarWidget.ui | 100 + ArchitectureColoredPainting/RendererWidget.ui | 18 +- ArchitectureColoredPainting/darkstyle.qss | 297 ++ ArchitectureColoredPainting/images/icon.png | Bin 0 -> 18122 bytes .../images/icon_window_close.png | Bin 0 -> 422 bytes .../images/icon_window_maximize.png | Bin 0 -> 386 bytes .../images/icon_window_minimize.png | Bin 0 -> 364 bytes .../images/icon_window_restore.png | Bin 0 -> 404 bytes ArchitectureColoredPainting/lightstyle.qss | 0 ArchitectureColoredPainting/msvc_make.bat | 2 + ArchitectureColoredPainting/qt.conf | 2 + .../src/CaptionButton.cpp | 261 ++ .../src/CaptionButton.h | 81 + .../src/IconWidget.cpp | 75 + ArchitectureColoredPainting/src/IconWidget.h | 52 + .../src/MainWindow.cpp | 485 ++- ArchitectureColoredPainting/src/MainWindow.h | 48 +- .../src/NavigationBarWidget.cpp | 14 + .../src/NavigationBarWidget.h | 18 + .../src/Renderer/RendererGLWidget.cpp | 19 +- .../src/Renderer/RendererGLWidget.h | 3 + .../src/Renderer/RendererWidget.cpp | 12 + .../src/Renderer/RendererWidget.h | 4 +- .../src/TitleWidget.cpp | 80 + ArchitectureColoredPainting/src/TitleWidget.h | 54 + QGoodWindow/.qmake.stash | 22 + QGoodWindow/QGoodWindow | 25 + QGoodWindow/QGoodWindow.pro | 92 + QGoodWindow/QGoodWindow.vcxproj | 213 + QGoodWindow/QGoodWindow.vcxproj.filters | 60 + QGoodWindow/QGoodWindow.write.1u.tlog | Bin 0 -> 370 bytes QGoodWindow/moc.read.1u.tlog | Bin 0 -> 272 bytes QGoodWindow/moc.write.1u.tlog | Bin 0 -> 570 bytes QGoodWindow/src/common.h | 79 + QGoodWindow/src/macosnative.h | 50 + QGoodWindow/src/macosnative.mm | 148 + QGoodWindow/src/notification.cpp | 67 + QGoodWindow/src/notification.h | 47 + QGoodWindow/src/qgoodwindow.cpp | 3652 +++++++++++++++++ QGoodWindow/src/qgoodwindow.h | 446 ++ QGoodWindow/src/shadow.cpp | 258 ++ QGoodWindow/src/shadow.h | 77 + 49 files changed, 7184 insertions(+), 27 deletions(-) create mode 100644 ArchitectureColoredPainting/FramelessWindow.ui create mode 100644 ArchitectureColoredPainting/NavigationBarWidget.ui create mode 100644 ArchitectureColoredPainting/darkstyle.qss create mode 100644 ArchitectureColoredPainting/images/icon.png create mode 100644 ArchitectureColoredPainting/images/icon_window_close.png create mode 100644 ArchitectureColoredPainting/images/icon_window_maximize.png create mode 100644 ArchitectureColoredPainting/images/icon_window_minimize.png create mode 100644 ArchitectureColoredPainting/images/icon_window_restore.png create mode 100644 ArchitectureColoredPainting/lightstyle.qss create mode 100644 ArchitectureColoredPainting/msvc_make.bat create mode 100644 ArchitectureColoredPainting/qt.conf create mode 100644 ArchitectureColoredPainting/src/CaptionButton.cpp create mode 100644 ArchitectureColoredPainting/src/CaptionButton.h create mode 100644 ArchitectureColoredPainting/src/IconWidget.cpp create mode 100644 ArchitectureColoredPainting/src/IconWidget.h create mode 100644 ArchitectureColoredPainting/src/NavigationBarWidget.cpp create mode 100644 ArchitectureColoredPainting/src/NavigationBarWidget.h create mode 100644 ArchitectureColoredPainting/src/TitleWidget.cpp create mode 100644 ArchitectureColoredPainting/src/TitleWidget.h create mode 100644 QGoodWindow/.qmake.stash create mode 100644 QGoodWindow/QGoodWindow create mode 100644 QGoodWindow/QGoodWindow.pro create mode 100644 QGoodWindow/QGoodWindow.vcxproj create mode 100644 QGoodWindow/QGoodWindow.vcxproj.filters create mode 100644 QGoodWindow/QGoodWindow.write.1u.tlog create mode 100644 QGoodWindow/moc.read.1u.tlog create mode 100644 QGoodWindow/moc.write.1u.tlog create mode 100644 QGoodWindow/src/common.h create mode 100644 QGoodWindow/src/macosnative.h create mode 100644 QGoodWindow/src/macosnative.mm create mode 100644 QGoodWindow/src/notification.cpp create mode 100644 QGoodWindow/src/notification.h create mode 100644 QGoodWindow/src/qgoodwindow.cpp create mode 100644 QGoodWindow/src/qgoodwindow.h create mode 100644 QGoodWindow/src/shadow.cpp create mode 100644 QGoodWindow/src/shadow.h diff --git a/ArchitectureColoredPainting.sln b/ArchitectureColoredPainting.sln index 1f4fb23..c034bd5 100644 --- a/ArchitectureColoredPainting.sln +++ b/ArchitectureColoredPainting.sln @@ -4,6 +4,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.2.32519.379 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ArchitectureColoredPainting", "ArchitectureColoredPainting\ArchitectureColoredPainting.vcxproj", "{3FE96A33-2BB7-4686-A710-3EB8E3BBD709}" + ProjectSection(ProjectDependencies) = postProject + {B982E745-C0B1-46B3-A27B-743AF105F2D0} = {B982E745-C0B1-46B3-A27B-743AF105F2D0} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QGoodWindow", "QGoodWindow\QGoodWindow.vcxproj", "{B982E745-C0B1-46B3-A27B-743AF105F2D0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +20,10 @@ Global {3FE96A33-2BB7-4686-A710-3EB8E3BBD709}.Debug|x64.Build.0 = Debug|x64 {3FE96A33-2BB7-4686-A710-3EB8E3BBD709}.Release|x64.ActiveCfg = Release|x64 {3FE96A33-2BB7-4686-A710-3EB8E3BBD709}.Release|x64.Build.0 = Release|x64 + {B982E745-C0B1-46B3-A27B-743AF105F2D0}.Debug|x64.ActiveCfg = Debug|x64 + {B982E745-C0B1-46B3-A27B-743AF105F2D0}.Debug|x64.Build.0 = Debug|x64 + {B982E745-C0B1-46B3-A27B-743AF105F2D0}.Release|x64.ActiveCfg = Release|x64 + {B982E745-C0B1-46B3-A27B-743AF105F2D0}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj b/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj index 0c3888c..0799117 100644 --- a/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj +++ b/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj @@ -32,7 +32,7 @@ 5.15.2_msvc2019_64 - core;gui;widgets + core;gui;widgets;winextras debug @@ -61,11 +61,13 @@ stdcpp17 + $(SolutionDir)QGoodWindow;%(AdditionalIncludeDirectories);$(Qt_INCLUDEPATH_) stdcpp17 + $(SolutionDir)QGoodWindow;%(AdditionalIncludeDirectories); @@ -95,9 +97,12 @@ + + + @@ -113,12 +118,17 @@ + + + + + @@ -137,6 +147,10 @@ + + + + @@ -156,6 +170,11 @@ + + + {b982e745-c0b1-46b3-a27b-743af105f2d0} + + diff --git a/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj.filters b/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj.filters index b013fdb..a03f502 100644 --- a/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj.filters +++ b/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj.filters @@ -9,10 +9,6 @@ {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - {99349809-55BA-4b9d-BF79-8FDBB0286EB3} ui @@ -21,9 +17,6 @@ {639EADAA-A684-42e4-A9AD-28FC9BCB8F7C} ts - - {60515177-3da7-420f-8f5b-27c16bb2b77b} - {bd91d673-6674-478d-a43d-54ac6c09d77f} @@ -36,14 +29,23 @@ {f52671e3-3263-45e6-8d6c-976d19dc7e24} + + {7a710fdd-46c9-44ff-aae7-7809e9c34d23} + + + {60515177-3da7-420f-8f5b-27c16bb2b77b} + - - Resource Files - Form Files + + Form Files + + + Form Files + @@ -100,6 +102,18 @@ Source Files\Renderer + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -114,6 +128,18 @@ Header Files\Renderer + + Header Files + + + Header Files + + + Header Files + + + Header Files + @@ -164,6 +190,12 @@ Resource Files\Shaders + + Resource Files + + + Resource Files + @@ -217,4 +249,9 @@ Source Files + + + Resource Files + + \ No newline at end of file diff --git a/ArchitectureColoredPainting/EditorWidget.ui b/ArchitectureColoredPainting/EditorWidget.ui index 4622380..68e9a6c 100644 --- a/ArchitectureColoredPainting/EditorWidget.ui +++ b/ArchitectureColoredPainting/EditorWidget.ui @@ -31,6 +31,9 @@ 纹理编辑 + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + diff --git a/ArchitectureColoredPainting/FramelessWindow.ui b/ArchitectureColoredPainting/FramelessWindow.ui new file mode 100644 index 0000000..9660810 --- /dev/null +++ b/ArchitectureColoredPainting/FramelessWindow.ui @@ -0,0 +1,208 @@ + + + FramelessWindow + + + + 0 + 0 + 560 + 544 + + + + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + #windowFrame{ background-color:palette(Window);} + + + + 0 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + false + + + #windowTitlebar{border: 0px none palette(base); border-top-left-radius:5px; border-top-right-radius:5px; background-color:palette(shadow); height:20px;} + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 4 + 0 + + + + + 4 + 16777215 + + + + + + + + + 16 + 16 + + + + + 16 + 16 + + + + Qt::NoContextMenu + + + #icon {background-color:palette(shadow);} + + + + + + + + + + + + + + + + + + + + + + + + + false + + + #windowContent{ + border: 0px none palette(base); + border-radius:0px 0px 5px 5px; +} + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + CaptionButton + QWidget +
CaptionButton.h
+ 1 +
+ + IconWidget + QWidget +
IconWidget.h
+
+ + TitleWidget + QWidget +
TitleWidget.h
+ 1 +
+
+ + +
diff --git a/ArchitectureColoredPainting/MainWindow.qrc b/ArchitectureColoredPainting/MainWindow.qrc index 150f170..8a9a1ea 100644 --- a/ArchitectureColoredPainting/MainWindow.qrc +++ b/ArchitectureColoredPainting/MainWindow.qrc @@ -16,5 +16,15 @@ Shaders/model_shadow.geom Shaders/shadow_mapping.comp Shaders/ssgi.comp + images/icon.png + images/icon_window_close.png + images/icon_window_maximize.png + images/icon_window_minimize.png + images/icon_window_restore.png + darkstyle.qss + lightstyle.qss + + + qt.conf diff --git a/ArchitectureColoredPainting/MainWindow.ui b/ArchitectureColoredPainting/MainWindow.ui index 89d242e..2922f49 100644 --- a/ArchitectureColoredPainting/MainWindow.ui +++ b/ArchitectureColoredPainting/MainWindow.ui @@ -13,6 +13,9 @@ MainWindow + + font: 10pt "Segoe UI, Microsoft YaHei UI"; + @@ -21,6 +24,9 @@ + + 0 + 0 @@ -34,7 +40,10 @@ 0 - + + + 0 + QLayout::SetDefaultConstraint @@ -42,10 +51,29 @@ - Microsoft YaHei UI - 13 + Segoe UI, Microsoft YaHei UI + 10 + 50 + false + false + + QTabBar::tab { +height: 0px; +margin-top:0px; +} +QTabWidget::tab-bar +{ + height: 0px; + top:0px; +} +QTabWidget::pane { + border: 0px; + background-color: rgba(0, 0, 0, 0); +} + + QTabWidget::North @@ -53,7 +81,13 @@ QTabWidget::Rounded - 1 + 0 + + + Qt::ElideNone + + + false diff --git a/ArchitectureColoredPainting/NavigationBarWidget.ui b/ArchitectureColoredPainting/NavigationBarWidget.ui new file mode 100644 index 0000000..32061cf --- /dev/null +++ b/ArchitectureColoredPainting/NavigationBarWidget.ui @@ -0,0 +1,100 @@ + + + NavigationBarWidgetClass + + + + 0 + 0 + 612 + 41 + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 0 + 0 + + + + NavigationBarWidget + + + QRadioButton::indicator { + width: 0px; + border: 0px; + background-color: rgba(0, 0, 0, 0); +} + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + 纹理编辑 + + + true + + + + + + + + 0 + 0 + + + + 场景渲染 + + + true + + + + + + + + + diff --git a/ArchitectureColoredPainting/RendererWidget.ui b/ArchitectureColoredPainting/RendererWidget.ui index d6f9fbf..190a6ca 100644 --- a/ArchitectureColoredPainting/RendererWidget.ui +++ b/ArchitectureColoredPainting/RendererWidget.ui @@ -13,7 +13,7 @@ RendererWidget - + 0 @@ -26,6 +26,22 @@ 0 + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + diff --git a/ArchitectureColoredPainting/darkstyle.qss b/ArchitectureColoredPainting/darkstyle.qss new file mode 100644 index 0000000..e4cbe88 --- /dev/null +++ b/ArchitectureColoredPainting/darkstyle.qss @@ -0,0 +1,297 @@ +QToolTip{ + color:palette(text); + background-color:palette(base); + border:1px solid palette(highlight); + border-radius:0px; +} +QStatusBar{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + color:palette(mid); +} +QMenuBar{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-bottom:2px solid rgba(25,25,25,75); +} +QMenuBar::item{ + spacing:2px; + padding:3px 4px; + background:transparent; +} +QMenuBar::item:selected{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(106,106,106,255),stop:1 rgba(106,106,106,75)); + border-left:1px solid rgba(106,106,106,127); + border-right:1px solid rgba(106,106,106,127); +} +QMenuBar::item:pressed{ + background-color:palette(highlight); + color:rgba(255,255,255,255); + border-left:1px solid rgba(25,25,25,127); + border-right:1px solid rgba(25,25,25,127); +} +QMenu{ + background-color:palette(window); + border:1px solid palette(shadow); +} +QMenu::item{ + padding:3px 15px 3px 15px; + border:1px solid transparent; +} +QMenu::item:disabled{ + background-color:rgba(35,35,35,127); + color:palette(disabled); +} +QMenu::item:selected{ + border-color:rgba(147,191,236,127); + background:palette(highlight); + color:rgba(255,255,255,255); +} +QMenu::icon:checked{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid palette(highlight); + border-radius:2px; +} +QMenu::separator{ + height:1px; + background:palette(alternate-base); + margin-left:5px; + margin-right:5px; +} +QMenu::indicator{ + width:15px; + height:15px; +} +QMenu::indicator:non-exclusive:checked{ + padding-left:2px; +} +QMenu::indicator:non-exclusive:unchecked{ + padding-left:2px; +} +QMenu::indicator:exclusive:checked{ + padding-left:2px; +} +QMenu::indicator:exclusive:unchecked{ + padding-left:2px; +} +QToolBar::top{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-bottom:3px solid qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); +} +QToolBar::bottom{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-top:3px solid qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); +} +QToolBar::left{ + background-color:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-right:3px solid qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); +} +QToolBar::right{ + background-color:qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-left:3px solid qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); +} +QMainWindow::separator{ + width:6px; + height:5px; + padding:2px; +} +QSplitter::handle:horizontal{ + width:10px; +} +QSplitter::handle:vertical{ + height:10px; +} +QMainWindow::separator:hover,QSplitter::handle:hover{ + background:palette(highlight); +} +QDockWidget::title{ + padding:4px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid rgba(25,25,25,75); + border-bottom:2px solid rgba(25,25,25,75); +} +QDockWidget{ + titlebar-close-icon:url(:/darkstyle/icon_close.png); + titlebar-normal-icon:url(:/darkstyle/icon_restore.png); +} +QDockWidget::close-button,QDockWidget::float-button{ + subcontrol-position:top right; + subcontrol-origin:margin; + position:absolute; + top:3px; + bottom:0px; + width:20px; + height:20px; +} +QDockWidget::close-button{ + right:3px; +} +QDockWidget::float-button{ + right:25px; +} +QGroupBox{ + background-color:rgba(66,66,66,50%); + margin-top:27px; + border:1px solid rgba(25,25,25,127); + border-radius:4px; +} +QGroupBox::title{ + subcontrol-origin:margin; + subcontrol-position:left top; + padding:4px 6px; + margin-left:3px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid rgba(25,25,25,75); + border-bottom:2px solid rgb(127,127,127); + border-top-left-radius:4px; + border-top-right-radius:4px; +} +QTabWidget::pane{ + background-color:rgba(66,66,66,50%); + border-top:1px solid rgba(25,25,25,50%); +} +QTabWidget::tab-bar{ + left:3px; + top:1px; +} +QTabBar{ + background-color:transparent; + qproperty-drawBase:0; + border-bottom:1px solid rgba(25,25,25,50%); +} +QTabBar::tab{ + padding:4px 6px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid rgba(25,25,25,75); + border-top-left-radius:4px; + border-top-right-radius:4px; +} +QTabBar::tab:selected,QTabBar::tab:hover{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(53,53,53,127),stop:1 rgba(66,66,66,50%)); + border-bottom-color:rgba(66,66,66,75%); +} +QTabBar::tab:selected{ + border-bottom:2px solid palette(highlight); +} +QTabBar::tab::selected:disabled{ + border-bottom:2px solid rgb(127,127,127); +} +QTabBar::tab:!selected{ + margin-top:2px; +} +QTabBar::close-button { + border:1px solid transparent; + border-radius:2px; +} +QTabBar::close-button:hover { + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(106,106,106,255),stop:1 rgba(106,106,106,75)); + border:1px solid palette(base); +} +QTabBar::close-button:pressed { + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid palette(base); +} +QCheckBox::indicator{ + width:18px; + height:18px; +} +QRadioButton::indicator{ + width:18px; + height:18px; +} +QTreeView, QTableView{ + alternate-background-color:palette(window); + background:palette(base); +} +QTreeView QHeaderView::section, QTableView QHeaderView::section{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-style:none; + border-bottom:1px solid palette(dark); + padding-left:5px; + padding-right:5px; +} +QTreeView::item:selected:disabled, QTableView::item:selected:disabled{ + background:rgb(80,80,80); +} +QTreeView::branch{ + background-color:palette(base); +} +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings{ + border-image:none; +} +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings{ + border-image:none; +} +QScrollBar:vertical{ + background:palette(base); + border-top-right-radius:2px; + border-bottom-right-radius:2px; + width:16px; + margin:0px; +} +QScrollBar::handle:vertical{ + background-color:palette(alternate-base); + border-radius:2px; + min-height:20px; + margin:2px 4px 2px 4px; +} +QScrollBar::handle:vertical:hover{ + background-color:palette(highlight); +} +QScrollBar::add-line:vertical{ + background:none; + height:0px; + subcontrol-position:right; + subcontrol-origin:margin; +} +QScrollBar::sub-line:vertical{ + background:none; + height:0px; + subcontrol-position:left; + subcontrol-origin:margin; +} +QScrollBar:horizontal{ + background:palette(base); + height:16px; + margin:0px; +} +QScrollBar::handle:horizontal{ + background-color:palette(alternate-base); + border-radius:2px; + min-width:20px; + margin:4px 2px 4px 2px; +} +QScrollBar::handle:horizontal:hover{ + background-color:palette(highlight); +} +QScrollBar::add-line:horizontal{ + background:none; + width:0px; + subcontrol-position:bottom; + subcontrol-origin:margin; +} +QScrollBar::sub-line:horizontal{ + background:none; + width:0px; + subcontrol-position:top; + subcontrol-origin:margin; +} +QSlider::handle:horizontal{ + border-radius:4px; + border:1px solid rgba(25,25,25,255); + background-color:palette(alternate-base); + min-height:20px; + margin:0 -4px; +} +QSlider::handle:horizontal:hover{ + background:palette(highlight); +} +QSlider::add-page:horizontal{ + background:palette(base); +} +QSlider::sub-page:horizontal{ + background:palette(highlight); +} +QSlider::sub-page:horizontal:disabled{ + background:rgb(80,80,80); +} diff --git a/ArchitectureColoredPainting/images/icon.png b/ArchitectureColoredPainting/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4bac0815aabcb207729a2a2b341b9cec91aeb913 GIT binary patch literal 18122 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 Gp$PyJ(T2tV literal 0 HcmV?d00001 diff --git a/ArchitectureColoredPainting/images/icon_window_maximize.png b/ArchitectureColoredPainting/images/icon_window_maximize.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae289d010134bbd2218c1d94bb7cf62380a7c5 GIT binary patch literal 386 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ua371Hn({-St3PI#6{MB|NsA=uf!}V z8F(gT(N_MpeXKe=R4yEzByd2q+}XavO;=)T0!KuHv3hXSs!M0t7biSmWXLPwQLw2j ziv=2|TH+c}l9E`GYL#4+3Zxi}3=GY64a{_nj6)2KtxSxpOpUY+46FA`FK1~g9gZk;<9wBq{QM>-O{2=hP2F_R4aXb{gT`Q{oKU#%;ap{{K#-q Y=hDOi$%hHwfqED`UHx3vIVCg!04C3CMF0Q* literal 0 HcmV?d00001 diff --git a/ArchitectureColoredPainting/images/icon_window_minimize.png b/ArchitectureColoredPainting/images/icon_window_minimize.png new file mode 100644 index 0000000000000000000000000000000000000000..29bceed06b98e0268aeaf23477b585859f561a98 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ua371Hn({-St3+E?Ks8O}VHKHUXu_Vv=^7b_ z7#drd7+V>dXd4(<85r2O>C2*M$jwj5OsmAL!B=|vLZAi>kPXFU=~hXJ#i_cbMVSm~ unK`Le`uh4Mxdr;UiRqci*}5*y1}5d9=3)9;b7g>f7(8A5T-G@yGywpqYG70V literal 0 HcmV?d00001 diff --git a/ArchitectureColoredPainting/images/icon_window_restore.png b/ArchitectureColoredPainting/images/icon_window_restore.png new file mode 100644 index 0000000000000000000000000000000000000000..be29650401564c0bb4d3f1527dbcd1931ca46708 GIT binary patch literal 404 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ua371Hn({-St3PI1gGYY|NsBjPvS{Q zaWS0SdCe|C@p|f!UsL|s9e64f^Qfi9tyrF?mb+k?Qc>{P2Ok+XHcaqm+t1HtBmdwF zv=^7b_7#drd z7+V>cXd4(<85nFjsqqg*LvDUbW?Cg~4Tq;pZ~|)30NGGnmTr}lSe&X`T9nC +#include +#include + +class CaptionButton : public QWidget +{ + Q_OBJECT +public: + explicit CaptionButton(QWidget *parent = nullptr); + ~CaptionButton(); + + enum class IconType + { + Minimize, + Restore, + Maximize, + Close + }; + + void init(IconType type); + +signals: + void clicked(); + +public slots: + void setIconMode(bool icon_dark); + void setActive(bool is_active); + void setState(int state); + +private: + //Functions + QPixmap drawIcon(const QPixmap &icon, bool active, bool force_light = false); + void setColors(); + void drawIcons(); + void paintEvent(QPaintEvent *event); + + //Variables + QPixmap m_inactive_icon; + QPixmap m_active_icon; + + QPixmap m_close_icon_hover; + + QColor m_normal; + QColor m_hover; + QColor m_pressed; + + IconType m_type; + bool m_is_active; + bool m_is_under_mouse; + bool m_is_pressed; + bool m_icon_dark; +}; + +#endif // CAPTIONBUTTON_H diff --git a/ArchitectureColoredPainting/src/IconWidget.cpp b/ArchitectureColoredPainting/src/IconWidget.cpp new file mode 100644 index 0000000..65412b2 --- /dev/null +++ b/ArchitectureColoredPainting/src/IconWidget.cpp @@ -0,0 +1,75 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "iconwidget.h" + +#define ICONWIDTH 16 +#define ICONHEIGHT 16 + +IconWidget::IconWidget(QWidget *parent) : QWidget(parent) +{ + m_active = true; +} + +void IconWidget::setPixmap(const QPixmap &pixmap) +{ + m_pixmap = pixmap; + + QImage tmp = m_pixmap.toImage(); + + for (int i = 0; i < m_pixmap.width(); i++) + { + for (int j = 0; j < m_pixmap.height(); j++) + { + int gray = qGray(tmp.pixel(i, j)); + + int alpha = qAlpha(tmp.pixel(i, j)); + + tmp.setPixel(i, j, qRgba(gray, gray, gray, alpha)); + } + } + + m_grayed_pixmap = QPixmap::fromImage(tmp); + + repaint(); +} + +void IconWidget::setActive(bool active) +{ + m_active = active; + repaint(); +} + +void IconWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + + painter.drawPixmap((width() - ICONWIDTH)/ 2, + (height() - ICONHEIGHT) / 2, + ICONWIDTH, ICONHEIGHT, + m_active ? m_pixmap : m_grayed_pixmap); +} diff --git a/ArchitectureColoredPainting/src/IconWidget.h b/ArchitectureColoredPainting/src/IconWidget.h new file mode 100644 index 0000000..94f489d --- /dev/null +++ b/ArchitectureColoredPainting/src/IconWidget.h @@ -0,0 +1,52 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef ICONWIDGET_H +#define ICONWIDGET_H + +#include +#include +#include + +class IconWidget : public QWidget +{ + Q_OBJECT +public: + explicit IconWidget(QWidget *parent = nullptr); + +public slots: + void setPixmap(const QPixmap &pixmap); + void setActive(bool active); + +private: + //Functions + void paintEvent(QPaintEvent *event); + + //Variables + QPixmap m_pixmap; + QPixmap m_grayed_pixmap; + bool m_active; +}; + +#endif // ICONWIDGET_H diff --git a/ArchitectureColoredPainting/src/MainWindow.cpp b/ArchitectureColoredPainting/src/MainWindow.cpp index 6ac0d73..d5c1af8 100644 --- a/ArchitectureColoredPainting/src/MainWindow.cpp +++ b/ArchitectureColoredPainting/src/MainWindow.cpp @@ -1,12 +1,491 @@ #include "MainWindow.h" #include "Renderer/RendererGLWidget.h" #include "qslider.h" +#include +#include "NavigationBarWidget.h" +#include -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) +inline void fontTheme() { - ui.setupUi(this); + QFont defaultFont = qApp->font(); + defaultFont.setPointSize(defaultFont.pointSize() + 2); + qApp->setFont(defaultFont); +} + +inline void setThemeStyleSheet(bool dark) +{ + QFile file(dark ? ":/darkstyle.qss" : ":/lightstyle.qss"); + + if (!file.open(QFile::ReadOnly)) + return; + + const QString style_sheet = QLatin1String(file.readAll()); + + file.close(); + + qApp->setStyleSheet(style_sheet); +} + +inline void darkTheme() +{ + QPalette darkPalette = qApp->palette(); + + darkPalette.setColor(QPalette::Window, QColor(53, 53, 53)); + darkPalette.setColor(QPalette::WindowText, QColor(255, 255, 255)); + darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127)); + darkPalette.setColor(QPalette::Base, QColor(42, 42, 42)); + darkPalette.setColor(QPalette::AlternateBase, QColor(66, 66, 66)); + darkPalette.setColor(QPalette::ToolTipBase, QColor(255, 255, 255)); + darkPalette.setColor(QPalette::ToolTipText, QColor(255, 255, 255)); + darkPalette.setColor(QPalette::Text, QColor(255, 255, 255)); + darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); + darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35)); + darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20)); + darkPalette.setColor(QPalette::Button, QColor(53, 53, 53)); + darkPalette.setColor(QPalette::ButtonText, QColor(255, 255, 255)); + darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127)); + darkPalette.setColor(QPalette::BrightText, QColor(255, 0, 0)); + darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80)); + darkPalette.setColor(QPalette::HighlightedText, QColor(255, 255, 255)); + darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127)); + + qApp->setPalette(darkPalette); + + setThemeStyleSheet(true /*dark*/); +} + + +inline void lightTheme() +{ + QPalette lightPalette = qApp->palette(); + + lightPalette.setColor(QPalette::Window, QColor(240, 240, 240)); + lightPalette.setColor(QPalette::WindowText, QColor(0, 0, 0)); + lightPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(120, 120, 120)); + lightPalette.setColor(QPalette::Base, QColor(255, 255, 255)); + lightPalette.setColor(QPalette::AlternateBase, QColor(233, 231, 227)); + lightPalette.setColor(QPalette::ToolTipBase, QColor(255, 255, 220)); + lightPalette.setColor(QPalette::ToolTipText, QColor(0, 0, 0)); + lightPalette.setColor(QPalette::Text, QColor(0, 0, 0)); + lightPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(120, 120, 120)); + lightPalette.setColor(QPalette::Dark, QColor(160, 160, 160)); + lightPalette.setColor(QPalette::Shadow, QColor(105, 105, 105)); + lightPalette.setColor(QPalette::Button, QColor(240, 240, 240)); + lightPalette.setColor(QPalette::ButtonText, QColor(0, 0, 0)); + lightPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(120, 120, 120)); + lightPalette.setColor(QPalette::BrightText, QColor(0, 0, 255)); + lightPalette.setColor(QPalette::Link, QColor(51, 153, 255)); + lightPalette.setColor(QPalette::Highlight, QColor(0, 0, 255)); + lightPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(51, 153, 255)); + lightPalette.setColor(QPalette::HighlightedText, QColor(255, 255, 255)); + lightPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(255, 255, 255)); + + qApp->setPalette(lightPalette); + + setThemeStyleSheet(false /*dark*/); +} + +FramelessWindow::FramelessWindow(QWidget* parent) : QWidget(parent), ui(new Ui::FramelessWindow) +{ + ui->setupUi(this); +} + +FramelessWindow::~FramelessWindow() +{ + delete ui; +} + +CentralWidget::CentralWidget(QWidget* parent) : QMainWindow(parent) +{ + ui.setupUi(this); + NavigationBarWidget* navigationBarWidget = new NavigationBarWidget(); + + QHBoxLayout* tabBarLayout = new QHBoxLayout(ui.tabWidget); + tabBarLayout->setSpacing(0); + tabBarLayout->setMargin(0); + tabBarLayout->addWidget(navigationBarWidget, 0, Qt::AlignTop | Qt::AlignHCenter); + + QObject::connect(navigationBarWidget->buttonGroup, &QButtonGroup::idClicked, + ui.tabWidget, &QTabWidget::setCurrentIndex); + QObject::connect(ui.tabWidget, &QTabWidget::currentChanged, + ui.rendererWidget, &RendererWidget::currentTabChanged); +} + +CentralWidget::~CentralWidget() +{ +} + + +MainWindow::MainWindow(QWidget* parent) + : QGoodWindow(parent) +{ + + // if the system will draw borders or if this application must do that + m_draw_borders = !QGoodWindow::shouldBordersBeDrawnBySystem(); + + m_dark = QGoodWindow::isSystemThemeDark(); + + // create frameless window + m_window = new FramelessWindow(this); + + m_central_widget = new CentralWidget(m_window); + + // add the mainwindow to our custom frameless window + m_window->ui->windowContent->layout()->addWidget(m_central_widget); + + connect(this, &QGoodWindow::windowTitleChanged, this, [=](const QString& title) { + m_window->ui->titleWidget->setText(title); + }); + + connect(this, &QGoodWindow::windowIconChanged, this, [=](const QIcon& icon) { + m_window->ui->icon->setPixmap(icon.pixmap(16, 16)); + }); + int iconWidth = 30; + int captionButtonWidth = 45, captionButtonHeight = 30; + m_window->ui->icon->setFixedSize(iconWidth, 20); + m_window->ui->titleWidget->setFixedHeight(captionButtonHeight); + m_window->ui->minimizeButton->setFixedSize(captionButtonWidth, captionButtonHeight); + m_window->ui->maximizeButton->setFixedSize(captionButtonWidth, captionButtonHeight); + m_window->ui->restoreButton->setFixedSize(captionButtonWidth, captionButtonHeight); + m_window->ui->closeButton->setFixedSize(captionButtonWidth, captionButtonHeight); + + m_window->ui->minimizeButton->init(CaptionButton::IconType::Minimize); + m_window->ui->maximizeButton->init(CaptionButton::IconType::Maximize); + m_window->ui->restoreButton->init(CaptionButton::IconType::Restore); + m_window->ui->closeButton->init(CaptionButton::IconType::Close); + + connect(m_window->ui->minimizeButton, &CaptionButton::clicked, this, &MainWindow::showMinimized); + connect(m_window->ui->maximizeButton, &CaptionButton::clicked, this, &MainWindow::showMaximized); + connect(m_window->ui->restoreButton, &CaptionButton::clicked, this, &MainWindow::showNormal); + connect(m_window->ui->closeButton, &CaptionButton::clicked, this, &MainWindow::close); + + connect(this, &QGoodWindow::captionButtonStateChanged, this, &MainWindow::captionButtonStateChanged); + + + setMargins(captionButtonHeight, iconWidth, 0, captionButtonWidth * 3); + + setCaptionButtonsHandled(true, Qt::TopRightCorner); + + // Overlap close with maximize and maximize with minimize + setCloseMask(QRect(captionButtonWidth * 2, 0, rightCaptionButtonsRect().width(), rightCaptionButtonsRect().height())); + setMaximizeMask(QRect(captionButtonWidth * 1, 0, rightCaptionButtonsRect().width(), rightCaptionButtonsRect().height())); + setMinimizeMask(QRect(0, 0, rightCaptionButtonsRect().width(), rightCaptionButtonsRect().height())); + + auto theme_change_func = [=] { + if (m_dark) + darkTheme(); + else + lightTheme(); + + //Icon color inverse of m_dark to contrast. + m_window->ui->minimizeButton->setIconMode(!m_dark); + m_window->ui->maximizeButton->setIconMode(!m_dark); + m_window->ui->restoreButton->setIconMode(!m_dark); + m_window->ui->closeButton->setIconMode(!m_dark); + }; + + QShortcut* shortcut1 = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_S), this); + + connect(shortcut1, &QShortcut::activated, this, [=] { + m_dark = !m_dark; + theme_change_func(); + }); + + connect(this, &QGoodWindow::systemThemeChanged, this, [=] { + m_dark = QGoodWindow::isSystemThemeDark(); + theme_change_func(); + }); + + theme_change_func(); + + + setWindowIcon(QIcon(":/images/icon.png")); + setWindowTitle("ArchitectureColoredPainting"); + + + resize(m_central_widget->size()); + setCentralWidget(m_window); + + move(QGuiApplication::primaryScreen()->availableGeometry().center() - rect().center()); + } MainWindow::~MainWindow() {} + +void MainWindow::styleWindow() +{ + bool window_active = isActiveWindow(); + bool window_no_state = windowState().testFlag(Qt::WindowNoState); + + //bool draw_borders = m_draw_borders; + bool draw_borders = false; + + if (window_active) + { + if (window_no_state) + { + if (draw_borders) + { + m_window->ui->windowTitlebar->setStyleSheet( + QStringLiteral("#windowTitlebar{border: 0px none palette(shadow);}")); + m_window->ui->windowFrame->setStyleSheet( + QStringLiteral("#windowFrame{border: 1px solid palette(highlight);" + "background-color: palette(Window);}")); + } + else + { + m_window->ui->windowTitlebar->setStyleSheet( + QStringLiteral("#windowTitlebar{border: 0px none palette(shadow);}")); + m_window->ui->windowFrame->setStyleSheet( + QStringLiteral("#windowFrame{border: 0px solid palette(highlight);" + "background-color: palette(Window);}")); + } + } + else + { + m_window->ui->windowTitlebar->setStyleSheet( + QStringLiteral("#windowTitlebar{border: 0px none palette(shadow);" + "background-color :palette(shadow); height:20px;}")); + m_window->ui->windowFrame->setStyleSheet( + QStringLiteral("#windowFrame{border: 0px none palette(dark);" + "background-color: palette(Window);}")); + } + } + else + { + if (window_no_state) + { + if (draw_borders) + { + m_window->ui->windowTitlebar->setStyleSheet( + QStringLiteral("#windowTitlebar{border: 0px none palette(shadow);" + "background-color: palette(dark); height:20px;}")); + m_window->ui->windowFrame->setStyleSheet( + QStringLiteral("#windowFrame{border: 1px solid #000000;" + "background-color: palette(Window);}")); + } + else + { + m_window->ui->windowTitlebar->setStyleSheet( + QStringLiteral("#windowTitlebar{border: 0px none palette(shadow);" + "background-color: palette(dark); height:20px;}")); + m_window->ui->windowFrame->setStyleSheet( + QStringLiteral("#windowFrame{border: 0px solid #000000;" + "background-color: palette(Window);}")); + } + } + else + { + m_window->ui->windowTitlebar->setStyleSheet( + QStringLiteral("#titlebarWidget{border: 0px none palette(shadow);" + "background-color: palette(dark); height: 20px;}")); + m_window->ui->windowFrame->setStyleSheet( + QStringLiteral("#windowFrame{border: 0px none palette(shadow);" + "background-color: palette(Window);}")); + } + } + + m_window->ui->icon->setActive(window_active); + + m_window->ui->titleWidget->setActive(window_active); + + if (!isMinimized()) + { + m_window->ui->maximizeButton->setVisible(window_no_state); + m_window->ui->restoreButton->setVisible(!window_no_state); + } + + m_window->ui->minimizeButton->setActive(window_active); + m_window->ui->maximizeButton->setActive(window_active); + m_window->ui->restoreButton->setActive(window_active); + m_window->ui->closeButton->setActive(window_active); +} + +void MainWindow::captionButtonStateChanged(const QGoodWindow::CaptionButtonState& state) +{ + switch (state) + { + // Hover enter + case QGoodWindow::CaptionButtonState::MinimizeHoverEnter: + { + m_window->ui->minimizeButton->setState(QEvent::HoverEnter); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizeHoverEnter: + { + if (!isMaximized()) + m_window->ui->maximizeButton->setState(QEvent::HoverEnter); + else + m_window->ui->restoreButton->setState(QEvent::HoverEnter); + + break; + } + case QGoodWindow::CaptionButtonState::CloseHoverEnter: + { + m_window->ui->closeButton->setState(QEvent::HoverEnter); + + break; + } + // Hover leave + case QGoodWindow::CaptionButtonState::MinimizeHoverLeave: + { + m_window->ui->minimizeButton->setState(QEvent::HoverLeave); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizeHoverLeave: + { + if (!isMaximized()) + m_window->ui->maximizeButton->setState(QEvent::HoverLeave); + else + m_window->ui->restoreButton->setState(QEvent::HoverLeave); + + break; + } + case QGoodWindow::CaptionButtonState::CloseHoverLeave: + { + m_window->ui->closeButton->setState(QEvent::HoverLeave); + + break; + } + // Mouse button press + case QGoodWindow::CaptionButtonState::MinimizePress: + { + m_window->ui->minimizeButton->setState(QEvent::MouseButtonPress); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizePress: + { + if (!isMaximized()) + m_window->ui->maximizeButton->setState(QEvent::MouseButtonPress); + else + m_window->ui->restoreButton->setState(QEvent::MouseButtonPress); + + break; + } + case QGoodWindow::CaptionButtonState::ClosePress: + { + m_window->ui->closeButton->setState(QEvent::MouseButtonPress); + + break; + } + // Mouse button release + case QGoodWindow::CaptionButtonState::MinimizeRelease: + { + m_window->ui->minimizeButton->setState(QEvent::MouseButtonRelease); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizeRelease: + { + if (!isMaximized()) + m_window->ui->maximizeButton->setState(QEvent::MouseButtonRelease); + else + m_window->ui->restoreButton->setState(QEvent::MouseButtonRelease); + + break; + } + case QGoodWindow::CaptionButtonState::CloseRelease: + { + m_window->ui->closeButton->setState(QEvent::MouseButtonRelease); + + break; + } + // Mouse button clicked + case QGoodWindow::CaptionButtonState::MinimizeClicked: + { + emit m_window->ui->minimizeButton->clicked(); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizeClicked: + { + if (!isMaximized()) + emit m_window->ui->maximizeButton->clicked(); + else + emit m_window->ui->restoreButton->clicked(); + + break; + } + case QGoodWindow::CaptionButtonState::CloseClicked: + { + emit m_window->ui->closeButton->clicked(); + + break; + } + default: + break; + } +} + +bool MainWindow::event(QEvent* event) +{ + switch (event->type()) + { + case QEvent::Show: + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + case QEvent::WindowStateChange: + { + styleWindow(); + break; + } + case QEvent::StyleChange: + { + QColor active_color = qApp->palette().color(QPalette::WindowText); + QColor inactive_color = qApp->palette().color(QPalette::Disabled, QPalette::WindowText); + + m_window->ui->titleWidget->setTitleColor(active_color, inactive_color); + + break; + } + default: + break; + } + + return QGoodWindow::event(event); +} + +bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result) +{ +#ifdef Q_OS_WIN + MSG* msg = static_cast(message); + + switch (msg->message) + { + case WM_THEMECHANGED: + case WM_DWMCOMPOSITIONCHANGED: + { + //Keep window theme on Windows theme change events. + QTimer::singleShot(1000, this, [=] { + if (m_dark) + darkTheme(); + else + lightTheme(); + }); + + break; + } + default: + break; + } + +#endif + return QGoodWindow::nativeEvent(eventType, message, result); +} + + +void MainWindow::closeEvent(QCloseEvent* event) +{ + /* int result = QMessageBox::question(this, "Close window", "Are you sure to close?"); + + if (result != QMessageBox::Yes) + event->ignore();*/ +} + + diff --git a/ArchitectureColoredPainting/src/MainWindow.h b/ArchitectureColoredPainting/src/MainWindow.h index 337ecec..1fdb6f2 100644 --- a/ArchitectureColoredPainting/src/MainWindow.h +++ b/ArchitectureColoredPainting/src/MainWindow.h @@ -2,15 +2,55 @@ #include #include "ui_MainWindow.h" +#include "ui_FramelessWindow.h" -class MainWindow : public QMainWindow +#define QGOODWINDOW +#include + +class FramelessWindow : public QWidget { Q_OBJECT - public: - MainWindow(QWidget *parent = nullptr); + explicit FramelessWindow(QWidget* parent = nullptr); + + ~FramelessWindow(); + + Ui::FramelessWindow* ui; +}; + + +class CentralWidget : public QMainWindow +{ + Q_OBJECT +public: + explicit CentralWidget(QWidget* parent = nullptr); + + ~CentralWidget(); + + Ui::MainWindowClass ui; +}; + + +class MainWindow : public QGoodWindow +{ + Q_OBJECT +public: + explicit MainWindow(QWidget* parent = nullptr); ~MainWindow(); private: - Ui::MainWindowClass ui; + //Functions + void styleWindow(); + void captionButtonStateChanged(const QGoodWindow::CaptionButtonState& state); + bool event(QEvent* event); + bool nativeEvent(const QByteArray& eventType, void* message, long* result); + + void closeEvent(QCloseEvent* event); + + //Variables + FramelessWindow* m_window; + + CentralWidget* m_central_widget; + bool m_draw_borders; + bool m_dark; }; diff --git a/ArchitectureColoredPainting/src/NavigationBarWidget.cpp b/ArchitectureColoredPainting/src/NavigationBarWidget.cpp new file mode 100644 index 0000000..e4cdacc --- /dev/null +++ b/ArchitectureColoredPainting/src/NavigationBarWidget.cpp @@ -0,0 +1,14 @@ +#include "NavigationBarWidget.h" + +NavigationBarWidget::NavigationBarWidget(QWidget *parent) + : QWidget(parent) +{ + ui.setupUi(this); + buttonGroup = new QButtonGroup(this); + buttonGroup->addButton(ui.radioButton0, 0); + buttonGroup->addButton(ui.radioButton1, 1); + ui.radioButton0->setChecked(true); +;} + +NavigationBarWidget::~NavigationBarWidget() +{} diff --git a/ArchitectureColoredPainting/src/NavigationBarWidget.h b/ArchitectureColoredPainting/src/NavigationBarWidget.h new file mode 100644 index 0000000..07d48e7 --- /dev/null +++ b/ArchitectureColoredPainting/src/NavigationBarWidget.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include "ui_NavigationBarWidget.h" + +class NavigationBarWidget : public QWidget +{ + Q_OBJECT + +public: + NavigationBarWidget(QWidget *parent = nullptr); + ~NavigationBarWidget(); + QButtonGroup* buttonGroup; +private: + Ui::NavigationBarWidgetClass ui; + +}; diff --git a/ArchitectureColoredPainting/src/Renderer/RendererGLWidget.cpp b/ArchitectureColoredPainting/src/Renderer/RendererGLWidget.cpp index de162b5..0a821b6 100644 --- a/ArchitectureColoredPainting/src/Renderer/RendererGLWidget.cpp +++ b/ArchitectureColoredPainting/src/Renderer/RendererGLWidget.cpp @@ -17,8 +17,7 @@ RendererGLWidget::RendererGLWidget(QWidget* parent) , camera(QVector3D(0.0f, 100.0f, 0.0f)) , light(&camera) { - //startTimer(1000 / QGuiApplication::primaryScreen()->refreshRate()); - startTimer(1); + //startTimer(); lastFrame = std::clock(); setFocusPolicy(Qt::StrongFocus); QSurfaceFormat format; @@ -51,6 +50,19 @@ RendererGLWidget::~RendererGLWidget() } } +void RendererGLWidget::startTimer() +{ + //startTimer(1000 / QGuiApplication::primaryScreen()->refreshRate()); + if (timerId == -1) + timerId = QObject::startTimer(1); +} + +void RendererGLWidget::stopTimer() +{ + killTimer(timerId); + timerId = -1; +} + void RendererGLWidget::setMainLightPitch(float pitch) { //qDebug() << "pitch" << pitch; @@ -393,7 +405,7 @@ void RendererGLWidget::paintGL() void RendererGLWidget::resizeGL(int width, int height) { - frameWidth = ceil( devicePixelRatioF() * width); + frameWidth = ceil(devicePixelRatioF() * width); frameHeight = ceil(devicePixelRatioF() * height); qDebug() << frameWidth << "x" << frameHeight; camera.Ratio = (float)frameWidth / (float)frameHeight; @@ -557,6 +569,7 @@ void RendererGLWidget::timerEvent(QTimerEvent* event) float yoffset = center.y() - cursor().pos().y(); camera.ProcessMouseMovement(xoffset, yoffset); cursor().setPos(center); + //qDebug() << center; } if (pressedKeys.contains(Qt::Key_W)) { diff --git a/ArchitectureColoredPainting/src/Renderer/RendererGLWidget.h b/ArchitectureColoredPainting/src/Renderer/RendererGLWidget.h index dd8c5e7..5d4635f 100644 --- a/ArchitectureColoredPainting/src/Renderer/RendererGLWidget.h +++ b/ArchitectureColoredPainting/src/Renderer/RendererGLWidget.h @@ -19,6 +19,8 @@ public: RendererGLWidget(QWidget* parent = nullptr); ~RendererGLWidget(); + void startTimer(); + void stopTimer(); public slots: void setMainLightPitch(float pitch); void setMainLightYaw(float yaw); @@ -34,6 +36,7 @@ protected: void focusOutEvent(QFocusEvent* event) override; private: + int timerId = -1; int frameWidth, frameHeight; int depthWidth, depthHeight; QSet pressedKeys; diff --git a/ArchitectureColoredPainting/src/Renderer/RendererWidget.cpp b/ArchitectureColoredPainting/src/Renderer/RendererWidget.cpp index 32fcad2..8eeb965 100644 --- a/ArchitectureColoredPainting/src/Renderer/RendererWidget.cpp +++ b/ArchitectureColoredPainting/src/Renderer/RendererWidget.cpp @@ -13,3 +13,15 @@ RendererWidget::RendererWidget(QWidget *parent) RendererWidget::~RendererWidget() {} + +void RendererWidget::currentTabChanged(int index) +{ + if (index == 1) + { + ui.openGLWidget->startTimer(); + } + else + { + ui.openGLWidget->stopTimer(); + } +} \ No newline at end of file diff --git a/ArchitectureColoredPainting/src/Renderer/RendererWidget.h b/ArchitectureColoredPainting/src/Renderer/RendererWidget.h index b09b0e7..d1c518c 100644 --- a/ArchitectureColoredPainting/src/Renderer/RendererWidget.h +++ b/ArchitectureColoredPainting/src/Renderer/RendererWidget.h @@ -8,9 +8,11 @@ class RendererWidget : public QWidget Q_OBJECT public: + RendererWidget(QWidget *parent = nullptr); ~RendererWidget(); - +public slots: + void currentTabChanged(int index); private: Ui::RendererWidgetClass ui; }; diff --git a/ArchitectureColoredPainting/src/TitleWidget.cpp b/ArchitectureColoredPainting/src/TitleWidget.cpp new file mode 100644 index 0000000..cd4d5b6 --- /dev/null +++ b/ArchitectureColoredPainting/src/TitleWidget.cpp @@ -0,0 +1,80 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "titlewidget.h" + +TitleWidget::TitleWidget(QWidget *parent) : QWidget(parent) +{ + m_active = false; +} + +void TitleWidget::setText(const QString &text) +{ + m_title = text; + repaint(); +} + +void TitleWidget::setActive(bool active) +{ + m_active = active; + repaint(); +} + +void TitleWidget::setTitleColor(const QColor &active_color, const QColor &inactive_color) +{ + m_active_color = active_color; + m_inactive_color = inactive_color; + + repaint(); +} + +void TitleWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing); + + QFont font; + font.setPointSize(10); +#ifdef Q_OS_WIN + font.setFamily("Segoe UI, Microsoft YaHei UI"); +#else + font.setFamily(qApp->font().family()); +#endif + + painter.setFont(font); + + QPen pen; + pen.setColor(m_active ? m_active_color : m_inactive_color); + + painter.setPen(pen); + + QFontMetrics metrics(painter.font()); + QSize title_size = metrics.size(0, m_title); + + QString title = metrics.elidedText(m_title, Qt::ElideRight, width()); + + painter.drawText(0, (height() - title_size.height()) / 2, title_size.width(), title_size.height(), 0, title); +} diff --git a/ArchitectureColoredPainting/src/TitleWidget.h b/ArchitectureColoredPainting/src/TitleWidget.h new file mode 100644 index 0000000..ea297b7 --- /dev/null +++ b/ArchitectureColoredPainting/src/TitleWidget.h @@ -0,0 +1,54 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef TITLEWIDGET_H +#define TITLEWIDGET_H + +#include +#include +#include + +class TitleWidget : public QWidget +{ + Q_OBJECT +public: + explicit TitleWidget(QWidget *parent = nullptr); + +public slots: + void setText(const QString &text); + void setActive(bool active); + void setTitleColor(const QColor &active_color, const QColor &inactive_color); + +private: + //Functions + void paintEvent(QPaintEvent *event); + + //Variables + QString m_title; + bool m_active; + QColor m_active_color; + QColor m_inactive_color; +}; + +#endif // TITLEWIDGET_H diff --git a/QGoodWindow/.qmake.stash b/QGoodWindow/.qmake.stash new file mode 100644 index 0000000..8525508 --- /dev/null +++ b/QGoodWindow/.qmake.stash @@ -0,0 +1,22 @@ +QMAKE_CXX.QT_COMPILER_STDCXX = 199711L +QMAKE_CXX.QMAKE_MSC_VER = 1932 +QMAKE_CXX.QMAKE_MSC_FULL_VER = 193231329 +QMAKE_CXX.COMPILER_MACROS = \ + QT_COMPILER_STDCXX \ + QMAKE_MSC_VER \ + QMAKE_MSC_FULL_VER +QMAKE_CXX.INCDIRS = \ + "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.32.31326\\ATLMFC\\include" \ + "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.32.31326\\include" \ + "C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\include\\um" \ + "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.19041.0\\ucrt" \ + "C:\\Program Files (x86)\\Windows Kits\\10\\\\include\\10.0.19041.0\\\\shared" \ + "C:\\Program Files (x86)\\Windows Kits\\10\\\\include\\10.0.19041.0\\\\um" \ + "C:\\Program Files (x86)\\Windows Kits\\10\\\\include\\10.0.19041.0\\\\winrt" \ + "C:\\Program Files (x86)\\Windows Kits\\10\\\\include\\10.0.19041.0\\\\cppwinrt" +QMAKE_CXX.LIBDIRS = \ + "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.32.31326\\ATLMFC\\lib\\x64" \ + "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.32.31326\\lib\\x64" \ + "C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\lib\\um\\x64" \ + "C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.19041.0\\ucrt\\x64" \ + "C:\\Program Files (x86)\\Windows Kits\\10\\\\lib\\10.0.19041.0\\\\um\\x64" diff --git a/QGoodWindow/QGoodWindow b/QGoodWindow/QGoodWindow new file mode 100644 index 0000000..6bc1130 --- /dev/null +++ b/QGoodWindow/QGoodWindow @@ -0,0 +1,25 @@ +/* +The MIT License (MIT) + +Copyright © 2021 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "src/qgoodwindow.h" diff --git a/QGoodWindow/QGoodWindow.pro b/QGoodWindow/QGoodWindow.pro new file mode 100644 index 0000000..0bb2163 --- /dev/null +++ b/QGoodWindow/QGoodWindow.pro @@ -0,0 +1,92 @@ +#The MIT License (MIT) + +#Copyright © 2021-2022 Antonio Dias + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in all +#copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +#SOFTWARE. + +QT += core gui widgets + +CONFIG += c++11 + +SOURCES += \ + $$PWD/src/qgoodwindow.cpp + +HEADERS += \ + $$PWD/src/qgoodwindow.h + +INCLUDEPATH += $$PWD + +win32 { +equals(QT_MAJOR_VERSION, 5){ +QT += winextras +} + +SOURCES += \ + $$PWD/src/shadow.cpp + +HEADERS += \ + $$PWD/src/common.h \ + $$PWD/src/shadow.h + +LIBS += -lUser32 -lGdi32 + +DEFINES += QGOODWINDOW +CONFIG += qgoodwindow +} + +unix:!mac:!android { +equals(QT_MAJOR_VERSION, 5){ +QT += testlib x11extras +} + +equals(QT_MAJOR_VERSION, 6){ +QT += gui-private +} + +SOURCES += \ + $$PWD/src/shadow.cpp + +HEADERS += \ + $$PWD/src/shadow.h + +LIBS += -lX11 + +CONFIG += link_pkgconfig +PKGCONFIG += gtk+-2.0 + +DEFINES += QGOODWINDOW +CONFIG += qgoodwindow +} + +mac { +OBJECTIVE_SOURCES += \ + $$PWD/src/macosnative.mm + +SOURCES += \ + $$PWD/src/notification.cpp + +HEADERS += \ + $$PWD/src/macosnative.h \ + $$PWD/src/notification.h + +LIBS += -framework Foundation -framework Cocoa -framework AppKit + +DEFINES += QGOODWINDOW +CONFIG += qgoodwindow +} diff --git a/QGoodWindow/QGoodWindow.vcxproj b/QGoodWindow/QGoodWindow.vcxproj new file mode 100644 index 0000000..2b9b86f --- /dev/null +++ b/QGoodWindow/QGoodWindow.vcxproj @@ -0,0 +1,213 @@ + + + + + Release + x64 + + + Debug + x64 + + + + {B982E745-C0B1-46B3-A27B-743AF105F2D0} + QGoodWindow + QtVS_v304 + 10.0.19041.0 + 10.0.19041.0 + $(MSBuildProjectDirectory)\QtMsBuild + + + + v143 + release\ + false + NotSet + StaticLibrary + release\ + QGoodWindow + + + v143 + debug\ + false + NotSet + StaticLibrary + debug\ + QGoodWindow + + + + + + + + + + + + + + + + + + debug\ + debug\ + QGoodWindow + true + + + release\ + release\ + QGoodWindow + true + false + + + 5.15.2_msvc2019_64 + core;gui;widgets;winextras + + + 5.15.2_msvc2019_64 + core;gui;widgets;winextras + + + + + + + GeneratedFiles\$(ConfigurationName);GeneratedFiles;.;release;/include;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + release\ + false + None + 4577;4467;%(DisableSpecificWarnings) + Sync + release\ + MaxSpeed + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QGOODWINDOW;NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions) + false + + + MultiThreadedDLL + true + true + Level3 + true + + + User32.lib;Gdi32.lib;shell32.lib;%(AdditionalDependencies) + C:\openssl\lib;C:\Utils\my_sql\mysql-5.7.25-winx64\lib;C:\Utils\postgresql\pgsql\lib;%(AdditionalLibraryDirectories) + "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) + true + false + true + false + true + $(OutDir)\QGoodWindow.exe + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QGOODWINDOW;NDEBUG;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_CORE_LIB;%(PreprocessorDefinitions) + + + msvc + D:/??2022/ArchitectureColoredPainting/QGoodWindow/$(Configuration)/moc_predefs.h + Moc'ing %(Identity)... + output + $(Configuration) + moc_%(Filename).cpp + + + + + GeneratedFiles\$(ConfigurationName);GeneratedFiles;.;debug;/include;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + debug\ + false + ProgramDatabase + 4577;4467;%(DisableSpecificWarnings) + Sync + debug\ + Disabled + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QGOODWINDOW;%(PreprocessorDefinitions) + false + MultiThreadedDebugDLL + true + true + Level3 + true + + + User32.lib;Gdi32.lib;shell32.lib;%(AdditionalDependencies) + C:\openssl\lib;C:\Utils\my_sql\mysql-5.7.25-winx64\lib;C:\Utils\postgresql\pgsql\lib;%(AdditionalLibraryDirectories) + "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) + true + true + true + $(OutDir)\QGoodWindow.exe + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QGOODWINDOW;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_CORE_LIB;_DEBUG;%(PreprocessorDefinitions) + + + msvc + D:/??2022/ArchitectureColoredPainting/QGoodWindow/$(Configuration)/moc_predefs.h + Moc'ing %(Identity)... + output + $(Configuration) + moc_%(Filename).cpp + + + + + + + + + + + + + + + + Document + true + $(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2>NUL >debug\moc_predefs.h + Generate moc_predefs.h + debug\moc_predefs.h;%(Outputs) + + + Document + $(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2>NUL >release\moc_predefs.h + Generate moc_predefs.h + release\moc_predefs.h;%(Outputs) + true + + + + + + + + \ No newline at end of file diff --git a/QGoodWindow/QGoodWindow.vcxproj.filters b/QGoodWindow/QGoodWindow.vcxproj.filters new file mode 100644 index 0000000..4b17046 --- /dev/null +++ b/QGoodWindow/QGoodWindow.vcxproj.filters @@ -0,0 +1,60 @@ + + + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Generated Files + + + Generated Files + + + + + + + \ No newline at end of file diff --git a/QGoodWindow/QGoodWindow.write.1u.tlog b/QGoodWindow/QGoodWindow.write.1u.tlog new file mode 100644 index 0000000000000000000000000000000000000000..6e0efc483ac84a012f19a7785af52124a70ab72e GIT binary patch literal 370 zcmd6iF$=;l6okK7@E7<4+=_H`69w1mB0^AV8rp)kY9s!8^^&D^aF-)_kG#8km-p*H z#~q8|MoB9aJgI3^TXhK)V@9l)i!^08IEsP5=M^ literal 0 HcmV?d00001 diff --git a/QGoodWindow/moc.read.1u.tlog b/QGoodWindow/moc.read.1u.tlog new file mode 100644 index 0000000000000000000000000000000000000000..5cb46b790459eff98ec00d66c6a7b03563a9c240 GIT binary patch literal 272 zcmchR%?dzJ6okLE@&q2hPO`LGBIUR}?%_9pQl7}m!#(yM&;-23V$`VXm1c~>*gr?d}aN*X_OjC5t=S|Ji2jj}X zxj$8b3qdsyQ<0F7(~wFvw3@gocxbscO6|*e$@PBMxwm?uGP-4E(w*k@a^IF8>7^rw nT}VviH~q$GpQ1|`H>e|drOL_nO{rEMvC{DD{+-R5<(J48q}o(I literal 0 HcmV?d00001 diff --git a/QGoodWindow/src/common.h b/QGoodWindow/src/common.h new file mode 100644 index 0000000..f65cd3e --- /dev/null +++ b/QGoodWindow/src/common.h @@ -0,0 +1,79 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef COMMON_H +#define COMMON_H + +#ifdef _WIN32 + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif + +#define _WIN32_WINNT _WIN32_WINNT_VISTA + +#endif + +#include +#include +#include + +#ifdef Q_OS_LINUX + +#define BORDERWIDTH 10 //PIXELS + +#define MOVERESIZE_MOVE 8 //X11 Fixed Value + +#endif + +#if defined Q_OS_LINUX || defined Q_OS_MAC +//The positive values are mandatory on Linux and arbitrary on macOS, +//using the same for convenience. +//The negative value are arbitrary on both platforms. + +#define NO_WHERE -1 +#define HTMINBUTTON -2 +#define HTMAXBUTTON -3 +#define HTCLOSE -4 +#define TOP_LEFT 0 +#define TOP 1 +#define TOP_RIGHT 2 +#define LEFT 7 +#define RIGHT 3 +#define BOTTOM_LEFT 6 +#define BOTTOM 5 +#define BOTTOM_RIGHT 4 +#define TITLE_BAR 8 + +#endif + +#ifdef Q_OS_WIN + +#include + +#define BORDERWIDTH (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER)) + +#endif + +#endif // COMMON_H diff --git a/QGoodWindow/src/macosnative.h b/QGoodWindow/src/macosnative.h new file mode 100644 index 0000000..59d1615 --- /dev/null +++ b/QGoodWindow/src/macosnative.h @@ -0,0 +1,50 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef MACOSNATIVE_H +#define MACOSNATIVE_H + +#include "notification.h" + +extern Notification notification; + +//\cond HIDDEN_SYMBOLS +namespace macOSNative +{ + void registerThemeChangeNotification(); + void registerNotification(const char *notification_name, long wid); + void unregisterNotification(); + + inline void handleNotification(const char *notification_name, long wid) + { + notification.notification(notification_name, wid); + } + + void setStyle(long winid, bool fullscreen); + + const char *themeName(); +} +//\endcond + +#endif // MACOSNATIVE_H diff --git a/QGoodWindow/src/macosnative.mm b/QGoodWindow/src/macosnative.mm new file mode 100644 index 0000000..7d056b4 --- /dev/null +++ b/QGoodWindow/src/macosnative.mm @@ -0,0 +1,148 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "macosnative.h" +#include + +void macOSNative::setStyle(long winid, bool fullscreen) +{ + NSView *nativeView = reinterpret_cast(winid); + NSWindow *nativeWindow = [nativeView window]; + + if (!fullscreen) + { + [nativeWindow setStyleMask:NSWindowStyleMaskResizable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskClosable | + NSWindowStyleMaskTitled | + NSWindowStyleMaskFullSizeContentView]; + [nativeWindow setMovableByWindowBackground:NO]; + [nativeWindow setMovable:NO]; + [nativeWindow setTitlebarAppearsTransparent:YES]; + [nativeWindow setShowsToolbarButton:NO]; + [nativeWindow setTitleVisibility:NSWindowTitleHidden]; + [nativeWindow standardWindowButton:NSWindowMiniaturizeButton].hidden = YES; + [nativeWindow standardWindowButton:NSWindowCloseButton].hidden = YES; + [nativeWindow standardWindowButton:NSWindowZoomButton].hidden = YES; + [nativeWindow makeKeyWindow]; + } + else + { + [nativeWindow setStyleMask:0]; + } +} + +//\cond HIDDEN_SYMBOLS +@interface Handler : NSObject +{ +} +@end +//\endcond + +Handler *m_handler; + +void macOSNative::registerNotification(const char *notification_name, long wid) +{ + NSView *nativeView = reinterpret_cast(wid); + NSWindow *nativeWindow = [nativeView window]; + + [[NSNotificationCenter defaultCenter] + addObserver:m_handler + selector:@selector(NotificationHandler:) + name:[NSString stringWithUTF8String:notification_name] + object:nativeWindow]; +} + +void macOSNative::unregisterNotification() +{ + [[NSNotificationCenter defaultCenter] + removeObserver:m_handler]; + + [m_handler release]; +} + +//\cond HIDDEN_SYMBOLS +@implementation Handler ++ (void)load +{ + m_handler = static_cast(self); +} + ++(void)NotificationHandler:(NSNotification*)notification +{ + const NSString *str = [notification name]; + + const NSWindow *nativeWindow = [notification object]; + + const NSView *nativeView = [nativeWindow contentView]; + + const char *cstr = [str cStringUsingEncoding:NSUTF8StringEncoding]; + + macOSNative::handleNotification(cstr, long(nativeView)); +} +@end +//\endcond + +//\cond HIDDEN_SYMBOLS +@interface ThemeChangeHandler : NSObject +{ +} +@end +//\endcond + +ThemeChangeHandler *m_theme_change_handler; + +//\cond HIDDEN_SYMBOLS +@implementation ThemeChangeHandler ++ (void)load +{ + m_theme_change_handler = static_cast(self); +} + ++(void)ThemeChangeNotification:(NSNotification*)notification +{ + const NSString *str = [notification name]; + + const char *cstr = [str cStringUsingEncoding:NSUTF8StringEncoding]; + + macOSNative::handleNotification(cstr, 0); +} +@end +//\endcond + +void macOSNative::registerThemeChangeNotification() +{ + [[NSDistributedNotificationCenter defaultCenter] + addObserver:m_theme_change_handler + selector:@selector(ThemeChangeNotification:) + name:@"AppleInterfaceThemeChangedNotification" + object:nil]; +} + +const char *macOSNative::themeName() +{ + NSString *str = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; + const char *cstr = [str cStringUsingEncoding:NSUTF8StringEncoding]; + return cstr; +} diff --git a/QGoodWindow/src/notification.cpp b/QGoodWindow/src/notification.cpp new file mode 100644 index 0000000..082d5d5 --- /dev/null +++ b/QGoodWindow/src/notification.cpp @@ -0,0 +1,67 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "notification.h" +#include "qgoodwindow.h" +#include "macosnative.h" + +Notification::Notification() +{ + +} + +void Notification::addWindow(void *ptr) +{ + m_ptr_list.append(ptr); +} + +void Notification::removeWindow(void *ptr) +{ + m_ptr_list.removeAll(ptr); +} + +void Notification::registerNotification(const QByteArray &name, WId wid) +{ + macOSNative::registerNotification(name.constData(), long(wid)); +} + +void Notification::notification(const char *notification_name, long wid) +{ + const QByteArray notification = QByteArray(notification_name); + + for (void *ptr : m_ptr_list) + { + QGoodWindow *gw = static_cast(ptr); + + if (wid == 0) + { + gw->notificationReceiver(notification); + } + else if (gw->winId() == WId(wid)) + { + gw->notificationReceiver(notification); + break; + } + } +} diff --git a/QGoodWindow/src/notification.h b/QGoodWindow/src/notification.h new file mode 100644 index 0000000..acf2b2c --- /dev/null +++ b/QGoodWindow/src/notification.h @@ -0,0 +1,47 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NOTIFICATION_H +#define NOTIFICATION_H + +#include +#include + +//\cond HIDDEN_SYMBOLS +class Notification +{ +public: + Notification(); + + void addWindow(void *ptr); + void removeWindow(void *ptr); + void notification(const char *notification_name, long wid); + void registerNotification(const QByteArray &name, WId wid); + +private: + QList m_ptr_list; +}; +//\endcond + +#endif // NOTIFICATION_H diff --git a/QGoodWindow/src/qgoodwindow.cpp b/QGoodWindow/src/qgoodwindow.cpp new file mode 100644 index 0000000..2f3c44e --- /dev/null +++ b/QGoodWindow/src/qgoodwindow.cpp @@ -0,0 +1,3652 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#if defined QGOODWINDOW && defined __linux__ +#include +#endif + +#include "common.h" +#include "qgoodwindow.h" +#include "shadow.h" + +#ifndef QGOODWINDOW +#ifdef Q_OS_WIN +#undef Q_OS_WIN +#endif +#ifdef Q_OS_LINUX +#undef Q_OS_LINUX +#endif +#ifdef Q_OS_MAC +#undef Q_OS_MAC +#endif +#endif + +#ifdef Q_OS_WIN + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include +#endif +#include +#include +#include + +namespace QGoodWindowUtils +{ +class ParentWindow : public QWidget +{ +public: + explicit ParentWindow(QWidget *parent) : QWidget(parent, Qt::Window) + { + + } + +private: + bool event(QEvent *event) + { + switch (event->type()) + { + case QEvent::ChildRemoved: + { + delete this; + return true; + } + default: + break; + } + + return QWidget::event(event); + } +}; + +class NativeEventFilter : public QAbstractNativeEventFilter +{ +public: + NativeEventFilter(QGoodWindow *gw) + { + m_gw = gw; + m_gw_hwnd = HWND(m_gw->winId()); + } + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override +#else + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override +#endif + { + Q_UNUSED(eventType) + + MSG *msg = static_cast(message); + + if (!IsWindowVisible(msg->hwnd)) + return false; + + if (!IsChild(m_gw_hwnd, msg->hwnd)) + return false; + + switch (msg->message) + { + case WM_NCHITTEST: + { + QPoint pos = QPoint(GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam)); + + HRESULT lresult = m_gw->ncHitTest(pos.x(), pos.y()); + + if (lresult == HTNOWHERE) + break; + + //If region not contains the mouse, pass the + //WM_NCHITTEST event to main window. + *result = HTTRANSPARENT; + return true; + } + case WM_KEYDOWN: + { + if (GetKeyState(VK_SHIFT) & 0x8000) + { + switch (msg->wParam) + { + case VK_TAB: + //Prevent that SHIFT+TAB crashes the application. + return true; + default: + break; + } + } + + break; + } + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + { + //Pass to main window... + + if ((GetKeyState(VK_SHIFT) & 0x8000) && msg->wParam == VK_F10) + { + // ...when SHIFT+F10 is pressed. + SendMessageW(m_gw_hwnd, msg->message, msg->wParam, msg->lParam); + return true; + } + + if ((GetKeyState(VK_MENU) & 0x8000) && msg->wParam == VK_SPACE) + { + // ...when ALT+SPACE is pressed. + SendMessageW(m_gw_hwnd, msg->message, msg->wParam, msg->lParam); + return true; + } + + break; + } + default: + break; + } + + return false; + } + +private: + QPointer m_gw; + HWND m_gw_hwnd; +}; + +inline bool isWin11OrGreater() +{ + bool is_win_11_or_greater = false; + + typedef NTSTATUS(WINAPI *tRtlGetVersion)(LPOSVERSIONINFOEXW); + tRtlGetVersion pRtlGetVersion = tRtlGetVersion(QLibrary::resolve("ntdll", "RtlGetVersion")); + + if (pRtlGetVersion) + { + OSVERSIONINFOEXW os_info; + memset(&os_info, 0, sizeof(OSVERSIONINFOEXW)); + os_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + NTSTATUS status = pRtlGetVersion(&os_info); + if (status == 0) + is_win_11_or_greater = (os_info.dwMajorVersion >= 10 && os_info.dwBuildNumber >= 22000); + } + + return is_win_11_or_greater; +} +} +#endif + +#ifdef Q_OS_LINUX + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include +#include +#else +#include +#endif +#include +#include +#include + +#define SHADOW_WINDOW_COUNT 8 + +namespace QGoodWindowUtils +{ +QList m_gw_list; + +GtkSettings *m_settings = nullptr; + +void themeChangeNotification() +{ + for (QGoodWindow *gw : m_gw_list) + { + if (gw->m_theme_change_timer) + gw->m_theme_change_timer->start(); + } +} + +void registerThemeChangeNotification() +{ + if (!m_settings) + { + m_settings = gtk_settings_get_default(); + g_signal_connect(m_settings, "notify::gtk-theme-name", themeChangeNotification, nullptr); + } +} +} +#endif + +#ifdef Q_OS_MAC + +#include "macosnative.h" + +Notification notification; + +namespace QGoodWindowUtils +{ +bool m_theme_change_registered = false; +} +#endif + +QGoodWindow::QGoodWindow(QWidget *parent, const QColor &clear_color) : QMainWindow(parent) +{ +#ifdef QGOODWINDOW + setParent(nullptr); + + m_dark = isSystemThemeDark(); + + m_caption_buttons_handled = false; + + m_is_caption_button_pressed = false; + m_last_caption_button_hovered = -1; + m_caption_button_pressed = -1; + + m_theme_change_timer = new QTimer(this); + connect(m_theme_change_timer, &QTimer::timeout, this, [=]{ + bool dark = isSystemThemeDark(); + + if (m_dark != dark) + { + m_dark = dark; + emit systemThemeChanged(); + } + }); + m_theme_change_timer->setSingleShot(true); + m_theme_change_timer->setInterval(0); + + m_hover_timer = new QTimer(this); + m_hover_timer->setSingleShot(true); + m_hover_timer->setInterval(300); +#endif +#ifdef Q_OS_WIN + m_clear_color = clear_color; +#else + Q_UNUSED(clear_color) +#endif +#if defined Q_OS_WIN || defined Q_OS_LINUX + m_fixed_size = false; +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC + m_last_move_button = -1; +#endif +#ifdef Q_OS_MAC + m_mouse_button_pressed = false; + m_on_animate_event = false; +#endif +#ifdef Q_OS_WIN + m_closed = false; + + m_is_full_screen = false; + + m_active_state = false; + + m_state = Qt::WindowNoState; + + m_native_event = nullptr; + + HINSTANCE hInstance = GetModuleHandleW(nullptr); + + WNDCLASSEXW wcx; + memset(&wcx, 0, sizeof(WNDCLASSEXW)); + wcx.cbSize = sizeof(WNDCLASSEXW); + wcx.style = CS_HREDRAW | CS_VREDRAW; + wcx.hInstance = hInstance; + wcx.lpfnWndProc = WNDPROC(WndProc); + wcx.cbClsExtra = 0; + wcx.cbWndExtra = 0; + wcx.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wcx.hbrBackground = HBRUSH(CreateSolidBrush(RGB(m_clear_color.red(), + m_clear_color.green(), + m_clear_color.blue()))); + wcx.lpszClassName = L"QGoodWindowClass"; + + RegisterClassExW(&wcx); + + m_hwnd = CreateWindowW(wcx.lpszClassName, nullptr, + WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr); + + if (!m_hwnd) + { + QMessageBox::critical(nullptr, "Error", "Error creating window"); + return; + } + + SetWindowLongPtrW(m_hwnd, GWLP_USERDATA, reinterpret_cast(this)); + + connect(qApp, &QApplication::aboutToQuit, this, [=]{ + //Fix QApplication::exit() crash. + SetWindowLongPtrW(m_hwnd, GWLP_USERDATA, reinterpret_cast(nullptr)); + }); + + m_win_use_native_borders = QGoodWindowUtils::isWin11OrGreater(); + + m_window_handle = QWindow::fromWinId(WId(m_hwnd)); + + initGW(); + + m_helper_widget = new QWidget(); + + QScreen *screen = windowHandle()->screen(); + //qreal pixel_ratio = screen->logicalDotsPerInch() / qreal(96); + qreal pixel_ratio = 1; + if (!m_win_use_native_borders) + m_shadow = new Shadow(pixel_ratio, m_hwnd); + + m_main_window = static_cast(this); + m_main_window->installEventFilter(this); + + if (!m_native_event) + { + m_native_event = new QGoodWindowUtils::NativeEventFilter(this); + qApp->installNativeEventFilter(m_native_event); + } + + setMargins(30, 0, 0, 0); + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (!QtWin::isCompositionEnabled()) + { + disableCaption(); + frameChanged(); + } +#endif + +#endif +#ifdef Q_OS_LINUX + m_resize_move = false; + m_resize_move_started = false; + + setWindowFlags(Qt::Window | Qt::FramelessWindowHint); + + for (int i = 0; i < SHADOW_WINDOW_COUNT; i++) + { + Shadow *shadow = new Shadow(this); + m_shadow_list.append(shadow); + } + + installEventFilter(this); + setMouseTracking(true); + + createWinId(); + + QGoodWindowUtils::registerThemeChangeNotification(); + + QGoodWindowUtils::m_gw_list.append(this); + + QScreen *screen = windowHandle()->screen(); + m_pixel_ratio = screen->logicalDotsPerInch() / qreal(96); + + setMargins(30, 0, 0, 0); +#endif +#ifdef Q_OS_MAC + installEventFilter(this); + setMouseTracking(true); + + createWinId(); + + notification.addWindow(this); + + notification.registerNotification("NSWindowWillEnterFullScreenNotification", winId()); + notification.registerNotification("NSWindowDidExitFullScreenNotification", winId()); + + if (!QGoodWindowUtils::m_theme_change_registered) + { + QGoodWindowUtils::m_theme_change_registered = true; + macOSNative::registerThemeChangeNotification(); + } + + setMargins(30, 0, 0, 0); +#endif +} + +QGoodWindow::~QGoodWindow() +{ +#ifdef Q_OS_WIN + if (m_native_event) + { + qApp->removeNativeEventFilter(m_native_event); + delete m_native_event; + m_native_event = nullptr; + } +#endif +#ifdef Q_OS_LINUX + QGoodWindowUtils::m_gw_list.removeAll(this); +#endif +#ifdef Q_OS_MAC + notification.removeWindow(this); +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC + removeEventFilter(this); +#endif +} + +WId QGoodWindow::winId() const +{ +#ifdef Q_OS_WIN + return WId(m_hwnd); +#else + return QMainWindow::winId(); +#endif +} + +/*** QGOODWINDOW FUNCTIONS BEGIN ***/ + +bool QGoodWindow::isSystemThemeDark() +{ + bool dark = false; +#ifdef Q_OS_WIN + typedef LONG(WINAPI *tRegGetValueW)(HKEY,LPCWSTR,LPCWSTR,DWORD,LPDWORD,PVOID,LPDWORD); + tRegGetValueW pRegGetValueW = tRegGetValueW(QLibrary::resolve("advapi32", "RegGetValueW")); + + if (pRegGetValueW) + { + DWORD value; + DWORD size = sizeof(value); + if (pRegGetValueW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + L"AppsUseLightTheme", RRF_RT_DWORD, nullptr, &value, &size) == ERROR_SUCCESS) + dark = (value == 0); + } +#endif +#ifdef Q_OS_LINUX + GtkSettings *settings = gtk_settings_get_default(); + gchar *theme_name; + g_object_get(settings, "gtk-theme-name", &theme_name, nullptr); + dark = QString(theme_name).endsWith("Dark", Qt::CaseInsensitive); +#endif +#ifdef Q_OS_MAC + dark = QString(macOSNative::themeName()).endsWith("Dark", Qt::CaseInsensitive); +#endif + return dark; +} + +bool QGoodWindow::shouldBordersBeDrawnBySystem() +{ + bool drawn_by_system = true; +#ifdef Q_OS_WIN + drawn_by_system = QGoodWindowUtils::isWin11OrGreater(); +#endif +#ifdef Q_OS_LINUX + drawn_by_system = false; +#endif + return drawn_by_system; +} + +int QGoodWindow::titleBarHeight() const +{ +#ifdef QGOODWINDOW + return m_title_bar_height; +#else + return 0; +#endif +} + +int QGoodWindow::iconWidth() const +{ +#ifdef QGOODWINDOW + return m_icon_width; +#else + return 0; +#endif +} + +int QGoodWindow::leftMargin() const +{ +#ifdef QGOODWINDOW + return m_left_margin; +#else + return 0; +#endif +} + +int QGoodWindow::rightMargin() const +{ +#ifdef QGOODWINDOW + return m_right_margin; +#else + return 0; +#endif +} + +void QGoodWindow::setMargins(int title_bar_height, int icon_width, int left, int right) +{ +#ifdef QGOODWINDOW + if (title_bar_height < 0) + title_bar_height = 0; + + m_title_bar_height = title_bar_height; + + if (icon_width < 0) + icon_width = 0; + + m_icon_width = icon_width; + + if (left < 0) + left = 0; + + m_left_margin = left; + + if (right < 0) + right = 0; + + m_right_margin = right; + + m_left_mask = QRegion(0, 0, m_left_margin, m_title_bar_height); + m_right_mask = QRegion(0, 0, m_right_margin, m_title_bar_height); + + m_caption_buttons_handled = false; + + m_min_mask = QRegion(); + m_max_mask = QRegion(); + m_cls_mask = QRegion(); + +#else + Q_UNUSED(title_bar_height) + Q_UNUSED(icon_width) + Q_UNUSED(left) + Q_UNUSED(right) +#endif +} + +void QGoodWindow::setLeftMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + m_left_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +void QGoodWindow::setRightMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + m_right_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +void QGoodWindow::setCaptionButtonsHandled(bool handled, const Qt::Corner &corner) +{ +#ifdef QGOODWINDOW + if (handled) + { + switch (corner) + { + case Qt::TopLeftCorner: + case Qt::TopRightCorner: + { + m_caption_buttons_corner = corner; + break; + } + default: + { + m_caption_buttons_corner = Qt::TopRightCorner; + break; + } + } + + m_caption_buttons_handled = true; + } + else + { + m_caption_buttons_handled = false; + + m_min_mask = QRegion(); + m_max_mask = QRegion(); + m_cls_mask = QRegion(); + } +#else + Q_UNUSED(handled) + Q_UNUSED(corner) +#endif +} + +void QGoodWindow::setMinimizeMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + if (m_caption_buttons_handled) + m_min_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +void QGoodWindow::setMaximizeMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + if (m_caption_buttons_handled) + m_max_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +void QGoodWindow::setCloseMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + if (m_caption_buttons_handled) + m_cls_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +QSize QGoodWindow::leftMaskSize() const +{ +#ifdef QGOODWINDOW + return QSize(m_left_margin, m_title_bar_height); +#else + return QSize(); +#endif +} + +QSize QGoodWindow::rightMaskSize() const +{ +#ifdef QGOODWINDOW + return QSize(m_right_margin, m_title_bar_height); +#else + return QSize(); +#endif +} + +QRect QGoodWindow::leftCaptionButtonsRect() const +{ +#ifdef QGOODWINDOW + return QRect(0, 0, m_left_margin, m_title_bar_height); +#else + return QRect(); +#endif +} + +QRect QGoodWindow::rightCaptionButtonsRect() const +{ +#ifdef QGOODWINDOW + return QRect(0, 0, m_right_margin, m_title_bar_height); +#else + return QRect(); +#endif +} + +/*** QGOODWINDOW FUNCTIONS END ***/ + +void QGoodWindow::setFixedSize(int w, int h) +{ +#if defined Q_OS_WIN || defined Q_OS_LINUX + m_fixed_size = true; +#endif +#ifdef Q_OS_WIN + SetWindowLongW(m_hwnd, GWL_STYLE, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX); + resize(w, h); +#else + QMainWindow::setFixedSize(w, h); +#endif +} + +void QGoodWindow::setFixedSize(const QSize &size) +{ +#if defined Q_OS_WIN || defined Q_OS_LINUX + setFixedSize(size.width(), size.height()); +#else + QMainWindow::setFixedSize(size); +#endif +} + +QRect QGoodWindow::frameGeometry() const +{ +#ifdef Q_OS_WIN + RECT window_rect; + GetWindowRect(m_hwnd, &window_rect); + + QRect rect = QRect(window_rect.left/devicePixelRatioF(), window_rect.top/devicePixelRatioF(), + (window_rect.right - window_rect.left)/devicePixelRatioF(), + (window_rect.bottom - window_rect.top)/devicePixelRatioF()); + + if (isMaximized()) + { + const int border_width = BORDERWIDTH; + rect.adjust(border_width, border_width, -border_width, -border_width); + } + + return rect; +#else + return QMainWindow::frameGeometry(); +#endif +} + +QRect QGoodWindow::geometry() const +{ +#ifdef Q_OS_WIN + RECT client_rect; + GetClientRect(m_hwnd, &client_rect); + + QPoint window_pos = pos(); + + QRect rect = QRect((window_pos.x() + client_rect.left)/devicePixelRatioF(), + (window_pos.y() + client_rect.top)/devicePixelRatioF(), + (client_rect.right - client_rect.left)/devicePixelRatioF(), + (client_rect.bottom - client_rect.top)/devicePixelRatioF()); + + return rect; +#else + return QMainWindow::geometry(); +#endif +} + +QRect QGoodWindow::rect() const +{ +#ifdef Q_OS_WIN + return QRect(0, 0, width(), height()); +#else + return QMainWindow::rect(); +#endif +} + +QPoint QGoodWindow::pos() const +{ +#ifdef Q_OS_WIN + return frameGeometry().topLeft(); +#else + return QMainWindow::pos(); +#endif +} + +QSize QGoodWindow::size() const +{ +#ifdef Q_OS_WIN + return QSize(width(), height()); +#else + return QMainWindow::size(); +#endif +} + +int QGoodWindow::x() const +{ +#ifdef Q_OS_WIN + return pos().x(); +#else + return QMainWindow::x(); +#endif +} + +int QGoodWindow::y() const +{ +#ifdef Q_OS_WIN + return pos().y(); +#else + return QMainWindow::y(); +#endif +} + +int QGoodWindow::width() const +{ +#ifdef Q_OS_WIN + return geometry().width(); +#else + return QMainWindow::width(); +#endif +} + +int QGoodWindow::height() const +{ +#ifdef Q_OS_WIN + return geometry().height(); +#else + return QMainWindow::height(); +#endif +} + +void QGoodWindow::move(int x, int y) +{ +#ifdef Q_OS_WIN + SetWindowPos(m_hwnd, nullptr, x*devicePixelRatioF(), y*devicePixelRatioF(), 0, 0, SWP_NOSIZE | SWP_NOACTIVATE); +#else + QMainWindow::move(x, y); +#endif +} + +void QGoodWindow::move(const QPoint &pos) +{ +#ifdef Q_OS_WIN + move(pos.x(), pos.y()); +#else + QMainWindow::move(pos); +#endif +} + +void QGoodWindow::resize(int width, int height) +{ +#ifdef Q_OS_WIN + if (m_win_use_native_borders) + { + const int border_width = BORDERWIDTH; + width += border_width * 2; + height += border_width; + } + + SetWindowPos(m_hwnd, nullptr, 0, 0, width*devicePixelRatioF(), height*devicePixelRatioF(), SWP_NOMOVE | SWP_NOACTIVATE); +#else + QMainWindow::resize(width, height); +#endif +} + +void QGoodWindow::resize(const QSize &size) +{ +#ifdef Q_OS_WIN + resize(size.width(), size.height()); +#else + QMainWindow::resize(size); +#endif +} + +void QGoodWindow::setGeometry(int x, int y, int w, int h) +{ +#ifdef Q_OS_WIN + move(x, y); + resize(w, h); +#else + QMainWindow::setGeometry(x, y, w, h); +#endif +} + +void QGoodWindow::setGeometry(const QRect &rect) +{ +#ifdef Q_OS_WIN + setGeometry(rect.x(), rect.y(), rect.width(), rect.height()); +#else + QMainWindow::setGeometry(rect); +#endif +} + +void QGoodWindow::activateWindow() +{ +#ifdef Q_OS_WIN + SetForegroundWindow(m_hwnd); +#else + QMainWindow::activateWindow(); +#endif +} + +void QGoodWindow::show() +{ +#ifdef Q_OS_WIN + if (m_is_full_screen) + showNormal(); + ShowWindow(m_hwnd, SW_SHOW); +#else + QMainWindow::show(); +#endif +} + +void QGoodWindow::showNormal() +{ +#ifdef Q_OS_WIN + ShowWindow(m_hwnd, SW_SHOWNORMAL); + + if (m_is_full_screen) + { + m_is_full_screen = false; + SetWindowLongW(m_hwnd, GWL_STYLE, GetWindowLongW(m_hwnd, GWL_STYLE) | WS_OVERLAPPEDWINDOW); + setGeometry(m_rect_origin); + m_rect_origin = QRect(); + sizeMoveWindow(); + } +#else + QMainWindow::showNormal(); +#endif +} + +void QGoodWindow::showMaximized() +{ +#if defined Q_OS_WIN || defined Q_OS_LINUX + if (m_fixed_size) + return; +#endif +#ifdef Q_OS_WIN + if (m_is_full_screen) + showNormal(); + + ShowWindow(m_hwnd, SW_SHOWMAXIMIZED); +#else + QMainWindow::showMaximized(); +#endif +} + +void QGoodWindow::showMinimized() +{ +#ifdef Q_OS_WIN + if (m_is_full_screen) + showNormal(); + + ShowWindow(m_hwnd, SW_SHOWMINIMIZED); +#else + QMainWindow::showMinimized(); +#endif +} + +void QGoodWindow::showFullScreen() +{ +#if defined Q_OS_WIN || defined Q_OS_LINUX + if (m_fixed_size) + return; +#endif +#ifdef Q_OS_WIN + if (!m_is_full_screen) + { + ShowWindow(m_hwnd, SW_SHOWNORMAL); + + m_rect_origin = geometry(); + + SetWindowLongW(m_hwnd, GWL_STYLE, GetWindowLongW(m_hwnd, GWL_STYLE) & ~WS_OVERLAPPEDWINDOW); + + QTimer::singleShot(0, this, [=]{ + m_is_full_screen = true; + sizeMoveWindow(); + }); + } +#else + QMainWindow::showFullScreen(); +#endif +} + +void QGoodWindow::hide() +{ +#ifdef Q_OS_WIN + if (m_is_full_screen) + showNormal(); + + ShowWindow(m_hwnd, SW_HIDE); +#else + QMainWindow::hide(); +#endif +} + +void QGoodWindow::close() +{ +#ifdef Q_OS_WIN + PostMessageW(m_hwnd, WM_CLOSE, 0, 0); +#else + QMainWindow::close(); +#endif +} + +bool QGoodWindow::isVisible() const +{ +#ifdef Q_OS_WIN + return IsWindowVisible(m_hwnd); +#else + return QMainWindow::isVisible(); +#endif +} + +bool QGoodWindow::isEnabled() const +{ +#ifdef Q_OS_WIN + return IsWindowEnabled(m_hwnd); +#else + return QMainWindow::isEnabled(); +#endif +} + +bool QGoodWindow::isActiveWindow() const +{ +#ifdef Q_OS_WIN + return (GetForegroundWindow() == m_hwnd); +#else + return QMainWindow::isActiveWindow(); +#endif +} + +bool QGoodWindow::isMaximized() const +{ +#ifdef Q_OS_WIN + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(m_hwnd, &wp); + return (wp.showCmd == SW_SHOWMAXIMIZED); +#else + return QMainWindow::isMaximized(); +#endif +} + +bool QGoodWindow::isMinimized() const +{ +#ifdef Q_OS_WIN + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(m_hwnd, &wp); + return (wp.showCmd == SW_SHOWMINIMIZED); +#else + return QMainWindow::isMinimized(); +#endif +} + +bool QGoodWindow::isFullScreen() const +{ +#ifdef Q_OS_WIN + return m_is_full_screen; +#else + return QMainWindow::isFullScreen(); +#endif +} + +Qt::WindowStates QGoodWindow::windowState() const +{ +#ifdef Q_OS_WIN + return m_state; +#else + return QMainWindow::windowState(); +#endif +} + +void QGoodWindow::setWindowState(Qt::WindowStates state) +{ +#ifdef Q_OS_WIN + if (state.testFlag(Qt::WindowFullScreen)) + showFullScreen(); + else if (state.testFlag(Qt::WindowMaximized)) + showMaximized(); + else if (state.testFlag(Qt::WindowMinimized)) + showMinimized(); + else if (state.testFlag(Qt::WindowNoState)) + showNormal(); + + if (state.testFlag(Qt::WindowActive)) + activateWindow(); +#else + QMainWindow::setWindowState(state); +#endif +} + +QWindow *QGoodWindow::windowHandle() const +{ +#ifdef Q_OS_WIN + return m_window_handle; +#else + return QMainWindow::windowHandle(); +#endif +} + +qreal QGoodWindow::windowOpacity() const +{ + return windowHandle()->opacity(); +} + +void QGoodWindow::setWindowOpacity(qreal level) +{ + windowHandle()->setOpacity(level); +} + +QString QGoodWindow::windowTitle() const +{ + return QMainWindow::windowTitle(); +} + +void QGoodWindow::setWindowTitle(const QString &title) +{ +#ifdef Q_OS_WIN + SetWindowTextW(m_hwnd, reinterpret_cast(title.utf16())); + + QMainWindow::setWindowTitle(title); +#else + QMainWindow::setWindowTitle(title); +#endif +} + +QIcon QGoodWindow::windowIcon() const +{ + return QMainWindow::windowIcon(); +} + +void QGoodWindow::setWindowIcon(const QIcon &icon) +{ +#ifdef Q_OS_WIN + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + HICON hicon_big = QtWin::toHICON(icon.pixmap(GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON))); + HICON hicon_small = QtWin::toHICON(icon.pixmap(GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON))); +#else + HICON hicon_big = icon.pixmap(GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON)).toImage().toHICON(); + HICON hicon_small = icon.pixmap(GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON)).toImage().toHICON(); +#endif + + SendMessageW(m_hwnd, WM_SETICON, ICON_BIG, LPARAM(hicon_big)); + SendMessageW(m_hwnd, WM_SETICON, ICON_SMALL, LPARAM(hicon_small)); + + QMainWindow::setWindowIcon(icon); +#else + QMainWindow::setWindowIcon(icon); +#endif +} + +bool QGoodWindow::event(QEvent *event) +{ +#if defined Q_OS_LINUX || defined Q_OS_MAC + switch (event->type()) + { + case QEvent::Leave: + { + buttonLeave(m_last_caption_button_hovered); + m_last_move_button = -1; + + break; + } + case QEvent::Hide: + case QEvent::HoverLeave: + { + if (QApplication::overrideCursor()) + QApplication::restoreOverrideCursor(); + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_WIN + switch (event->type()) + { + case QEvent::ChildPolished: + { + QChildEvent *child_event = static_cast(event); + + QWidget *widget = qobject_cast(child_event->child()); + + if (!widget->isWindow()) + break; + + if (!widget->isModal()) + break; + + widget->adjustSize(); + + widget->setParent(bestParentForModalWindow(), widget->windowFlags()); + + break; + } + case QEvent::WindowBlocked: + { + EnableWindow(m_hwnd, FALSE); + break; + } + case QEvent::WindowUnblocked: + { + EnableWindow(m_hwnd, TRUE); + break; + } + case QEvent::WindowStateChange: + { + bool window_no_state = windowState().testFlag(Qt::WindowNoState); + + for (QSizeGrip *size_grip : findChildren()) + { + if (!size_grip->window()->windowFlags().testFlag(Qt::SubWindow)) + size_grip->setVisible(window_no_state); + } + + break; + } + case QEvent::Resize: + { + if (isVisible() && size() != m_main_window->size()) + resize(m_main_window->size()); + + break; + } + case QEvent::Close: + { + if (m_closed) + { + hide(); + return true; + } + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_LINUX + switch (event->type()) + { + case QEvent::WindowActivate: + { + QTimer::singleShot(0, this, &QGoodWindow::sizeMoveBorders); + + break; + } + case QEvent::Hide: + case QEvent::WindowDeactivate: + { + for (Shadow *shadow : m_shadow_list) + shadow->hide(); + + break; + } + case QEvent::WindowStateChange: + { + if (!windowState().testFlag(Qt::WindowNoState)) + { + for (Shadow *shadow : m_shadow_list) + shadow->hide(); + } + + break; + } + case QEvent::Show: + case QEvent::Resize: + { + setMask(rect()); + + QTimer::singleShot(0, this, &QGoodWindow::sizeMoveBorders); + + break; + } + case QEvent::Move: + { + QTimer::singleShot(0, this, &QGoodWindow::sizeMoveBorders); + + break; + } + case QEvent::WindowBlocked: + { + setEnabled(false); + + m_last_fixed_size_value = m_fixed_size; + + setFixedSize(size()); + + if (QWidget *w = qApp->activeModalWidget()) + w->setEnabled(true); + + break; + } + case QEvent::WindowUnblocked: + { + setEnabled(true); + + if (!m_last_fixed_size_value) + { + setMinimumSize(0, 0); + setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + + m_fixed_size = false; + } + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_MAC + switch (event->type()) + { + case QEvent::Show: + { + if (isFullScreen()) + break; + + macOSNative::setStyle(long(winId()), false); + + break; + } + default: + break; + } +#endif + return QMainWindow::event(event); +} + +bool QGoodWindow::eventFilter(QObject *watched, QEvent *event) +{ +#ifdef Q_OS_WIN + if (watched == m_main_window) + { + switch (event->type()) + { + case QEvent::WindowActivate: + { + if (!m_main_window->isActiveWindow() || m_active_state) + return true; + + m_active_state = true; + + break; + } + case QEvent::WindowDeactivate: + { + if (m_main_window->isActiveWindow() || !m_active_state) + return true; + + m_active_state = false; + + break; + } + default: + break; + } + } +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC + if (!isEnabled()) + return QMainWindow::eventFilter(watched, event); + + QPoint cursor_pos = QCursor::pos(); + long button = ncHitTest(cursor_pos.x(), cursor_pos.y()); + + switch (event->type()) + { +#ifdef Q_OS_MAC + case QEvent::ChildAdded: + case QEvent::ChildRemoved: + { + if (qApp->activeModalWidget()) + break; + + if (isFullScreen()) + break; + + if (m_on_animate_event) + break; + + macOSNative::setStyle(long(winId()), false); + + break; + } +#endif + case QEvent::ChildPolished: + { + QChildEvent *child_event = static_cast(event); + + QWidget *widget = qobject_cast(child_event->child()); + + if (!widget) + break; + + widget->setMouseTracking(true); + widget->installEventFilter(this); + + for (QWidget *w : widget->findChildren()) + { + w->setMouseTracking(true); + w->installEventFilter(this); + } + + break; + } + case QEvent::MouseButtonPress: + { + QMouseEvent *mouse_event = static_cast(event); + + if (mouse_event->button() != Qt::LeftButton) + break; + + switch (button) + { + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + buttonPress(button); + break; + } + default: + break; + } + + break; + } + case QEvent::MouseMove: + { + if (m_last_move_button != button) + { + m_last_move_button = button; + + buttonLeave(m_last_caption_button_hovered); + + switch (button) + { + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + if (!m_is_caption_button_pressed || (button == m_caption_button_pressed)) + buttonEnter(button); + + break; + } + default: + break; + } + } + + break; + } + case QEvent::MouseButtonRelease: + { + QMouseEvent *mouse_event = static_cast(event); + + if (mouse_event->button() != Qt::LeftButton) + break; + + m_last_move_button = -1; + + buttonRelease(m_caption_button_pressed, (button == m_caption_button_pressed)); + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_LINUX + if (m_is_caption_button_pressed) + return QMainWindow::eventFilter(watched, event); + + switch (event->type()) + { + case QEvent::MouseButtonDblClick: + { + QMouseEvent *mouse_event = static_cast(event); + + if (mouse_event->button() == Qt::LeftButton) + { + if (m_margin == TITLE_BAR) + { + if (!isMaximized()) + showMaximized(); + else + showNormal(); + } + } + + switch (m_margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseButtonPress: + { + setCursorForCurrentPos(); + + QMouseEvent *mouse_event = static_cast(event); + + if (!m_resize_move && mouse_event->button() == Qt::LeftButton) + { + if (m_margin != NO_WHERE) + m_resize_move = true; + } + + switch (m_margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseMove: + { + setCursorForCurrentPos(); + + QMouseEvent *mouse_event = static_cast(event); + + if (m_resize_move && mouse_event->buttons() == Qt::LeftButton) + sizeMove(); + + switch (m_margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseButtonRelease: + { + setCursorForCurrentPos(); + + QMouseEvent *mouse_event = static_cast(event); + + if (m_resize_move && mouse_event->button() == Qt::LeftButton) + { + if (m_margin != NO_WHERE) + m_resize_move = false; + } + + switch (m_margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::Wheel: + { + switch (m_margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::ContextMenu: + { + switch (m_margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_MAC + if (m_is_caption_button_pressed) + return QMainWindow::eventFilter(watched, event); + + int margin = ncHitTest(cursor_pos.x(), cursor_pos.y()); + + switch (event->type()) + { + case QEvent::MouseButtonDblClick: + { + if (margin == TITLE_BAR) + { + QMouseEvent *mouse_event = static_cast(event); + + if (!isFullScreen()) + { + if (mouse_event->button() == Qt::LeftButton) + { + if (margin == TITLE_BAR) + { + if (!isMaximized()) + showMaximized(); + else + showNormal(); + } + } + } + } + + switch (margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseButtonPress: + { + if (margin == TITLE_BAR) + { + QMouseEvent *mouse_event = static_cast(event); + + if (!isFullScreen()) + { + if (mouse_event->button() == Qt::LeftButton) + { + m_pos = cursor_pos - pos(); + } + } + } + + m_mouse_button_pressed = true; + + switch (margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseMove: + { + if (!m_pos.isNull()) + { + QMouseEvent *mouse_event = static_cast(event); + + if (!isFullScreen()) + { + if (mouse_event->buttons() == Qt::LeftButton) + { + move(cursor_pos - m_pos); + } + } + } + + if (!m_mouse_button_pressed) + { + QWidget *widget = QApplication::widgetAt(QCursor::pos()); + + if (widget) + { + switch (margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + if (QApplication::overrideCursor() && + QApplication::overrideCursor()->shape() == Qt::ArrowCursor) + break; + + QApplication::setOverrideCursor(Qt::ArrowCursor); + + break; + } + case NO_WHERE: + { + if (!QApplication::overrideCursor()) + break; + + if (QApplication::overrideCursor()->shape() != Qt::ArrowCursor) + break; + + QApplication::restoreOverrideCursor(); + + break; + } + default: + break; + } + } + } + + switch (margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseButtonRelease: + { + if (!m_pos.isNull()) + { + QMouseEvent *mouse_event = static_cast(event); + + if (!isFullScreen()) + { + if (mouse_event->button() == Qt::LeftButton) + m_pos = QPoint(); + } + } + + m_mouse_button_pressed = false; + + switch (margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::Wheel: + { + switch (margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::ContextMenu: + { + switch (margin) + { + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + default: + break; + } +#endif + return QMainWindow::eventFilter(watched, event); +} + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +bool QGoodWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) +#else +bool QGoodWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) +#endif +{ +#ifdef Q_OS_LINUX +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (eventType == "xcb_generic_event_t") + { + xcb_generic_event_t *event = static_cast(message); + + if (event->response_type == XCB_GE_GENERIC) + { + if (m_resize_move_started) + { + m_resize_move_started = false; + + //Fix mouse problems after resize or move. + QTest::mouseClick(windowHandle(), Qt::NoButton, Qt::NoModifier); + } + else + { + //Fix no mouse event after moving mouse from resize borders. + QEvent event(QEvent::MouseMove); + QApplication::sendEvent(this, &event); + } + } + } +#endif +#endif + return QMainWindow::nativeEvent(eventType, message, result); +} + +#ifdef Q_OS_WIN +void QGoodWindow::initGW() +{ + setProperty("_q_embedded_native_parent_handle", WId(m_hwnd)); + setWindowFlags(Qt::FramelessWindowHint); + + QEvent event(QEvent::EmbeddingControl); + QApplication::sendEvent(this, &event); +} + +void QGoodWindow::destroyGW() +{ + QMainWindow::close(); + QMainWindow::destroy(); +} + +LRESULT QGoodWindow::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + QGoodWindow *gw = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + + if (!gw) + return DefWindowProcW(hwnd, message, wParam, lParam); + + switch (message) + { + case WM_ACTIVATE: + { + switch (wParam) + { + case WA_ACTIVE: + case WA_CLICKACTIVE: + { + QTimer::singleShot(0, gw, [=]{ + gw->setWidgetFocus(); + gw->handleActivation(); + }); + + break; + } + case WA_INACTIVE: + { + if (gw->focusWidget()) + { + //If app going to be inactive, + //save current focused widget + //to restore focus later. + + gw->m_focus_widget = gw->focusWidget(); + } + + QTimer::singleShot(0, gw, [=]{ + gw->handleDeactivation(); + }); + + if (!IsWindowEnabled(hwnd)) + { + if (gw->m_shadow) + gw->m_shadow->hide(); + } + + break; + } + default: + break; + } + + break; + } + case WM_SETFOCUS: + { + //Pass focus to last focused widget. + gw->setWidgetFocus(); + gw->m_main_window->activateWindow(); + + break; + } + case WM_SIZE: + { + gw->sizeMoveWindow(); + + break; + } + case WM_MOVE: + { + gw->sizeMoveWindow(); + + QMoveEvent event(QPoint(gw->x(), gw->y()), QPoint()); + QApplication::sendEvent(gw, &event); + + break; + } + case WM_GETMINMAXINFO: + { + MINMAXINFO *mmi = reinterpret_cast(lParam); + + QSize minimum = gw->minimumSize(); + + QSize sizeHint = gw->minimumSizeHint(); + + const int border_width = BORDERWIDTH; + + mmi->ptMinTrackSize.x = qMax(minimum.width(), sizeHint.width()) + + (gw->m_win_use_native_borders ? border_width * 2 : 0); + mmi->ptMinTrackSize.y = qMax(minimum.height(), sizeHint.height()) + + (gw->m_win_use_native_borders ? border_width : 0); + + QSize maximum = gw->maximumSize(); + + mmi->ptMaxTrackSize.x = maximum.width() + (gw->m_win_use_native_borders ? border_width * 2 : 0); + mmi->ptMaxTrackSize.y = maximum.height() + (gw->m_win_use_native_borders ? border_width : 0); + + break; + } + case WM_CLOSE: + { + //Send Qt QCloseEvent to the window, + //which allows to accept or reject the window close. + QCloseEvent event; + QApplication::sendEvent(gw, &event); + + if (!event.isAccepted()) + return 0; + + gw->m_closed = true; + + //Do the needed cleanup. + gw->destroyGW(); + + if (gw->m_shadow) + delete gw->m_shadow; + if (gw->m_helper_widget) + delete gw->m_helper_widget; + + SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(nullptr)); + + if (gw->testAttribute(Qt::WA_DeleteOnClose)) + delete gw; + + return DefWindowProcW(hwnd, message, wParam, lParam); + } + case WM_DESTROY: + { + return DefWindowProcW(hwnd, message, wParam, lParam); + } + case WM_NCHITTEST: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + LRESULT lresult = LRESULT(gw->ncHitTest(pos.x(), pos.y())); + + if (gw->m_is_full_screen) + { + //If on full screen, the whole window can be clicked. + + lresult = HTNOWHERE; + + return lresult; + } + + if (gw->m_fixed_size) + { + //If have fixed size, then only HTCAPTION hit test is valid, + //which means that only the title bar click and move is valid. + + if (lresult != HTNOWHERE && lresult != HTCAPTION) + lresult = HTNOWHERE; + + return lresult; + } + + return lresult; + } + case WM_NCMOUSELEAVE: + { + if (gw->m_is_caption_button_pressed) + break; + + gw->buttonLeave(gw->m_last_caption_button_hovered); + + break; + } + case WM_NCMOUSEMOVE: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + long button = gw->ncHitTest(pos.x(), pos.y()); + + bool valid_caption_button = gw->winButtonHover(button); + + if (valid_caption_button) + { + if (gw->m_is_caption_button_pressed) + { + if (GetCapture() != hwnd) + { + SetCapture(hwnd); + } + } + } + else + { + gw->buttonLeave(gw->m_last_caption_button_hovered); + } + + break; + } + case WM_MOUSEMOVE: + { + QPoint pos = gw->mapToGlobal(QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + + long button = gw->ncHitTest(pos.x(), pos.y()); + + bool valid_caption_button = gw->winButtonHover(button); + + if (!valid_caption_button) + { + if (!gw->m_is_caption_button_pressed && GetCapture() == hwnd) + { + ReleaseCapture(); + } + + gw->buttonLeave(gw->m_last_caption_button_hovered); + } + + break; + } + case WM_NCLBUTTONDOWN: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + long button = gw->ncHitTest(pos.x(), pos.y()); + + bool valid_caption_button = gw->buttonPress(button); + + if (valid_caption_button) + return 0; + + break; + } + case WM_NCLBUTTONUP: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + long button = gw->ncHitTest(pos.x(), pos.y()); + + bool valid_caption_button = gw->buttonRelease(gw->m_caption_button_pressed, + (button == gw->m_caption_button_pressed)); + + if (valid_caption_button) + return 0; + + break; + } + case WM_LBUTTONDOWN: + { + QPoint pos = gw->mapToGlobal(QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + + long button = gw->ncHitTest(pos.x(), pos.y()); + + gw->buttonPress(button); + + break; + } + case WM_LBUTTONUP: + { + QPoint pos = gw->mapToGlobal(QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + + long button = gw->ncHitTest(pos.x(), pos.y()); + + if (button != gw->m_caption_button_pressed) + gw->buttonEnter(button); + + gw->buttonRelease(gw->m_caption_button_pressed, (button == gw->m_caption_button_pressed)); + + break; + } + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + { + if ((GetKeyState(VK_SHIFT) & 0x8000) && wParam == VK_F10) + { + //When SHIFT+F10 is pressed. + gw->showContextMenu(); + return 0; + } + + if ((GetKeyState(VK_MENU) & 0x8000) && wParam == VK_SPACE) + { + //When ALT+SPACE is pressed. + gw->showContextMenu(); + return 0; + } + + break; + } + case WM_NCRBUTTONUP: + { + //Show context menu on right click on title bar. + + int x = GET_X_LPARAM(lParam); + int y = GET_Y_LPARAM(lParam); + + LRESULT lRet = gw->ncHitTest(x, y); + + if (lRet == HTCAPTION || lRet == HTSYSMENU) + gw->showContextMenu(x, y); + + break; + } + case WM_NCCALCSIZE: + { + if (gw->isFullScreen()) + break; + + if (gw->m_win_use_native_borders && !gw->isMaximized()) + { + const int border_width = BORDERWIDTH; + + RECT *rect = reinterpret_cast(lParam); + rect->left += border_width; + rect->bottom -= border_width; + rect->right -= border_width; + } + + if (gw->isMaximized()) + { + //Compensate window client area when maximized, + //by removing BORDERWIDTH value for all edges. + + const int border_width = BORDERWIDTH; + + InflateRect(reinterpret_cast(lParam), -border_width, -border_width); + } + + //Make the whole window as client area. + return 0; + } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + case WM_NCPAINT: + case WM_NCACTIVATE: + { + //Prevent undesired painting on window when DWM is not enabled. + + if (!QtWin::isCompositionEnabled()) + return TRUE; + + break; + } +#endif + case WM_WINDOWPOSCHANGING: + { + Qt::WindowState state; + + if (gw->isMinimized()) + state = Qt::WindowMinimized; + else if (gw->isMaximized()) + state = Qt::WindowMaximized; + else if (gw->isFullScreen()) + state = Qt::WindowFullScreen; + else + state = Qt::WindowNoState; + + if (state != gw->m_state) + { + //If window state changed. + + gw->m_state = state; + + QWindowStateChangeEvent event(state); + QApplication::sendEvent(gw, &event); + + if (gw->m_shadow) + { + if (state != Qt::WindowNoState) + { + //Hide shadow if not on "normal" state. + gw->m_shadow->hide(); + } + else if (gw->isVisible()) + { + //Show shadow if switching to "normal" state, with delay. + gw->m_shadow->showLater(); + } + } + + if (state == Qt::WindowMinimized) + { + if (gw->focusWidget()) + { + //If app going to be minimized, + //save current focused widget + //to restore focus later. + + gw->m_focus_widget = gw->focusWidget(); + } + } + } + + WINDOWPOS *pwp = reinterpret_cast(lParam); + + if (pwp->flags & SWP_SHOWWINDOW) + { + QShowEvent event; + QApplication::sendEvent(gw, &event); + + if (gw->m_fixed_size) + { + SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX); + } + + { + //Restore brush with clear_color. + + HBRUSH brush = HBRUSH(CreateSolidBrush(RGB(gw->m_clear_color.red(), + gw->m_clear_color.green(), + gw->m_clear_color.blue()))); + HBRUSH oldbrush = HBRUSH(SetWindowLongPtrW(hwnd, GCLP_HBRBACKGROUND, + reinterpret_cast(brush))); + + DeleteObject(oldbrush); + InvalidateRect(hwnd, nullptr, TRUE); + } + + if (gw->m_shadow) + { + if (state == Qt::WindowNoState) + { + //Show shadow if on "normal" state with delay. + gw->m_shadow->showLater(); + } + } + + SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_FRAMECHANGED); + + QTimer::singleShot(0, gw->m_main_window, [=]{ + if (gw->m_main_window->isVisible()) + return; + + //Fix bug that make the window frozen + //when initializing window. + QWidget *central_widget = gw->centralWidget(); + if (central_widget) central_widget->hide(); + gw->m_main_window->show(); + gw->m_main_window->resize(gw->size()); + if (central_widget) central_widget->show(); + }); + } + else if (pwp->flags & SWP_HIDEWINDOW) + { + if (gw->focusWidget()) + { + //If app have a valid focused widget, + //save them to restore focus later. + + gw->m_focus_widget = gw->focusWidget(); + } + + { + //Set NULL brush. + + HBRUSH brush = HBRUSH(GetStockObject(NULL_BRUSH)); + HBRUSH oldbrush = HBRUSH(SetWindowLongPtrW(hwnd, GCLP_HBRBACKGROUND, + reinterpret_cast(brush))); + + DeleteObject(oldbrush); + InvalidateRect(hwnd, nullptr, TRUE); + } + + QHideEvent event; + QApplication::sendEvent(gw, &event); + + if (gw->m_shadow) + gw->m_shadow->hide(); + } + else if (pwp->flags == (SWP_NOSIZE + SWP_NOMOVE)) + { + //Activate window to fix no activation + //problem when QGoodWindow isn't shown initially in + //active state. + + gw->m_main_window->activateWindow(); + + if (gw->m_shadow) + gw->m_shadow->showLater(); + } + + break; + } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + case WM_WINDOWPOSCHANGED: + { + if (!QtWin::isCompositionEnabled() && gw->isMaximized()) + { + //Hack for prevent window goes to full screen when it's being maximized, + //enable WS_CAPTION and schedule for disable it, + //not mantaining WS_CAPTION all the time to prevent undesired painting on window + //when title or icon of the window is changed when DWM is not enabled. + + gw->enableCaption(); + QTimer::singleShot(0, gw, &QGoodWindow::disableCaption); + } + + break; + } +#endif + case WM_SETTEXT: + { + emit gw->windowTitleChanged(gw->windowTitle()); + break; + } + case WM_SETICON: + { + emit gw->windowIconChanged(gw->windowIcon()); + break; + } + case WM_DISPLAYCHANGE: + { + if (gw->isFullScreen()) + gw->showNormal(); + + break; + } + case WM_SETTINGCHANGE: + { + if (gw->m_theme_change_timer) + { + if (QString::fromWCharArray(LPCWSTR(lParam)) == "ImmersiveColorSet") + { + gw->m_theme_change_timer->start(); + } + } + + break; + } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + case WM_THEMECHANGED: + case WM_DWMCOMPOSITIONCHANGED: + { + //Send the window change event to m_helper_widget, + //this hack corrects the background color when switching between + //Windows composition modes or system themes. + SendMessageW(HWND(gw->m_helper_widget->winId()), message, 0, 0); + + if (QtWin::isCompositionEnabled()) + { + QTimer::singleShot(500, gw, &QGoodWindow::enableCaption); + QTimer::singleShot(750, gw, &QGoodWindow::frameChanged); + + QTimer::singleShot(1000, gw, [=]{ + SetWindowRgn(hwnd, nullptr, TRUE); + }); + } + else + { + QTimer::singleShot(500, gw, &QGoodWindow::disableCaption); + QTimer::singleShot(750, gw, &QGoodWindow::frameChanged); + } + + QTimer::singleShot(500, gw, &QGoodWindow::sizeMoveWindow); + + QTimer::singleShot(500, gw, [=]{ + gw->repaint(); + }); + + break; + } +#endif + default: + break; + } + + MSG msg; + msg.hwnd = hwnd; + msg.message = message; + msg.lParam = lParam; + msg.wParam = wParam; + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + long result = 0; +#else + qintptr result = 0; +#endif + + bool return_value = gw->nativeEvent(QByteArray(), &msg, &result); + + if (return_value) + return result; + + return DefWindowProcW(hwnd, message, wParam, lParam); +} + +void QGoodWindow::handleActivation() +{ + if (m_shadow) + { + if (m_state == Qt::WindowNoState) + { + //If in "normal" state, make shadow visible. + m_shadow->setActive(true); + m_shadow->show(); + } + } +} + +void QGoodWindow::handleDeactivation() +{ + if (m_shadow) + m_shadow->setActive(false); + + if (!isEnabled()) + buttonLeave(m_last_caption_button_hovered); +} + +void QGoodWindow::setWidgetFocus() +{ + if (!m_focus_widget) + { + bool have_focusable_widget = false; + + for (QWidget *next_focus : findChildren()) + { + if (next_focus->focusPolicy() != Qt::NoFocus) + { + //Set focus to first focusable widget. + have_focusable_widget = true; + m_focus_widget = next_focus; + break; + } + } + + if (!have_focusable_widget) + { + //If not have focusable widget + //set focus to m_main_window. + m_focus_widget = m_main_window; + } + } + + if (m_focus_widget) + { + //If have a valid m_focus_widget, + //set the focus to this widget + m_focus_widget->setFocus(); + } +} + +void QGoodWindow::enableCaption() +{ + SetWindowLongW(m_hwnd, GWL_STYLE, GetWindowLongW(m_hwnd, GWL_STYLE) | WS_CAPTION); +} + +void QGoodWindow::disableCaption() +{ + SetWindowLongW(m_hwnd, GWL_STYLE, GetWindowLongW(m_hwnd, GWL_STYLE) & ~WS_CAPTION); +} + +void QGoodWindow::frameChanged() +{ + SetWindowPos(m_hwnd, nullptr, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_FRAMECHANGED); +} + +void QGoodWindow::sizeMoveWindow() +{ + for (QScreen *screen : QApplication::screens()) + { + if (screen->geometry().contains(frameGeometry().center())) + { + if (windowHandle()->screen() != screen) + windowHandle()->setScreen(screen); + } + } + + if (isFullScreen()) + { + QScreen *screen = windowHandle()->screen(); + setGeometry(screen->geometry()); + } + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (!QtWin::isCompositionEnabled()) + { + QRect window_rect = rect(); + + const int border_width = BORDERWIDTH; + + if (isMaximized()) + window_rect.moveTopLeft(QPoint(border_width, border_width)); + + SetWindowRgn(m_hwnd, QtWin::toHRGN(window_rect), TRUE); + } +#endif + + m_main_window->setGeometry(rect()); + + if (m_shadow) + { + if (m_state == Qt::WindowNoState) + { + const int shadow_width = m_shadow->shadowWidth(); + + m_shadow->move(x() - shadow_width, y() - shadow_width); + m_shadow->resize(width() + shadow_width * 2, height() + shadow_width * 2); + + m_shadow->setActive(isActiveWindow()); + } + } +} + +LRESULT QGoodWindow::ncHitTest(int x, int y) +{ + QPoint gpos(x,y); + QWindow * handle = windowHandle(); + if(handle && handle->screen()) + { + QScreen * screen = handle->screen(); + QPoint offset = screen->geometry().topLeft(); + gpos = (gpos - offset) / screen->devicePixelRatio() + offset; + } + + x=gpos.x(); + y=gpos.y(); + for (QSizeGrip *size_grip : findChildren()) + { + QPoint pos = QPoint(x, y); + + if (size_grip->isEnabled() && + !size_grip->window()->windowFlags().testFlag(Qt::SubWindow)) + { + if (size_grip->rect().contains(size_grip->mapFromGlobal(pos))) + return HTBOTTOMRIGHT; + } + } + + if (!m_win_use_native_borders) + { + const int border_width = (m_state == Qt::WindowNoState ? 1 : 0); //in pixels. + int title_height = titleBarHeight(); //in pixels. + + //Get the point coordinates for the hit test. + QPoint mouse_pos = QPoint(x, y); + + //Get the window rectangle. + QRect window_rect = frameGeometry(); + + //Determine if the hit test is for resizing. Default middle (1,1). + int row = 1; + int col = 1; + bool on_resize_border = false; + + //Determine if the point is at the top or bottom of the window. + if (mouse_pos.y() < window_rect.top() + title_height) + { + on_resize_border = (mouse_pos.y() < (window_rect.top() + border_width)); + row = 0; + } + else if (mouse_pos.y() >= window_rect.bottom() - border_width) + { + row = 2; + } + + //Determine if the point is at the left or right of the window. + if (mouse_pos.x() < window_rect.left() + border_width) + { + col = 0; //left side + } + else if (mouse_pos.x() > window_rect.right()) + { + col = 2; //right side + } + else if (row == 0 && !on_resize_border) + { + const QRegion left_mask = m_left_mask.translated(iconWidth(), 0); + const QRegion right_mask = m_right_mask.translated(width() - rightMargin(), 0); + + QPoint mouse_pos_map = mapFromGlobal(mouse_pos); + + if (mouse_pos.x() > window_rect.right() - rightMargin()) + { + if (m_caption_buttons_handled && m_caption_buttons_corner == Qt::TopRightCorner) + { + QPoint pos = mouse_pos_map; + pos.setX(pos.x() - (window_rect.right() - window_rect.left() - rightMargin())); + + if (m_cls_mask.contains(pos)) + return HTCLOSE; //title bar close button + else if (m_max_mask.contains(pos)) + return HTMAXBUTTON; //title bar maximize button + else if (m_min_mask.contains(pos)) + return HTMINBUTTON; //title bar minimize button + } + + if (right_mask.contains(mouse_pos_map)) + { + return HTNOWHERE; //title bar buttons right + } + } + else if (mouse_pos.x() > window_rect.left() + iconWidth() && + mouse_pos.x() < window_rect.left() + iconWidth() + leftMargin()) + { + if (m_caption_buttons_handled && m_caption_buttons_corner == Qt::TopLeftCorner) + { + QPoint pos = mouse_pos_map; + pos.setX(pos.x() - iconWidth()); + + if (m_cls_mask.contains(pos)) + return HTCLOSE; //title bar close button + else if (m_max_mask.contains(pos)) + return HTMAXBUTTON; //title bar maximize button + else if (m_min_mask.contains(pos)) + return HTMINBUTTON; //title bar minimize button + } + + if (left_mask.contains(mouse_pos_map)) + { + return HTNOWHERE; //custom title bar buttons left + } + } + else if (mouse_pos.x() < window_rect.left() + iconWidth()) + { + return HTSYSMENU; //title bar icon + } + } + + //Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) + LRESULT hitTests[3][3] = + { + {HTTOPLEFT, on_resize_border ? HTTOP : HTCAPTION, HTTOPRIGHT}, + {HTLEFT, HTNOWHERE, HTRIGHT}, + {HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT}, + }; + + return hitTests[row][col]; + } + else + { + const int border_width = (m_state == Qt::WindowNoState ? BORDERWIDTH : 0); //in pixels. + int title_height = titleBarHeight(); //in pixels. + + //Get the point coordinates for the hit test. + QPoint mouse_pos = QPoint(x, y); + + //Get the window rectangle. + QRect window_rect = geometry(); + + //Determine if the hit test is for resizing. Default middle (1,1). + int row = 1; + int col = 1; + bool on_resize_border = false; + + //Determine if the point is at the top or bottom of the window. + if (mouse_pos.y() < window_rect.top() + title_height) + { + on_resize_border = (mouse_pos.y() < (window_rect.top() + border_width) && + mouse_pos.x() > (window_rect.left() + border_width) && + mouse_pos.x() < (window_rect.right() + border_width * 2)); + row = 0; + } + else if (mouse_pos.y() >= window_rect.bottom()) + { + row = 2; + } + + //Determine if the point is at the left or right of the window. + if (mouse_pos.x() < window_rect.left() + border_width) + { + col = 0; //left side + } + else if (mouse_pos.x() > window_rect.right() + border_width) + { + col = 2; //right side + } + else if (row == 0 && !on_resize_border) + { + const QRegion left_mask = m_left_mask.translated(iconWidth(), 0); + const QRegion right_mask = m_right_mask.translated(width() - rightMargin(), 0); + + QPoint mouse_pos_map = mapFromGlobal(mouse_pos); + + if (mouse_pos.x() > window_rect.right() - rightMargin() + border_width) + { + if (m_caption_buttons_handled && m_caption_buttons_corner == Qt::TopRightCorner) + { + QPoint pos = mouse_pos_map; + pos.setX(pos.x() - (window_rect.right() - window_rect.left() - rightMargin())); + + if (m_cls_mask.contains(pos)) + return HTCLOSE; //title bar close button + else if (m_max_mask.contains(pos)) + return HTMAXBUTTON; //title bar maximize button + else if (m_min_mask.contains(pos)) + return HTMINBUTTON; //title bar minimize button + } + + if (right_mask.contains(mouse_pos_map)) + { + return HTNOWHERE; //title bar buttons right + } + } + else if (mouse_pos.x() > window_rect.left() + iconWidth() && + mouse_pos.x() < window_rect.left() + iconWidth() + leftMargin()) + { + if (m_caption_buttons_handled && m_caption_buttons_corner == Qt::TopLeftCorner) + { + QPoint pos = mouse_pos_map; + pos.setX(pos.x() - iconWidth()); + + if (m_cls_mask.contains(pos)) + return HTCLOSE; //title bar close button + else if (m_max_mask.contains(pos)) + return HTMAXBUTTON; //title bar maximize button + else if (m_min_mask.contains(pos)) + return HTMINBUTTON; //title bar minimize button + } + + if (left_mask.contains(mouse_pos_map)) + { + return HTNOWHERE; //custom title bar buttons left + } + } + else if (mouse_pos.x() < window_rect.left() + iconWidth()) + { + return HTSYSMENU; //title bar icon + } + } + + //Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) + LRESULT hitTests[3][3] = + { + {HTTOPLEFT, on_resize_border ? HTTOP : HTCAPTION, HTTOPRIGHT}, + {HTLEFT, HTNOWHERE, HTRIGHT}, + {HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT}, + }; + + return hitTests[row][col]; + } +} + +void QGoodWindow::showContextMenu(int x, int y) +{ + HMENU menu = GetSystemMenu(m_hwnd, FALSE); + + if (!menu) + return; + + MENUITEMINFOW mi; + memset(&mi, 0, sizeof(MENUITEMINFOW)); + mi.cbSize = sizeof(MENUITEMINFOW); + mi.fMask = MIIM_STATE; + + mi.fState = MF_ENABLED; + + SetMenuItemInfoW(menu, SC_RESTORE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_SIZE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MOVE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MAXIMIZE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MINIMIZE, FALSE, &mi); + + mi.fState = MF_GRAYED; + + WINDOWPLACEMENT wp; + GetWindowPlacement(m_hwnd, &wp); + + switch (wp.showCmd) + { + case SW_SHOWMAXIMIZED: + { + SetMenuItemInfoW(menu, SC_SIZE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MOVE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MAXIMIZE, FALSE, &mi); + SetMenuDefaultItem(menu, SC_CLOSE, FALSE); + break; + } + case SW_SHOWMINIMIZED: + { + SetMenuItemInfoW(menu, SC_MINIMIZE, FALSE, &mi); + SetMenuDefaultItem(menu, SC_RESTORE, FALSE); + break; + } + case SW_SHOWNORMAL: + { + SetMenuItemInfoW(menu, SC_RESTORE, FALSE, &mi); + SetMenuDefaultItem(menu, SC_CLOSE, FALSE); + break; + } + default: + break; + } + + if (!(GetWindowLongW(m_hwnd, GWL_STYLE) & WS_MAXIMIZEBOX)) + { + SetMenuItemInfoW(menu, SC_MAXIMIZE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_RESTORE, FALSE, &mi); + } + + if (!(GetWindowLongW(m_hwnd, GWL_STYLE) & WS_MINIMIZEBOX)) + { + SetMenuItemInfoW(menu, SC_MINIMIZE, FALSE, &mi); + } + + if (!(GetWindowLongW(m_hwnd, GWL_STYLE) & WS_SIZEBOX)) + { + SetMenuItemInfoW(menu, SC_SIZE, FALSE, &mi); + } + + int cmd = int(TrackPopupMenu(menu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, + x, y, 0, m_hwnd, nullptr)); + + if (cmd) + PostMessageW(m_hwnd, WM_SYSCOMMAND, WPARAM(cmd), 0); +} + +void QGoodWindow::showContextMenu() +{ + const int border_width = BORDERWIDTH; + + int x_pos = x() + (!isMaximized() ? border_width : 0); + int y_pos = y() + titleBarHeight() - (isMaximized() ? border_width : 0); + + showContextMenu(x_pos, y_pos); +} + +QWidget *QGoodWindow::bestParentForModalWindow() +{ + QGoodWindowUtils::ParentWindow *parent = new QGoodWindowUtils::ParentWindow(this); + moveCenterWindow(parent); + + return parent; +} + +void QGoodWindow::moveCenterWindow(QWidget *widget) +{ + const int title_bar_height = (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) + + GetSystemMetrics(SM_CXPADDEDBORDER)); + const int border_width = (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER)); + + QScreen *screen = windowHandle()->screen(); + QRect rect; + + if (!isMinimized() && isVisible()) + rect = frameGeometry(); + else + rect = screen->availableGeometry(); + + QRect screen_rect = screen->availableGeometry(); + + QRect dialog_rect = widget->geometry(); + + dialog_rect.moveCenter(rect.center()); + + dialog_rect.moveLeft(qMax(dialog_rect.left(), screen_rect.left() + border_width)); + dialog_rect.moveTop(qMax(dialog_rect.top() + titleBarHeight() / 2, screen_rect.top() + title_bar_height)); + dialog_rect.moveRight(qMin(dialog_rect.right(), screen_rect.right() - border_width)); + dialog_rect.moveBottom(qMin(dialog_rect.bottom(), screen_rect.bottom() - border_width)); + + widget->setGeometry(dialog_rect); + + if (widget->windowIcon().isNull()) + widget->setWindowIcon(windowIcon()); + + if (isMinimized()) + showNormal(); +} + +bool QGoodWindow::winButtonHover(long button) +{ + if (button == -1) + return false; + + switch (button) + { + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + if (!m_is_caption_button_pressed) + { + if (button != m_last_caption_button_hovered) + buttonLeave(m_last_caption_button_hovered); + + if (isEnabled()) + buttonEnter(button); + } + else + { + if (button != m_last_caption_button_hovered) + buttonLeave(m_last_caption_button_hovered); + + if (!m_is_caption_button_pressed || (button == m_caption_button_pressed)) + buttonEnter(button); + } + + return true; + } + default: + { + buttonLeave(m_last_caption_button_hovered); + return false; + } + } +} +#endif +#ifdef Q_OS_LINUX +void QGoodWindow::setCursorForCurrentPos() +{ + const QPoint cursor_pos = QCursor::pos(); + const int margin = ncHitTest(cursor_pos.x(), cursor_pos.y()); + + m_cursor_pos = cursor_pos; + m_margin = margin; + + QWidget *widget = QApplication::widgetAt(cursor_pos); + + if (!widget) + return; + + Display *dpy = QX11Info::display(); + + switch (margin) + { + case TOP_LEFT: + { + Cursor cursor = XCreateFontCursor(dpy, XC_top_left_corner); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case TOP: + { + Cursor cursor; + + if (!m_fixed_size) + cursor = XCreateFontCursor(dpy, XC_top_side); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case TOP_RIGHT: + { + Cursor cursor; + + if (!m_fixed_size) + cursor = XCreateFontCursor(dpy, XC_top_right_corner); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case LEFT: + { + Cursor cursor; + + if (!m_fixed_size) + cursor = XCreateFontCursor(dpy, XC_left_side); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case RIGHT: + { + Cursor cursor; + + if (!m_fixed_size) + cursor = XCreateFontCursor(dpy, XC_right_side); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case BOTTOM_LEFT: + { + Cursor cursor; + + if (!m_fixed_size) + cursor = XCreateFontCursor(dpy, XC_bottom_left_corner); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case BOTTOM: + { + Cursor cursor; + + if (!m_fixed_size) + cursor = XCreateFontCursor(dpy, XC_bottom_side); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case BOTTOM_RIGHT: + { + Cursor cursor; + + if (!m_fixed_size) + cursor = XCreateFontCursor(dpy, XC_bottom_right_corner); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case TITLE_BAR: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + if (QApplication::overrideCursor() && + QApplication::overrideCursor()->shape() == Qt::ArrowCursor) + break; + + QApplication::setOverrideCursor(Qt::ArrowCursor); + + break; + } + case NO_WHERE: + { + if (!QApplication::overrideCursor()) + break; + + if (QApplication::overrideCursor()->shape() != Qt::ArrowCursor) + break; + + QApplication::restoreOverrideCursor(); + + break; + } + default: + break; + } +} + +void QGoodWindow::startSystemMoveResize() +{ + const int margin = m_margin; + + if (margin == NO_WHERE) + return; + + if (m_fixed_size && margin != TITLE_BAR) + return; + + QPoint cursor_pos = m_cursor_pos; + + XClientMessageEvent xmsg; + memset(&xmsg, 0, sizeof(XClientMessageEvent)); + + xmsg.type = ClientMessage; + xmsg.window = Window(winId()); + xmsg.message_type = XInternAtom(QX11Info::display(), "_NET_WM_MOVERESIZE", False); + xmsg.format = 32; + xmsg.data.l[0] = long(cursor_pos.x()); + xmsg.data.l[1] = long(cursor_pos.y()); + + if (margin == TITLE_BAR) + xmsg.data.l[2] = MOVERESIZE_MOVE; + else + xmsg.data.l[2] = long(margin); + + xmsg.data.l[3] = 0; + xmsg.data.l[4] = 0; + + XSendEvent(QX11Info::display(), QX11Info::appRootWindow(), False, + SubstructureRedirectMask | SubstructureNotifyMask, + reinterpret_cast(&xmsg)); + + XUngrabPointer(QX11Info::display(), QX11Info::appTime()); + XFlush(QX11Info::display()); + + QTimer::singleShot(500, this, [=]{ + m_resize_move_started = true; + }); +} + +void QGoodWindow::sizeMove() +{ + if (!m_resize_move) + return; + + m_resize_move = false; + + const int margin = m_margin; + + if (margin == TITLE_BAR) + { + QTimer::singleShot(0, this, [=]{ + startSystemMoveResize(); + }); + } + else if (m_shadow_list.size() == SHADOW_WINDOW_COUNT) + { + startSystemMoveResize(); + } +} + +void QGoodWindow::sizeMoveBorders() +{ + if (m_shadow_list.size() != SHADOW_WINDOW_COUNT) + return; + + if (!windowState().testFlag(Qt::WindowNoState)) + return; + + const int border_width = qCeil(BORDERWIDTH * m_pixel_ratio); + + QRect geom = geometry(); + geom.adjust(-border_width, -border_width, border_width, border_width); + + QRegion available_geom; + + for (const QScreen *screen : QApplication::screens()) + { + available_geom += screen->availableGeometry(); + } + + { //TOP LEFT + Shadow *shadow = m_shadow_list.at(0); + + QRect geom1 = geom; + geom1.moveTo(geom1.left(), geom1.top()); + geom1.setWidth(border_width); + geom1.setHeight(border_width); + + if (available_geom.intersects(geom1)) + { + shadow->setGeometry(geom1); + shadow->setGeometry(available_geom.intersected(shadow->frameGeometry()).boundingRect()); + shadow->show(); + } + else + { + shadow->hide(); + } + } + + { //TOP RIGHT + Shadow *shadow = m_shadow_list.at(1); + + QRect geom1 = geom; + geom1.moveTo(geom1.right() - border_width + 1, geom1.top()); + geom1.setWidth(border_width); + geom1.setHeight(border_width); + + if (available_geom.intersects(geom1)) + { + shadow->setGeometry(geom1); + shadow->setGeometry(available_geom.intersected(shadow->frameGeometry()).boundingRect()); + shadow->show(); + } + else + { + shadow->hide(); + } + } + + { //BOTTOM LEFT + Shadow *shadow = m_shadow_list.at(2); + + QRect geom1 = geom; + geom1.moveTo(geom1.left(), geom1.bottom() - border_width + 1); + geom1.setWidth(border_width); + geom1.setHeight(border_width); + + if (available_geom.intersects(geom1)) + { + shadow->setGeometry(geom1); + shadow->setGeometry(available_geom.intersected(shadow->frameGeometry()).boundingRect()); + shadow->show(); + } + else + { + shadow->hide(); + } + } + + { //BOTTOM RIGHT + Shadow *shadow = m_shadow_list.at(3); + + QRect geom1 = geom; + geom1.moveTo(geom1.right() - border_width + 1, geom1.bottom() - border_width + 1); + geom1.setWidth(border_width); + geom1.setHeight(border_width); + + if (available_geom.intersects(geom1)) + { + shadow->setGeometry(geom1); + shadow->setGeometry(available_geom.intersected(shadow->frameGeometry()).boundingRect()); + shadow->show(); + } + else + { + shadow->hide(); + } + } + + { //TOP + Shadow *shadow = m_shadow_list.at(4); + + QRect geom1 = geom; + geom1.moveTo(geom1.left() + border_width, geom1.top()); + geom1.setWidth(geom1.width() - border_width * 2); + geom1.setHeight(border_width); + + if (available_geom.intersects(geom1)) + { + shadow->setGeometry(geom1); + shadow->setGeometry(available_geom.intersected(shadow->frameGeometry()).boundingRect()); + shadow->show(); + } + else + { + shadow->hide(); + } + } + + { //LEFT + Shadow *shadow = m_shadow_list.at(5); + + QRect geom1 = geom; + geom1.moveTo(geom1.left(), geom1.top() + border_width); + geom1.setWidth(border_width); + geom1.setHeight(geom1.height() - border_width * 2); + + if (available_geom.intersects(geom1)) + { + shadow->setGeometry(geom1); + shadow->setGeometry(available_geom.intersected(shadow->frameGeometry()).boundingRect()); + shadow->show(); + } + else + { + shadow->hide(); + } + } + + { //RIGHT + Shadow *shadow = m_shadow_list.at(6); + + QRect geom1 = geom; + geom1.moveTo(geom1.right() - border_width + 1, geom1.top() + border_width); + geom1.setWidth(border_width); + geom1.setHeight(geom1.height() - border_width * 2); + + if (available_geom.intersects(geom1)) + { + shadow->setGeometry(geom1); + shadow->setGeometry(available_geom.intersected(shadow->frameGeometry()).boundingRect()); + shadow->show(); + } + else + { + shadow->hide(); + } + } + + { //BOTTOM + Shadow *shadow = m_shadow_list.at(7); + + QRect geom1 = geom; + geom1.moveTo(geom1.left() + border_width, geom1.bottom() - border_width + 1); + geom1.setWidth(geom1.width() - border_width * 2); + geom1.setHeight(border_width); + + if (available_geom.intersects(geom1)) + { + shadow->setGeometry(geom1); + shadow->setGeometry(available_geom.intersected(shadow->frameGeometry()).boundingRect()); + shadow->show(); + } + else + { + shadow->hide(); + } + } +} +#endif +#ifdef Q_OS_MAC +void QGoodWindow::notificationReceiver(const QByteArray ¬ification) +{ + if (notification == "NSWindowWillEnterFullScreenNotification") + { + m_on_animate_event = true; + macOSNative::setStyle(long(winId()), true); + } + else if (notification == "NSWindowDidExitFullScreenNotification") + { + macOSNative::setStyle(long(winId()), false); + m_on_animate_event = false; + } + else if (notification == "AppleInterfaceThemeChangedNotification") + { + if (m_theme_change_timer) + m_theme_change_timer->start(); + } +} +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC +int QGoodWindow::ncHitTest(int x, int y) +{ + const int title_height = titleBarHeight(); //in pixels. + + //Get the point coordinates for the hit test. + QPoint mouse_pos = QPoint(x, y); + + //Get the window rectangle. + QRect window_rect = frameGeometry(); + + //Determine if the hit test is for resizing. Default middle (1,1). + int row = 1; + int col = 1; + bool on_resize_border = false; + + //Determine if the point is at the top or bottom of the window. + if (mouse_pos.y() < window_rect.top() + title_height) + { + on_resize_border = (mouse_pos.y() < window_rect.top()); + row = 0; + } + else if (mouse_pos.y() > window_rect.bottom()) + { + row = 2; + } + + //Determine if the point is at the left or right of the window. + if (mouse_pos.x() < window_rect.left()) + { + col = 0; //left side + } + else if (mouse_pos.x() > window_rect.right()) + { + col = 2; //right side + } + else if (row == 0 && !on_resize_border) + { + const QRegion left_mask = m_left_mask.translated(iconWidth(), 0); + const QRegion right_mask = m_right_mask.translated(width() - rightMargin(), 0); + + const QPoint mouse_pos_map = mapFromGlobal(mouse_pos); + + if (mouse_pos.x() > window_rect.right() - rightMargin()) + { + if (m_caption_buttons_handled && m_caption_buttons_corner == Qt::TopRightCorner) + { + QPoint pos = mouse_pos_map; + pos.setX(pos.x() - (window_rect.right() - window_rect.left() - rightMargin())); + + if (m_cls_mask.contains(pos)) + return HTCLOSE; //title bar close button + else if (m_max_mask.contains(pos)) + return HTMAXBUTTON; //title bar maximize button + else if (m_min_mask.contains(pos)) + return HTMINBUTTON; //title bar minimize button + } + + if (right_mask.contains(mouse_pos_map)) + return NO_WHERE; //custom title bar buttons right + } + else if (mouse_pos.x() > window_rect.left() + iconWidth() && + mouse_pos.x() < window_rect.left() + iconWidth() + leftMargin()) + { + if (m_caption_buttons_handled && m_caption_buttons_corner == Qt::TopLeftCorner) + { + QPoint pos = mouse_pos_map; + pos.setX(pos.x() - iconWidth()); + + if (m_cls_mask.contains(pos)) + return HTCLOSE; //title bar close button + else if (m_max_mask.contains(pos)) + return HTMAXBUTTON; //title bar maximize button + else if (m_min_mask.contains(pos)) + return HTMINBUTTON; //title bar minimize button + } + + if (left_mask.contains(mouse_pos_map)) + return NO_WHERE; //custom title bar buttons left + } + } + + //Hit test (TOP_LEFT, ... BOTTOM_RIGHT) + int hitTests[3][3] = + { + {TOP_LEFT, on_resize_border ? TOP : TITLE_BAR, TOP_RIGHT}, + {LEFT, NO_WHERE, RIGHT}, + {BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT}, + }; + + return hitTests[row][col]; +} +#endif +#ifdef QGOODWINDOW +void QGoodWindow::buttonEnter(long button) +{ + if (button == -1) + return; + + m_last_caption_button_hovered = button; + + switch (button) + { + case HTMINBUTTON: + { + emit captionButtonStateChanged(CaptionButtonState::MinimizeHoverEnter); + + break; + } + case HTMAXBUTTON: + { + emit captionButtonStateChanged(CaptionButtonState::MaximizeHoverEnter); + + break; + } + case HTCLOSE: + { + emit captionButtonStateChanged(CaptionButtonState::CloseHoverEnter); + + break; + } + default: + break; + } +} + +void QGoodWindow::buttonLeave(long button) +{ + if (button == -1) + return; + + if (button != m_last_caption_button_hovered) + return; + + m_last_caption_button_hovered = -1; + + switch (button) + { + case HTMINBUTTON: + { + emit captionButtonStateChanged(CaptionButtonState::MinimizeHoverLeave); + + break; + } + case HTMAXBUTTON: + { + emit captionButtonStateChanged(CaptionButtonState::MaximizeHoverLeave); + + break; + } + case HTCLOSE: + { + emit captionButtonStateChanged(CaptionButtonState::CloseHoverLeave); + + break; + } + default: + break; + } +} + +bool QGoodWindow::buttonPress(long button) +{ + if (button == -1) + return false; + + switch (button) + { + case HTMINBUTTON: + { + m_is_caption_button_pressed = true; + m_caption_button_pressed = HTMINBUTTON; + + emit captionButtonStateChanged(CaptionButtonState::MinimizePress); + + activateWindow(); + + return true; + } + case HTMAXBUTTON: + { + m_is_caption_button_pressed = true; + m_caption_button_pressed = HTMAXBUTTON; + + emit captionButtonStateChanged(CaptionButtonState::MaximizePress); + + activateWindow(); + + return true; + } + case HTCLOSE: + { + m_is_caption_button_pressed = true; + m_caption_button_pressed = HTCLOSE; + + emit captionButtonStateChanged(CaptionButtonState::ClosePress); + + activateWindow(); + + return true; + } + default: + break; + } + + return false; +} + +bool QGoodWindow::buttonRelease(long button, bool valid_click) +{ + if (button == -1) + return false; + + if (!m_is_caption_button_pressed) + return false; + + if (m_hover_timer->isActive()) + return false; + + m_is_caption_button_pressed = false; + m_caption_button_pressed = -1; + + switch (button) + { + case HTMINBUTTON: + { + emit captionButtonStateChanged(CaptionButtonState::MinimizeRelease); + + if (valid_click) + { + emit captionButtonStateChanged(CaptionButtonState::MinimizeClicked); + m_hover_timer->start(); + } + + return true; + } + case HTMAXBUTTON: + { + emit captionButtonStateChanged(CaptionButtonState::MaximizeRelease); + + if (valid_click) + { + emit captionButtonStateChanged(CaptionButtonState::MaximizeClicked); + m_hover_timer->start(); + } + + return true; + } + case HTCLOSE: + { + emit captionButtonStateChanged(CaptionButtonState::CloseRelease); + + if (valid_click) + { + emit captionButtonStateChanged(CaptionButtonState::CloseClicked); + m_hover_timer->start(); + } + + return true; + } + default: + break; + } + + return false; +} +#endif diff --git a/QGoodWindow/src/qgoodwindow.h b/QGoodWindow/src/qgoodwindow.h new file mode 100644 index 0000000..bc3b3e7 --- /dev/null +++ b/QGoodWindow/src/qgoodwindow.h @@ -0,0 +1,446 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODWINDOW_H +#define QGOODWINDOW_H + +#include +#include +#include + +#ifdef QGOODWINDOW + +#ifdef Q_OS_WIN +namespace QGoodWindowUtils +{ +class NativeEventFilter; +} +#endif + +#if defined Q_OS_WIN || defined Q_OS_LINUX +class Shadow; +#endif + +#endif + +/** **QGoodWindow** class contains the public API's to control the behavior of the customized window. + * + * On Windows, **QGoodWindow** class inherits from `QMainWindow` which is used as a native widget that + * creates, fill and control the native window of **QGoodWindow**. + * + * On Linux and macOS the **QGoodWindow** behaves like a frameless `QMainWindow`. + */ +class QGoodWindow : public QMainWindow +{ + Q_OBJECT +public: + /** Constructor of *QGoodWindow*. + * + * On Windows creates the native window, turns the `QMainWindow` as a native widget, + * creates the shadow, initialize default values and calls the `QMainWindow` + * parent constructor. + * + * On Linux creates the frameless `QMainWindow`, use the shadow only to create resize borders, + * and the real shadow is draw by current Linux window manager. + * + * On macOS creates a `QMainWindow` with full access to the title bar, + * and hide native minimize, zoom and close buttons. + */ + explicit QGoodWindow(QWidget *parent = nullptr, + const QColor &clear_color = + QColor(!isSystemThemeDark() ? Qt::white : Qt::black)); + + /** Destructor of *QGoodWindow*. */ + ~QGoodWindow(); + + /** Enum that contains caption buttons states when it's states are handled by *QGoodWindow*.*/ + enum class CaptionButtonState + { + /** Minimize button hover enter. */ + MinimizeHoverEnter, + + /** Minimize button hover leave. */ + MinimizeHoverLeave, + + /** Minimize button press. */ + MinimizePress, + + /** Minimize button release. */ + MinimizeRelease, + + /** Minimize button clicked. */ + MinimizeClicked, + + /** Maximize or restore button hover enter. */ + MaximizeHoverEnter, + + /** Maximize or restore button hover leave. */ + MaximizeHoverLeave, + + /** Maximize or restore button press. */ + MaximizePress, + + /** Maximize or restore button release. */ + MaximizeRelease, + + /** Maximize or restore button clicked. */ + MaximizeClicked, + + /** Close button hover enter. */ + CloseHoverEnter, + + /** Close button hover leave. */ + CloseHoverLeave, + + /** Close button press. */ + ClosePress, + + /** Close button release. */ + CloseRelease, + + /** Close button clicked. */ + CloseClicked + }; + + //Functions + /** Returns the window id of the *QGoodWindow*. */ + WId winId() const; + + //Variables + /** Reserved. */ + QPointer m_theme_change_timer; + +signals: + /** On handled caption buttons, this SIGNAL report the state of these buttons. */ + void captionButtonStateChanged(const QGoodWindow::CaptionButtonState &state); + + /** Notify that the system has changed between light and dark mode. */ + void systemThemeChanged(); + +public slots: + /*** QGOODWINDOW FUNCTIONS BEGIN ***/ + + /** Returns if the current system theme is dark or not. */ + static bool isSystemThemeDark(); + + /** Returns if there is a one pixel margin around window for resizing or not, i.e. if system draw margins. */ + static bool shouldBordersBeDrawnBySystem(); + + /** On Windows, Linux and macOS, returns the actual title bar height, on other OSes returns 0. */ + int titleBarHeight() const; + + /** On Windows, Linux and macOS, return the actual icon width, on other OSes returns 0. */ + int iconWidth() const; + + /** On Windows, Linux and macOS, returns the left margin of the customized title bar, on other OSes returns 0. */ + int leftMargin() const; + + /** On Windows, Linux and macOS, returns the right margin of the customized title bar, on other OSes returns 0. */ + int rightMargin() const; + + /** Set the tile bar height, icon width, left and right margins of the customized title bar. */ + void setMargins(int title_bar_height, int icon_width, int left, int right); + + /** Set the mask for the left margin of the customized title bar. */ + void setLeftMask(const QRegion &mask); + + /** Set the mask for the right margin of the customized title bar. */ + void setRightMask(const QRegion &mask); + + /** Set if the caption buttons should be handled by *QGoodWindow* and on which \e corner, valid only top left and top right corners. */ + void setCaptionButtonsHandled(bool handled, const Qt::Corner &corner = Qt::TopRightCorner); + + /** Set the location and shape of handled minimize button, relative to handled corner. */ + void setMinimizeMask(const QRegion &mask); + + /** Set the location and shape of handled maximize button, relative to handled corner. */ + void setMaximizeMask(const QRegion &mask); + + /** Set the location and shape of handled close button, relative to handled corner. */ + void setCloseMask(const QRegion &mask); + + /** Get the size that should be the size of the mask on the left margin of the customized title bar. */ + QSize leftMaskSize() const; + + /** Get the size that should be the size of the mask on the right margin of the customized title bar. */ + QSize rightMaskSize() const; + + /** If caption buttons are handled on left corner, their buttons masks should be in the bounds of this rect. */ + QRect leftCaptionButtonsRect() const; + + /** If caption buttons are handled on right corner, their buttons masks should be in the bounds of this rect. */ + QRect rightCaptionButtonsRect() const; + + /*** QGOODWINDOW FUNCTIONS END ***/ + + /** Set fixed size for *QGoodWindow* to width \e w and height \e h. */ + void setFixedSize(int w, int h); + + /** Set fixed size for *QGoodWindow* to \e size. */ + void setFixedSize(const QSize &size); + + /** Returns the geometry for *QGoodWindow* including extended frame and excluding shadow. */ + QRect frameGeometry() const; + + /** Returns the client area geometry. */ + QRect geometry() const; + + /** Returns the client area size, position is always `QPoint(0, 0)`. */ + QRect rect() const; + + /** Position of the window on screen. */ + QPoint pos() const; + + /** Size of the window on screen. */ + QSize size() const; + + /** X position of the window on screen. */ + int x() const; + + /** Y position of the window on screen. */ + int y() const; + + /** Width of the window. */ + int width() const; + + /** Height of the window. */ + int height() const; + + /** Move the window to \e x - \e y coordinates. */ + void move(int x, int y); + + /** Move the window to \e pos. */ + void move(const QPoint &pos); + + /** Resize the window to \e width - \e height size. */ + void resize(int width, int height); + + /** Resize the window to \e size. */ + void resize(const QSize &size); + + /** Set geometry to pos \e x - \e y, width \e w and height \e h. */ + void setGeometry(int x, int y, int w, int h); + + /** Set geometry to \e rect. */ + void setGeometry(const QRect &rect); + + /** Activates the *QGoodWindow*. */ + void activateWindow(); + + /** Shows the *QGoodWindow*. */ + void show(); + + /** Shows or restore the *QGoodWindow*. */ + void showNormal(); + + /** Shows or maximize the *QGoodWindow*. */ + void showMaximized(); + + /** Minimize the *QGoodWindow*. */ + void showMinimized(); + + /** Turns the *QGoodWindow* into full screen mode. Including the title bar. */ + void showFullScreen(); + + /** Hide the *QGoodWindow*. */ + void hide(); + + /** Close the *QGoodWindow*. */ + void close(); + + /** Returns if the *QGoodWindow* is visible or not. */ + bool isVisible() const; + + /** Returns if the *QGoodWindow* is enabled or not. */ + bool isEnabled() const; + + /** Returns if the *QGoodWindow* is the foreground window or not. */ + bool isActiveWindow() const; + + /** Returns if the *QGoodWindow* is maximized or not. */ + bool isMaximized() const; + + /** Returns if the *QGoodWindow* is minimized or not. */ + bool isMinimized() const; + + /** Returns if the *QGoodWindow* is in full screen mode or not. */ + bool isFullScreen() const; + + /** Returns the *QGoodWindow* state. */ + Qt::WindowStates windowState() const; + + /** Sets the state of the *QGoodWindow* to \e state. */ + void setWindowState(Qt::WindowStates state); + + /** Returns the window handle of the *QGoodWindow*. */ + QWindow *windowHandle() const; + + /** Returns the opacity of the *QGoodWindow*. */ + qreal windowOpacity() const; + + /** Sets the opacity of the *QGoodWindow* to \e level. Where 0.0 is fully transparent and 1.0 fully opaque. */ + void setWindowOpacity(qreal level); + + /** Returns the title of the *QGoodWindow*. */ + QString windowTitle() const; + + /** Sets the title of the *QGoodWindow* to \e title. */ + void setWindowTitle(const QString &title); + + /** Returns the icon of the *QGoodWindow*. */ + QIcon windowIcon() const; + + /** Sets the icon of the *QGoodWindow* to \e icon. */ + void setWindowIcon(const QIcon &icon); + +protected: + //Functions + bool event(QEvent *event); + bool eventFilter(QObject *watched, QEvent *event); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + bool nativeEvent(const QByteArray &eventType, void *message, long *result); +#else + bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result); +#endif + +private: +#ifdef QGOODWINDOW +#ifdef Q_OS_WIN + //Functions + void initGW(); + void destroyGW(); + static LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + void handleActivation(); + void handleDeactivation(); + void setWidgetFocus(); + void enableCaption(); + void disableCaption(); + void frameChanged(); + void sizeMoveWindow(); + LRESULT ncHitTest(int x, int y); + void showContextMenu(int x, int y); + void showContextMenu(); + QWidget *bestParentForModalWindow(); + void moveCenterWindow(QWidget *widget); + bool winButtonHover(long button); + + //Variables + HWND m_hwnd; + bool m_win_use_native_borders; + QPointer m_main_window; + QPointer m_shadow; + QPointer m_helper_widget; + QGoodWindowUtils::NativeEventFilter *m_native_event; + QWindow *m_window_handle; + + QPointer m_focus_widget; + + bool m_closed; + + bool m_is_full_screen; + QRect m_rect_origin; + + bool m_active_state; + + Qt::WindowState m_state; + + QColor m_clear_color; + + friend class QGoodWindowUtils::NativeEventFilter; +#endif +#ifdef Q_OS_LINUX + //Functions + void setCursorForCurrentPos(); + void startSystemMoveResize(); + void sizeMove(); + void sizeMoveBorders(); + + //Variables + QList> m_shadow_list; + + int m_margin; + QPoint m_cursor_pos; + bool m_resize_move; + bool m_resize_move_started; + qreal m_pixel_ratio; + + friend class Shadow; +#endif +#ifdef Q_OS_MAC + //Functions + void notificationReceiver(const QByteArray ¬ification); + + //Variables + QPoint m_pos; + bool m_mouse_button_pressed; + bool m_on_animate_event; + + friend class Notification; +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC + //Functions + int ncHitTest(int x, int y); + + //Variables + int m_last_move_button; +#endif +#if defined Q_OS_WIN || defined Q_OS_LINUX + bool m_fixed_size; +#endif +#ifdef Q_OS_LINUX + bool m_last_fixed_size_value; +#endif + //Functions + void buttonEnter(long button); + void buttonLeave(long button); + bool buttonPress(long button); + bool buttonRelease(long button, bool valid_click); + + //Variables + QPointer m_hover_timer; + + QRegion m_left_mask; + QRegion m_right_mask; + + QRegion m_min_mask; + QRegion m_max_mask; + QRegion m_cls_mask; + + bool m_dark; + + bool m_caption_buttons_handled; + Qt::Corner m_caption_buttons_corner; + + int m_title_bar_height; + int m_icon_width; + int m_left_margin; + int m_right_margin; + + bool m_is_caption_button_pressed; + long m_last_caption_button_hovered; + long m_caption_button_pressed; +#endif +}; + +#endif // QGOODWINDOW_H diff --git a/QGoodWindow/src/shadow.cpp b/QGoodWindow/src/shadow.cpp new file mode 100644 index 0000000..39026f0 --- /dev/null +++ b/QGoodWindow/src/shadow.cpp @@ -0,0 +1,258 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "shadow.h" + +#ifdef Q_OS_LINUX +#include "qgoodwindow.h" +#endif + +#ifdef Q_OS_WIN +#define SHADOWWIDTH qCeil(10 * m_pixel_ratio) +#define COLOR1 QColor(0, 0, 0, 75) +#define COLOR2 QColor(0, 0, 0, 30) +#define COLOR3 QColor(0, 0, 0, 1) +#endif + +#ifdef Q_OS_WIN +Shadow::Shadow(qreal pixel_ratio, HWND hwnd) : QWidget() +{ + m_pixel_ratio = pixel_ratio; + + m_hwnd = hwnd; + m_active = true; + + setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::Tool); + + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + + setAttribute(Qt::WA_TransparentForMouseEvents); + + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &Shadow::show); + m_timer->setInterval(500/*Time to wait before showing shadow when showLater() is callled.*/); + m_timer->setSingleShot(true); +} +#endif +#ifdef Q_OS_LINUX +Shadow::Shadow(QWidget *parent) : QWidget(parent) +{ + m_parent = qobject_cast(parent); + + setWindowFlags(Qt::Window | + Qt::FramelessWindowHint | + Qt::WindowDoesNotAcceptFocus | + Qt::BypassWindowManagerHint); + + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); +} +#endif + +int Shadow::shadowWidth() +{ +#ifdef Q_OS_WIN + return SHADOWWIDTH; +#else + return 0; +#endif +} + +void Shadow::showLater() +{ +#ifdef Q_OS_WIN + m_timer->stop(); + m_timer->start(); +#endif +} + +void Shadow::show() +{ +#ifdef Q_OS_WIN + if (m_timer->isActive()) + return; + + if (!IsWindowEnabled(m_hwnd)) + return; + + QWidget::show(); + QWidget::raise(); + + SetWindowPos(m_hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); +#else + QWidget::show(); +#endif +} + +void Shadow::hide() +{ +#ifdef Q_OS_WIN + m_timer->stop(); + + if (!isVisible()) + return; + + QWidget::hide(); +#else + QWidget::hide(); +#endif +} + +void Shadow::setActive(bool active) +{ +#ifdef Q_OS_WIN + m_active = active; + repaint(); +#else + Q_UNUSED(active) +#endif +} + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +bool Shadow::nativeEvent(const QByteArray &eventType, void *message, long *result) +#else +bool Shadow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) +#endif +{ +#ifdef Q_OS_WIN + MSG* msg = static_cast(message); + + switch (msg->message) + { + case WM_ACTIVATE: + { + switch (msg->wParam) + { + case WA_ACTIVE: + case WA_CLICKACTIVE: + { + //When shadow got focus, transfer it to main window. + SetForegroundWindow(m_hwnd); + + break; + } + default: + break; + } + + break; + } + case WM_MOUSEACTIVATE: + { + //When shadow got focus, transfer it to main window. + SetForegroundWindow(m_hwnd); + + //Prevent main window from "focus flickering". + *result = MA_NOACTIVATE; + + return true; + } + case WM_NCMOUSEMOVE: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCLBUTTONDBLCLK: + case WM_NCHITTEST: + { + //Transfer the above messages to main window, + //this way the resize and snap effects happens also + //when interacting with the shadow, and acts like a secondary border. + *result = long(SendMessageW(m_hwnd, msg->message, msg->wParam, msg->lParam)); + return true; + } + default: + break; + } +#endif + + return QWidget::nativeEvent(eventType, message, result); +} + +void Shadow::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + +#ifdef Q_OS_WIN + //Draw shadow + + const int shadow_width = SHADOWWIDTH; + + QPainter painter(this); + + painter.setCompositionMode(QPainter::CompositionMode_Source); + + if (!m_active) + { + painter.fillRect(rect(), QColor(0, 0, 0, 1)); + + QRect rect1 = rect().adjusted(shadow_width, shadow_width, -shadow_width, -shadow_width); + + painter.fillRect(rect1, Qt::transparent); + + return; + } + + QPixmap radial_gradient = QPixmap(shadow_width * 2, shadow_width * 2); + + { + //Draw a radial gradient then split it in 4 parts and draw it to corners and edges + + radial_gradient.fill(QColor(0, 0, 0, 1)); + + QPainter painter(&radial_gradient); + painter.setRenderHint(QPainter::Antialiasing); + painter.setCompositionMode(QPainter::CompositionMode_Source); + + QRadialGradient gradient(shadow_width, shadow_width, shadow_width); + gradient.setColorAt(0.0, COLOR1); + gradient.setColorAt(0.2, COLOR2); + gradient.setColorAt(0.5, COLOR3); + + QPen pen(Qt::transparent, 0); + painter.setPen(pen); + painter.setBrush(gradient); + painter.drawEllipse(0, 0, shadow_width * 2, shadow_width * 2); + } + + QRect rect1 = rect().adjusted(shadow_width, shadow_width, -shadow_width, -shadow_width); + + painter.drawPixmap(0, 0, shadow_width, shadow_width, radial_gradient, 0, 0, shadow_width, shadow_width); //Top-left corner + painter.drawPixmap(rect().width() - shadow_width, 0, radial_gradient, shadow_width, 0, shadow_width, shadow_width); //Top-right corner + painter.drawPixmap(0, rect().height() - shadow_width, radial_gradient, 0, shadow_width, shadow_width, shadow_width); //Bottom-left corner + painter.drawPixmap(rect().width() - shadow_width, rect().height() - shadow_width, radial_gradient, shadow_width, shadow_width, shadow_width, shadow_width); //Bottom-right corner + + painter.drawPixmap(shadow_width, 0, rect1.width(), shadow_width, radial_gradient, shadow_width, 0, 1, shadow_width); //Top + painter.drawPixmap(0, shadow_width, shadow_width, rect1.height(), radial_gradient, 0, shadow_width, shadow_width, 1); //Left + painter.drawPixmap(rect1.width() + shadow_width, shadow_width, shadow_width, rect1.height(), radial_gradient, shadow_width, shadow_width, shadow_width, 1); //Right + painter.drawPixmap(shadow_width, rect1.height() + shadow_width, rect1.width(), shadow_width, radial_gradient, shadow_width, shadow_width, 1, SHADOWWIDTH); //Bottom +#endif +#ifdef Q_OS_LINUX + if (!m_parent) + return; + + QPainter painter(this); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.fillRect(rect(), Qt::transparent); +#endif +} diff --git a/QGoodWindow/src/shadow.h b/QGoodWindow/src/shadow.h new file mode 100644 index 0000000..cc40b4d --- /dev/null +++ b/QGoodWindow/src/shadow.h @@ -0,0 +1,77 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2022 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SHADOW_H +#define SHADOW_H + +#include +#include +#include + +#ifdef Q_OS_LINUX +class QGoodWindow; +#endif + +//\cond HIDDEN_SYMBOLS +class Shadow : public QWidget +{ + Q_OBJECT +public: +#ifdef Q_OS_WIN + explicit Shadow(qreal pixel_ratio, HWND hwnd); +#endif +#ifdef Q_OS_LINUX + explicit Shadow(QWidget *parent); +#endif + +public slots: + void showLater(); + void show(); + void hide(); + void setActive(bool active); + int shadowWidth(); + +private: + //Functions +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + bool nativeEvent(const QByteArray &eventType, void *message, long *result); +#else + bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result); +#endif + void paintEvent(QPaintEvent *event); + + //Variables +#ifdef Q_OS_WIN + HWND m_hwnd; + QTimer *m_timer; + bool m_active; + qreal m_pixel_ratio; +#endif +#ifdef Q_OS_LINUX + QPointer m_parent; +#endif +}; +//\endcond + +#endif // SHADOW_H