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

View File

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

View File

@@ -217,8 +217,10 @@ Rectangle {
delegate: Rectangle {
id: itemCell
clip: true
implicitWidth: 100
implicitHeight: itemText.height
implicitHeight: StudioTheme.Values.baseHeight
border.color: dataTypeWarning !== CollectionDetails.Warning.None ?
StudioTheme.Values.themeWarning : StudioTheme.Values.themeControlBackgroundInteraction
border.width: 1
@@ -231,25 +233,50 @@ Rectangle {
acceptedButtons: Qt.NoButton
}
Text {
id: itemText
Loader {
id: cellContentLoader
text: display
color: StudioTheme.Values.themePlaceholderTextColorInteraction
width: parent.width
leftPadding: 5
topPadding: 3
bottomPadding: 3
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
property int cellColumnType: columnType ? columnType : 0
Component {
id: cellText
Text {
text: display
color: itemSelected
? StudioTheme.Values.themeInteraction
: StudioTheme.Values.themePlaceholderTextColorInteraction
leftPadding: 5
topPadding: 3
bottomPadding: 3
font.pixelSize: StudioTheme.Values.baseFontSize
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
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 {
anchors {
top: itemText.top
left: itemText.left
top: itemCell.top
left: itemCell.left
}
}
@@ -262,11 +289,6 @@ Rectangle {
target: itemCell
color: StudioTheme.Values.themeControlBackground
}
PropertyChanges {
target: itemText
color: StudioTheme.Values.themePlaceholderTextColorInteraction
}
},
State {
name: "selected"
@@ -277,11 +299,6 @@ Rectangle {
color: StudioTheme.Values.themeControlBackgroundInteraction
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 showExtendedFunctionButton: hexTextField.showExtendedFunctionButton
property alias showHexTextField: hexTextField.visible
property bool shapeGradients: false
property color originalColor

View File

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

View File

@@ -10,6 +10,7 @@ ButtonRowButton 2.0 ButtonRowButton.qml
CharacterSection 2.0 CharacterSection.qml
CheckBox 2.0 CheckBox.qml
ColorEditor 2.0 ColorEditor.qml
ColorEditorPopup 2.0 ColorEditorPopup.qml
ColorLogic 2.0 ColorLogic.qml
ComboBox 2.0 ComboBox.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()
: d(new Private())
{}
@@ -278,8 +296,10 @@ bool CollectionDetails::setPropertyType(int column, DataType type)
for (QJsonObject &element : d->elements) {
if (element.contains(property.name)) {
QJsonValue value = element.value(property.name);
element.insert(property.name, valueToVariant(value, type).toJsonValue());
const QJsonValue value = element.value(property.name);
const QVariant properTypedValue = valueToVariant(value, type);
const QJsonValue properTypedJsonValue = variantToJsonValue(properTypedValue);
element.insert(property.name, properTypedJsonValue);
changed = true;
}
}

View File

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