QmlDesigner: Use ColorEditor as view delegate in CollectionEditor

Use StudioControls.ColorEditor as a view delegate for the
CollectionDetailsView

Task-number: QDS-11114
Change-Id: Ic91d734c4fc62ddb51c4db7029714d409d51b732
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
This commit is contained in:
Ali Kianian
2023-11-27 16:04:23 +02:00
parent f8e9592824
commit daf23602fe
9 changed files with 448 additions and 167 deletions

View File

@@ -3,7 +3,6 @@
import QtQuick import QtQuick
import CollectionDetails 1.0 as CollectionDetails import CollectionDetails 1.0 as CollectionDetails
import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls import StudioControls 1.0 as StudioControls
import StudioHelpers as StudioHelpers import StudioHelpers as StudioHelpers
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
@@ -11,41 +10,54 @@ import QtQuick.Templates as T
Item { Item {
id: root id: root
required property var columnType required property var columnType
property var __modifier : textEditor
property bool __changesAccepted: true
TableView.onCommit: { TableView.onCommit: {
if (root.__changesAccepted) if (editorLoader.changesAccepted && edit !== editorLoader.acceptedValue)
edit = __modifier.editor.editValue edit = editorLoader.acceptedValue
}
Component.onCompleted: {
__changesAccepted = true
if (edit && edit !== "")
root.__modifier.editor.editValue = edit
} }
onActiveFocusChanged: { onActiveFocusChanged: {
if (root.activeFocus) if (root.activeFocus && !editorLoader.triggered && editorLoader.item) {
root.__modifier.editor.forceActiveFocus() editorLoader.triggered = true
editorLoader.item.open()
} }
// active focus should be checked again, because it might be affected by editorLoader.item
if (root.activeFocus && editorLoader.editor)
editorLoader.editor.forceActiveFocus()
}
Loader {
id: editorLoader
active: true
property var editor: editorLoader.item ? editorLoader.item.editor : null
property var editValue: editorLoader.editor ? editorLoader.editor.editValue : null
property var acceptedValue: null
property bool changesAccepted: true
property bool triggered: false
Connections { Connections {
id: modifierFocusConnection id: modifierFocusConnection
target: root.__modifier.editor target: editorLoader.editor
enabled: editorLoader.item !== undefined
function onActiveFocusChanged() { function onActiveFocusChanged() {
if (!modifierFocusConnection.target.activeFocus) if (!modifierFocusConnection.target.activeFocus) {
editorLoader.acceptedValue = editorLoader.editValue
root.TableView.commit() root.TableView.commit()
} }
} }
}
EditorPopup { Component {
id: textEditor id: textEditor
EditorPopup {
editor: textField editor: textField
StudioControls.TextField { StudioControls.TextField {
@@ -56,13 +68,16 @@ Item {
actionIndicator.visible: false actionIndicator.visible: false
translationIndicatorVisible: false translationIndicatorVisible: false
onRejected: root.__changesAccepted = false onRejected: editorLoader.changesAccepted = false
}
} }
} }
EditorPopup { Component {
id: numberEditor id: numberEditor
EditorPopup {
editor: numberField editor: numberField
StudioControls.RealSpinBox { StudioControls.RealSpinBox {
@@ -77,9 +92,12 @@ Item {
decimals: 6 decimals: 6
} }
} }
}
Component {
id: boolEditor
EditorPopup { EditorPopup {
id: boolEditor
editor: boolField editor: boolField
@@ -91,39 +109,6 @@ Item {
actionIndicatorVisible: false actionIndicatorVisible: false
} }
} }
EditorPopup {
id: colorEditor
editor: colorPicker
implicitHeight: colorPicker.height + topPadding + bottomPadding
implicitWidth: colorPicker.width + leftPadding + rightPadding
padding: 8
StudioHelpers.ColorBackend {
id: colorBackend
}
StudioControls.ColorEditorPopup {
id: colorPicker
property alias editValue: colorBackend.color
color: colorBackend.color
width: 200
Keys.onEnterPressed: colorPicker.focus = false
onActivateColor: function(color) {
colorBackend.activateColor(color)
}
}
background: Rectangle {
color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeInteraction
border.width: StudioTheme.Values.border
} }
} }
@@ -135,7 +120,7 @@ Item {
implicitHeight: contentHeight implicitHeight: contentHeight
implicitWidth: contentWidth implicitWidth: contentWidth
enabled: visible focus: true
visible: false visible: false
Connections { Connections {
@@ -144,6 +129,8 @@ Item {
function onActiveFocusChanged() { function onActiveFocusChanged() {
if (!editorPopup.editor.activeFocus) if (!editorPopup.editor.activeFocus)
editorPopup.close() editorPopup.close()
else if (edit)
editorPopup.editor.editValue = edit
} }
} }
@@ -151,7 +138,7 @@ Item {
target: editorPopup.editor.Keys target: editorPopup.editor.Keys
function onEscapePressed() { function onEscapePressed() {
root.__changesAccepted = false editorLoader.changesAccepted = false
editorPopup.close() editorPopup.close()
} }
} }
@@ -165,14 +152,8 @@ Item {
&& columnType !== CollectionDetails.DataType.Number && columnType !== CollectionDetails.DataType.Number
PropertyChanges { PropertyChanges {
target: root target: editorLoader
__modifier: textEditor sourceComponent: textEditor
}
PropertyChanges {
target: textEditor
visible: true
focus: true
} }
}, },
State { State {
@@ -180,14 +161,8 @@ Item {
when: columnType === CollectionDetails.DataType.Number when: columnType === CollectionDetails.DataType.Number
PropertyChanges { PropertyChanges {
target: root target: editorLoader
__modifier: numberEditor sourceComponent: numberEditor
}
PropertyChanges {
target: numberEditor
visible: true
focus: true
} }
}, },
State { State {
@@ -195,14 +170,8 @@ Item {
when: columnType === CollectionDetails.DataType.Boolean when: columnType === CollectionDetails.DataType.Boolean
PropertyChanges { PropertyChanges {
target: root target: editorLoader
__modifier: boolEditor sourceComponent: boolEditor
}
PropertyChanges {
target: boolEditor
visible: true
focus: true
} }
}, },
State { State {
@@ -210,14 +179,8 @@ Item {
when: columnType === CollectionDetails.DataType.Color when: columnType === CollectionDetails.DataType.Color
PropertyChanges { PropertyChanges {
target: root target: editorLoader
__modifier: colorEditor sourceComponent: null
}
PropertyChanges {
target: colorEditor
visible: true
focus: true
} }
} }
] ]

View File

@@ -114,7 +114,6 @@ Item {
} }
} }
PlatformWidgets.FileDialog { PlatformWidgets.FileDialog {
id: fileDialog id: fileDialog

View File

@@ -217,8 +217,10 @@ Rectangle {
delegate: Rectangle { delegate: Rectangle {
id: itemCell id: itemCell
clip: true
implicitWidth: 100 implicitWidth: 100
implicitHeight: itemText.height implicitHeight: StudioTheme.Values.baseHeight
border.color: dataTypeWarning !== CollectionDetails.Warning.None ? border.color: dataTypeWarning !== CollectionDetails.Warning.None ?
StudioTheme.Values.themeWarning : StudioTheme.Values.themeControlBackgroundInteraction StudioTheme.Values.themeWarning : StudioTheme.Values.themeControlBackgroundInteraction
border.width: 1 border.width: 1
@@ -231,12 +233,19 @@ Rectangle {
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
} }
Text { Loader {
id: itemText id: cellContentLoader
property int cellColumnType: columnType ? columnType : 0
Component {
id: cellText
Text {
text: display text: display
color: StudioTheme.Values.themePlaceholderTextColorInteraction color: itemSelected
width: parent.width ? StudioTheme.Values.themeInteraction
: StudioTheme.Values.themePlaceholderTextColorInteraction
leftPadding: 5 leftPadding: 5
topPadding: 3 topPadding: 3
bottomPadding: 3 bottomPadding: 3
@@ -245,11 +254,29 @@ Rectangle {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight elide: Text.ElideRight
} }
}
Component {
id: colorEditorComponent
ColorViewDelegate {}
}
function resetSource() {
if (columnType == CollectionDetails.DataType.Color)
cellContentLoader.sourceComponent = colorEditorComponent
else
cellContentLoader.sourceComponent = cellText
}
Component.onCompleted: resetSource()
onCellColumnTypeChanged: resetSource()
}
TableView.editDelegate: CollectionDetailsEditDelegate { TableView.editDelegate: CollectionDetailsEditDelegate {
anchors { anchors {
top: itemText.top top: itemCell.top
left: itemText.left left: itemCell.left
} }
} }
@@ -262,11 +289,6 @@ Rectangle {
target: itemCell target: itemCell
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
} }
PropertyChanges {
target: itemText
color: StudioTheme.Values.themePlaceholderTextColorInteraction
}
}, },
State { State {
name: "selected" name: "selected"
@@ -277,11 +299,6 @@ Rectangle {
color: StudioTheme.Values.themeControlBackgroundInteraction color: StudioTheme.Values.themeControlBackgroundInteraction
border.color: StudioTheme.Values.themeControlBackground border.color: StudioTheme.Values.themeControlBackground
} }
PropertyChanges {
target: itemText
color: StudioTheme.Values.themeInteraction
}
} }
] ]
} }

