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/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/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/dbackdropnode.cpp b/src/private/dbackdropnode.cpp index 37183bbe..91c398ab 100644 --- a/src/private/dbackdropnode.cpp +++ b/src/private/dbackdropnode.cpp @@ -161,34 +161,51 @@ class Q_DECL_HIDDEN DataManager : public DataManagerBase return static_cast(parent()); } - Data *resolve(Data *data, DataKeys&&... keys) { - if (data && get()->check(data->data, std::forward(keys)...) - && dataList.contains(data)) { - return data; + std::weak_ptr resolve(std::weak_ptr data, DataKeys&&... keys) { + struct TryClean { + TryClean(DataManager *m) + : manager(m) {} + ~TryClean() { + manager->tryClean(); + } + DataManager *manager; + }; + + TryClean cleanJob(this); + Q_UNUSED(cleanJob) + + { + auto d = data.lock(); + if (d && dataList.contains(d)) { + if (get()->check(d->data, std::forward(keys)...)) { + d->released = 0; + return data; + } + release(data); + } } - if (data) - release(data); - for (auto data : dataList) { + for (auto data : std::as_const(dataList)) { if (get()->check(data->data, std::forward(keys)...)) { data->released = 0; return data; } } - data = new Data(); - if ((data->data = get()->create(std::forward(keys)...))) { - dataList.append(data); - return data; + auto newData = std::shared_ptr(new Data()); + if ((newData->data = get()->create(std::forward(keys)...))) { + dataList.append(newData); + return newData; } - delete data; - return nullptr; + return {}; } - inline void release(Data *data) { - data->released++; - ensureCleanJob(); + inline void release(std::weak_ptr data) { + auto d = data.lock(); + if (!d) + return; + d->released++; } protected: @@ -202,21 +219,18 @@ class Q_DECL_HIDDEN DataManager : public DataManagerBase manager->cleanJob = nullptr; - QList tmp; + QList> tmp; std::swap(manager->dataList, tmp); manager->dataList.reserve(tmp.size()); - for (Data *data : tmp) { + for (const auto &data : std::as_const(tmp)) { if (data->released > 2) { manager->get()->destroy(data->data); - delete data; } else { manager->dataList << data; - if (data->released > 0) { - data->released++; - manager->ensureCleanJob(); - } + if (data->released > 0) + ++data->released; } } } @@ -224,8 +238,8 @@ class Q_DECL_HIDDEN DataManager : public DataManagerBase QPointer manager; }; - inline void ensureCleanJob() { - if (!cleanJob) { + inline void tryClean() { + if (Q_LIKELY(!cleanJob)) { cleanJob = new CleanJob(this); owner()->scheduleRenderJob(cleanJob, QQuickWindow::AfterRenderingStage); } @@ -246,13 +260,12 @@ class Q_DECL_HIDDEN DataManager : public DataManagerBase using QObject::deleteLater; ~DataManager() { - for (auto data : dataList) { + for (auto data : std::as_const(dataList)) { Derive::destroy(data->data); - delete data; } } - QList dataList; + QList> dataList; QRunnable *cleanJob = nullptr; }; @@ -706,8 +719,9 @@ class Q_DECL_HIDDEN RhiNode : public DBackdropNode { if (oldManager != manager) { sgTexture()->setTexture(nullptr); - if (oldManager && texture) + if (oldManager) oldManager->release(texture); + texture.reset(); } rhi = rhi->resolve(rhi, window); @@ -747,10 +761,11 @@ class Q_DECL_HIDDEN RhiNode : public DBackdropNode { } texture = manager->resolve(texture, ct ? ct->format() : glfbManager->format(), itemPixelSize); - if (Q_UNLIKELY(!texture)) { + if (Q_UNLIKELY(texture.expired())) { reset(); return; } + auto texture = this->texture.lock(); Q_ASSERT(texture->data); if (renderData) { @@ -800,6 +815,7 @@ class Q_DECL_HIDDEN RhiNode : public DBackdropNode { texture->data->pixelSize().height() / float(m_rect.height() * devicePixelRatio)}); rhi->render(renderData->rt.get()); } else { + auto rhi = this->rhi->rhi(); QPointF sourcePos = renderMatrix.map(m_rect.topLeft()) * devicePixelRatio; if (ct) { @@ -914,9 +930,9 @@ class Q_DECL_HIDDEN RhiNode : public DBackdropNode { if (!sgTexture()->rhiTexture() && notifyTexture) doNotifyTextureChanged(); sgTexture()->setTexture(nullptr); - if (texture && manager) - manager->release(texture); - texture = nullptr; + if (!texture.expired() && manager) + manager->release(texture.lock()); + texture.reset(); #ifndef QT_NO_OPENGL if (glfbManager) @@ -929,14 +945,14 @@ class Q_DECL_HIDDEN RhiNode : public DBackdropNode { renderData.reset(); node.reset(); manager = nullptr; - texture = nullptr; + texture.reset(); #ifndef QT_NO_OPENGL glfbManager = nullptr; #endif } DataManagerPointer manager; - RhiTextureManager::Data *texture = nullptr; + std::weak_ptr texture; DataManagerPointer rhi; #ifndef QT_NO_OPENGL @@ -1078,7 +1094,7 @@ class Q_DECL_HIDDEN SoftwareNode : public DBackdropNode { QImage toImage() const override { - return image ? *image->data : QImage(); + return image.expired() ? QImage() : *image.lock()->data; } void render(const RenderState *state) override { @@ -1097,8 +1113,9 @@ class Q_DECL_HIDDEN SoftwareNode : public DBackdropNode { if (oldManager != manager) { texture()->setTexture(nullptr); - if (oldManager && image) + if (oldManager) oldManager->release(image); + image.reset(); } const bool hasRotation = matrix.flags().testAnyFlags(QMatrix4x4::Rotation2D | QMatrix4x4::Rotation); @@ -1137,6 +1154,7 @@ class Q_DECL_HIDDEN SoftwareNode : public DBackdropNode { image = manager->resolve(image, sourceImage.format(), pixelSize); } + auto image = this->image.lock(); painter.begin(image->data); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.setCompositionMode(QPainter::CompositionMode_Source); @@ -1170,20 +1188,19 @@ class Q_DECL_HIDDEN SoftwareNode : public DBackdropNode { if (!texture()->image().isNull() && notifyTexture) doNotifyTextureChanged(); texture()->setTexture(nullptr); - if (image && manager) + if (manager) manager->release(image); - image = nullptr; + image.reset(); } void destroy() { reset(false); manager = nullptr; - image = nullptr; } friend class DBackdropNode; DataManagerPointer manager; - QImageManager::Data *image = nullptr; + std::weak_ptr image; QPainter painter; }; 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/dquickbackdropblitter.cpp b/src/private/dquickbackdropblitter.cpp index 59ba1565..646c12e5 100644 --- a/src/private/dquickbackdropblitter.cpp +++ b/src/private/dquickbackdropblitter.cpp @@ -44,6 +44,10 @@ class Q_DECL_HIDDEN DQuickBackdropBlitterPrivate : public DCORE_NAMESPACE::DObje } + ~DQuickBackdropBlitterPrivate() { + cleanTextureProvider(); + } + static inline DQuickBackdropBlitterPrivate *get(DQuickBackdropBlitter *qq) { return qq->d_func(); } @@ -59,6 +63,7 @@ class Q_DECL_HIDDEN DQuickBackdropBlitterPrivate : public DCORE_NAMESPACE::DObje } BlitTextureProvider *ensureTextureProvider() const; + void cleanTextureProvider(); D_DECLARE_PUBLIC(DQuickBackdropBlitter) Content *content; @@ -178,6 +183,14 @@ BlitTextureProvider *DQuickBackdropBlitterPrivate::ensureTextureProvider() const return tp; } +void DQuickBackdropBlitterPrivate::cleanTextureProvider() +{ + if (tp) { + QQuickWindowQObjectCleanupJob::schedule(q_func()->window(), tp); + tp = nullptr; + } +} + DQuickBackdropBlitter::DQuickBackdropBlitter(QQuickItem *parent) : QQuickItem(parent) , DObject(*new DQuickBackdropBlitterPrivate(this)) @@ -240,23 +253,8 @@ static void onTextureChanged(DBackdropNode *node, void *data) { if (!d->tp) return; - const bool textureChanged = node->texture() != d->tp->texture(); d->tp->setTexture(node->texture()); - - struct Notifer : public QRunnable { - void run() override { - if (tp) - Q_EMIT tp->textureChanged(); - } - - QPointer tp; - }; - - auto notifer = new Notifer(); - notifer->tp = d->tp; - - if (textureChanged) - d->content->window()->scheduleRenderJob(notifer, QQuickWindow::BeforeSynchronizingStage); + Q_EMIT d->tp->textureChanged(); } QSGNode *DQuickBackdropBlitter::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *oldData) @@ -278,6 +276,10 @@ QSGNode *DQuickBackdropBlitter::updatePaintNode(QSGNode *oldNode, QQuickItem::Up node->setContentItem(d->container); node->setTextureChangedCallback(onTextureChanged, d); + connect(this, &QObject::destroyed, this, [node](){ + // fix callback crashed... + node->setTextureChangedCallback(nullptr, nullptr); + }); node->resize(size()); onTextureChanged(node, d); @@ -307,10 +309,7 @@ void DQuickBackdropBlitter::geometryChange(const QRectF &newGeometry, const QRec void DQuickBackdropBlitter::releaseResources() { D_D(DQuickBackdropBlitter); - if (d->tp) { - QQuickWindowQObjectCleanupJob::schedule(window(), d->tp); - d->tp = nullptr; - } + d->cleanTextureProvider(); } DQUICK_END_NAMESPACE diff --git a/src/private/dquickdciiconimage.cpp b/src/private/dquickdciiconimage.cpp index cffac58a..696df34e 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,55 @@ 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(); + }); + + // 只在初始化和 sourceSizeChanged 时更新图标大小 + // 防止出现 dpr > 1.0 时未设置 sourceSize 的 item 在 updatePlayer 时图标一直放大 + 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); + + player->setDevicePixelRatio(devicePixelRatio); +} + DQuickDciIconImagePrivate::DQuickDciIconImagePrivate(DQuickDciIconImage *qq) : DObjectPrivate(qq) , imageItem(new DQuickIconImage(*new DQuickDciIconImageItemPrivate(this), qq)) @@ -76,6 +182,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 +230,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 +344,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/dquickinwindowblur.cpp b/src/private/dquickinwindowblur.cpp index fe364109..f8d07a16 100644 --- a/src/private/dquickinwindowblur.cpp +++ b/src/private/dquickinwindowblur.cpp @@ -125,6 +125,11 @@ QSGNode *DQuickInWindowBlur::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDa blurNode->setRenderCallback(onRender, this); node->appendChildNode(blurNode); node->setRenderCallback(updateBlurNodeTexture, blurNode); + connect(this, &QObject::destroyed, this, [node, blurNode](){ + // fix callback crashed... + blurNode->setRenderCallback(nullptr, nullptr); + node->setRenderCallback(nullptr, nullptr); + }); } #ifndef QT_NO_OPENGL else if (ga == QSGRendererInterface::OpenGL @@ -138,6 +143,11 @@ QSGNode *DQuickInWindowBlur::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDa blurNode->setRenderCallback(onRender, this); node->appendChildNode(blurNode); node->setRenderCallback(updateBlurNodeTexture, blurNode); + connect(this, &QObject::destroyed, this, [node, blurNode](){ + // fix callback crashed... + blurNode->setRenderCallback(nullptr, nullptr); + node->setRenderCallback(nullptr, nullptr); + }); } #endif else {