This commit is contained in:
zhuzihcu 2023-03-02 18:21:43 +08:00
parent 91be0e4da2
commit 1b5223a7d5
23 changed files with 746 additions and 361 deletions

12
example/InstallHelper.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "InstallHelper.h"
InstallHelper::InstallHelper(QObject *parent)
: QObject{parent}
{
installing(false);
}
void InstallHelper::install(const QString& path,bool isHome,bool isStartMenu){
installing(true);
qDebug()<<path;
}

20
example/InstallHelper.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef INSTALLHELPER_H
#define INSTALLHELPER_H
#include <QObject>
#include <QDebug>
#include "stdafx.h"
class InstallHelper : public QObject
{
Q_OBJECT
Q_PROPERTY_AUTO(bool,installing)
public:
explicit InstallHelper(QObject *parent = nullptr);
Q_INVOKABLE void install(const QString& path,bool isHome,bool isStartMenu);
signals:
};
#endif // INSTALLHELPER_H

View File

@ -1,5 +1,8 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3 as Dialogs
import Qt.labs.platform 1.1
import UI 1.0
import FluentUI 1.0
FluWindow {
@ -7,15 +10,37 @@ FluWindow {
id:window
width: 800
height: 400
maximumSize: Qt.size(800,400)
minimumSize: Qt.size(800,400)
minimumWidth:800
maximumWidth:800
minimumHeight:400
maximumHeight:400
title:"安装向导"
property string installPath: "C:\\Program Files"
property string installName: "FluentUI"
FluAppBar{
id:appbar
title: "安装向导"
}
Item{
id:data
InstallHelper{
id:helper
}
Dialogs.FileDialog {
id: fileDialog
selectFolder: true
folder: "file:///"+installPath
onAccepted: {
installPath = String(fileDialog.fileUrls[0]).replace("file:///","").replace(RegExp("/",'g'),"\\")
}
}
}
ColumnLayout{
width: parent.width
@ -36,18 +61,37 @@ FluWindow {
}
FluTextBox{
id:textbox_path
Layout.preferredHeight: 40
Layout.fillWidth: true
text:"C:\\Program Files\\RustDesk"
text:installPath+ "\\" +installName
readOnly:true
}
FluButton{
text:"更改路径"
Layout.rightMargin: 30
onClicked: {
fileDialog.open()
}
}
}
FluCheckBox{
id:checkbox_startmenu
Layout.topMargin: 20
Layout.leftMargin: 30
checked: true
text:"创建启动菜单快捷方式"
}
FluCheckBox{
id:checkbox_home
Layout.leftMargin: 30
Layout.topMargin: 5
checked: true
text:"创建桌面图标"
}
Item{
width: 1
Layout.fillHeight: true
@ -77,6 +121,9 @@ FluWindow {
}
FluFilledButton{
text:"同意并安装"
onClicked: {
helper.install(textbox_path.text,checkbox_home.checked,checkbox_startmenu.checked)
}
}
FluButton{
text:"不安装直接运行"
@ -88,4 +135,34 @@ FluWindow {
}
}
}
Rectangle{
anchors.fill: parent
visible: helper.installing
color: "#80000000"
MouseArea{
anchors.fill: parent
hoverEnabled: true
}
FluProgressBar{
id:progress
anchors.centerIn: parent
}
FluText{
text:"正在安装..."
color:"#FFFFFF"
font.pixelSize: 20
anchors{
horizontalCenter: progress.horizontalCenter
bottom: progress.top
bottomMargin: 10
}
}
}
}

View File

@ -3,6 +3,7 @@ import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import FluentUI 1.0
FluWindow {
@ -10,51 +11,79 @@ FluWindow {
width: 800
height: 600
title: "FluentUI"
minimumSize: Qt.size(600,400)
// property var maximumSize
minimumWidth: 600
minimumHeight: 400
FluAppBar{
id:appbar
title: "FluentUI"
}
ListModel{
id:nav_items
ListElement{
text:"Buttons"
page:"qrc:/T_Buttons.qml"
Item{
id:data
ListModel{
id:nav_items
ListElement{
text:"Buttons"
page:"qrc:/T_Buttons.qml"
}
ListElement{
text:"TextBox"
page:"qrc:/T_TextBox.qml"
}
ListElement{
text:"ToggleSwitch"
page:"qrc:/T_ToggleSwitch.qml"
}
ListElement{
text:"Slider"
page:"qrc:/T_Slider.qml"
}
ListElement{
text:"InfoBar"
page:"qrc:/T_InfoBar.qml"
}
ListElement{
text:"Progress"
page:"qrc:/T_Progress.qml"
}
ListElement{
text:"Rectangle"
page:"qrc:/T_Rectangle.qml"
}
ListElement{
text:"Awesome"
page:"qrc:/T_Awesome.qml"
}
ListElement{
text:"Typography"
page:"qrc:/T_Typography.qml"
}
}
ListElement{
text:"TextBox"
page:"qrc:/T_TextBox.qml"
FluMenu{
id:menu
FluMenuItem{
text:"123"
}
FluMenuItem{
text:"456"
}
}
ListElement{
text:"ToggleSwitch"
page:"qrc:/T_ToggleSwitch.qml"
}
FluIconButton{
icon:FluentIcons.FA_navicon
anchors{
left: parent.left
bottom: parent.bottom
leftMargin: 12
bottomMargin: 12
}
ListElement{
text:"Slider"
page:"qrc:/T_Slider.qml"
}
ListElement{
text:"InfoBar"
page:"qrc:/T_InfoBar.qml"
}
ListElement{
text:"Progress"
page:"qrc:/T_Progress.qml"
}
ListElement{
text:"Rectangle"
page:"qrc:/T_Rectangle.qml"
}
ListElement{
text:"Awesome"
page:"qrc:/T_Awesome.qml"
}
ListElement{
text:"Typography"
page:"qrc:/T_Typography.qml"
onClicked:{
menu.popup()
}
}
@ -64,8 +93,9 @@ FluWindow {
top: appbar.bottom
bottom: parent.bottom
topMargin: 20
bottomMargin: 20
bottomMargin: 52
}
clip: true
width: 160
model: nav_items
delegate: Item{

View File

@ -1,4 +1,5 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import FluentUI 1.0
@ -13,18 +14,32 @@ Item {
FluTextBox{
id:text_box
placeholderText: "搜索"
placeholderText: "请输入关键字"
anchors{
topMargin: 20
top:title.bottom
}
}
FluFilledButton{
text:"搜索"
anchors{
left: text_box.right
verticalCenter: text_box.verticalCenter
leftMargin: 14
}
onClicked: {
grid_view.model = FluApp.awesomelist(text_box.text)
}
}
GridView{
id:grid_view
cellWidth: 120
cellHeight: 60
clip: true
model:FluApp.awesomelist()
ScrollBar.vertical: FluScrollBar {}
anchors{
topMargin: 10
top:text_box.bottom

View File

@ -4,6 +4,7 @@ CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS QT_NO_WARNING_OUTPUT
SOURCES += \
InstallHelper.cpp \
main.cpp
RESOURCES += qml.qrc
@ -42,3 +43,7 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
# PRE_TARGETDEPS += $$OUT_PWD/../bin/FluentUI/lib$${LIBNAME}.a
### 注意:静态库 .so .dylib .dll 是自动安装的Qt qml plugin目录中不需要此步配置
HEADERS += \
InstallHelper.h \
stdafx.h

View File

@ -1,5 +1,6 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "InstallHelper.h"
#if defined(STATICLIB)
#include <FluentUI.h>
@ -7,6 +8,10 @@
int main(int argc, char *argv[])
{
QCoreApplication::setOrganizationName("ZhuZiChu");
QCoreApplication::setOrganizationDomain("https://zhuzichu520.github.io");
QCoreApplication::setApplicationName("FluentUI");
qputenv("QSG_RENDER_LOOP","basic");
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
@ -16,12 +21,15 @@ int main(int argc, char *argv[])
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round);
#endif
// QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
// QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
#if defined(STATICLIB)
FluentUI::create(&engine);
#endif
qmlRegisterType<InstallHelper>("UI",1,0,"InstallHelper");
#if defined(STATICLIB)
FluentUI::create(&engine);
#endif
const QUrl url(QStringLiteral("qrc:/App.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {

21
example/stdafx.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef STDAFX_H
#define STDAFX_H
#define Q_PROPERTY_AUTO(TYPE, M) \
Q_PROPERTY(TYPE M MEMBER _##M NOTIFY M##Changed) \
public: \
Q_SIGNAL void M##Changed(); \
void M(TYPE in_##M) \
{ \
_##M = in_##M; \
Q_EMIT M##Changed(); \
} \
TYPE M() \
{ \
return _##M; \
} \
\
private: \
TYPE _##M;
#endif // STDAFX_H

View File

@ -26,8 +26,6 @@ FluApp::FluApp(QObject *parent)
isFps(true);
}
void FluApp::setAppWindow(QWindow *window){
appWindow = window;
}
@ -43,7 +41,7 @@ void FluApp::navigate(const QString& route){
}
bool isAppWindow = route==initialRoute();
FramelessView *view = new FramelessView();
view->setColor(isDark() ? QColor(0,0,0,1) : QColor(255, 255, 255, 1));
view->setColor(QColor(0,0,0,0));
QObject::connect(view, &QQuickView::statusChanged, view, [&](QQuickView::Status status) {
if (status == QQuickView::Status::Ready) {
Q_EMIT windowReady(view);
@ -65,17 +63,26 @@ bool FluApp::equalsWindow(FramelessView *view,QWindow *window){
return view->winId() == window->winId();
}
QJsonArray FluApp::awesomelist()
QJsonArray FluApp::awesomelist(const QString& keyword)
{
QJsonArray arr;
QMetaEnum enumType = Fluent_Awesome::staticMetaObject.enumerator(Fluent_Awesome::staticMetaObject.indexOfEnumerator("Fluent_AwesomeType"));
for(int i=0; i < enumType.keyCount(); ++i){
QJsonObject obj;
QString name = enumType.key(i);
int icon = enumType.value(i);
obj.insert("name",name);
obj.insert("icon",icon);
arr.append(obj);
if(keyword.isEmpty()){
QJsonObject obj;
obj.insert("name",name);
obj.insert("icon",icon);
arr.append(obj);
}else{
if(name.mid(3).contains(keyword)){
QJsonObject obj;
obj.insert("name",name);
obj.insert("icon",icon);
arr.append(obj);
}
}
}
return arr;
}

View File

@ -32,7 +32,7 @@ public:
Q_INVOKABLE bool equalsWindow(FramelessView *view,QWindow *window);
Q_INVOKABLE QJsonArray awesomelist();
Q_INVOKABLE QJsonArray awesomelist(const QString& keyword = "");
Q_INVOKABLE void clipText(const QString& text);

View File

@ -30,6 +30,10 @@ void Fluent::registerTypes(const char *uri){
qmlRegisterType<WindowHelper>(uri,major,minor,"WindowHelper");
qmlRegisterType(QUrl("qrc:/com.zhuzichu/controls/FluMenu.qml"),uri,major,minor,"FluMenu");
qmlRegisterType(QUrl("qrc:/com.zhuzichu/controls/FluMenuItem.qml"),uri,major,minor,"FluMenuItem");
qmlRegisterType(QUrl("qrc:/com.zhuzichu/controls/FluScrollBar.qml"),uri,major,minor,"FluScrollBar");
qmlRegisterType(QUrl("qrc:/com.zhuzichu/controls/FluMultiLineTextBox.qml"),uri,major,minor,"FluMultiLineTextBox");
qmlRegisterType(QUrl("qrc:/com.zhuzichu/controls/FluDropShadow.qml"),uri,major,minor,"FluDropShadow");
qmlRegisterType(QUrl("qrc:/com.zhuzichu/controls/FluTooltip.qml"),uri,major,minor,"FluTooltip");

View File

@ -9,6 +9,7 @@ class FramelessViewPrivate
public:
bool m_isMax = false;
bool m_isFull = false;
bool m_deleteLater = false;
QQuickItem *m_titleItem = nullptr;
};
FramelessView::FramelessView(QWindow *parent) : Super(parent), d(new FramelessViewPrivate)
@ -58,6 +59,10 @@ void FramelessView::moveToScreenCenter()
setGeometry(geo);
update();
}
void FramelessView::closeDeleteLater(){
d->m_deleteLater = true;
}
bool FramelessView::isMax() const
{
return d->m_isMax;
@ -95,7 +100,6 @@ bool FramelessView::nativeEvent(const QByteArray &eventType, void *message, qint
#else
bool FramelessView::nativeEvent(const QByteArray &eventType, void *message, long *result)
#endif
{
return Super::nativeEvent(eventType, message, result);
}

View File

@ -1,93 +1,17 @@
#include "FramelessView.h"
#include <QGuiApplication>
#include <QQuickItem>
#include <QScreen>
#include <QWindow>
#include <VersionHelpers.h>
#include <WinUser.h>
#include <dwmapi.h>
#include <objidl.h> // Fixes error C2504: 'IUnknown' : base class undefined
#include <windows.h>
#include <windowsx.h>
#include <wtypes.h>
#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
QMap<WId,FramelessView*>* FramelessView::windowCache = new QMap<WId,FramelessView*>;
enum class Style : DWORD
class FramelessViewPrivate
{
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
public:
bool m_isMax = false;
bool m_isFull = false;
bool m_deleteLater = false;
QQuickItem *m_titleItem = nullptr;
};
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)
{
@ -98,53 +22,10 @@ static bool isFullWin(QQuickView* win)
return win->windowState() == Qt::WindowFullScreen;
}
class FramelessViewPrivate
{
public:
bool m_firstRun = true;
bool m_isMax = false;
bool m_isFull = false;
bool m_deleteLater = 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<Style>(::GetWindowLongPtrW(handle, GWL_STYLE));
if (oldStyle != newStyle)
{
borderless = enabled;
//todo 有待研究这个
// ::SetWindowLongPtrW(handle, GWL_STYLE, static_cast<LONG>(newStyle));
// when switching between borderless and windowed, restore appropriate shadow state
setShadow(handle, borderless_shadow && (newStyle != Style::windowed));
// redraw frame
::SetWindowPos(handle, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
::ShowWindow(handle, SW_SHOW);
}
}
void setBorderLessShadow(HWND handle, bool enabled)
{
if (borderless)
{
borderless_shadow = enabled;
setShadow(handle, enabled);
}
}
};
FramelessView::FramelessView(QWindow* parent)
: QQuickView(parent)
, d(new FramelessViewPrivate)
FramelessView::FramelessView(QWindow *parent) : Super(parent), d(new FramelessViewPrivate)
{
//此处不需要设置flags
// setFlags(Qt::CustomizeWindowHint | Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint |
// Qt::WindowSystemMenuHint);
setFlags(Qt::CustomizeWindowHint | Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
setResizeMode(SizeRootObjectToView);
setIsMax(windowState() == Qt::WindowMaximized);
@ -154,79 +35,26 @@ FramelessView::FramelessView(QWindow* parent)
setIsMax(windowState() == Qt::WindowMaximized);
setIsFull(windowState() == Qt::WindowFullScreen);
});
QObject::connect(this, &QQuickView::statusChanged, this, [&](QQuickView::Status status) {
if (status == QQuickView::Status::Ready) {
FramelessView::windowCache->insert(this->winId(),this);
}
});
}
void FramelessView::showEvent(QShowEvent* e)
{
if (d->m_firstRun)
{
d->m_firstRun = false;
//第一次show的时候设置无边框。不在构造函数中设置。取winId会触发QWindowsWindow::create,直接创建win32窗口,引起错乱(win7 或者虚拟机启动即黑屏)。
d->setBorderLess((HWND)(winId()), d->borderless);
{
// Qt 5.15.2 的bug; 问题复现及解决方法当使用WM_NCCALCSIZE 修改非客户区大小后移动窗口到其他屏幕时qwindows.dll 源码 qwindowswindow.cpp:2447
// updateFullFrameMargins() 函数 处会调用qwindowswindow.cpp:2453 的
// calculateFullFrameMargins函数重新获取默认的非客户区大小导致最外层窗口移动屏幕时会触发resize消息引起40像素左右的黑边故此处创建Menu
// 使其调用qwindowswindow.cpp:2451 的 QWindowsContext::forceNcCalcSize() 函数计算非客户区大小
//已知负面效果: 引入win32 MENU后Qt程序中如果有alt开头的快捷键会不生效被Qt滤掉了需要修改Qt源码
// QWindowsKeyMapper::translateKeyEventInternal 中的
// if (msgType == WM_SYSKEYDOWN && (nModifiers & AltAny) != 0 && GetMenu(msg.hwnd) != nullptr)
// return false;
// 这两行屏蔽掉
d->mMenuHandler = ::CreateMenu();
::SetMenu((HWND)winId(), d->mMenuHandler);
}
}
Super::showEvent(e);
}
FramelessView::~FramelessView()
{
if (d->mMenuHandler != NULL)
{
::DestroyMenu(d->mMenuHandler);
}
FramelessView::windowCache->remove(this->winId());
delete d;
}
bool FramelessView::isMax() const
void FramelessView::showEvent(QShowEvent *e)
{
return d->m_isMax;
Super::showEvent(e);
}
bool FramelessView::isFull() const
{
return d->m_isFull;
}
QQuickItem* FramelessView::titleItem() const
{
return d->m_titleItem;
}
void FramelessView::setTitleItem(QQuickItem* item)
{
d->m_titleItem = item;
}
QRect FramelessView::calcCenterGeo(const QRect& screenGeo, const QSize& normalSize)
QRect FramelessView::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)
{
if (screenGeo.width() < w) {
x = screenGeo.x();
w = screenGeo.width();
}
if (screenGeo.height() < h)
{
if (screenGeo.height() < h) {
y = screenGeo.y();
h = screenGeo.height();
}
@ -236,18 +64,28 @@ QRect FramelessView::calcCenterGeo(const QRect& screenGeo, const QSize& normalSi
void FramelessView::moveToScreenCenter()
{
auto geo = calcCenterGeo(screen()->availableGeometry(), size());
if (minimumWidth() > geo.width() || minimumHeight() > geo.height())
{
if (minimumWidth() > geo.width() || minimumHeight() > geo.height()) {
setMinimumSize(geo.size());
}
setGeometry(geo);
update();
}
void FramelessView::closeDeleteLater(){
d->m_deleteLater = true;
}
bool FramelessView::isMax() const
{
return d->m_isMax;
}
bool FramelessView::isFull() const
{
return d->m_isFull;
}
QQuickItem *FramelessView::titleItem() const
{
return d->m_titleItem;
}
void FramelessView::setIsMax(bool isMax)
{
if (d->m_isMax == isMax)
@ -258,56 +96,43 @@ void FramelessView::setIsMax(bool isMax)
}
void FramelessView::setIsFull(bool isFull)
{
if (d->m_isFull == isFull)
if(d->m_isFull == isFull)
return;
d->m_isFull = isFull;
emit isFullChanged(d->m_isFull);
}
void FramelessView::resizeEvent(QResizeEvent* e)
void FramelessView::setTitleItem(QQuickItem *item)
{
// SetWindowRgn(HWND(winId()), CreateRoundRectRgn(0, 0, width(), height(), 4, 4), true);
Super::resizeEvent(e);
d->m_titleItem = item;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
bool FramelessView::nativeEvent(const QByteArray& eventType, void* message, qintptr* result)
bool FramelessView::nativeEvent(const QByteArray &eventType, void *message, qintptr *result)
#else
bool FramelessView::nativeEvent(const QByteArray& eventType, void* message, long* result)
bool FramelessView::nativeEvent(const QByteArray &eventType, void *message, long *result)
#endif
{
const long border_width = 4;
if (!result)
{
//防御式编程
//一般不会发生这种情况win7一些极端情况会传空指针进来。解决方案是升级驱动、切换到basic主题。
return false;
}
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
// Work-around a bug caused by typo which only exists in Qt 5.11.1
const auto msg = *reinterpret_cast<MSG**>(message);
#else
const auto msg = static_cast<LPMSG>(message);
#endif
if (!msg || !msg->hwnd)
{
return false;
}
switch (msg->message)
{
case WM_CLOSE:{
if(d->m_deleteLater){
deleteLater();
}
}
case WM_NCCALCSIZE: {
#if 1
const auto mode = static_cast<BOOL>(msg->wParam);
const auto clientRect = mode ? &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam)->rgrc[0]) : reinterpret_cast<LPRECT>(msg->lParam);
if (mode == TRUE && d->borderless)
if (mode == TRUE)
{
*result = WVR_REDRAW;
//规避 拖动border进行resize时界面闪烁
@ -333,51 +158,11 @@ bool FramelessView::nativeEvent(const QByteArray& eventType, void* message, long
#endif
break;
}
case WM_NCACTIVATE: {
if (!isCompositionEnabled())
{
// Prevents window frame reappearing on window activation
// in "basic" theme, where no aero shadow is present.
*result = 1;
return true;
}
break;
}
case WM_NCHITTEST: {
if (d->borderless)
{
RECT winrect;
GetWindowRect(HWND(winId()), &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
*result = 0;
if (!isMaxWin(this) && !isFullWin(this))
{ //非最大化、非全屏时,进行命中测试,处理边框拖拽
if(!((maximumHeight()==minimumHeight())&&(maximumWidth()==minimumWidth()))){
*result = hitTest(winrect, x, y, border_width);
if (0 != *result)
{
return true;
}
}
}
if (d->m_titleItem)
{
auto titlePos = d->m_titleItem->mapToGlobal({ 0, 0 });
titlePos = mapFromGlobal(titlePos.toPoint());
auto titleRect = QRect(titlePos.x(), titlePos.y(), d->m_titleItem->width(), d->m_titleItem->height());
double dpr = qApp->devicePixelRatio();
QPoint pos = mapFromGlobal(QPoint(x / dpr, y / dpr));
if (titleRect.contains(pos))
{
*result = HTCAPTION;
return true;
}
}
return false;
}
break;
} // end case WM_NCHITTEST
}
return Super::nativeEvent(eventType, message, result);
}
void FramelessView::resizeEvent(QResizeEvent *e)
{
Super::resizeEvent(e);
}

View File

@ -15,14 +15,15 @@ void WindowHelper::initWindow(FramelessView* window){
this->window = window;
}
void WindowHelper::setMinimumSize(const QSize &size){
this->window->setMinimumSize(size);
void WindowHelper::setMinimumWidth(int width){
this->window->setMinimumWidth(width);
}
void WindowHelper::setMaximumSize(const QSize &size){
this->window->setMaximumSize(size);
void WindowHelper::setMaximumWidth(int width){
this->window->setMaximumWidth(width);
}
void WindowHelper::refreshWindow(){
this->window->setFlag(Qt::NoDropShadowWindowHint,true);
this->window->setFlag(Qt::NoDropShadowWindowHint,false);
void WindowHelper::setMinimumHeight(int height){
this->window->setMinimumHeight(height);
}
void WindowHelper::setMaximumHeight(int height){
this->window->setMaximumHeight(height);
}

View File

@ -16,9 +16,10 @@ public:
Q_INVOKABLE void initWindow(FramelessView* window);
Q_INVOKABLE void setTitle(const QString& text);
Q_INVOKABLE void setMinimumSize(const QSize &size);
Q_INVOKABLE void setMaximumSize(const QSize &size);
Q_INVOKABLE void refreshWindow();
Q_INVOKABLE void setMinimumWidth(int width);
Q_INVOKABLE void setMaximumWidth(int width);
Q_INVOKABLE void setMinimumHeight(int height);
Q_INVOKABLE void setMaximumHeight(int height);
private:
FramelessView* window;

View File

@ -818,14 +818,21 @@ Module {
Parameter { name: "text"; type: "string" }
}
Method {
name: "setMinimumSize"
Parameter { name: "size"; type: "QSize" }
name: "setMinimumWidth"
Parameter { name: "width"; type: "int" }
}
Method {
name: "setMaximumSize"
Parameter { name: "size"; type: "QSize" }
name: "setMaximumWidth"
Parameter { name: "width"; type: "int" }
}
Method {
name: "setMinimumHeight"
Parameter { name: "height"; type: "int" }
}
Method {
name: "setMaximumHeight"
Parameter { name: "height"; type: "int" }
}
Method { name: "refreshWindow" }
}
Component {
prototype: "QQuickRectangle"
@ -883,6 +890,8 @@ Module {
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
Property { name: "checked"; type: "bool" }
Property { name: "text"; type: "string" }
}
Component {
prototype: "QQuickItem"
@ -1040,6 +1049,22 @@ Module {
}
Property { name: "children"; type: "QObject"; isList: true; isReadonly: true }
}
Component {
prototype: "QQuickMenu"
name: "FluentUI/FluMenu 1.0"
exports: ["FluentUI/FluMenu 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "contentData"
}
Component {
prototype: "QQuickMenuItem"
name: "FluentUI/FluMenuItem 1.0"
exports: ["FluentUI/FluMenuItem 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
}
Component {
prototype: "QQuickTextArea"
name: "FluentUI/FluMultiLineTextBox 1.0"
@ -1110,6 +1135,14 @@ Module {
Property { name: "borderWidth"; type: "int" }
Property { name: "contentItem"; type: "QQuickItem"; isList: true; isReadonly: true }
}
Component {
prototype: "QQuickScrollBar"
name: "FluentUI/FluScrollBar 1.0"
exports: ["FluentUI/FluScrollBar 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
}
Component {
prototype: "QQuickItem"
name: "FluentUI/FluSlider 1.0"
@ -1180,18 +1213,21 @@ Module {
defaultProperty: "contentData"
}
Component {
prototype: "QQuickRectangle"
prototype: "QQuickItem"
name: "FluentUI/FluWindow 1.0"
exports: ["FluentUI/FluWindow 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
Property { name: "isMax"; type: "bool" }
Property { name: "title"; type: "string" }
Property { name: "winId"; type: "string" }
Property { name: "minimumSize"; type: "QVariant" }
Property { name: "maximumSize"; type: "QVariant" }
defaultProperty: "content"
Property { name: "window"; type: "QVariant" }
Property { name: "color"; type: "QColor" }
Property { name: "title"; type: "string" }
Property { name: "minimumWidth"; type: "int" }
Property { name: "maximumWidth"; type: "int" }
Property { name: "minimumHeight"; type: "int" }
Property { name: "maximumHeight"; type: "int" }
Property { name: "borderless"; type: "int" }
Property { name: "content"; type: "QQuickItem"; isList: true; isReadonly: true }
Method {
name: "showSuccess"
type: "QVariant"

View File

@ -1,6 +1,85 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0
Item {
id:root
property bool checked: false
property string text: "Check Box"
width: childrenRect.width
height: childrenRect.height
RowLayout{
spacing: 4
Rectangle{
width: 22
height: 22
radius: 4
border.color: {
if(FluApp.isDark){
if(checked){
return Qt.rgba(76/255,160/255,224/255,1)
}
return Qt.rgba(160/255,160/255,160/255,1)
}else{
if(checked){
if(mouse_area.containsMouse){
return Qt.rgba(25/255,117/255,187/255,1)
}
return Qt.rgba(0/255,102/255,180/255,1)
}
return Qt.rgba(136/255,136/255,136/255,1)
}
}
border.width: 1
color: {
if(FluApp.isDark){
if(checked){
if(mouse_area.containsMouse){
return Qt.rgba(74/255,149/255,207/255,1)
}
return Qt.rgba(76/255,160/255,224/255,1)
}
if(mouse_area.containsMouse){
return Qt.rgba(62/255,62/255,62/255,1)
}
return Qt.rgba(45/255,45/255,45/255,1)
}else{
if(checked){
if(mouse_area.containsMouse){
return Qt.rgba(25/255,117/255,187/255,1)
}
return Qt.rgba(0/255,102/255,180/255,1)
}
if(mouse_area.containsMouse){
return Qt.rgba(244/255,244/255,244/255,1)
}
return Qt.rgba(247/255,247/255,247/255,1)
}
}
FluIcon {
anchors.centerIn: parent
icon: FluentIcons.FA_check
iconSize: 15
visible: checked
color: FluApp.isDark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
}
}
FluText{
text:root.text
}
}
MouseArea{
id:mouse_area
anchors.fill: parent
hoverEnabled: true
onClicked: {
checked = !checked
}
}
}

41
src/controls/FluMenu.qml Normal file
View File

@ -0,0 +1,41 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.impl 2.15
import QtQuick.Templates 2.15 as T
import QtQuick.Window 2.15
import QtGraphicalEffects 1.15
T.Menu {
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding)
margins: 0
delegate: FluMenuItem { }
contentItem: ListView {
implicitHeight: contentHeight
model: control.contentModel
interactive: Window.window ? contentHeight > Window.window.height : false
clip: true
currentIndex: control.currentIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Item {
implicitWidth: 122
implicitHeight: 30
Rectangle{
anchors.fill: parent
color: "#FFFFFF"
layer.effect: FluDropShadow{}
layer.enabled: true
}
}
}

View File

@ -0,0 +1,29 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.impl 2.15
import QtQuick.Templates 2.15 as T
import QtQuick.Shapes 1.15
T.MenuItem {
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
padding: 0
spacing: 6
contentItem: FluText {
text: control.text
}
background: Rectangle {
implicitWidth: 120
implicitHeight: 30
width: control.width
height: control.height
}
}

View File

@ -0,0 +1,7 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import FluentUI 1.0
ScrollBar {
}

View File

@ -2,27 +2,11 @@
import QtQuick.Window 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0
import QtGraphicalEffects 1.15
Rectangle {
Item {
id:root
property bool isMax: {
if(Window.window == null)
return false
return Window.Maximized === Window.window.visibility
}
property string title: "FluentUI"
property string winId
property var minimumSize
property var maximumSize
Behavior on opacity{
NumberAnimation{
duration: 100
}
}
property var window : {
if(Window.window == null)
@ -30,17 +14,42 @@ Rectangle {
return Window.window
}
onIsMaxChanged: {
if(isMax){
root.anchors.margins = 8*(1/Screen.devicePixelRatio)
root.anchors.fill = parent
}else{
root.anchors.margins = 0
root.anchors.fill = null
property color color: FluApp.isDark ? "#202020" : "#F3F3F3"
property string title: "FluentUI"
property int minimumWidth
property int maximumWidth
property int minimumHeight
property int maximumHeight
property int borderless:{
if(Window.window == null)
return 4
if(Window.window.visibility === Window.Maximized){
return 0
}
return 4
}
default property alias content: container.children
FluWindowResize{}
Behavior on opacity{
NumberAnimation{
duration: 100
}
}
color : FluApp.isDark ? "#202020" : "#F3F3F3"
Rectangle{
id:container
color:root.color
anchors.fill: parent
anchors.margins: borderless
layer.enabled: true
layer.effect: DropShadow {
radius: 5
samples: 4
color: "#40000000"
}
}
Component.onCompleted: {
@ -52,13 +61,18 @@ Rectangle {
if(FluApp.equalsWindow(view,window)){
helper.initWindow(view);
helper.setTitle(title);
if(minimumSize){
helper.setMinimumSize(minimumSize)
if(minimumWidth){
helper.setMinimumWidth(minimumWidth)
}
if(maximumSize){
helper.setMaximumSize(maximumSize)
if(maximumWidth){
helper.setMaximumWidth(maximumWidth)
}
if(minimumHeight){
helper.setMinimumHeight(minimumHeight)
}
if(maximumHeight){
helper.setMaximumHeight(maximumHeight)
}
helper.refreshWindow()
}
}
}

View File

@ -0,0 +1,185 @@
import QtQuick 2.15
import QtQuick.Window 2.15
MouseArea {
property int border: 4
property bool fixedSize: {
if(Window.window == null)
return true
if(Window.window.visibility === Window.Maximized || Window.window.visibility === Window.FullScreen){
return true
}
return (Window.window.minimumWidth === Window.window.maximumWidth && Window.window.minimumHeight === Window.window.maximumHeight)
}
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
preventStealing: true
propagateComposedEvents: true
z: -65535
onReleased: {
Window.window.width = Window.window.width+1
Window.window.width = Window.window.width-1
}
onPressed :
(mouse)=> {
if (fixedSize) {
return;
}
var rc = Qt.rect(0, 0, 0, 0);
let e = 0;
//top-left
rc = Qt.rect(0, 0, border, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
e = Qt.TopEdge | Qt.LeftEdge;
window.startSystemResize(e);
return;
}
//top
rc = Qt.rect(border, 0, window.width-border*2, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
e = Qt.TopEdge;
window.startSystemResize(e);
return;
}
//top-right
rc = Qt.rect(window.width-border, 0, border, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
e = Qt.TopEdge | Qt.RightEdge;
window.startSystemResize(e);
return;
}
//right
rc = Qt.rect(window.width-border, border, border, window.height-border*2);
if (ptInRect(rc, mouse.x, mouse.y)) {
e = Qt.RightEdge;
window.startSystemResize(e);
return;
}
//bottom-right
rc = Qt.rect(window.width-border, window.height-border, border, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
e = Qt.BottomEdge | Qt.RightEdge;
window.startSystemResize(e);
return;
}
//bottom
rc = Qt.rect(border, window.height-border, window.width-border*2, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
e = Qt.BottomEdge;
window.startSystemResize(e);
return;
}
//bottom_left
rc = Qt.rect(0, window.height-border,border, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
e = Qt.BottomEdge | Qt.LeftEdge;
window.startSystemResize(e);
return;
}
//left
rc = Qt.rect(0, border,border, window.height-border*2);
if (ptInRect(rc, mouse.x, mouse.y)) {
e = Qt.LeftEdge;
window.startSystemResize(e);
return;
}
}
onPositionChanged:
(mouse)=> {
if (fixedSize) {
cursorShape = Qt.ArrowCursor;
return;
}
var rc = Qt.rect(0, 0, 0, 0);
//top-left
rc = Qt.rect(0, 0, border, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
cursorShape = Qt.SizeFDiagCursor;
return;
}
//top
rc = Qt.rect(border, 0, window.width-border*2, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
cursorShape = Qt.SizeVerCursor;
return;
}
//top-right
rc = Qt.rect(window.width-border, 0, border, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
cursorShape = Qt.SizeBDiagCursor;
return;
}
//right
rc = Qt.rect(window.width-border, border, border, window.height-border*2);
if (ptInRect(rc, mouse.x, mouse.y)) {
cursorShape = Qt.SizeHorCursor;
return;
}
//bottom-right
rc = Qt.rect(window.width-border, window.height-border, border, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
cursorShape = Qt.SizeFDiagCursor;
return;
}
//bottom
rc = Qt.rect(border, window.height-border, window.width-border*2, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
cursorShape = Qt.SizeVerCursor;
return;
}
//bottom_left
rc = Qt.rect(0, window.height-border,border, border);
if (ptInRect(rc, mouse.x, mouse.y)) {
cursorShape = Qt.SizeBDiagCursor;
return;
}
//left
rc = Qt.rect(0, border,border, window.height-border*2);
if (ptInRect(rc, mouse.x, mouse.y)) {
cursorShape = Qt.SizeHorCursor;
return;
}
//default
cursorShape = Qt.ArrowCursor;
}
onExited: {
cursorShape = Qt.ArrowCursor;
}
function ptInRect(rc, x, y)
{
if ((rc.x <= x && x <= (rc.x + rc.width)) &&
(rc.y <= y && y <= (rc.y + rc.height))) {
return true;
}
return false;
}
}

View File

@ -27,5 +27,9 @@
<file>controls/TFpsMonitor.qml</file>
<file>controls/FluTextBoxBackground.qml</file>
<file>controls/FluMultiLineTextBox.qml</file>
<file>controls/FluWindowResize.qml</file>
<file>controls/FluScrollBar.qml</file>
<file>controls/FluMenu.qml</file>
<file>controls/FluMenuItem.qml</file>
</qresource>
</RCC>