diff --git a/chameleon/CMakeLists.txt b/chameleon/CMakeLists.txt index e7b6ca9f..fd2ad28e 100644 --- a/chameleon/CMakeLists.txt +++ b/chameleon/CMakeLists.txt @@ -24,6 +24,7 @@ set(QML_FILES Frame.qml GroupBox.qml ItemDelegate.qml + ListView.qml MenuItem.qml Menu.qml MenuSeparator.qml diff --git a/chameleon/ListView.qml b/chameleon/ListView.qml new file mode 100644 index 00000000..43fe1d9c --- /dev/null +++ b/chameleon/ListView.qml @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import org.deepin.dtk 1.0 as D + +D.ListView { + +} diff --git a/examples/exhibition/ControlGroup.qml b/examples/exhibition/ControlGroup.qml new file mode 100644 index 00000000..a0b83da3 --- /dev/null +++ b/examples/exhibition/ControlGroup.qml @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.0 +import QtQuick.Window 2.11 +import QtQuick.Layouts 1.11 +import org.deepin.dtk 1.0 +import ".." + + ControlGroup { + title: "磁盘" + ControlGroupItem { + Label { + visible: true + text: "test0111111111111" + } + Label { + visible: true + text: "test13333333333" + } + } + ControlGroupItem { + Rectangle { + width: 100 + height: 100 + color: "red" + border.color: "black" + border.width: 5 + radius: 10 + } + Switch { + checked: true + Layout.alignment: Qt.AlignHCenter + } + } + ControlGroupItem { + Rectangle { + width: 100 + height: 100 + color: "green" + border.color: "black" + border.width: 5 + radius: 10 + } + Button { + width: 100 + height: 100 + Layout.alignment: Qt.AlignHCenter + } + } + } diff --git a/examples/exhibition/qml-qt6.qrc b/examples/exhibition/qml-qt6.qrc index 8d9a8f2d..750b0636 100644 --- a/examples/exhibition/qml-qt6.qrc +++ b/examples/exhibition/qml-qt6.qrc @@ -25,5 +25,6 @@ ToolBar.qml Dialog.qml ProgressBar.qml + ControlGroup.qml diff --git a/qmlplugin/qmlplugin_plugin.cpp b/qmlplugin/qmlplugin_plugin.cpp index 43b84638..0178de15 100644 --- a/qmlplugin/qmlplugin_plugin.cpp +++ b/qmlplugin/qmlplugin_plugin.cpp @@ -34,6 +34,7 @@ #include "private/dquickcontrolpalette_p.h" #include +#include #include #include @@ -206,6 +207,7 @@ void QmlpluginPlugin::registerTypes(const char *uri) dtkRegisterUncreatableType(uri, implUri, 1, 0, "Color", QStringLiteral("Color is only available as enums.")); dtkRegisterUncreatableType(uri, implUri, 1, 0, "PopupHandle", "PopupWindow Attached"); + dtkRegisterUncreatableType(uri, implUri, 1, 0, "PlatformHandle", "PlatformHandle"); qRegisterMetaType(); qRegisterMetaType("ControlColor"); diff --git a/qt6/src/dquickextendregister_p.h b/qt6/src/dquickextendregister_p.h index 69785a40..605d9b42 100644 --- a/qt6/src/dquickextendregister_p.h +++ b/qt6/src/dquickextendregister_p.h @@ -9,6 +9,7 @@ #include #include #include +#include #include DQUICK_BEGIN_NAMESPACE @@ -51,4 +52,11 @@ struct DFontManagerForeign QML_NAMED_ELEMENT(FontManager) }; +struct DPlatformHandleForeign +{ + Q_GADGET + QML_FOREIGN(DPlatformHandle) + QML_NAMED_ELEMENT(PlatformHandle) +}; + DQUICK_END_NAMESPACE diff --git a/qt6/src/dtkdeclarative_qml.qrc b/qt6/src/dtkdeclarative_qml.qrc index 2b88b5b3..899a09ac 100644 --- a/qt6/src/dtkdeclarative_qml.qrc +++ b/qt6/src/dtkdeclarative_qml.qrc @@ -63,6 +63,7 @@ qml/private/ButtonPanel.qml qml/ActionButton.qml qml/ItemDelegate.qml + qml/ListView.qml qml/CheckDelegate.qml qml/TipsSlider.qml qml/SliderTipItem.qml @@ -109,6 +110,8 @@ qml/ButtonIndicator.qml qml/EmbeddedProgressBar.qml qml/WaterProgressBar.qml + qml/ControlGroup.qml + qml/ControlGroupItem.qml qml/private/ProgressBarImpl.qml qml/private/ProgressBarPanel.qml qml/PlaceholderText.qml diff --git a/qt6/src/qml.cmake b/qt6/src/qml.cmake index 965e28db..0a40268a 100644 --- a/qt6/src/qml.cmake +++ b/qt6/src/qml.cmake @@ -52,6 +52,7 @@ set(QML_DTK_CONTROLS "qml/ComboBox.qml" "qml/ActionButton.qml" "qml/ItemDelegate.qml" + "qml/ListView.qml" "qml/CheckDelegate.qml" "qml/TipsSlider.qml" "qml/SliderTipItem.qml" @@ -96,6 +97,9 @@ set(QML_DTK_CONTROLS "qml/EmbeddedProgressBar.qml" "qml/WaterProgressBar.qml" "qml/PlaceholderText.qml" + "qml/ControlGroup.qml" + "qml/ControlGroupItem.qml" + "qml/DragItemsImage.qml" ) foreach(QML_FILE ${QML_DTK_CONTROLS}) diff --git a/qt6/src/qml/ArrowListView.qml b/qt6/src/qml/ArrowListView.qml index a7e53fb6..94bc24f9 100644 --- a/qt6/src/qml/ArrowListView.qml +++ b/qt6/src/qml/ArrowListView.qml @@ -5,6 +5,7 @@ import QtQuick 2.11 import QtQuick.Window 2.11 import QtQuick.Layouts 1.11 +import org.deepin.dtk 1.0 as D import org.deepin.dtk.style 1.0 as DS import org.deepin.dtk.private 1.0 as P diff --git a/qt6/src/qml/ButtonBox.qml b/qt6/src/qml/ButtonBox.qml index 7502dba9..fb5688cc 100644 --- a/qt6/src/qml/ButtonBox.qml +++ b/qt6/src/qml/ButtonBox.qml @@ -6,6 +6,7 @@ import QtQuick 2.11 import QtQuick.Layouts 1.11 import org.deepin.dtk 1.0 as D import org.deepin.dtk.style 1.0 as DS +import org.deepin.dtk.private 1.0 as P Control { id: control @@ -31,4 +32,27 @@ Control { outsideBorderColor: null color2: color1 } + + ParallelAnimation { + running: btnGroup.checkedButton && (btnGroup.checkedButton.x !== backgroundPanel.x || btnGroup.checkedButton.y !== backgroundPanel.y) + NumberAnimation { target: backgroundPanel; property: "x"; to: btnGroup.checkedButton ? btnGroup.checkedButton.x : backgroundPanel.x; duration: 200 } + NumberAnimation { target: backgroundPanel; property: "y"; to: btnGroup.checkedButton ? btnGroup.checkedButton.y : backgroundPanel.y; duration: 200 } + NumberAnimation { target: backgroundPanel; property: "width"; to: btnGroup.checkedButton ? btnGroup.checkedButton.width : backgroundPanel.width; duration: 200 } + NumberAnimation { target: backgroundPanel; property: "height"; to: btnGroup.checkedButton ? btnGroup.checkedButton.height : backgroundPanel.height; duration: 200 } + } + + P.ButtonPanel { + id: backgroundPanel + visible: btnGroup.checkedButton + implicitWidth: visible ? btnGroup.checkedButton.width : 0 + implicitHeight: visible ? btnGroup.checkedButton.height : 0 + button: control + outsideBorderColor: null + color1: D.Palette { + normal { + common: Qt.rgba(0, 0, 0, 0.2) + } + } + color2: color1 + } } diff --git a/qt6/src/qml/CheckDelegate.qml b/qt6/src/qml/CheckDelegate.qml index 8db95ae2..ec1cb380 100644 --- a/qt6/src/qml/CheckDelegate.qml +++ b/qt6/src/qml/CheckDelegate.qml @@ -12,6 +12,8 @@ T.CheckDelegate { id: control property Component content property D.Palette backgroundColor: DS.Style.itemDelegate.checkBackgroundColor + property string indicatorIcon: control.checkState === Qt.Unchecked ? "item_unchecked" : "item_checked" + property bool indicatorVisible: control.checked implicitWidth: DS.Style.control.implicitWidth(control) implicitHeight: DS.Style.control.implicitHeight(control) @@ -26,15 +28,22 @@ T.CheckDelegate { indicator: Loader { x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding y: control.topPadding + (control.availableHeight - height) / 2 - active: control.checked + active: indicatorVisible sourceComponent: D.DciIcon { palette: control.D.DTK.makeIconPalette(control.palette) mode: control.D.ColorSelector.controlState theme: control.D.ColorSelector.controlTheme - name: "menu_select" + name: indicatorIcon sourceSize: Qt.size(DS.Style.itemDelegate.checkIndicatorIconSize, DS.Style.itemDelegate.checkIndicatorIconSize) fallbackToQIcon: false + onNameChanged: { + play(D.DTK.NormalState); + } + Component.onCompleted: { + if (indicatorVisible) + play(D.DTK.NormalState); + } } } @@ -58,12 +67,12 @@ T.CheckDelegate { } } - background: Item { + background: Control { implicitWidth: DS.Style.itemDelegate.width implicitHeight: DS.Style.itemDelegate.height Rectangle { anchors.fill: parent - visible: !checked + visible: !checked && !control.ListView.view color: control.D.ColorSelector.backgroundColor radius: DS.Style.control.radius } @@ -74,4 +83,12 @@ T.CheckDelegate { radius: DS.Style.control.radius } } + + onHoveredChanged: { + if (checked || !ListView.view) + return + + if (ListView.view) + ListView.view.setHoverItem(control.hovered ? control : null) + } } diff --git a/qt6/src/qml/ControlGroup.qml b/qt6/src/qml/ControlGroup.qml new file mode 100644 index 00000000..6f7ddc9c --- /dev/null +++ b/qt6/src/qml/ControlGroup.qml @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.0 +import QtQuick.Layouts 1.11 +import org.deepin.dtk 1.0 as D +import org.deepin.dtk.style 1.0 as DS + +ColumnLayout { + id: root + spacing: 10 + + property string title + property bool isExpanded: true + property int interval: 400 / (itemLayout.children.length) + property int index: 0 + property int titleHeight: 36 + default property alias childItem: itemLayout.children + + Control { + id: title + Layout.fillWidth: true + Layout.preferredHeight: 36 + property int inset: 4 + leftInset: inset + rightInset: inset + topInset: inset + bottomInset: inset + state: "normal" + states: [ + State { + name: "normal" + PropertyChanges { + target: title + inset:4 + } + }, + State { + name: "hovered" + PropertyChanges { + target: title + inset:0 + } + }, + State { + name: "pressed" + PropertyChanges { + target: title + inset: 4 + } + } + ] + transitions: [ + Transition { + from: "normal" + to: "hovered" + NumberAnimation { + properties: "inset" + easing.type: Easing.Linear + duration: 500 + } + }, + Transition { + from: "hovered" + to: "normal" + NumberAnimation { + properties: "inset" + easing.type: Easing.Linear + duration: 500 + } + }, + Transition { + from: "hovered" + to: "pressed" + NumberAnimation { + properties: "inset" + easing.type: Easing.Linear + duration: 500 + } + }, + Transition { + from: "pressed" + to: "hovered" + NumberAnimation { + properties: "inset" + easing.type: Easing.Linear + duration: 500 + } + } + ] + RowLayout { + anchors.fill: parent + spacing: 0 + Label { + Layout.fillWidth: true + Layout.fillHeight: true + text: root.title + font: DTK.fontManager.t5 + verticalAlignment: Qt.AlignVCenter + } + D.DciIcon { + rotation: root.isExpanded ? 0 : - 90 + name: "arrow_ordinary_down" + mode: title.D.ColorSelector.controlState + theme: title.D.ColorSelector.controlTheme + palette: D.DTK.makeIconPalette(title.palette) + sourceSize: Qt.size(36, 36) + + Behavior on rotation { + NumberAnimation { + duration: root.interval + easing.type: Easing.Linear + } + } + } + } + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: root.isExpanded = !root.isExpanded + onPressed: title.state = "pressed" + onReleased: title.state = "hovered" + onEntered: title.state = "hovered" + onExited: title.state = "normal" + } + background: Item { + Loader { + anchors.fill: parent + active: title.hovered + sourceComponent: D.RoundRectangle { + color: DS.Style.itemDelegate.normalColor + radius: DS.Style.control.radius + corners: D.RoundRectangle.TopLeftCorner | D.RoundRectangle.TopRightCorner | D.RoundRectangle.BottomLeftCorner | D.RoundRectangle.BottomRightCorner + } + } + } + } + + Timer { + id: timer + } + + ColumnLayout { + id: itemLayout + Layout.fillHeight: false + } + + onIsExpandedChanged: (isExpanded) => { + for(let i = 0; i < itemLayout.children.length; ++i) { + itemLayout.children[i].isExpanded = !itemLayout.children[i].isExpanded + } + // delay(0, timeout) + } + + function timeout() { + itemLayout.children[index].isExpanded = !itemLayout.children[index].isExpanded + ++index + if (index === itemLayout.children.length) { + timer.stop() + timer.triggered.disconnect(timeout) + index = 0 + } + } + + function delay(delayTime, cb) { + timer.interval = delayTime + timer.repeat = true + timer.triggered.connect(cb) + timer.restart() + } +} diff --git a/qt6/src/qml/ControlGroupItem.qml b/qt6/src/qml/ControlGroupItem.qml new file mode 100644 index 00000000..2a98212a --- /dev/null +++ b/qt6/src/qml/ControlGroupItem.qml @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.0 +import QtQuick.Layouts 1.11 +import QtQml.Models 2.11 +import org.deepin.dtk 1.0 + +RowLayout { + id: root + + property int initY + property bool isExpanded: true + + y: isExpanded ? initY : - parent.parent.titleHeight + opacity: isExpanded ? 1.0 : 0.0 + visible: opacity === 0.0 ? false : true + + Layout.fillWidth: true + Layout.fillHeight: true + + Behavior on y { + NumberAnimation { + duration: parent.parent.interval + easing.type: Easing.Linear + } + } + Behavior on opacity { + NumberAnimation { + duration: parent.parent.interval + easing.type: Easing.Linear + } + } + + Component.onCompleted: { + root.initY = root.y + } +} diff --git a/qt6/src/qml/DialogTitleBar.qml b/qt6/src/qml/DialogTitleBar.qml index 11fa1a82..e8feb26d 100644 --- a/qt6/src/qml/DialogTitleBar.qml +++ b/qt6/src/qml/DialogTitleBar.qml @@ -2,13 +2,13 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later -import QtQuick 2.11 -import QtQuick.Window 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Window +import QtQuick.Layouts import org.deepin.dtk 1.0 as D import org.deepin.dtk.style 1.0 as DS -Control { +Item { id: control z: D.DTK.TopOrder width: Window.window.width @@ -39,15 +39,17 @@ Control { } } - background: D.InWindowBlur { + D.InWindowBlur { id: background visible: false + anchors.fill: parent } - contentItem: ColumnLayout { + ColumnLayout { id: content spacing: 0 + anchors.fill: parent Rectangle { id: separatorTop Layout.preferredWidth: parent.width @@ -63,15 +65,18 @@ Control { Layout.preferredWidth: parent.width Layout.leftMargin: DS.Style.titleBar.leftMargin - D.DciIcon { - id: iconLabel - visible: name !== "" - mode: control.D.ColorSelector.controlState - theme: control.D.ColorSelector.controlTheme - palette: D.DTK.makeIconPalette(control.palette) - sourceSize { - width: DS.Style.dialogWindow.iconSize - height: DS.Style.dialogWindow.iconSize + Control { + id: iconControl + visible: iconLabel.name !== "" + contentItem: D.DciIcon { + id: iconLabel + mode: iconControl.D.ColorSelector.controlState + theme: iconControl.D.ColorSelector.controlTheme + palette: D.DTK.makeIconPalette(iconControl.palette) + sourceSize { + width: DS.Style.dialogWindow.iconSize + height: DS.Style.dialogWindow.iconSize + } } } diff --git a/qt6/src/qml/DialogWindow.qml b/qt6/src/qml/DialogWindow.qml index b208f881..7bd44968 100644 --- a/qt6/src/qml/DialogWindow.qml +++ b/qt6/src/qml/DialogWindow.qml @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later -import QtQuick 2.11 -import QtQuick.Layouts 1.11 -import QtQuick.Window 2.11 +import QtQuick +import QtQuick.Layouts +import QtQuick.Window import org.deepin.dtk 1.0 as D import org.deepin.dtk.style 1.0 as DS @@ -31,10 +31,11 @@ Window { default property alias content: contentLoader.children property alias palette : content.palette - Control { + Item { id: content palette: control.active ? D.DTK.palette : D.DTK.inactivePalette - contentItem: ColumnLayout { + implicitHeight: childrenRect.height + ColumnLayout { id: layout spacing: 0 diff --git a/qt6/src/qml/DragItemsImage.qml b/qt6/src/qml/DragItemsImage.qml new file mode 100644 index 00000000..e978a683 --- /dev/null +++ b/qt6/src/qml/DragItemsImage.qml @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.15 + +Item { + id: control + property list items + + signal aboutToGrabToImage(Item item) + signal grabToImageFinished(Item item) + + onItemsChanged: function updateGeomotry() { + var w = 0 + var h = 0 + for (var i = 0; i < control.items.length; ++i) { + let item = control.items[i] + w = Math.max(w, item.width) + h = Math.max(h, item.height) + } + control.width = w + 30 + control.height = h * 2 + 30 + } + + function addItem(item) { + if (control.items.include(item)) + return + + items.push(item) + } + + function removeItem(item) { + if (!control.items.include(item)) + return + + var index = control.items.indexOf(item); + if (index !== -1) { + control.items.splice(index, 1); + } + } + + Repeater { + model: control.items.length + delegate: Image { + id: img + anchors.centerIn: parent + antialiasing: true + rotation: index ? (index % 2 === 0 ? 10 : -10) : 0 + opacity: (1 - index * 0.2) + z: -index + + Component.onCompleted: { + let item = control.items[index] + if (!item) + return + + img.width = item.width + img.height = item.height + + aboutToGrabToImage(item) + item.grabToImage(function(result) { + img.source = result.url + grabToImageFinished(item) + }) + } + } + } + + RoundButton { + id: number + checked: true + anchors.right: dragItem.right + anchors.top: dragItem.top + anchors.margins: 8 + implicitWidth: 24 + implicitHeight: 24 + text: control.items.length > 1 ? control.items.length : "" + opacity: control.items.length > 1 ? 1 : 0 + } +} diff --git a/qt6/src/qml/FloatingMessage.qml b/qt6/src/qml/FloatingMessage.qml index 5b1d9cb9..c80a09bd 100644 --- a/qt6/src/qml/FloatingMessage.qml +++ b/qt6/src/qml/FloatingMessage.qml @@ -23,23 +23,39 @@ D.FloatingMessageContainer { width: DS.Style.floatingMessage.closeButtonSize height: DS.Style.floatingMessage.closeButtonSize } - onClicked: D.DTK.closeMessage(control) + onClicked: floatingPanel.state = "small" } + onDelayClose: floatingPanel.state = "small" duration: 4000 panel: FloatingPanel { id: floatingPanel + property bool animationFinished: false implicitWidth: DS.Style.control.contentImplicitWidth(floatingPanel) leftPadding: 10 rightPadding: 10 topPadding: 0 bottomPadding: 0 + opacity: 0.0 + state: "small" + onAnimationFinishedChanged: (finished) => { + if (floatingPanel.animationFinished === true) { + D.DTK.closeMessage(control) + } + } + Timer { + id: timer + interval: 100; running: false; repeat: false + onTriggered: floatingPanel.state = "normal" + } + Component.onCompleted: { + timer.running = true + } contentItem: RowLayout { height: DS.Style.floatingMessage.minimumHeight width: Math.min(DS.Style.floatingMessage.maximumWidth, children.width + floatingPanel.leftPadding - floatingPanel.rightPadding) spacing: 10 - Loader { id: iconLoader Layout.alignment: Qt.AlignVCenter @@ -77,6 +93,69 @@ D.FloatingMessageContainer { visible: active sourceComponent: button } + + ParallelAnimation { + running: closeButton.item ? closeButton.item.hovered : false + NumberAnimation { target: closeButton; property: "scale"; to: 1.25; duration: 500 } + NumberAnimation { target: closeButton; property: "rotation"; to: 90; duration: 500 } + } + ParallelAnimation { + running: closeButton.item ? !closeButton.item.hovered : false + NumberAnimation { target: closeButton; property: "scale"; to: 1; duration: 500 } + NumberAnimation { target: closeButton; property: "rotation"; to: 0; duration: 500 } + } } + + states: [ + State { + name: "normal" + PropertyChanges { + target: floatingPanel + y: 0 + opacity: 1.0 + scale: 1.0 + } + }, + State { + name: "small" + PropertyChanges { + target: floatingPanel + y: floatingPanel.parent ? floatingPanel.parent.height : 0 + opacity: 0.0 + scale: 0.2 + } + } + ] + + transitions: [ + Transition { + from: "normal" + to: "small" + SequentialAnimation { + NumberAnimation { + properties: "y, opacity, scale" + easing.type: Easing.Linear + duration: 300 + } + PropertyAction { target: floatingPanel; property: "animationFinished"; value: true; } + } + }, + Transition { + from: "small" + to: "normal" + SequentialAnimation { + PropertyAction { + target: floatingPanel + property: "y" + value: floatingPanel.parent ? floatingPanel.parent.height : 0 + } + NumberAnimation { + properties: "y, opacity, scale" + easing.type: Easing.Linear + duration: 300 + } + } + } + ] } } diff --git a/qt6/src/qml/FlowStyle.qml b/qt6/src/qml/FlowStyle.qml index fc8dc607..37641dbf 100644 --- a/qt6/src/qml/FlowStyle.qml +++ b/qt6/src/qml/FlowStyle.qml @@ -814,7 +814,7 @@ QtObject { } property D.Palette handleGradientColor: D.Palette { - normal: D.DTK.makeColor(D.Color.Highlight).hue(-10).saturation(+40).lightness(+20) + normal: D.DTK.makeColor(D.Color.Highlight).hue(-0).saturation(+30).lightness(+30) } } diff --git a/qt6/src/qml/ItemDelegate.qml b/qt6/src/qml/ItemDelegate.qml index 0a2acca4..f8c5a205 100644 --- a/qt6/src/qml/ItemDelegate.qml +++ b/qt6/src/qml/ItemDelegate.qml @@ -1,12 +1,13 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022-2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later -import QtQuick 2.11 +import QtQuick import QtQuick.Templates as T import QtQuick.Layouts 1.11 import org.deepin.dtk 1.0 as D import org.deepin.dtk.style 1.0 as DS +import org.deepin.dtk.private 1.0 as P T.ItemDelegate { id: control @@ -17,6 +18,12 @@ T.ItemDelegate { property Component content property D.Palette checkedTextColor: DS.Style.checkedButton.text property int corners: D.RoundRectangle.TopLeftCorner | D.RoundRectangle.TopRightCorner | D.RoundRectangle.BottomLeftCorner | D.RoundRectangle.BottomRightCorner + property bool dragActive: false + // drag + property bool enableDrag: false + Drag.mimeData: { "text/plain": control.text } + Drag.dragType: Drag.Automatic + function getCornersForBackground(index, count) { if (count <= 1) return D.RoundRectangle.TopLeftCorner | D.RoundRectangle.TopRightCorner | D.RoundRectangle.BottomLeftCorner | D.RoundRectangle.BottomRightCorner @@ -35,7 +42,7 @@ T.ItemDelegate { spacing: DS.Style.control.spacing checkable: true autoExclusive: true - palette.windowText: checked && !control.cascadeSelected ? D.ColorSelector.checkedTextColor : undefined + palette.windowText: checked && !control.cascadeSelected && control.backgroundVisible && !dragActive? D.ColorSelector.checkedTextColor : undefined D.DciIcon.mode: D.ColorSelector.controlState D.DciIcon.theme: D.ColorSelector.controlTheme @@ -83,12 +90,13 @@ T.ItemDelegate { } background: Item { + visible: backgroundVisible implicitWidth: DS.Style.itemDelegate.width implicitHeight: DS.Style.itemDelegate.height Loader { anchors.fill: parent - active: checked && !control.cascadeSelected + active: checked && !control.cascadeSelected && !dragActive sourceComponent: HighlightPanel {} } @@ -104,12 +112,57 @@ T.ItemDelegate { Loader { anchors.fill: parent - active: !checked && control.backgroundVisible + active: !control.ListView.view && !checked && control.backgroundVisible sourceComponent: D.RoundRectangle { color: DS.Style.itemDelegate.normalColor radius: DS.Style.control.radius corners: control.corners } } + + Loader { + anchors.fill: parent + active: dragActive + sourceComponent: Rectangle { + border.color: Qt.rgba(0, 0, 0, 0.09) + radius: DS.Style.control.radius + } + } + } + + DragHandler { + id: dragHandler + enabled: enableDrag + onActiveChanged: { + if (active) { + let dragItem = control.ListView.view.dragItem + if (!dragItem) + return + + let md = JSON.stringify(dragItem.Drag.mimeData) + + if (md.length > 2) // '{}' + control.Drag.mimeData = dragItem.Drag.mimeData + + dragItem.grabToImage(function(result) { + control.Drag.imageSource = result.url; + control.Drag.hotSpot = Qt.point(dragItem.width / 2, dragItem.height / 2) + control.Drag.active = true + }) + } + } + } + + onHoveredChanged: { + if (checked || control.cascadeSelected || !backgroundVisible || dragActive) + return + + if (ListView.view) + ListView.view.setHoverItem(control.hovered ? control : null) + } + + onCheckedChanged: { + if (ListView.view) + ListView.view.updateCheckedItems() } } diff --git a/qt6/src/qml/ListView.qml b/qt6/src/qml/ListView.qml new file mode 100644 index 00000000..ba2da84d --- /dev/null +++ b/qt6/src/qml/ListView.qml @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Templates as T +import org.deepin.dtk 1.0 as D +import org.deepin.dtk.style 1.0 as DS +import org.deepin.dtk.private 1.0 as P + +ListView { + id: control + property int duration: 100 + property bool bgVisible: false + property Item hoveredItem + property list checkedItems + property alias dragItem: dragItem + + function setHoverItem(item) { + if (item) { + hoveredItem = item + // if hover from null to item do not show animation + bgVisible = (background.x <= 0 && background.y <= 0) ? false : hoveredItem.hovered + background.x = hoveredItem.x - contentX + background.y = hoveredItem.y - contentY + background.width = hoveredItem.implicitWidth + background.height = hoveredItem.implicitHeight + bgVisible = hoveredItem.hovered + control.state = bgVisible ? "Visible" : "Invisible" + } else { + hideTimer.start() + } + } + + function updateCheckedItems() { + var items = [] + for (var i = 0; i < count; ++i) { + let item = itemAtIndex(i) + if (item && item.checked) { + items.push(item) + } + } + + checkedItems = items + } + + DragItemsImage { + id: dragItem + items: checkedItems + visible: Drag.active + // Drag.mimeData: {"text/plain": ""} + + onAboutToGrabToImage: function(item) { + item.dragActive = true + } + onGrabToImageFinished: function(item) { + item.dragActive = false + } + } + + onContentXChanged: { + if (hoveredItem && hoveredItem.hovered && background) + background.x = hoveredItem.x - contentX + } + + onContentYChanged: { + if (hoveredItem && hoveredItem.hovered && background) + background.y = hoveredItem.y - contentY + } + + // ItemDelegate hover item0 ==> item1, add timer ignore [item0.unhovered] + // item0.hovered, item0.unhovered, item1.hovered + Timer { + id: hideTimer + interval: control.duration + onTriggered: { + bgVisible = hoveredItem ? hoveredItem.hovered : false + control.state = bgVisible ? "Visible" : "Invisible" + } + } + + // visible animation + states: [ + State{ + name: "Visible" + PropertyChanges { target: background; opacity: 1.0 } + PropertyChanges { target: background; scale: 1.0 } + PropertyChanges { target: background; visible: true } + }, + State{ + name:"Invisible" + PropertyChanges { target: background; opacity: 0.0 } + PropertyChanges { target: background; scale: 0.5 } + PropertyChanges { target: background; visible: false } + } + ] + + transitions: [ + Transition { + from: "Visible" + to: "Invisible" + + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + target: background + property: "opacity" + duration: control.duration + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: background + property: "scale" + duration: control.duration + easing.type: Easing.InOutQuad + } + } + NumberAnimation { + target: background + property: "visible" + duration: 0 + } + } + }, + Transition { + from: "Invisible" + to: "Visible" + SequentialAnimation { + NumberAnimation { + target: background + property: "visible" + duration: 0 + } + ParallelAnimation { + NumberAnimation { + target: background + property: "opacity" + duration: control.duration + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: background + property: "scale" + duration: control.duration + easing.type: Easing.InOutQuad + } + } + } + } + ] + + Item { + id: background + implicitWidth: DS.Style.itemDelegate.width + implicitHeight: DS.Style.itemDelegate.height + z: -1 + + Loader { + anchors.fill: parent + active: hoveredItem + sourceComponent: P.ButtonPanel { + button: hoveredItem + enableAnimation: false // avoid hover animation + outsideBorderColor: null + insideBorderColor: null + visible: hoveredItem && !hoveredItem.checked && hoveredItem.enabled + color1: D.Palette { + normal { + common: Qt.rgba(0, 0, 0, 0.1) + } + normalDark { + common: Qt.rgba(1, 1, 1, 0.1) + } + } + color2: color1 + } + } + + onVisibleChanged: { + if (!visible) { + // ensure do not show animation when hoveredItem from null to item + background.x = -1 + background.y = -1 + } + } + + // move and resize animation + Behavior on x { + enabled: bgVisible + NumberAnimation { duration: control.duration } + } + Behavior on y { + enabled: bgVisible + NumberAnimation { duration: control.duration } + } + Behavior on width { + enabled: bgVisible + NumberAnimation { duration: control.duration } + } + Behavior on height { + enabled: bgVisible + NumberAnimation { duration: control.duration } + } + } +} diff --git a/qt6/src/qml/Menu.qml b/qt6/src/qml/Menu.qml index 5c43d4d6..110c070d 100644 --- a/qt6/src/qml/Menu.qml +++ b/qt6/src/qml/Menu.qml @@ -42,13 +42,14 @@ T.Menu { } contentItem: Control { - topPadding: 10 // TODO how to clip radius + topPadding: 6 // TODO how to clip radius bottomPadding: topPadding leftPadding: 0 rightPadding: leftPadding contentItem: ColumnLayout { id: viewLayout + spacing: 0 Loader { Layout.fillWidth: true @@ -78,6 +79,27 @@ T.Menu { onCountChanged: refreshContentItemWidth() onWidthChanged: refreshContentItemWidth() + view.highlightFollowsCurrentItem: true + view.highlightMoveDuration: 50 + view.highlightMoveVelocity: -1 + + view.highlight: Rectangle { + anchors.left: parent ? parent.left : undefined + anchors.right: parent ? parent.right : undefined + anchors.leftMargin: 6 + anchors.rightMargin: 6 + scale: 0.9 + property D.Palette backgroundColor: DS.Style.highlightPanel.background + color: D.ColorSelector.backgroundColor + radius: 6 + Component.onCompleted: { + scale = 1.0 + } + Behavior on scale { + NumberAnimation { duration: 100 } + } + + } } Loader { Layout.fillWidth: true diff --git a/qt6/src/qml/MenuItem.qml b/qt6/src/qml/MenuItem.qml index 0107be5a..61fffa75 100644 --- a/qt6/src/qml/MenuItem.qml +++ b/qt6/src/qml/MenuItem.qml @@ -43,6 +43,9 @@ T.MenuItem { text: control.text font: control.font color: control.palette.windowText + Behavior on color { + ColorAnimation { duration: control.hovered ? 100 : 0; } + } icon: D.DTK.makeIcon(control.icon, control.D.DciIcon) } @@ -102,15 +105,5 @@ T.MenuItem { radius: DS.Style.control.radius } } - - Loader { - anchors.fill: parent - active: control.down || control.highlighted - sourceComponent: Rectangle { - property D.Palette backgroundColor: DS.Style.highlightPanel.background - color: D.ColorSelector.backgroundColor - radius: 1 // TODO can't display background when using dtk's InWindowBlur. - } - } } } diff --git a/qt6/src/qml/Switch.qml b/qt6/src/qml/Switch.qml index 4f148062..4844b967 100644 --- a/qt6/src/qml/Switch.qml +++ b/qt6/src/qml/Switch.qml @@ -20,43 +20,50 @@ T.Switch { topPadding: DS.Style.control.vPadding bottomPadding: DS.Style.control.vPadding spacing: DS.Style.control.spacing + D.ColorSelector.hovered: false // disable hover ==> normal animation - indicator: Rectangle { + indicator: D.DciIcon { + id: handle implicitWidth: DS.Style.switchButton.indicatorWidth implicitHeight: DS.Style.switchButton.indicatorHeight x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 y: control.topPadding + (control.availableHeight - height) / 2 - radius: DS.Style.control.radius - color: control.D.ColorSelector.backgroundColor - opacity: control.D.ColorSelector.controlState === D.DTK.DisabledState ? 0.4 : 1 - D.DciIcon { - id: handle - x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2))) - y: (parent.height - height) / 2 - width: DS.Style.switchButton.handleWidth - height: DS.Style.switchButton.handleHeight - sourceSize: Qt.size(DS.Style.switchButton.handleWidth, DS.Style.switchButton.handleHeight) - name: DS.Style.switchButton.iconName - opacity: control.D.ColorSelector.controlState === D.DTK.DisabledState && control.checked ? 0.4 : 1 - palette { - highlight: control.checked ? control.palette.highlight : control.D.ColorSelector.handleColor - highlightForeground: control.palette.highlightedText - foreground: control.palette.windowText - background: control.palette.window - } - mode: control.D.ColorSelector.controlState - theme: control.D.ColorSelector.controlTheme - fallbackToQIcon: false + width: DS.Style.switchButton.handleWidth + height: DS.Style.switchButton.handleHeight + sourceSize: Qt.size(DS.Style.switchButton.indicatorWidth, DS.Style.switchButton.indicatorWidth) + name: !control.checked ? "switch_on" : "switch_off" + opacity: control.D.ColorSelector.controlState === D.DTK.DisabledState && control.checked ? 0.4 : 1 + palette: DTK.makeIconPalette(control.palette) + mode: control.D.ColorSelector.controlState + theme: control.D.ColorSelector.controlTheme + fallbackToQIcon: false + } - Behavior on x { - enabled: !control.down - SmoothedAnimation { velocity: 200 } - } + Timer { + id: toggletimer + interval: 300 + onTriggered: { + control.toggle() } } + function palyAndSetImage() { + handle.play(D.DTK.NormalState) + toggletimer.start(); + } + + Keys.onSpacePressed: palyAndSetImage() + Keys.onEnterPressed: palyAndSetImage() + Keys.onReturnPressed: palyAndSetImage() + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: palyAndSetImage() + } + contentItem: Label { leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 diff --git a/qt6/src/qml/ToolButton.qml b/qt6/src/qml/ToolButton.qml index 5fc097c3..1e07efd5 100644 --- a/qt6/src/qml/ToolButton.qml +++ b/qt6/src/qml/ToolButton.qml @@ -10,7 +10,7 @@ import org.deepin.dtk.private 1.0 as P T.ToolButton { id: control - property D.Palette textColor: checked ? DS.Style.checkedButton.text : (highlighted ? DS.Style.highlightedButton.text : DS.Style.button.text) + property D.Palette textColor: checked ? DS.Style.highlightedButton.text : (highlighted ? DS.Style.highlightedButton.text : DS.Style.button.text) implicitWidth: DS.Style.control.implicitWidth(control) implicitHeight: DS.Style.control.implicitHeight(control) @@ -66,10 +66,46 @@ T.ToolButton { } } + states: [ + State { + name: "hovered" + when: control.hovered && !control.checked + PropertyChanges { + target: contentItem + scale : 1.2 + } + PropertyChanges { + target: background + scale : 1.0 + } + + }, + State { + name: "checked" + when: control.checked + PropertyChanges { + target: contentItem + scale : 1.0 + } + } + ] + + transitions: Transition { + NumberAnimation { properties: "scale"; easing.type: Easing.InOutQuad } + } + background: P.ButtonPanel { + visible: control.state === "hovered" + scale : 0.9 implicitWidth: DS.Style.toolButton.width implicitHeight: DS.Style.toolButton.height button: control outsideBorderColor: null + color1: D.Palette { + normal { + common: Qt.rgba(0, 0, 0, 0.1) + } + } + color2 : color1 } } diff --git a/qt6/src/qml/private/ButtonPanel.qml b/qt6/src/qml/private/ButtonPanel.qml index f8c62798..a77aa8bb 100644 --- a/qt6/src/qml/private/ButtonPanel.qml +++ b/qt6/src/qml/private/ButtonPanel.qml @@ -11,6 +11,7 @@ BoxPanel { id: control property Item button + property bool enableAnimation: control.D.ColorSelector.controlState === D.DTK.HoveredState && control.D.ColorSelector.family === D.Palette.CommonColor color1: selectValue(DS.Style.button.background1, DS.Style.checkedButton.background, DS.Style.highlightedButton.background1) color2: selectValue(DS.Style.button.background2, DS.Style.checkedButton.background, DS.Style.highlightedButton.background2) insideBorderColor: selectValue(DS.Style.button.insideBorder, null, DS.Style.highlightedButton.border) @@ -52,8 +53,7 @@ BoxPanel { CicleSpreadAnimation { id: hoverAnimation anchors.fill: parent - visible: control.D.ColorSelector.controlState === D.DTK.HoveredState - && control.D.ColorSelector.family === D.Palette.CommonColor + visible: enableAnimation Rectangle { anchors.fill: parent diff --git a/qt6/src/qml/private/ProgressBarImpl.qml b/qt6/src/qml/private/ProgressBarImpl.qml index d9270975..dd29b673 100644 --- a/qt6/src/qml/private/ProgressBarImpl.qml +++ b/qt6/src/qml/private/ProgressBarImpl.qml @@ -39,12 +39,32 @@ Item { visible: progressBar.visualPosition > 0 Rectangle { + id: rect anchors.fill: parent radius: parent.cornerRadius + property int count + property real lightPosition gradient: Gradient { - GradientStop { position: 1 - 1 / progressBar.visualPosition; color: progressBar.palette.highlight } - GradientStop {position: 1 - 0.28 / progressBar.visualPosition; color: progressBar.palette.highlight } - GradientStop { position: 1; color: control.D.ColorSelector.handleGradientColor } + GradientStop { position: 0; color: progressBar.palette.highlight } + GradientStop { position: rect.lightPosition; color: control.D.ColorSelector.handleGradientColor } + GradientStop { position: 1; color: progressBar.palette.highlight } + } + Timer { + id: moveTimer + interval: 10 + repeat: true + running: rect.visible + onTriggered: { + moveTimer.interval = 10 + if (rect.count === 100) { + rect.count = 0 + rect.lightPosition = 0.0 + moveTimer.interval = 2000 + return; + } + rect.count += 1 + rect.lightPosition = rect.count * 0.01 + } } } } @@ -98,13 +118,32 @@ Item { rotation: -90 Rectangle { + id: indeterminateRect anchors.fill: parent radius: indeterminateProgressContent.cornerRadius + property int count + property real lightPosition gradient: Gradient { GradientStop { position: 0.0; color: progressBar.palette.highlight } - GradientStop { position: 0.39; color: control.D.ColorSelector.handleGradientColor } + GradientStop { position: indeterminateRect.lightPosition; color: control.D.ColorSelector.handleGradientColor } GradientStop { position: 1.0; color: progressBar.palette.highlight } } + Timer { + id: indeterminateMoveTimer + interval: 50 + repeat: true + running: indeterminateRect.visible + onTriggered: { + indeterminateMoveTimer.interval = 50 + if (indeterminateRect.count === 100) { + indeterminateRect.count = 0 + indeterminateMoveTimer.interval = 2000 + return; + } + indeterminateRect.count += 5 + indeterminateRect.lightPosition = indeterminateRect.count * 0.01 + } + } } SequentialAnimation { diff --git a/src/dquickwindow.cpp b/src/dquickwindow.cpp index 42a4eeaa..101791e6 100644 --- a/src/dquickwindow.cpp +++ b/src/dquickwindow.cpp @@ -108,6 +108,24 @@ inline static void updateValue(QPoint &value, Func call) } } +template +inline static void updateValue(DTK_GUI_NAMESPACE::DPlatformHandle::EffectScenes &value, Func call) +{ + if (value != DTK_GUI_NAMESPACE::DPlatformHandle::EffectScene(0)) { + call(value); + value = DTK_GUI_NAMESPACE::DPlatformHandle::EffectScene(0); + } +} + +template +inline static void updateValue(DTK_GUI_NAMESPACE::DPlatformHandle::EffectTypes &value, Func call) +{ + if (value != DTK_GUI_NAMESPACE::DPlatformHandle::EffectType(0)) { + call(value); + value = DTK_GUI_NAMESPACE::DPlatformHandle::EffectType(0); + } +} + bool DQuickWindowAttachedPrivate::ensurePlatformHandle() { if (handle) @@ -133,6 +151,8 @@ bool DQuickWindowAttachedPrivate::ensurePlatformHandle() updateValue(explicitEnableSystemResize, std::bind(&DPlatformHandle::setEnableSystemResize, handle, std::placeholders::_1)); updateValue(explicitEnableSystemMove, std::bind(&DPlatformHandle::setEnableSystemMove, handle, std::placeholders::_1)); updateValue(explicitEnableBlurWindow, std::bind(&DPlatformHandle::setEnableBlurWindow, handle, std::placeholders::_1)); + updateValue(explicitEffectScene, std::bind(&DPlatformHandle::setWindowEffect, handle, std::placeholders::_1)); + updateValue(explicitEffectType, std::bind(&DPlatformHandle::setWindowStartUpEffect, handle, std::placeholders::_1)); QObject::connect(handle, &DPlatformHandle::borderColorChanged, q, &DQuickWindowAttached::borderColorChanged); QObject::connect(handle, &DPlatformHandle::borderWidthChanged, q, &DQuickWindowAttached::borderWidthChanged); @@ -145,6 +165,8 @@ bool DQuickWindowAttachedPrivate::ensurePlatformHandle() QObject::connect(handle, &DPlatformHandle::enableSystemResizeChanged, q, &DQuickWindowAttached::enableSystemResizeChanged); QObject::connect(handle, &DPlatformHandle::enableBlurWindowChanged, q, &DQuickWindowAttached::enableBlurWindowChanged); QObject::connect(handle, SIGNAL(enableBlurWindowChanged()), q, SLOT(_q_updateBlurAreaForWindow())); + QObject::connect(handle, &DPlatformHandle::windowEffectChanged, q, &DQuickWindowAttached::windowEffectChanged); + QObject::connect(handle, &DPlatformHandle::windowStartUpEffectChanged, q, &DQuickWindowAttached::windowStartUpEffectChanged); Q_EMIT q->enabledChanged(); return true; } @@ -486,6 +508,28 @@ void DQuickWindowAttached::setLoadingOverlay(QQmlComponent *component) Q_EMIT loadingOverlayChanged(); } +void DQuickWindowAttached::setWindowEffect(DTK_GUI_NAMESPACE::DPlatformHandle::EffectScenes effect) +{ + D_D(DQuickWindowAttached); + + d->ensurePlatformHandle(); + if (d->handle) + d->handle->setWindowEffect(effect); + else + d->explicitEffectScene = effect; +} + +void DQuickWindowAttached::setWindowStartUpEffect(DTK_GUI_NAMESPACE::DPlatformHandle::EffectTypes type) +{ + D_D(DQuickWindowAttached); + + d->ensurePlatformHandle(); + if (d->handle) + d->handle->setWindowStartUpEffect(type); + else + d->explicitEffectType = type; +} + /*! * \~chinese \property DQuickWindowAttached::translucentBackground * \~chinese \brief 如果此属性值为 true,则在更新窗口绘制内容之前会先清空要更新区域内的图像,否则不清空。 @@ -620,6 +664,29 @@ void DQuickWindowAttached::resetThemeType() } #endif + +DTK_GUI_NAMESPACE::DPlatformHandle::EffectScene DQuickWindowAttached::windowEffect() const +{ + D_DC(DQuickWindowAttached); + + if (!d->handle) { + return DTK_GUI_NAMESPACE::DPlatformHandle::EffectScene(0); + } + + return d->handle->windowEffect(); +} + +DTK_GUI_NAMESPACE::DPlatformHandle::EffectType DQuickWindowAttached::windowStartUpEffect() const +{ + D_DC(DQuickWindowAttached); + + if (!d->handle) { + return DTK_GUI_NAMESPACE::DPlatformHandle::EffectType(0); + } + + return d->handle->windowStartUpEffect(); +} + /*! * \~chinese \brief DQuickWindowAttached::setEnabled 设置当前的窗口为 DTK 风格。 * \~chinese \note 只能把默认风格设置为 DTK 风格,不能把 DTK 设置为默认风格。 diff --git a/src/dquickwindow.h b/src/dquickwindow.h index 1d8cc918..f2b68abc 100644 --- a/src/dquickwindow.h +++ b/src/dquickwindow.h @@ -72,6 +72,8 @@ class DQuickWindowAttached : public QObject, public DTK_CORE_NAMESPACE::DObject #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) Q_PROPERTY(DTK_GUI_NAMESPACE::DGuiApplicationHelper::ColorType themeType READ themeType WRITE setThemeType RESET resetThemeType NOTIFY themeTypeChanged) #endif + Q_PROPERTY(DTK_GUI_NAMESPACE::DPlatformHandle::EffectScene windowEffect READ windowEffect WRITE setWindowEffect NOTIFY windowEffectChanged) + Q_PROPERTY(DTK_GUI_NAMESPACE::DPlatformHandle::EffectType windowStartUpEffect READ windowStartUpEffect WRITE setWindowStartUpEffect NOTIFY windowStartUpEffectChanged) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QML_ANONYMOUS #endif @@ -117,6 +119,9 @@ class DQuickWindowAttached : public QObject, public DTK_CORE_NAMESPACE::DObject void resetThemeType(); #endif + DTK_GUI_NAMESPACE::DPlatformHandle::EffectScene windowEffect() const; + DTK_GUI_NAMESPACE::DPlatformHandle::EffectType windowStartUpEffect() const; + public Q_SLOTS: void setEnabled(bool e); @@ -149,6 +154,9 @@ public Q_SLOTS: void setOverlayExited(QQuickTransition *exit); void setLoadingOverlay(QQmlComponent *component); + void setWindowEffect(DTK_GUI_NAMESPACE::DPlatformHandle::EffectScenes effect); + void setWindowStartUpEffect(DTK_GUI_NAMESPACE::DPlatformHandle::EffectTypes type); + protected: bool eventFilter(QObject *watched, QEvent *event) override; @@ -175,6 +183,8 @@ public Q_SLOTS: #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) void themeTypeChanged(); #endif + void windowEffectChanged(); + void windowStartUpEffectChanged(); private: D_DECLARE_PRIVATE(DQuickWindowAttached) diff --git a/src/dtkdeclarative_assets.qrc b/src/dtkdeclarative_assets.qrc index c4126298..a10294a3 100644 --- a/src/dtkdeclarative_assets.qrc +++ b/src/dtkdeclarative_assets.qrc @@ -46,7 +46,8 @@ icons/bloom/slider_point_left.dci icons/bloom/slider_round_hor.dci icons/bloom/slider_round_ver.dci - icons/bloom/switch_button.dci + icons/bloom/switch_on.dci + icons/bloom/switch_off.dci icons/bloom/slider_point_up.dci icons/bloom/checkbox_mix.dci icons/bloom/checkbox_checked.dci @@ -86,6 +87,8 @@ icons/bloom/list_delete.dci icons/bloom/menu_arrow.dci icons/bloom/menu_select.dci + icons/bloom/item_checked.dci + icons/bloom/item_unchecked.dci icons/bloom/window_sidebar.dci icons/bloom/action_add.dci icons/bloom/radio_checked.dci diff --git a/src/icons/bloom/checkbox_checked.dci b/src/icons/bloom/checkbox_checked.dci index bae6718e..de394945 100644 Binary files a/src/icons/bloom/checkbox_checked.dci and b/src/icons/bloom/checkbox_checked.dci differ diff --git a/src/icons/bloom/checkbox_unchecked.dci b/src/icons/bloom/checkbox_unchecked.dci index e68eed24..413e4145 100644 Binary files a/src/icons/bloom/checkbox_unchecked.dci and b/src/icons/bloom/checkbox_unchecked.dci differ diff --git a/src/icons/bloom/item_checked.dci b/src/icons/bloom/item_checked.dci new file mode 100644 index 00000000..e38bb103 Binary files /dev/null and b/src/icons/bloom/item_checked.dci differ diff --git a/src/icons/bloom/item_unchecked.dci b/src/icons/bloom/item_unchecked.dci new file mode 100644 index 00000000..4da8717c Binary files /dev/null and b/src/icons/bloom/item_unchecked.dci differ diff --git a/src/icons/bloom/radio_checked.dci b/src/icons/bloom/radio_checked.dci index 1075ebd3..69df89f3 100644 Binary files a/src/icons/bloom/radio_checked.dci and b/src/icons/bloom/radio_checked.dci differ diff --git a/src/icons/bloom/radio_unchecked.dci b/src/icons/bloom/radio_unchecked.dci index 641029d6..792ce8ec 100644 Binary files a/src/icons/bloom/radio_unchecked.dci and b/src/icons/bloom/radio_unchecked.dci differ diff --git a/src/icons/bloom/switch_off.dci b/src/icons/bloom/switch_off.dci new file mode 100644 index 00000000..957f499a Binary files /dev/null and b/src/icons/bloom/switch_off.dci differ diff --git a/src/icons/bloom/switch_on.dci b/src/icons/bloom/switch_on.dci new file mode 100644 index 00000000..20ec32c8 Binary files /dev/null and b/src/icons/bloom/switch_on.dci differ diff --git a/src/private/dmessagemanager.cpp b/src/private/dmessagemanager.cpp index 4d1bd33d..e80259f4 100644 --- a/src/private/dmessagemanager.cpp +++ b/src/private/dmessagemanager.cpp @@ -54,6 +54,19 @@ void FloatingMessageContainer::setDuration(int duration) Q_EMIT durationChanged(); } +bool FloatingMessageContainer::immediateClose() const +{ + return m_immediateClose; +} + +void FloatingMessageContainer::setImmediateClose(bool immediateClose) +{ + if (m_immediateClose == immediateClose) + return; + + m_immediateClose = immediateClose; +} + void FloatingMessageContainer::close() { if (auto manager = qobject_cast(parent())) { @@ -188,9 +201,10 @@ void MessageManager::ensureLayout() QQmlComponent columnCom(qmlEngine(parent())); columnCom.setData("import QtQuick 2.11\n" "Column {\n" + " spacing: 0\n" " anchors {\n" " bottom: parent.bottom\n" - " bottomMargin: 10;\n" + " bottomMargin: 20;\n" " horizontalCenter: parent.horizontalCenter\n" " }\n" "}\n", QUrl()); @@ -307,7 +321,11 @@ void MessageManager::timerEvent(QTimerEvent *e) killTimer(e->timerId()); e->accept(); if (auto container = item.second) { - close(container); + if (container->immediateClose()) { + close(container); + } else { + Q_EMIT container->delayClose(); + } } break; } diff --git a/src/private/dmessagemanager_p.h b/src/private/dmessagemanager_p.h index 3e613015..002fe0ba 100644 --- a/src/private/dmessagemanager_p.h +++ b/src/private/dmessagemanager_p.h @@ -18,6 +18,7 @@ class FloatingMessageContainer : public QObject Q_PROPERTY(QQuickItem *panel READ panel WRITE setPanel) Q_PROPERTY(QVariant message READ message WRITE setMessage NOTIFY messageChanged) Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged) + Q_PROPERTY(bool immediateClose READ immediateClose WRITE setImmediateClose) Q_CLASSINFO("DefaultProperty", "panel") #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QML_NAMED_ELEMENT(FloatingMessageContainer) @@ -33,6 +34,8 @@ class FloatingMessageContainer : public QObject void setMessageId(const QString &msgId); int duration() const; void setDuration(int duration); + bool immediateClose() const; + void setImmediateClose(bool immediateClose); public Q_SLOT: void close(); @@ -40,6 +43,7 @@ public Q_SLOT: Q_SIGNALS: void messageChanged(); void durationChanged(); + void delayClose(); private: friend MessageManager; @@ -48,6 +52,7 @@ public Q_SLOT: QVariant m_message; QString m_msgId; int m_duration = 4000; + bool m_immediateClose = false; }; class MessageManager : public QObject diff --git a/src/private/dquickdciiconimage.cpp b/src/private/dquickdciiconimage.cpp index cffac58a..3bf4d794 100644 --- a/src/private/dquickdciiconimage.cpp +++ b/src/private/dquickdciiconimage.cpp @@ -18,6 +18,47 @@ static QString appIconThemeName() return DGuiApplicationHelper::instance()->applicationTheme()->iconThemeName(); } +static QString findDciIconPath(const QString &iconName, const QString &themeName) +{ + QString iconPath; + auto cached = DIconTheme::cached(); + + if (cached) { + iconPath = cached->findDciIconFile(iconName, themeName); + } else { + iconPath = DIconTheme::findDciIconFile(iconName, themeName); + } + return iconPath; +} + +static DDciIcon::Mode controlState2DciMode(int state) +{ + DDciIcon::Mode dcimode = DDciIcon::Normal; + switch (state) { + case DQMLGlobalObject::NormalState: + dcimode = DDciIcon::Normal; + break; + case DQMLGlobalObject::DisabledState: + dcimode = DDciIcon::Disabled; + break; + case DQMLGlobalObject::HoveredState: + dcimode = DDciIcon::Hover; + break; + case DQMLGlobalObject::PressedState: + dcimode = DDciIcon::Pressed; + break; + default: + break; + } + + return dcimode; +} + +static inline DDciIcon::Theme dciTheme(DGuiApplicationHelper::ColorType type) +{ + return type == DGuiApplicationHelper::DarkType ? DDciIcon::Dark : DDciIcon::Light; +} + DQuickDciIconImageItemPrivate::DQuickDciIconImageItemPrivate(DQuickDciIconImagePrivate *pqq) : parentPriv(pqq) { @@ -31,11 +72,27 @@ void DQuickDciIconImageItemPrivate::maybeUpdateUrl() return DQuickIconImagePrivate::maybeUpdateUrl(); } - QUrl url; - url.setScheme(QLatin1String("image")); - url.setHost(QLatin1String("dtk.dci.icon")); - url.setQuery(getUrlQuery()); - q->setSource(url); + QString iconPath = findDciIconPath(parentPriv->imageItem->name(), appIconThemeName()); + if (iconPath.isEmpty()) + return DQuickIconImagePrivate::maybeUpdateUrl(); + + updatePlayer(); + + if (player) + player->setMode(controlState2DciMode(parentPriv->mode)); +} + +void DQuickDciIconImageItemPrivate::play(int mode) +{ + Q_Q(DQuickIconImage); + if (parentPriv->imageItem->name().isEmpty() || iconType != ThemeIconName) { + return; + } + + updatePlayer(); + + if (player) + player->play(controlState2DciMode(mode)); } QUrlQuery DQuickDciIconImageItemPrivate::getUrlQuery() @@ -56,6 +113,53 @@ QUrlQuery DQuickDciIconImageItemPrivate::getUrlQuery() return query; } +void DQuickDciIconImageItemPrivate::updatePlayerIconSize() +{ + if (!player) + return; + + int boundingSize = qMax(q_func()->sourceSize().width(), q_func()->sourceSize().height()); + + player->setIconSize(boundingSize); +} + +void DQuickDciIconImageItemPrivate::updatePlayer() +{ + if (!player) { + Q_Q(DQuickIconImage); + player = new DDciIconPlayer(parentPriv->imageItem); + QObject::connect(player, &DDciIconPlayer::updated, parentPriv->imageItem, [this](){ + parentPriv->imageItem->setImage(player->currentImage()); + }); + QObject::connect(parentPriv->imageItem, &DQuickIconImage::sourceSizeChanged, player, [this](){ + updatePlayerIconSize(); + }); + } + + QString iconPath = findDciIconPath(parentPriv->imageItem->name(), appIconThemeName()); + + // 防止频繁构造 dciicon + if (iconPathCache != iconPath) { + DDciIcon dciIcon(iconPath); + if (!dciIcon.isNull()) { + player->setIcon(dciIcon); + iconPathCache = iconPath; + } + } + + player->setTheme(dciTheme(parentPriv->theme)); + + DDciIconPalette palette = parentPriv->palette; + if (!parentPriv->palette.foreground().isValid() && q_func()->color().isValid()) + palette.setForeground(q_func()->color()); + + player->setPalette(palette); + + updatePlayerIconSize(); + + player->setDevicePixelRatio(devicePixelRatio); +} + DQuickDciIconImagePrivate::DQuickDciIconImagePrivate(DQuickDciIconImage *qq) : DObjectPrivate(qq) , imageItem(new DQuickIconImage(*new DQuickDciIconImageItemPrivate(this), qq)) @@ -76,6 +180,11 @@ void DQuickDciIconImagePrivate::updateImageSourceUrl() imageItem->d_func()->maybeUpdateUrl(); } +void DQuickDciIconImagePrivate::play(DQMLGlobalObject::ControlState mode) +{ + imageItem->d_func()->play(mode); +} + DQuickDciIconImage::DQuickDciIconImage(QQuickItem *parent) : QQuickItem(parent) , DObject(*new DQuickDciIconImagePrivate(this)) @@ -119,6 +228,13 @@ void DQuickDciIconImage::setMode(DQMLGlobalObject::ControlState mode) Q_EMIT modeChanged(); } +void DQuickDciIconImage::play(DQMLGlobalObject::ControlState mode) +{ + D_D(DQuickDciIconImage); + if (d->imageItem) + d->play(mode); +} + DGuiApplicationHelper::ColorType DQuickDciIconImage::theme() const { D_DC(DQuickDciIconImage); @@ -226,15 +342,7 @@ Dtk::Quick::DQuickIconImage *DQuickDciIconImage::imageItem() const bool DQuickDciIconImage::isNull(const QString &iconName) { - QString iconPath; - auto cached = DIconTheme::cached(); - - if (cached) { - iconPath = cached->findDciIconFile(iconName, appIconThemeName()); - } else { - iconPath = DIconTheme::findDciIconFile(iconName, appIconThemeName()); - } - return iconPath.isEmpty(); + return findDciIconPath(iconName, appIconThemeName()).isEmpty(); } DQuickIconAttached *DQuickDciIconImage::qmlAttachedProperties(QObject *object) diff --git a/src/private/dquickdciiconimage_p.h b/src/private/dquickdciiconimage_p.h index 8b7fb3d5..1d61f0f2 100644 --- a/src/private/dquickdciiconimage_p.h +++ b/src/private/dquickdciiconimage_p.h @@ -46,6 +46,7 @@ class DQuickDciIconImage : public QQuickItem, DCORE_NAMESPACE::DObject DQMLGlobalObject::ControlState mode() const; void setMode(DQMLGlobalObject::ControlState mode); + Q_INVOKABLE void play(DQMLGlobalObject::ControlState mode); DGuiApplicationHelper::ColorType theme() const; void setTheme(DGuiApplicationHelper::ColorType theme); diff --git a/src/private/dquickdciiconimage_p_p.h b/src/private/dquickdciiconimage_p_p.h index 81fd039b..5d2ea787 100644 --- a/src/private/dquickdciiconimage_p_p.h +++ b/src/private/dquickdciiconimage_p_p.h @@ -12,6 +12,7 @@ #include #include +#include DQUICK_BEGIN_NAMESPACE class DQuickDciIconImageItemPrivate; @@ -22,10 +23,15 @@ class DQuickDciIconImageItemPrivate : public DQuickIconImagePrivate public: DQuickDciIconImageItemPrivate(DQuickDciIconImagePrivate *pqq); void maybeUpdateUrl(); + void play(int mode); QUrlQuery getUrlQuery(); + void updatePlayer(); + void updatePlayerIconSize(); private: DQuickDciIconImagePrivate *parentPriv; + DDciIconPlayer *player = nullptr; + QString iconPathCache; }; class DQuickDciIconImagePrivate : public DCORE_NAMESPACE::DObjectPrivate @@ -36,6 +42,7 @@ class DQuickDciIconImagePrivate : public DCORE_NAMESPACE::DObjectPrivate DQuickDciIconImagePrivate(DQuickDciIconImage *qq); void layout(); void updateImageSourceUrl(); + void play(DQMLGlobalObject::ControlState mode); DDciIconPalette palette; DQuickIconImage *imageItem; diff --git a/src/private/dquickiconimage.cpp b/src/private/dquickiconimage.cpp index 1772751a..a2f19c11 100644 --- a/src/private/dquickiconimage.cpp +++ b/src/private/dquickiconimage.cpp @@ -108,6 +108,11 @@ void DQuickIconImagePrivate::maybeUpdateUrl() q->setSource(url); } +void DQuickIconImagePrivate::play(int mode) +{ + Q_UNUSED(mode) +} + QUrlQuery DQuickIconImagePrivate::getUrlQuery() { QUrlQuery query; @@ -316,6 +321,12 @@ void DQuickIconImage::setFallbackSource(const QUrl &newSource) d->maybeUpdateUrl(); } +void DQuickIconImage::setImage(const QImage &img) +{ + D_D(DQuickIconImage); + d->setImage(img); +} + DQuickIconImage::DQuickIconImage(DQuickIconImagePrivate &dd, QQuickItem *parent) : QQuickImage(dd, parent) { diff --git a/src/private/dquickiconimage_p.h b/src/private/dquickiconimage_p.h index 3afe3260..58fdb98b 100644 --- a/src/private/dquickiconimage_p.h +++ b/src/private/dquickiconimage_p.h @@ -65,6 +65,7 @@ public Q_SLOTS: void setMode(Mode mode); void setColor(const QColor &color); void setFallbackSource(const QUrl &newSource); + void setImage(const QImage &img); Q_SIGNALS: void nameChanged(); diff --git a/src/private/dquickiconimage_p_p.h b/src/private/dquickiconimage_p_p.h index e6973c53..847775af 100644 --- a/src/private/dquickiconimage_p_p.h +++ b/src/private/dquickiconimage_p_p.h @@ -23,6 +23,7 @@ class DQuickIconImagePrivate : public QQuickImagePrivate public: void init(); virtual void maybeUpdateUrl(); + virtual void play(int mode); QUrlQuery getUrlQuery(); DQuickIconImage::Mode getIconMode() const; diff --git a/src/private/dquickiconlabel.cpp b/src/private/dquickiconlabel.cpp index cc7c7f99..94dda0ea 100644 --- a/src/private/dquickiconlabel.cpp +++ b/src/private/dquickiconlabel.cpp @@ -473,6 +473,7 @@ void DQuickIconLabel::setColor(const QColor &color) d->color = color; if (d->label) d->label->setColor(color); + Q_EMIT colorChanged(color); } DQuickIconLabel::Display DQuickIconLabel::display() const diff --git a/src/private/dquickiconlabel_p.h b/src/private/dquickiconlabel_p.h index ac675040..19db472f 100644 --- a/src/private/dquickiconlabel_p.h +++ b/src/private/dquickiconlabel_p.h @@ -21,7 +21,7 @@ class DQuickIconLabel : public QQuickItem Q_PROPERTY(DTK_QUICK_NAMESPACE::DQuickDciIcon icon READ icon WRITE setIcon FINAL) Q_PROPERTY(QString text READ text WRITE setText FINAL) Q_PROPERTY(QFont font READ font WRITE setFont FINAL) - Q_PROPERTY(QColor color READ color WRITE setColor FINAL) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL) Q_PROPERTY(Display display READ display WRITE setDisplay FINAL) Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing FINAL) Q_PROPERTY(bool mirrored READ isMirrored WRITE setMirrored FINAL) @@ -87,6 +87,9 @@ class DQuickIconLabel : public QQuickItem void setBottomPadding(qreal padding); void resetBottomPadding(); +Q_SIGNALS: + void colorChanged(const QColor &color); + protected: void componentComplete() override; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) diff --git a/src/private/dquickwindow_p.h b/src/private/dquickwindow_p.h index dd442aee..17d50221 100644 --- a/src/private/dquickwindow_p.h +++ b/src/private/dquickwindow_p.h @@ -65,6 +65,8 @@ class DQuickWindowAttachedPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate QPoint explicitShadowOffset; QColor explicitBorderColor; QColor explicitShadowColor; + DPlatformHandle::EffectScenes explicitEffectScene = DPlatformHandle::EffectScenes(0); + DPlatformHandle::EffectTypes explicitEffectType = DPlatformHandle::EffectTypes(0); DWindowManagerHelper::WmWindowTypes wmWindowTypes; DWindowManagerHelper::MotifFunctions motifFunctions;