This commit is contained in:
zhuzichu 2023-12-13 16:20:09 +08:00
parent 863c88411b
commit ec2378a07a
30 changed files with 215 additions and 264 deletions

1
3rdparty/framelesshelper vendored Submodule

@ -0,0 +1 @@
Subproject commit 27fcd913b4b21b51d5cca307e47e93d1ae75e1bb

View File

@ -13,8 +13,8 @@ FluWindow {
id:window
title: "FluentUI"
width: 1000
height: 640
width: 960
height: 600
minimumWidth: 520
minimumHeight: 200
launchMode: FluWindowType.SingleTask

View File

@ -16,8 +16,8 @@ FluWindow {
id:window
title: "FluentUI"
width: 1000
height: 640
width: 960
height: 600
minimumWidth: 520
minimumHeight: 200
launchMode: FluWindowType.SingleTask

View File

@ -1,137 +1,6 @@
#include "FluFrameless.h"
#include "FluFrameless.h"
#include <QGuiApplication>
#include <QScreen>
#ifdef Q_OS_WIN
#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "shcore.lib")
#pragma comment(lib, "Gdi32.lib")
#include <Windows.h>
#include <windowsx.h>
#include <winuser.h>
#include <dwmapi.h>
#include <codecvt>
#include <cmath>
static inline QByteArray qtNativeEventType()
{
static const auto result = "windows_generic_MSG";
return result;
}
#endif
FramelessEventFilter::FramelessEventFilter(QQuickWindow* window){
_window = window;
_current = window->winId();
}
bool FramelessEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result){
#ifdef Q_OS_WIN
if ((eventType != qtNativeEventType()) || !message || !result || !_window) {
return false;
}
const auto msg = static_cast<const MSG *>(message);
const HWND hWnd = msg->hwnd;
if (!hWnd) {
return false;
}
const qint64 wid = reinterpret_cast<qint64>(hWnd);
if(wid != _current){
return false;
}
const UINT uMsg = msg->message;
if (!msg || !msg->hwnd)
{
return false;
}
const LPARAM lParam = msg->lParam;
const int borderPadding = 8;
if (uMsg == WM_NCCALCSIZE) {
if (_window->visibility() == QWindow::FullScreen) {
NCCALCSIZE_PARAMS* sz = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
sz->rgrc[0].left += borderPadding;
sz->rgrc[0].top += borderPadding;
sz->rgrc[0].right -= borderPadding;
sz->rgrc[0].bottom -= borderPadding;
*result = WVR_REDRAW;
return true;
}
NCCALCSIZE_PARAMS* sz = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
if ( _window->visibility() == QWindow::Maximized) {
sz->rgrc[0].top += borderPadding;
} else {
sz->rgrc[0].top += 0;
}
sz->rgrc[0].right -= borderPadding;
sz->rgrc[0].bottom -= borderPadding;
sz->rgrc[0].left -= -borderPadding;
*result = WVR_REDRAW;
return true;
}else if (uMsg == WM_NCHITTEST){
const bool isResizable = !(_window->height()==_window->maximumHeight()&&_window->height()==_window->minimumHeight()&&_window->width()==_window->maximumWidth()&&_window->width()==_window->minimumWidth());
RECT winrect;
GetWindowRect(msg->hwnd, &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
if (x >= winrect.left && x < winrect.left + borderPadding &&
y < winrect.bottom && y >= winrect.bottom - borderPadding && isResizable) {
*result = HTBOTTOMLEFT;
return true;
}
if (x < winrect.right && x >= winrect.right - borderPadding &&
y < winrect.bottom && y >= winrect.bottom - borderPadding && isResizable) {
*result = HTBOTTOMRIGHT;
return true;
}
if (x >= winrect.left && x < winrect.left + borderPadding &&
y >= winrect.top && y < winrect.top + borderPadding && isResizable) {
*result = HTTOPLEFT;
return true;
}
if (x < winrect.right && x >= winrect.right - borderPadding &&
y >= winrect.top && y < winrect.top + borderPadding && isResizable) {
*result = HTTOPRIGHT;
return true;
}
if (x >= winrect.left && x < winrect.left + borderPadding && isResizable) {
*result = HTLEFT;
return true;
}
if (x < winrect.right && x >= winrect.right - borderPadding && isResizable) {
*result = HTRIGHT;
return true;
}
if (y < winrect.bottom && y >= winrect.bottom - borderPadding && isResizable) {
*result = HTBOTTOM;
return true;
}
if (y >= winrect.top && y < winrect.top + borderPadding && isResizable) {
*result = HTTOP;
return true;
}
return false;
}else if(uMsg == WM_COMMAND){
SendMessage(msg->hwnd, WM_SYSCOMMAND, msg->wParam, msg->lParam);
*result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
return true;
}else if(uMsg == WM_WINDOWPOSCHANGING){
WINDOWPOS* wp = reinterpret_cast<WINDOWPOS*>(msg->lParam);
if (wp != nullptr && (wp->flags & SWP_NOSIZE) == 0)
{
wp->flags |= SWP_NOCOPYBITS;
*result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
return true;
}
return false;
}
return false;
#endif
return false;
}
FluFrameless::FluFrameless(QObject *parent)
: QObject{parent}
@ -141,18 +10,7 @@ FluFrameless::FluFrameless(QObject *parent)
void FluFrameless::classBegin(){
}
void FluFrameless::refresLayout(){
#ifdef Q_OS_WIN
if(!_window.isNull()){
HWND hWnd = reinterpret_cast<HWND>(_window->winId());
RECT rect;
GetWindowRect(hWnd, &rect);
SetWindowPos(hWnd, nullptr, rect.left, rect.top, rect.right - rect.left,rect.bottom - rect.top,SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE |SWP_FRAMECHANGED);
}
#endif
}
void FluFrameless::updateCursor(Qt::Edges edges){
void FluFrameless::updateCursor(int edges){
switch (edges) {
case 0:
_window->setCursor(Qt::ArrowCursor);
@ -177,20 +35,27 @@ void FluFrameless::updateCursor(Qt::Edges edges){
}
bool FluFrameless::eventFilter(QObject *obj, QEvent *ev){
if (_window->flags()& Qt::FramelessWindowHint) {
static Qt::Edges edges = Qt::Edges();
if (!_window.isNull() && _window->flags()& Qt::FramelessWindowHint) {
static int edges = 0;
const int margin = 8;
switch (ev->type()) {
case QEvent::MouseButtonPress:
if(edges!=0){
updateCursor(edges);
_window->startSystemResize(edges);
_window->startSystemResize(Qt::Edges(edges));
}
break;
case QEvent::MouseButtonRelease:
qDebug() << Q_FUNC_INFO << ev;
edges = Qt::Edges();
updateCursor(edges);
break;
case QEvent::MouseMove: {
if(_window->visibility() == QWindow::Maximized || _window->visibility() == QWindow::FullScreen){
break;
}
if(_window->width() == _window->maximumWidth() && _window->width() == _window->minimumWidth() && _window->height() == _window->maximumHeight() && _window->height() == _window->minimumHeight()){
break;
}
edges = Qt::Edges();
QMouseEvent *event = static_cast<QMouseEvent*>(ev);
QPoint p =
@ -227,37 +92,20 @@ void FluFrameless::componentComplete(){
_window = (QQuickWindow*)o;
o = o->parent();
}
#ifdef Q_OS_WIN
if(!_window.isNull()){
_nativeEvent =new FramelessEventFilter(_window);
qApp->installNativeEventFilter(_nativeEvent);
// MARGINS margins[2]{{0, 0, 0, 0}, {0, 0, 1, 0}};
// HWND hWnd = reinterpret_cast<HWND>(_window->winId());
// DwmExtendFrameIntoClientArea(hWnd, &margins[false]);
refresLayout();
connect(_window,&QWindow::visibilityChanged,this,[=](QWindow::Visibility visibility){ refresLayout(); });
}
#endif
#ifdef Q_OS_LINUX
if(!_window.isNull()){
_window->setFlag(Qt::FramelessWindowHint,true);
_window->update();
QGuiApplication::processEvents();
_window->installEventFilter(this);
}
#endif
}
FluFrameless::~FluFrameless(){
#ifdef Q_OS_WIN
if (_nativeEvent) {
delete _nativeEvent;
_nativeEvent = nullptr;
refresLayout();
}
#endif
#ifdef Q_OS_LINUX
if (!_window.isNull()) {
_window->removeEventFilter(this);
_window->setFlag(Qt::FramelessWindowHint,false);
_window->update();
QGuiApplication::processEvents();
_window->removeEventFilter(this);
}
#endif
}

View File

@ -7,24 +7,6 @@
#include <QAbstractNativeEventFilter>
#include "stdafx.h"
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
using QT_NATIVE_EVENT_RESULT_TYPE = qintptr;
using QT_ENTER_EVENT_TYPE = QEnterEvent;
#else
using QT_NATIVE_EVENT_RESULT_TYPE = long;
using QT_ENTER_EVENT_TYPE = QEvent;
#endif
class FramelessEventFilter : public QAbstractNativeEventFilter
{
public:
FramelessEventFilter(QQuickWindow* window);
bool nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result) override;
public:
QQuickWindow* _window = nullptr;
qint64 _current = 0;
};
class FluFrameless : public QObject, public QQmlParserStatus
{
Q_OBJECT
@ -34,14 +16,12 @@ public:
~FluFrameless();
void classBegin() override;
void componentComplete() override;
Q_INVOKABLE void refresLayout();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
void updateCursor(Qt::Edges edges);
void updateCursor(int edges);
private:
QPointer<QQuickWindow> _window = nullptr;
FramelessEventFilter* _nativeEvent = nullptr;
};
#endif // FLUFRAMELESS_H

View File

@ -74,14 +74,16 @@ Rectangle{
property bool isRestore: win && Window.Maximized === win.visibility
property bool resizable: win && !(win.height === win.maximumHeight && win.height === win.minimumHeight && win.width === win.maximumWidth && win.width === win.minimumWidth)
}
TapHandler {
onTapped: if (tapCount === 2 && d.resizable) btn_maximize.clicked()
gesturePolicy: TapHandler.DragThreshold
MouseArea{
anchors.fill: parent
onPositionChanged: {
d.win.startSystemMove()
}
onDoubleClicked: {
if(d.resizable){
btn_maximize.clicked()
}
}
DragHandler {
target: null
grabPermissions: TapHandler.CanTakeOverFromAnything
onActiveChanged: if (active) { d.win.startSystemMove(); }
}
Row{
anchors{
@ -105,6 +107,49 @@ Rectangle{
anchors.verticalCenter: parent.verticalCenter
}
}
Component{
id:com_mac_buttons
RowLayout{
FluImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_close_normal.png"
hoveredImage: "../Image/btn_close_hovered.png"
pushedImage: "../Image/btn_close_pushed.png"
visible: showClose
onClicked: closeClickListener()
}
FluImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_min_normal.png"
hoveredImage: "../Image/btn_min_hovered.png"
pushedImage: "../Image/btn_min_pushed.png"
onClicked: minClickListener()
visible: showMinimize
}
FluImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_max_normal.png"
hoveredImage: "../Image/btn_max_hovered.png"
pushedImage: "../Image/btn_max_pushed.png"
onClicked: maxClickListener()
visible: d.resizable && showMaximize
}
}
}
FluLoader{
anchors{
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: 10
}
sourceComponent: isMac ? com_mac_buttons : undefined
}
RowLayout{
anchors.right: parent.right
height: control.height

View File

@ -0,0 +1,18 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import FluentUI 1.0
Button{
id:control
property string normalImage: ""
property string hoveredImage: ""
property string pushedImage: ""
background: Item{
implicitHeight: 12
implicitWidth: 12
BorderImage {
anchors.fill: parent
source: control.hovered ? (control.pressed ? control.pushedImage : control.hoveredImage ) : control.normalImage
}
}
}

View File

@ -37,8 +37,9 @@ Window {
property bool showMinimize: true
property bool showMaximize: true
property bool showStayTop: true
flags: Qt.Window | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint
property bool autoMaximize: false
property color resizeBorderColor: FluTheme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(210/255,210/255,210/255,1)
property int resizeBorderWidth: 1
property var closeListener: function(event){
if(closeDestory){
destoryOnClose()
@ -126,18 +127,18 @@ Window {
left: parent.left
right: parent.right
}
height: {
if(FluApp.useSystemAppBar){
return 0
}
return window.fitsAppBarWindows ? 0 : window.appBar.height
}
sourceComponent: FluApp.useSystemAppBar ? undefined : com_app_bar
}
Component{
id:com_app_bar
Item{
data: window.appBar
height: {
if(FluApp.useSystemAppBar){
return 0
}
return window.fitsAppBarWindows ? 0 : childrenRect.height
}
}
}
Item{
@ -225,27 +226,19 @@ Window {
WindowLifecycle{
id:lifecycle
}
FluLoader{
id:loader_window_border
anchors.fill: parent
z:999
sourceComponent: FluApp.useSystemAppBar ? undefined : com_window_border
}
Component{
id:com_window_border
Item{
Rectangle{
anchors.fill: parent
color: Qt.rgba(0,0,0,0)
border.width: 1
visible: FluTools.isLinux()
border.color: {
if(window.active){
return Qt.rgba(51/255,51/255,51/255,1)
}
return Qt.rgba(153/255,153/255,153/255,1)
color:"transparent"
border.width: window.resizeBorderWidth
border.color: window.resizeBorderColor
visible: {
if(FluApp.useSystemAppBar){
return false
}
if(window.visibility == Window.Maximized || window.visibility == Window.FullScreen){
return false
}
return true
}
}
function destoryOnClose(){

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -97,5 +97,15 @@
<file>FluentUI/Controls/FluClip.qml</file>
<file>FluentUI/Controls/FluLoader.qml</file>
<file>FluentUI/Controls/FluShortcutPicker.qml</file>
<file>FluentUI/Image/btn_close_hovered.png</file>
<file>FluentUI/Image/btn_close_normal.png</file>
<file>FluentUI/Image/btn_close_pushed.png</file>
<file>FluentUI/Image/btn_max_hovered.png</file>
<file>FluentUI/Image/btn_max_normal.png</file>
<file>FluentUI/Image/btn_max_pushed.png</file>
<file>FluentUI/Image/btn_min_hovered.png</file>
<file>FluentUI/Image/btn_min_normal.png</file>
<file>FluentUI/Image/btn_min_pushed.png</file>
<file>FluentUI/Controls/FluImageButton.qml</file>
</qresource>
</RCC>

View File

@ -74,14 +74,16 @@ Rectangle{
property bool isRestore: win && Window.Maximized === win.visibility
property bool resizable: win && !(win.height === win.maximumHeight && win.height === win.minimumHeight && win.width === win.maximumWidth && win.width === win.minimumWidth)
}
TapHandler {
onTapped: if (tapCount === 2 && d.resizable) btn_maximize.clicked()
gesturePolicy: TapHandler.DragThreshold
MouseArea{
anchors.fill: parent
onPositionChanged: {
d.win.startSystemMove()
}
onDoubleClicked: {
if(d.resizable){
btn_maximize.clicked()
}
}
DragHandler {
target: null
grabPermissions: TapHandler.CanTakeOverFromAnything
onActiveChanged: if (active) { d.win.startSystemMove(); }
}
Row{
anchors{
@ -105,6 +107,49 @@ Rectangle{
anchors.verticalCenter: parent.verticalCenter
}
}
Component{
id:com_mac_buttons
RowLayout{
FluImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_close_normal.png"
hoveredImage: "../Image/btn_close_hovered.png"
pushedImage: "../Image/btn_close_pushed.png"
visible: showClose
onClicked: closeClickListener()
}
FluImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_min_normal.png"
hoveredImage: "../Image/btn_min_hovered.png"
pushedImage: "../Image/btn_min_pushed.png"
onClicked: minClickListener()
visible: showMinimize
}
FluImageButton{
Layout.preferredHeight: 12
Layout.preferredWidth: 12
normalImage: "../Image/btn_max_normal.png"
hoveredImage: "../Image/btn_max_hovered.png"
pushedImage: "../Image/btn_max_pushed.png"
onClicked: maxClickListener()
visible: d.resizable && showMaximize
}
}
}
FluLoader{
anchors{
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: 10
}
sourceComponent: isMac ? com_mac_buttons : undefined
}
RowLayout{
anchors.right: parent.right
height: control.height

View File

@ -0,0 +1,18 @@
import QtQuick
import QtQuick.Controls
import FluentUI
Button{
id:control
property string normalImage: ""
property string hoveredImage: ""
property string pushedImage: ""
background: Item{
implicitHeight: 12
implicitWidth: 12
BorderImage {
anchors.fill: parent
source: control.hovered ? (control.pressed ? control.pushedImage : control.hoveredImage ) : control.normalImage
}
}
}

View File

@ -36,8 +36,9 @@ Window {
property bool showMinimize: true
property bool showMaximize: true
property bool showStayTop: true
flags: Qt.Window | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint
property bool autoMaximize: false
property color resizeBorderColor: FluTheme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(210/255,210/255,210/255,1)
property int resizeBorderWidth: 1
property var closeListener: function(event){
if(closeDestory){
destoryOnClose()
@ -125,18 +126,18 @@ Window {
left: parent.left
right: parent.right
}
height: {
if(FluApp.useSystemAppBar){
return 0
}
return window.fitsAppBarWindows ? 0 : window.appBar.height
}
sourceComponent: FluApp.useSystemAppBar ? undefined : com_app_bar
}
Component{
id:com_app_bar
Item{
data: window.appBar
height: {
if(FluApp.useSystemAppBar){
return 0
}
return window.fitsAppBarWindows ? 0 : childrenRect.height
}
}
}
Item{
@ -224,27 +225,19 @@ Window {
WindowLifecycle{
id:lifecycle
}
FluLoader{
id:loader_window_border
anchors.fill: parent
z:999
sourceComponent: FluApp.useSystemAppBar ? undefined : com_window_border
}
Component{
id:com_window_border
Item{
Rectangle{
anchors.fill: parent
color: Qt.rgba(0,0,0,0)
border.width: 1
visible: FluTools.isLinux()
border.color: {
if(window.active){
return Qt.rgba(51/255,51/255,51/255,1)
}
return Qt.rgba(153/255,153/255,153/255,1)
color:"transparent"
border.width: window.resizeBorderWidth
border.color: window.resizeBorderColor
visible: {
if(FluApp.useSystemAppBar){
return false
}
if(window.visibility == Window.Maximized || window.visibility == Window.FullScreen){
return false
}
return true
}
}
function destoryOnClose(){

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB