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 0000000..4bac081 Binary files /dev/null and b/ArchitectureColoredPainting/images/icon.png differ diff --git a/ArchitectureColoredPainting/images/icon_window_close.png b/ArchitectureColoredPainting/images/icon_window_close.png new file mode 100644 index 0000000..ece7c28 Binary files /dev/null and b/ArchitectureColoredPainting/images/icon_window_close.png differ diff --git a/ArchitectureColoredPainting/images/icon_window_maximize.png b/ArchitectureColoredPainting/images/icon_window_maximize.png new file mode 100644 index 0000000..53ae289 Binary files /dev/null and b/ArchitectureColoredPainting/images/icon_window_maximize.png differ diff --git a/ArchitectureColoredPainting/images/icon_window_minimize.png b/ArchitectureColoredPainting/images/icon_window_minimize.png new file mode 100644 index 0000000..29bceed Binary files /dev/null and b/ArchitectureColoredPainting/images/icon_window_minimize.png differ diff --git a/ArchitectureColoredPainting/images/icon_window_restore.png b/ArchitectureColoredPainting/images/icon_window_restore.png new file mode 100644 index 0000000..be29650 Binary files /dev/null and b/ArchitectureColoredPainting/images/icon_window_restore.png differ diff --git a/ArchitectureColoredPainting/lightstyle.qss b/ArchitectureColoredPainting/lightstyle.qss new file mode 100644 index 0000000..e69de29 diff --git a/ArchitectureColoredPainting/msvc_make.bat b/ArchitectureColoredPainting/msvc_make.bat new file mode 100644 index 0000000..416198b --- /dev/null +++ b/ArchitectureColoredPainting/msvc_make.bat @@ -0,0 +1,2 @@ +chcp 65001 +"E:\Qt\Tools\QtCreator\bin\jom\jom.exe" %* \ No newline at end of file diff --git a/ArchitectureColoredPainting/qt.conf b/ArchitectureColoredPainting/qt.conf new file mode 100644 index 0000000..367a59e --- /dev/null +++ b/ArchitectureColoredPainting/qt.conf @@ -0,0 +1,2 @@ +[Platforms] +WindowsArguments = fontengine=freetype diff --git a/ArchitectureColoredPainting/src/CaptionButton.cpp b/ArchitectureColoredPainting/src/CaptionButton.cpp new file mode 100644 index 0000000..ec4a2ea --- /dev/null +++ b/ArchitectureColoredPainting/src/CaptionButton.cpp @@ -0,0 +1,261 @@ +/* +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 "captionbutton.h" + +CaptionButton::CaptionButton(QWidget *parent) : QWidget(parent) +{ + m_is_active = false; + m_is_under_mouse = false; + m_is_pressed = false; + m_icon_dark = false; + + setAttribute(Qt::WA_Hover); +} + +CaptionButton::~CaptionButton() +{ + +} + +QPixmap CaptionButton::drawIcon(const QPixmap &icon, bool active, bool force_light) +{ + QImage tmp = icon.toImage(); + + if (!active) + { + for (int i = 0; i < tmp.height(); i++) + { + for (int j = 0; j < tmp.width(); j++) + { + QColor pixel = QColor::fromRgba(tmp.pixel(j, i)); + + pixel.setRedF(pixel.redF() * 0.5f); + pixel.setGreenF(pixel.greenF() * 0.5f); + pixel.setBlueF(pixel.blueF() * 0.5f); + + tmp.setPixel(j, i, pixel.rgba()); + } + } + } + + if (m_icon_dark && !force_light) + tmp.invertPixels(); + + return QPixmap::fromImage(tmp); +} + +void CaptionButton::init(IconType type) +{ + m_type = type; + + setColors(); + drawIcons(); +} + +void CaptionButton::drawIcons() +{ + switch (m_type) + { + case IconType::Minimize: + { + QPixmap icon = QPixmap(":/images/icon_window_minimize.png"); + + m_active_icon = drawIcon(icon, true); + m_inactive_icon = drawIcon(icon, false); + + break; + } + case IconType::Restore: + { + QPixmap icon = QPixmap(":/images/icon_window_restore.png"); + + m_active_icon = drawIcon(icon, true); + m_inactive_icon = drawIcon(icon, false); + + break; + } + case IconType::Maximize: + { + QPixmap icon = QPixmap(":/images/icon_window_maximize.png"); + + m_active_icon = drawIcon(icon, true); + m_inactive_icon = drawIcon(icon, false); + + break; + } + case IconType::Close: + { + QPixmap icon = QPixmap(":/images/icon_window_close.png"); + + m_active_icon = drawIcon(icon, true); + m_inactive_icon = drawIcon(icon, false); + m_close_icon_hover = drawIcon(icon, true, true); + + break; + } + } +} + +void CaptionButton::setColors() +{ + if (m_icon_dark) + { + if (m_type == IconType::Close) + { + m_normal = QColor("transparent"); + m_hover = QColor("#F00000"); + m_pressed = QColor("#F1707A"); + } + else + { + m_normal = QColor("transparent"); + m_hover = QColor("#E5E5E5"); + m_pressed = QColor("#CACACB"); + } + } + else + { + if (m_type == IconType::Close) + { + m_normal = QColor("transparent"); + m_hover = QColor("#F00000"); + m_pressed = QColor("#F1707A"); + } + else + { + m_normal = QColor("transparent"); + m_hover = QColor("#505050"); + m_pressed = QColor("#3F3F3F"); + } + } + + repaint(); +} + +void CaptionButton::setIconMode(bool icon_dark) +{ + m_icon_dark = icon_dark; + + drawIcons(); + setColors(); + + repaint(); +} + +void CaptionButton::setActive(bool is_active) +{ + m_is_active = is_active; + + repaint(); +} + +void CaptionButton::setState(int state) +{ + switch (state) + { + case QEvent::HoverEnter: + { + m_is_under_mouse = true; + + repaint(); + + break; + } + case QEvent::HoverLeave: + { + m_is_under_mouse = false; + + repaint(); + + break; + } + case QEvent::MouseButtonPress: + { + m_is_pressed = true; + + m_is_under_mouse = true; + + repaint(); + + break; + } + case QEvent::MouseButtonRelease: + { + m_is_pressed = false; + + m_is_under_mouse = false; + + repaint(); + + break; + } + default: + break; + } +} + +void CaptionButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPixmap current_icon = m_active_icon; + QColor current_color = m_normal; + + //Change icon if needed + if (m_is_under_mouse) + { + if (m_type == IconType::Close) + current_icon = m_close_icon_hover; + } + else + { + if (!m_is_active) + current_icon = m_inactive_icon; + } + + //Change background color if needed + if (m_is_pressed) + { + if (m_is_under_mouse) + current_color = m_pressed; + } + else + { + if (m_is_under_mouse) + current_color = m_hover; + } + + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + + painter.fillRect(rect(), current_color); + + QRect target_rect; + target_rect = current_icon.rect(); + target_rect.setSize(QSize(16, 16)); + target_rect = QRect(rect().center() - target_rect.center(), target_rect.size()); + painter.drawPixmap(target_rect, current_icon); +} diff --git a/ArchitectureColoredPainting/src/CaptionButton.h b/ArchitectureColoredPainting/src/CaptionButton.h new file mode 100644 index 0000000..e2a3f2b --- /dev/null +++ b/ArchitectureColoredPainting/src/CaptionButton.h @@ -0,0 +1,81 @@ +/* +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 CAPTIONBUTTON_H +#define CAPTIONBUTTON_H + +#include +#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 0000000..6e0efc4 Binary files /dev/null and b/QGoodWindow/QGoodWindow.write.1u.tlog differ diff --git a/QGoodWindow/moc.read.1u.tlog b/QGoodWindow/moc.read.1u.tlog new file mode 100644 index 0000000..5cb46b7 Binary files /dev/null and b/QGoodWindow/moc.read.1u.tlog differ diff --git a/QGoodWindow/moc.write.1u.tlog b/QGoodWindow/moc.write.1u.tlog new file mode 100644 index 0000000..b407de4 Binary files /dev/null and b/QGoodWindow/moc.write.1u.tlog differ 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