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