View File

@@ -0,0 +1,282 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes
import QtQuick.Templates as T
import HelperWidgets 2.0 as HelperWidgets
import StudioTheme as StudioTheme
import StudioControls as StudioControls
import QtQuickDesignerTheme
import QtQuickDesignerColorPalette
Row {
id: colorEditor
property color color
property bool supportGradient: false
readonly property color __editColor: edit
property variant value: {
if (!edit)
return "white" // default color for Rectangle
if (colorEditor.isVector3D) {
return Qt.rgba(__editColor.x,
__editColor.y,
__editColor.z, 1)
}
return __editColor
}
property alias gradientPropertyName: popupDialog.gradientPropertyName
property alias gradientThumbnail: gradientThumbnail
property alias shapeGradientThumbnail: shapeGradientThumbnail
property bool shapeGradients: false
property bool isVector3D: false
property color originalColor
property bool __block: false
function resetShapeColor() {
if (edit)
edit = ""
}
function writeColor() {
if (colorEditor.isVector3D) {
edit = Qt.vector3d(colorEditor.color.r,
colorEditor.color.g,
colorEditor.color.b)
} else {
edit = colorEditor.color
}
}
function initEditor() {
colorEditor.syncColor()
}
// Syncing color from backend to frontend and block reflection
function syncColor() {
colorEditor.__block = true
colorEditor.color = colorEditor.value
hexTextField.syncColor()
colorEditor.__block = false
}
Connections {
id: backendConnection
target: colorEditor
function onValueChanged() {
if (popupDialog.isSolid())
colorEditor.syncColor()
}
function on__EditColorChanged() {
if (popupDialog.isSolid())
colorEditor.syncColor()
}
}
Timer {
id: colorEditorTimer
repeat: false
interval: 100
running: false
onTriggered: {
backendConnection.enabled = false
colorEditor.writeColor()
hexTextField.syncColor()
backendConnection.enabled = true
}
}
onColorChanged: {
if (colorEditor.__block)
return
if (!popupDialog.isInValidState)
return
popupDialog.commitToGradient()
// Delay setting the color to keep ui responsive
if (popupDialog.isSolid())
colorEditorTimer.restart()
}
Rectangle {
id: preview
implicitWidth: StudioTheme.Values.twoControlColumnWidth
implicitHeight: StudioTheme.Values.height
color: colorEditor.color
border.color: StudioTheme.Values.themeControlOutline
border.width: StudioTheme.Values.border
Rectangle {
id: gradientThumbnail
anchors.fill: parent
anchors.margins: StudioTheme.Values.border
visible: !popupDialog.isSolid()
&& !colorEditor.shapeGradients
&& popupDialog.isLinearGradient()
}
Shape {
id: shape
anchors.fill: parent
anchors.margins: StudioTheme.Values.border
visible: !popupDialog.isSolid() && colorEditor.shapeGradients
ShapePath {
id: shapeGradientThumbnail
startX: shape.x - 1
startY: shape.y - 1
strokeWidth: -1
strokeColor: "green"
PathLine {
x: shape.x - 1
y: shape.height
}
PathLine {
x: shape.width
y: shape.height
}
PathLine {
x: shape.width
y: shape.y - 1
}
}
}
Image {
anchors.fill: parent
source: "qrc:/navigator/icon/checkers.png"
fillMode: Image.Tile
z: -1
}
MouseArea {
anchors.fill: parent
onClicked: {
popupDialog.visibility ? popupDialog.close() : popupDialog.open()
forceActiveFocus()
}
}
StudioControls.PopupDialog {
id: popupDialog
property bool isInValidState: loader.active ? popupDialog.loaderItem.isInValidState : true
property QtObject loaderItem: loader.item
property string gradientPropertyName
keepOpen: loader.item?.eyeDropperActive ?? false
width: 260
function commitToGradient() {
if (!loader.active)
return
if (colorEditor.supportGradient && popupDialog.loaderItem.gradientModel.hasGradient) {
var hexColor = convertColorToString(colorEditor.color)
hexTextField.text = hexColor
edit = hexColor
popupDialog.loaderItem.commitGradientColor()
}
}
function isSolid() {
if (!loader.active)
return true
return popupDialog.loaderItem.isSolid()
}
function isLinearGradient(){
if (!loader.active)
return false
return popupDialog.loaderItem.isLinearGradient()
}
function ensureLoader() {
if (!loader.active)
loader.active = true
}
function open() {
popupDialog.ensureLoader()
popupDialog.show(preview)
}
function determineActiveColorMode() {
if (loader.active && popupDialog.loaderItem)
popupDialog.loaderItem.determineActiveColorMode()
else
colorEditor.syncColor()
}
Loader {
id: loader
active: colorEditor.supportGradient
sourceComponent: HelperWidgets.ColorEditorPopup {
shapeGradients: colorEditor.shapeGradients
supportGradient: colorEditor.supportGradient
width: popupDialog.contentWidth
}
onLoaded: {
popupDialog.loaderItem.initEditor()
popupDialog.titleBar = loader.item.titleBarContent
}
}
}
}
HelperWidgets.LineEdit {
id: hexTextField
implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ StudioTheme.Values.actionIndicatorWidth
width: hexTextField.implicitWidth
enabled: popupDialog.isSolid()
writeValueManually: true
validator: RegularExpressionValidator {
regularExpression: /#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?/g
}
showTranslateCheckBox: false
showExtendedFunctionButton: false
indicatorVisible: false
onAccepted: colorEditor.color = hexTextField.text
onCommitData: {
colorEditor.color = hexTextField.text
if (popupDialog.isSolid())
colorEditor.writeColor()
}
function syncColor() {
hexTextField.text = colorEditor.color
}
}
Component.onCompleted: popupDialog.determineActiveColorMode()
on__EditColorChanged: popupDialog.determineActiveColorMode()
}

