From 31d563d2f6cc7d01c4b0fec4b8b6947b750ad5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=AD=90=E6=A5=9A=5Czhuzi?= Date: Sun, 26 Feb 2023 23:47:07 +0800 Subject: [PATCH] update --- example/App.qml | 21 + example/MainPage.qml | 102 +++ example/SettingPage.qml | 17 + example/T_Controls.qml | 52 ++ example/T_Typography.qml | 54 ++ example/TaoFrameLessView.h | 56 ++ example/TaoFrameLessView_unix.cpp | 106 +++ example/TaoFrameLessView_win.cpp | 361 ++++++++ example/example.pro | 44 +- example/main.cpp | 54 +- example/main.qml | 31 - example/qml.qrc | 6 +- src/Def.cpp | 3 + src/Def.h | 798 ++++++++++++++++++ src/FluApp.cpp | 56 ++ src/FluApp.h | 30 + src/Fluent.cpp | 59 ++ src/Fluent.h | 22 + src/FluentUI.cpp | 26 +- src/FluentUI.h | 15 +- src/FluentUI.pro | 21 +- src/FramelessView.h | 57 ++ src/FramelessView_unix.cpp | 106 +++ src/FramelessView_win.cpp | 372 ++++++++ src/build-preset/plugin.qmltypes | 0 src/build-preset/qmldir | 2 +- src/build_windows.pri | 9 - src/controls/FilledButton.qml | 28 - .../{StandardButton.qml => FluButton.qml} | 17 +- src/controls/FluCheckBox.qml | 5 + src/controls/FluComboBox.qml | 5 + src/controls/FluDropDownButton.qml | 5 + src/controls/FluFilledButton.qml | 49 ++ src/controls/FluIconButton.qml | 20 + src/controls/FluProgressBar.qml | 5 + src/controls/FluProgressRing.qml | 5 + src/controls/FluRadioButton.qml | 5 + src/controls/FluSlider.qml | 5 + src/controls/FluText.qml | 73 ++ src/controls/FluTextBox.qml | 5 + src/controls/FluTimePicker.qml | 7 + src/controls/FluToggleSwitch.qml | 30 + src/create_qmltypes.bat | 16 - src/qml_plugin.cpp | 10 +- src/qml_plugin.h | 2 - src/res.qrc | 19 +- src/res/font/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes src/stdafx.h | 21 + src/styles/Theme.qml | 0 src/styles/Typography.qml | 0 src/win_install.bat | 3 +- 51 files changed, 2649 insertions(+), 166 deletions(-) create mode 100644 example/App.qml create mode 100644 example/MainPage.qml create mode 100644 example/SettingPage.qml create mode 100644 example/T_Controls.qml create mode 100644 example/T_Typography.qml create mode 100644 example/TaoFrameLessView.h create mode 100644 example/TaoFrameLessView_unix.cpp create mode 100644 example/TaoFrameLessView_win.cpp delete mode 100644 example/main.qml create mode 100644 src/Def.cpp create mode 100644 src/Def.h create mode 100644 src/FluApp.cpp create mode 100644 src/FluApp.h create mode 100644 src/Fluent.cpp create mode 100644 src/Fluent.h create mode 100644 src/FramelessView.h create mode 100644 src/FramelessView_unix.cpp create mode 100644 src/FramelessView_win.cpp create mode 100644 src/build-preset/plugin.qmltypes delete mode 100644 src/controls/FilledButton.qml rename src/controls/{StandardButton.qml => FluButton.qml} (70%) create mode 100644 src/controls/FluCheckBox.qml create mode 100644 src/controls/FluComboBox.qml create mode 100644 src/controls/FluDropDownButton.qml create mode 100644 src/controls/FluFilledButton.qml create mode 100644 src/controls/FluIconButton.qml create mode 100644 src/controls/FluProgressBar.qml create mode 100644 src/controls/FluProgressRing.qml create mode 100644 src/controls/FluRadioButton.qml create mode 100644 src/controls/FluSlider.qml create mode 100644 src/controls/FluText.qml create mode 100644 src/controls/FluTextBox.qml create mode 100644 src/controls/FluTimePicker.qml create mode 100644 src/controls/FluToggleSwitch.qml delete mode 100644 src/create_qmltypes.bat create mode 100644 src/res/font/fontawesome-webfont.ttf create mode 100644 src/stdafx.h create mode 100644 src/styles/Theme.qml create mode 100644 src/styles/Typography.qml diff --git a/example/App.qml b/example/App.qml new file mode 100644 index 0000000..67daac1 --- /dev/null +++ b/example/App.qml @@ -0,0 +1,21 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 +import FluentUI 1.0 + +Window { + id:app + + Component.onCompleted: { + FluApp.setAppWindow(app) + FluApp.routes = { + "/":"qrc:/MainPage.qml", + "/Setting":"qrc:/SettingPage.qml" + } + FluApp.initialRoute = "/" + FluApp.run() + } + +} diff --git a/example/MainPage.qml b/example/MainPage.qml new file mode 100644 index 0000000..707fabe --- /dev/null +++ b/example/MainPage.qml @@ -0,0 +1,102 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 +import FluentUI 1.0 + +Rectangle { + id:rootwindow + width: 800 + height: 600 + color : "#F3F3F3" + + ListModel{ + id:nav_items + ListElement{ + text:"Controls" + page:"qrc:/T_Controls.qml" + } + ListElement{ + text:"Typography" + page:"qrc:/T_Typography.qml" + } + } + + ListView{ + id:nav_list + anchors{ + top: parent.top + bottom: parent.bottom + topMargin: 20 + bottomMargin: 20 + } + width: 160 + model: nav_items + delegate: Item{ + height: 38 + width: nav_list.width + + Rectangle{ + color: { + if(nav_list.currentIndex === index){ + return "#EAEAEB" + } + return item_mouse.containsMouse? "#EAEAEA" : "#00000000" + } + radius: 4 + anchors{ + top: parent.top + bottom: parent.bottom + left: parent.left + right: parent.right + topMargin: 2 + bottomMargin: 2 + leftMargin: 6 + rightMargin: 6 + } + } + + MouseArea{ + id:item_mouse + hoverEnabled: true + anchors.fill: parent + onClicked: { + nav_list.currentIndex = index + } + } + + Text{ + text:model.text + anchors.centerIn: parent + } + + } + } + + Rectangle{ + color: "#FFFFFF" + radius: 10 + clip: true + anchors{ + left: nav_list.right + leftMargin: 2 + top: parent.top + topMargin: 20 + right: parent.right + rightMargin: 10 + bottom: parent.bottom + bottomMargin: 20 + } + border.width: 1 + border.color: "#EEEEEE" + + Loader{ + anchors.fill: parent + anchors.margins:20 + source: nav_items.get(nav_list.currentIndex).page + } + + } + +} diff --git a/example/SettingPage.qml b/example/SettingPage.qml new file mode 100644 index 0000000..568f325 --- /dev/null +++ b/example/SettingPage.qml @@ -0,0 +1,17 @@ +import QtQuick 2.15 +import FluentUI 1.0 + +Item { + + width: 500 + height: 500 + + + FluText{ + text:"Display" + fontStyle: FluText.Display + anchors.centerIn: parent + + } + +} diff --git a/example/T_Controls.qml b/example/T_Controls.qml new file mode 100644 index 0000000..ee02072 --- /dev/null +++ b/example/T_Controls.qml @@ -0,0 +1,52 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Item { + + ColumnLayout{ + spacing: 5 + + + FluText{ + text:"Controls" + fontStyle: FluText.TitleLarge + } + + + + FluButton{ + + Layout.topMargin: 20 + } + + FluFilledButton{ + onClicked:{ + FluApp.navigate("/Setting") + console.debug("FluFilledButton:"+Window.window.x) + } + } + + FluFilledButton{ + disabled: true + onClicked:{ + console.debug("FluFilledButton-disabled") + } + } + + FluIconButton{ + Component.onCompleted: { + + } + icon:FluentIcons.FA_android + } + + FluToggleSwitch{ + + } + + } + + +} diff --git a/example/T_Typography.qml b/example/T_Typography.qml new file mode 100644 index 0000000..4374c48 --- /dev/null +++ b/example/T_Typography.qml @@ -0,0 +1,54 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Item { + + ColumnLayout{ + + spacing: 5 + + FluText{ + text:"Display" + fontStyle: FluText.Display + } + + FluText{ + text:"Title Large" + fontStyle: FluText.TitleLarge + } + + FluText{ + text:"Title" + fontStyle: FluText.Title + } + + FluText{ + text:"Subtitle" + fontStyle: FluText.Subtitle + } + + FluText{ + text:"Body Large" + fontStyle: FluText.BodyLarge + } + + FluText{ + text:"Body Strong" + fontStyle: FluText.BodyStrong + } + + FluText{ + text:"Body" + fontStyle: FluText.Body + } + + FluText{ + text:"Caption" + fontStyle: FluText.Caption + } + + + } + +} diff --git a/example/TaoFrameLessView.h b/example/TaoFrameLessView.h new file mode 100644 index 0000000..7e5c6a2 --- /dev/null +++ b/example/TaoFrameLessView.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include + +//无边框窗口,主要用来实现自定义标题栏。 +//Windows平台支持拖动和改变大小,支持Aero效果 +//非Windows平台,去掉边框,不做其它处理。由Qml模拟resize和拖动。 +class TaoFrameLessViewPrivate; +class TaoFrameLessView : public QQuickView +{ + Q_OBJECT + using Super = QQuickView; + Q_PROPERTY(bool isMax READ isMax NOTIFY isMaxChanged) + Q_PROPERTY(bool isFull READ isFull NOTIFY isFullChanged) +public: + explicit TaoFrameLessView(QWindow *parent = nullptr); + ~TaoFrameLessView(); + void moveToScreenCenter(); + bool isMax() const; + bool isFull() const; + QQuickItem *titleItem() const; + + static QRect calcCenterGeo(const QRect &screenGeo, const QSize &normalSize); +public slots: + void setIsMax(bool isMax); + void setIsFull(bool isFull); + void setTitleItem(QQuickItem* item); + +signals: + void isMaxChanged(bool isMax); + void isFullChanged(bool isFull); + void mousePressed(int xPos, int yPos, int button); + +protected: + void showEvent(QShowEvent *e) override; + void resizeEvent(QResizeEvent *e) override; +# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; +# else + bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; +# endif + void mousePressEvent(QMouseEvent* event) override + { +# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + emit mousePressed(event->position().x(), event->position().y(), event->button()); +#else + emit mousePressed(event->x(), event->y(), event->button()); +#endif + Super::mousePressEvent(event); + } + +private: + TaoFrameLessViewPrivate *d; +}; diff --git a/example/TaoFrameLessView_unix.cpp b/example/TaoFrameLessView_unix.cpp new file mode 100644 index 0000000..2ef9940 --- /dev/null +++ b/example/TaoFrameLessView_unix.cpp @@ -0,0 +1,106 @@ +#include "TaoFrameLessView.h" +#include +#include +#include +#include + +class TaoFrameLessViewPrivate +{ +public: + bool m_isMax = false; + bool m_isFull = false; + QQuickItem *m_titleItem = nullptr; +}; +TaoFrameLessView::TaoFrameLessView(QWindow *parent) : Super(parent), d(new TaoFrameLessViewPrivate) +{ + setFlags(Qt::CustomizeWindowHint | Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint); + setResizeMode(SizeRootObjectToView); + + setIsMax(windowState() == Qt::WindowMaximized); + setIsFull(windowState() == Qt::WindowFullScreen); + connect(this, &QWindow::windowStateChanged, this, [&](Qt::WindowState state) { + (void)state; + setIsMax(windowState() == Qt::WindowMaximized); + setIsFull(windowState() == Qt::WindowFullScreen); + }); +} +TaoFrameLessView::~TaoFrameLessView() +{ + delete d; +} +void TaoFrameLessView::showEvent(QShowEvent *e) +{ + Super::showEvent(e); +} +QRect TaoFrameLessView::calcCenterGeo(const QRect &screenGeo, const QSize &normalSize) +{ + int w = normalSize.width(); + int h = normalSize.height(); + int x = screenGeo.x() + (screenGeo.width() - w) / 2; + int y = screenGeo.y() + (screenGeo.height() - h) / 2; + if (screenGeo.width() < w) { + x = screenGeo.x(); + w = screenGeo.width(); + } + if (screenGeo.height() < h) { + y = screenGeo.y(); + h = screenGeo.height(); + } + + return { x, y, w, h }; +} +void TaoFrameLessView::moveToScreenCenter() +{ + auto geo = calcCenterGeo(screen()->availableGeometry(), size()); + if (minimumWidth() > geo.width() || minimumHeight() > geo.height()) { + setMinimumSize(geo.size()); + } + setGeometry(geo); + update(); +} +bool TaoFrameLessView::isMax() const +{ + return d->m_isMax; +} +bool TaoFrameLessView::isFull() const +{ + return d->m_isFull; +} +QQuickItem *TaoFrameLessView::titleItem() const +{ + return d->m_titleItem; +} +void TaoFrameLessView::setIsMax(bool isMax) +{ + if (d->m_isMax == isMax) + return; + + d->m_isMax = isMax; + emit isMaxChanged(d->m_isMax); +} +void TaoFrameLessView::setIsFull(bool isFull) +{ + if(d->m_isFull == isFull) + return; + + d->m_isFull = isFull; + emit isFullChanged(d->m_isFull); +} +void TaoFrameLessView::setTitleItem(QQuickItem *item) +{ + d->m_titleItem = item; +} +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +bool TaoFrameLessView::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) +#else +bool TaoFrameLessView::nativeEvent(const QByteArray &eventType, void *message, long *result) +#endif + +{ + return Super::nativeEvent(eventType, message, result); +} + +void TaoFrameLessView::resizeEvent(QResizeEvent *e) +{ + Super::resizeEvent(e); +} diff --git a/example/TaoFrameLessView_win.cpp b/example/TaoFrameLessView_win.cpp new file mode 100644 index 0000000..da892fa --- /dev/null +++ b/example/TaoFrameLessView_win.cpp @@ -0,0 +1,361 @@ +#include "TaoFrameLessView.h" + +#include +#include +#include +#include + +#include +#include +#include +#include // Fixes error C2504: 'IUnknown' : base class undefined +#include +#include +#include +#pragma comment(lib, "Dwmapi.lib") // Adds missing library, fixes error LNK2019: unresolved +#pragma comment(lib, "User32.lib") +#pragma comment(lib, "Gdi32.lib") +// we cannot just use WS_POPUP style +// WS_THICKFRAME: without this the window cannot be resized and so aero snap, de-maximizing and minimizing won't work +// WS_SYSMENU: enables the context menu with the move, close, maximize, minize... commands (shift + right-click on the task bar item) +// WS_CAPTION: enables aero minimize animation/transition +// WS_MAXIMIZEBOX, WS_MINIMIZEBOX: enable minimize/maximize +enum class Style : DWORD +{ + windowed = WS_OVERLAPPEDWINDOW | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX, + aero_borderless = WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX, + basic_borderless = WS_POPUP | WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX +}; +static bool isCompositionEnabled() +{ + BOOL composition_enabled = FALSE; + bool success = ::DwmIsCompositionEnabled(&composition_enabled) == S_OK; + return composition_enabled && success; +} +static Style selectBorderLessStyle() +{ + return isCompositionEnabled() ? Style::aero_borderless : Style::basic_borderless; +} +static void setShadow(HWND handle, bool enabled) +{ + if (isCompositionEnabled()) + { + static const MARGINS shadow_state[2] { { 0, 0, 0, 0 }, { 1, 1, 1, 1 } }; + ::DwmExtendFrameIntoClientArea(handle, &shadow_state[enabled]); + } +} +static long hitTest(RECT winrect, long x, long y, int borderWidth) +{ + // 鼠标区域位于窗体边框,进行缩放 + if ((x >= winrect.left) && (x < winrect.left + borderWidth) && (y >= winrect.top) && (y < winrect.top + borderWidth)) + { + return HTTOPLEFT; + } + else if (x < winrect.right && x >= winrect.right - borderWidth && y >= winrect.top && y < winrect.top + borderWidth) + { + return HTTOPRIGHT; + } + else if (x >= winrect.left && x < winrect.left + borderWidth && y < winrect.bottom && y >= winrect.bottom - borderWidth) + { + return HTBOTTOMLEFT; + } + else if (x < winrect.right && x >= winrect.right - borderWidth && y < winrect.bottom && y >= winrect.bottom - borderWidth) + { + return HTBOTTOMRIGHT; + } + else if (x >= winrect.left && x < winrect.left + borderWidth) + { + return HTLEFT; + } + else if (x < winrect.right && x >= winrect.right - borderWidth) + { + return HTRIGHT; + } + else if (y >= winrect.top && y < winrect.top + borderWidth) + { + return HTTOP; + } + else if (y < winrect.bottom && y >= winrect.bottom - borderWidth) + { + return HTBOTTOM; + } + else + { + return 0; + } +} + +static bool isMaxWin(QWindow* win) +{ + return win->windowState() == Qt::WindowMaximized; +} +static bool isFullWin(QQuickView* win) +{ + return win->windowState() == Qt::WindowFullScreen; +} + +class TaoFrameLessViewPrivate +{ +public: + bool m_firstRun = true; + bool m_isMax = false; + bool m_isFull = false; + QQuickItem* m_titleItem = nullptr; + HMENU mMenuHandler = NULL; + bool borderless = true; // is the window currently borderless + bool borderless_resize = true; // should the window allow resizing by dragging the borders while borderless + bool borderless_drag = true; // should the window allow moving my dragging the client area + bool borderless_shadow = true; // should the window display a native aero shadow while borderless + void setBorderLess(HWND handle, bool enabled) + { + auto newStyle = enabled ? selectBorderLessStyle() : Style::windowed; + auto oldStyle = static_cast