View File

@@ -36,6 +36,7 @@ SecondColumnLayout {
property alias shapeGradientThumbnail: shapeGradientThumbnail property alias shapeGradientThumbnail: shapeGradientThumbnail
property alias showExtendedFunctionButton: hexTextField.showExtendedFunctionButton property alias showExtendedFunctionButton: hexTextField.showExtendedFunctionButton
property alias showHexTextField: hexTextField.visible
property bool shapeGradients: false property bool shapeGradients: false
property color originalColor property color originalColor

View File

@@ -393,10 +393,7 @@ Column {
} }
} }
} }
}
Connections {
target: modelNodeBackend
function onSelectionChanged() { function onSelectionChanged() {
root.initEditor() root.initEditor()
} }

View File

@@ -10,6 +10,7 @@ ButtonRowButton 2.0 ButtonRowButton.qml
CharacterSection 2.0 CharacterSection.qml CharacterSection 2.0 CharacterSection.qml
CheckBox 2.0 CheckBox.qml CheckBox 2.0 CheckBox.qml
ColorEditor 2.0 ColorEditor.qml ColorEditor 2.0 ColorEditor.qml
ColorEditorPopup 2.0 ColorEditorPopup.qml
ColorLogic 2.0 ColorLogic.qml ColorLogic 2.0 ColorLogic.qml
ComboBox 2.0 ComboBox.qml ComboBox 2.0 ComboBox.qml
ComponentButton 2.0 ComponentButton.qml ComponentButton 2.0 ComponentButton.qml

View File

@@ -85,6 +85,24 @@ static QVariant valueToVariant(const QJsonValue &value, CollectionDetails::DataT
} }
} }
static QJsonValue variantToJsonValue(const QVariant &variant)
{
using VariantType = QVariant::Type;
switch (variant.type()) {
case VariantType::Bool:
return variant.toBool();
case VariantType::Double:
case VariantType::Int:
return variant.toDouble();
case VariantType::String:
case VariantType::Color:
case VariantType::Url:
default:
return variant.toString();
}
}
CollectionDetails::CollectionDetails() CollectionDetails::CollectionDetails()
: d(new Private()) : d(new Private())
{} {}
@@ -278,8 +296,10 @@ bool CollectionDetails::setPropertyType(int column, DataType type)
for (QJsonObject &element : d->elements) { for (QJsonObject &element : d->elements) {
if (element.contains(property.name)) { if (element.contains(property.name)) {
QJsonValue value = element.value(property.name); const QJsonValue value = element.value(property.name);
element.insert(property.name, valueToVariant(value, type).toJsonValue()); const QVariant properTypedValue = valueToVariant(value, type);
const QJsonValue properTypedJsonValue = variantToJsonValue(properTypedValue);
element.insert(property.name, properTypedJsonValue);
changed = true; changed = true;
} }
} }

View File

@@ -340,9 +340,10 @@ bool CollectionDetailsModel::setPropertyType(int column, const QString &newValue
newValue)); newValue));
if (changed) { if (changed) {
emit headerDataChanged(Qt::Horizontal, column, column); emit headerDataChanged(Qt::Horizontal, column, column);
emit dataChanged(index(0, column), emit dataChanged(
index(0, column),
index(rowCount() - 1, column), index(rowCount() - 1, column),
{Qt::DisplayRole, DataTypeRole, DataTypeWarningRole, ColumnDataTypeRole}); {Qt::DisplayRole, Qt::EditRole, DataTypeRole, DataTypeWarningRole, ColumnDataTypeRole});
} }
return changed; return changed;