From fa06ae0bf93776450df9f62e56d3770b35b5d771 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:06:48 +0200 Subject: [PATCH 01/26] Added the tableViewAttributeComponent We use repeaters instead of a real tableview as tablemodel does not seem to support dynamic layout generation. --- .../qml/GraphEditor/AttributeItemDelegate.qml | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index ce02166ab3..8733095705 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -830,6 +830,245 @@ RowLayout { } } + Component { + id: tableViewAttributeComponent + ColumnLayout { + id: tableLayout + spacing: 0 + width: parent ? parent.width : 400 + property var columnNames: { + if (!attribute || !attribute.value || attribute.value.count === 0) return [] + var firstRow = attribute.value.at(0) + if (!firstRow || !firstRow.value) return [] + var names = [] + for (var i = 0; i < firstRow.value.count; i++) { + var child = firstRow.value.at(i) + if (child) names.push(child.label) + } + return names + } + property var columnWidths: [] + property var rowHeights: [] + property real totalTableWidth: { + var w = columnWidths + if (!w || w.length === 0) return 0 + var t = 0 + for (var i = 0; i < w.length; i++) t += w[i] + t += Math.max(0, w.length - 1) + return t + } + property real totalTableHeight: { + var h = rowHeights + if (!h || h.length === 0) return 0 + var t = 0 + for (var i = 0; i < h.length; i++) t += h[i] + t += Math.max(0, h.length - 1) + return t + } + FontMetrics { + id: fontMetrics + font.bold: false + } + function initSizes() { + var names = tableLayout.columnNames + if (!names || names.length === 0) { + tableLayout.columnWidths = [] + tableLayout.rowHeights = [] + return + } + var widths = [] + for (var i = 0; i < names.length; i++) + widths.push(fontMetrics.advanceWidth(names[i]) + 20) + var heights = [] + if (attribute && attribute.value) { + for (var r = 0; r < attribute.value.count; r++) { + var rowAttr = attribute.value.at(r) + if (!rowAttr || !rowAttr.value) { heights.push(30); continue } + for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { + var cell = rowAttr.value.at(c) + var cellText = cell ? String(cell.value) : "" + var cw = fontMetrics.advanceWidth(cellText) + 20 + if (cw > widths[c]) widths[c] = cw + } + heights.push(30) + } + } + tableLayout.columnWidths = widths + tableLayout.rowHeights = heights + } + Component.onCompleted: tableLayout.initSizes() + Connections { + target: attribute ? attribute.value : null + function onCountChanged() { tableLayout.initSizes() } + function onModelReset() { tableLayout.initSizes() } + function onRowsInserted() { tableLayout.initSizes() } + function onDataChanged() { tableLayout.initSizes() } + } + Item { + id: outerFrame + Layout.fillWidth: true + Layout.preferredHeight: Math.min(tableLayout.totalTableHeight + 30, 330) + ScrollBar { + id: hBar + orientation: Qt.Horizontal + anchors.left: outerFrame.left + anchors.right: outerFrame.right + anchors.bottom: outerFrame.bottom + anchors.rightMargin: vBar.width + policy: ScrollBar.AlwaysOn + size: (outerFrame.width - vBar.width) / + Math.max(tableLayout.totalTableWidth, 1) + } + ScrollBar { + id: vBar + orientation: Qt.Vertical + anchors.top: outerFrame.top + anchors.bottom: outerFrame.bottom + anchors.right: outerFrame.right + anchors.bottomMargin: hBar.height + policy: ScrollBar.AlwaysOn + size: (outerFrame.height - hBar.height) / + Math.max(tableLayout.totalTableHeight + 30, 1) + } + Item { + id: viewport + anchors.left: outerFrame.left + anchors.top: outerFrame.top + anchors.right: outerFrame.right + anchors.bottom: outerFrame.bottom + anchors.rightMargin: vBar.width + anchors.bottomMargin: hBar.height + clip: true + Item { + id: content + width: tableLayout.totalTableWidth + height: tableLayout.totalTableHeight + 30 + x: -hBar.position * tableLayout.totalTableWidth + y: -vBar.position * (tableLayout.totalTableHeight + 30) + Row { + id: headerRow + spacing: 1 + Repeater { + model: tableLayout.columnNames + delegate: Item { + id: headerCell + required property int index + required property string modelData + width: tableLayout.columnWidths[index] || 100 + height: 30 + + Rectangle { + anchors.fill: parent + color: "#2d2d2d" + border.color: "#1d1d1d" + Text { + anchors.fill: parent + text: headerCell.modelData + color: "#aaaaaa" + font.bold: false + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + MouseArea { + width: 6 + height: parent.height + anchors.right: parent.right + cursorShape: Qt.SizeHorCursor + property real startX: 0 + property real startW: 0 + onPressed: function(mouse) { + startX = mouse.x + startW = tableLayout.columnWidths[headerCell.index] + } + onPositionChanged: function(mouse) { + if (!pressed) return + var newW = Math.max(40, startW + (mouse.x - startX)) + var arr = tableLayout.columnWidths.slice() + arr[headerCell.index] = newW + tableLayout.columnWidths = arr + } + } + } + } + } + Column { + spacing: 1 + anchors.top: headerRow.bottom + anchors.topMargin: 1 + Repeater { + model: attribute ? attribute.value : null + delegate: Item { + id: rowItem + required property int index + required property var object + width: tableLayout.totalTableWidth + height: tableLayout.rowHeights[index] || 30 + Row { + spacing: 1 + anchors.fill: parent + Repeater { + model: rowItem.object && rowItem.object.value + ? rowItem.object.value.count + : 0 + delegate: Rectangle { + id: cellRect + required property int index + width: tableLayout.columnWidths[index] || 100 + height: rowItem.height + color: rowItem.index % 2 === 0 ? "#2d2d2d" : "#333333" + border.color: cellInput.activeFocus ? "#5599ff" : "#1d1d1d" + clip: true + TextInput { + id: cellInput + anchors.fill: parent + anchors.margins: 4 + verticalAlignment: TextInput.AlignVCenter + horizontalAlignment: TextInput.AlignHCenter + color: "#aaaaaa" + selectionColor: "#5599ff" + clip: true + text: { + var cell = rowItem.object.value.at(index) + return cell ? String(cell.value) : "" + } + onEditingFinished: { + var cell = rowItem.object.value.at(cellRect.index) + if (cell) cell.value = text + } + } + } + } + } + MouseArea { + width: parent.width + height: 6 + anchors.bottom: parent.bottom + cursorShape: Qt.SizeVerCursor + property real startY: 0 + property real startH: 0 + onPressed: function(mouse) { + startY = mouse.y + startH = tableLayout.rowHeights[rowItem.index] + } + onPositionChanged: function(mouse) { + if (!pressed) return + var newH = Math.max(20, startH + (mouse.y - startY)) + var arr = tableLayout.rowHeights.slice() + arr[rowItem.index] = newH + tableLayout.rowHeights = arr + } + } + } + } + } + } + } + } + } + } + Component { id: groupAttributeComponent ColumnLayout { From 0340d3ac0443eba0da186c7c84dffde4746e4051 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:08:09 +0200 Subject: [PATCH 02/26] Added the listAttributeComponent/tableViewAttributeComponent switch --- meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index 8733095705..965cbf4760 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -297,7 +297,10 @@ RowLayout { case "BoolParam": return checkboxComponent case "ListAttribute": - return listAttributeComponent + if (attribute.baseType == "GroupAttribute") + return tableViewAttributeComponent + else + return listAttributeComponent case "GroupAttribute": return groupAttributeComponent case "StringParam": From ea2a73d5b51f4cbdb11096b0f0d77c3543aecacd Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:51:01 +0200 Subject: [PATCH 03/26] Undoable/Redoable value assignment Value assignement is now compatible with Meshroom's undo/redo stack --- meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index 965cbf4760..79653d4e05 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -1025,6 +1025,7 @@ RowLayout { clip: true TextInput { id: cellInput + property var cell: rowItem.object.value.at(index) anchors.fill: parent anchors.margins: 4 verticalAlignment: TextInput.AlignVCenter @@ -1032,13 +1033,11 @@ RowLayout { color: "#aaaaaa" selectionColor: "#5599ff" clip: true - text: { - var cell = rowItem.object.value.at(index) - return cell ? String(cell.value) : "" - } + text: cell ? String(cell.value) : "" onEditingFinished: { - var cell = rowItem.object.value.at(cellRect.index) - if (cell) cell.value = text + if (cell && text != String(cell.value)) { + _currentScene.setAttribute(cell, text) + } } } } From 53d0cbdc2ae8925b1d4daa83d39ff6173242b1c5 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:18:37 +0200 Subject: [PATCH 04/26] tablesView are collapsables/expandables And a counter displays the number of items in the list --- .../qml/GraphEditor/AttributeItemDelegate.qml | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index 79653d4e05..f34c0e663a 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -868,6 +868,27 @@ RowLayout { t += Math.max(0, h.length - 1) return t } + property bool expanded: false + RowLayout { + spacing: 4 + ToolButton { + text: tableLayout.expanded ? MaterialIcons.keyboard_arrow_down : MaterialIcons.keyboard_arrow_right + font.family: MaterialIcons.fontFamily + onClicked: tableLayout.expanded = !tableLayout.expanded + } + Label { + Layout.alignment: Qt.AlignVCenter + text: attribute.value.count + " elements" + } + ToolButton { + text: MaterialIcons.add_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + enabled: root.editable + onClicked: _currentScene.appendAttribute(attribute, undefined) + } + } FontMetrics { id: fontMetrics font.bold: false @@ -885,9 +906,9 @@ RowLayout { var heights = [] if (attribute && attribute.value) { for (var r = 0; r < attribute.value.count; r++) { - var rowAttr = attribute.value.at(r) - if (!rowAttr || !rowAttr.value) { heights.push(30); continue } - for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { + var rowAttr = attribute.value.at(r) // instance of a group attribute + if (!rowAttr || !rowAttr.value) continue + for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { // group member var cell = rowAttr.value.at(c) var cellText = cell ? String(cell.value) : "" var cw = fontMetrics.advanceWidth(cellText) + 20 @@ -910,7 +931,10 @@ RowLayout { Item { id: outerFrame Layout.fillWidth: true - Layout.preferredHeight: Math.min(tableLayout.totalTableHeight + 30, 330) + visible: tableLayout.expanded + Layout.preferredHeight: tableLayout.expanded + ? Math.min(tableLayout.totalTableHeight + 30, 330) + : 0 ScrollBar { id: hBar orientation: Qt.Horizontal From 4371878eeaafda9d25bebcb422f3c4636696b2bd Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:46:12 +0200 Subject: [PATCH 05/26] Added remove rows buttons for the tableview --- .../qml/GraphEditor/AttributeItemDelegate.qml | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index f34c0e663a..e33eca1c07 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -938,9 +938,9 @@ RowLayout { ScrollBar { id: hBar orientation: Qt.Horizontal - anchors.left: outerFrame.left - anchors.right: outerFrame.right - anchors.bottom: outerFrame.bottom + anchors.left: fixedStrip.right + anchors.right: outerFrame.right + anchors.bottom: outerFrame.bottom anchors.rightMargin: vBar.width policy: ScrollBar.AlwaysOn size: (outerFrame.width - vBar.width) / @@ -957,12 +957,47 @@ RowLayout { size: (outerFrame.height - hBar.height) / Math.max(tableLayout.totalTableHeight + 30, 1) } + Item { + id: fixedStrip + anchors.left: outerFrame.left + anchors.top: outerFrame.top + anchors.bottom: outerFrame.bottom + anchors.bottomMargin: hBar.height + width: 30 + clip: true + Column { + spacing: 1 + width: parent.width + y: 31 - vBar.position * (tableLayout.totalTableHeight + 30) + Repeater { + model: attribute ? attribute.value : null + delegate: Item { + id: removeDelegate + required property int index + required property var object + width: fixedStrip.width + height: tableLayout.rowHeights[index] || 30 + ToolButton { + anchors.centerIn: parent + enabled: root.editable + text: MaterialIcons.remove_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + ToolTip.text: "Remove Element" + ToolTip.visible: hovered + onClicked: _currentScene.removeAttribute(removeDelegate.object) + } + } + } + } + } Item { id: viewport - anchors.left: outerFrame.left - anchors.top: outerFrame.top - anchors.right: outerFrame.right - anchors.bottom: outerFrame.bottom + anchors.left: fixedStrip.right + anchors.top: outerFrame.top + anchors.right: outerFrame.right + anchors.bottom: outerFrame.bottom anchors.rightMargin: vBar.width anchors.bottomMargin: hBar.height clip: true From 4bf74bc1814f10dc50c238a0d099b42162273a22 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:06:17 +0200 Subject: [PATCH 06/26] Fix column resize For row resize, the whole node UI scrolls --- .../ui/qml/GraphEditor/AttributeItemDelegate.qml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index e33eca1c07..dbaef504df 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -1017,8 +1017,7 @@ RowLayout { required property int index required property string modelData width: tableLayout.columnWidths[index] || 100 - height: 30 - + height: 30 Rectangle { anchors.fill: parent color: "#2d2d2d" @@ -1041,12 +1040,14 @@ RowLayout { property real startX: 0 property real startW: 0 onPressed: function(mouse) { - startX = mouse.x + var stablePoint = mapToItem(tableLayout, mouse.x, mouse.y) + startX = stablePoint.x startW = tableLayout.columnWidths[headerCell.index] } onPositionChanged: function(mouse) { if (!pressed) return - var newW = Math.max(40, startW + (mouse.x - startX)) + var stablePoint = mapToItem(tableLayout, mouse.x, mouse.y) + var newW = Math.max(40, startW + (stablePoint.x - startX)) var arr = tableLayout.columnWidths.slice() arr[headerCell.index] = newW tableLayout.columnWidths = arr @@ -1110,12 +1111,14 @@ RowLayout { property real startY: 0 property real startH: 0 onPressed: function(mouse) { - startY = mouse.y + var stablePoint = mapToItem(tableLayout, mouse.x, mouse.y) + startY = stablePoint.y startH = tableLayout.rowHeights[rowItem.index] } onPositionChanged: function(mouse) { if (!pressed) return - var newH = Math.max(20, startH + (mouse.y - startY)) + var stablePoint = mapToItem(tableLayout, mouse.x, mouse.y) + var newH = Math.max(20, startH + (stablePoint.y - startY)) var arr = tableLayout.rowHeights.slice() arr[rowItem.index] = newH tableLayout.rowHeights = arr From b65d523da2c22695f6a667b88926cd0fa71a87d4 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:06:51 +0200 Subject: [PATCH 07/26] Switched to flickable to handle mouse wheel mouse wheel alone allows vertical scrolling. ctrl+mouse wheel allows horizontal scrolling. --- .../qml/GraphEditor/AttributeItemDelegate.qml | 68 +++++++++++++------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index dbaef504df..caed1fc4d4 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -937,25 +937,33 @@ RowLayout { : 0 ScrollBar { id: hBar - orientation: Qt.Horizontal anchors.left: fixedStrip.right anchors.right: outerFrame.right anchors.bottom: outerFrame.bottom anchors.rightMargin: vBar.width + orientation: Qt.Horizontal policy: ScrollBar.AlwaysOn - size: (outerFrame.width - vBar.width) / - Math.max(tableLayout.totalTableWidth, 1) + size: flickable.width / Math.max(tableLayout.totalTableWidth, 1) + position: flickable.contentX / Math.max(tableLayout.totalTableWidth - flickable.width, 1) + onPositionChanged: { + if (!pressed) return + flickable.contentX = position * Math.max(tableLayout.totalTableWidth - flickable.width, 1) + } } ScrollBar { id: vBar - orientation: Qt.Vertical - anchors.top: outerFrame.top - anchors.bottom: outerFrame.bottom - anchors.right: outerFrame.right + anchors.top: outerFrame.top + anchors.bottom: outerFrame.bottom + anchors.right: outerFrame.right anchors.bottomMargin: hBar.height + orientation: Qt.Vertical policy: ScrollBar.AlwaysOn - size: (outerFrame.height - hBar.height) / - Math.max(tableLayout.totalTableHeight + 30, 1) + size: flickable.height / Math.max(tableLayout.totalTableHeight + 30, 1) + position: flickable.contentY / Math.max(tableLayout.totalTableHeight + 30 - flickable.height, 1) + onPositionChanged: { + if (!pressed) return + flickable.contentY = position * Math.max(tableLayout.totalTableHeight + 30 - flickable.height, 1) + } } Item { id: fixedStrip @@ -968,7 +976,7 @@ RowLayout { Column { spacing: 1 width: parent.width - y: 31 - vBar.position * (tableLayout.totalTableHeight + 30) + y: 31 - flickable.contentY Repeater { model: attribute ? attribute.value : null delegate: Item { @@ -992,8 +1000,8 @@ RowLayout { } } } - Item { - id: viewport + Flickable { + id: flickable anchors.left: fixedStrip.right anchors.top: outerFrame.top anchors.right: outerFrame.right @@ -1001,12 +1009,27 @@ RowLayout { anchors.rightMargin: vBar.width anchors.bottomMargin: hBar.height clip: true + contentWidth: tableLayout.totalTableWidth + contentHeight: tableLayout.totalTableHeight + 30 + interactive: true + WheelHandler { + onWheel: function(event) { + if (event.modifiers & Qt.ControlModifier) { + flickable.contentX = Math.max(0, + Math.min(flickable.contentWidth - flickable.width, + flickable.contentX - event.angleDelta.y / 120 * 40)) + } else { + flickable.contentY = Math.max(0, + Math.min(flickable.contentHeight - flickable.height, + flickable.contentY - event.angleDelta.y / 120 * 40)) + } + event.accepted = true + } + } Item { id: content width: tableLayout.totalTableWidth height: tableLayout.totalTableHeight + 30 - x: -hBar.position * tableLayout.totalTableWidth - y: -vBar.position * (tableLayout.totalTableHeight + 30) Row { id: headerRow spacing: 1 @@ -1108,18 +1131,19 @@ RowLayout { height: 6 anchors.bottom: parent.bottom cursorShape: Qt.SizeVerCursor - property real startY: 0 - property real startH: 0 + // Prevent Flickable from stealing the drag + preventStealing: true + property real lastY: 0 onPressed: function(mouse) { - var stablePoint = mapToItem(tableLayout, mouse.x, mouse.y) - startY = stablePoint.y - startH = tableLayout.rowHeights[rowItem.index] + lastY = mapToGlobal(mouse.x, mouse.y).y } onPositionChanged: function(mouse) { if (!pressed) return - var stablePoint = mapToItem(tableLayout, mouse.x, mouse.y) - var newH = Math.max(20, startH + (stablePoint.y - startY)) - var arr = tableLayout.rowHeights.slice() + var globalY = mapToGlobal(mouse.x, mouse.y).y + var delta = globalY - lastY + lastY = globalY + var newH = Math.max(20, tableLayout.rowHeights[rowItem.index] + delta) + var arr = tableLayout.rowHeights.slice() arr[rowItem.index] = newH tableLayout.rowHeights = arr } From 64e41f122dbabe1189f853aee3907761d0271234 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:15:41 +0200 Subject: [PATCH 08/26] Added separated window options and param support rto the table view The tableviews now has a button for windowed visualisation, supports most meshroom param types, and some UI fixes --- .../qml/GraphEditor/AttributeItemDelegate.qml | 646 ++++++++++++++---- 1 file changed, 506 insertions(+), 140 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index caed1fc4d4..e8e2506694 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -869,10 +869,12 @@ RowLayout { return t } property bool expanded: false + property var appPalette: palette RowLayout { spacing: 4 ToolButton { - text: tableLayout.expanded ? MaterialIcons.keyboard_arrow_down : MaterialIcons.keyboard_arrow_right + text: tableLayout.expanded ? MaterialIcons.keyboard_arrow_down + : MaterialIcons.keyboard_arrow_right font.family: MaterialIcons.fontFamily onClicked: tableLayout.expanded = !tableLayout.expanded } @@ -888,6 +890,78 @@ RowLayout { enabled: root.editable onClicked: _currentScene.appendAttribute(attribute, undefined) } + ToolButton { + text: MaterialIcons.fullscreen + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + ToolTip.text: "Open in fullscreen" + ToolTip.visible: hovered + onClicked: { + outerFrame.anchors.fill = undefined + outerFrame.anchors.top = undefined + outerFrame.anchors.left = undefined + outerFrame.anchors.right = undefined + outerFrame.anchors.bottom = undefined + outerFrame.Layout.fillWidth = false + outerFrame.Layout.preferredHeight = 0 + outerFrame.visible = true + outerFrame.parent = fullscreenContent + outerFrame.x = 0 + outerFrame.y = 0 + outerFrame.width = Qt.binding(function() { return fullscreenWindow.width }) + outerFrame.height = Qt.binding(function() { return fullscreenWindow.height }) + outerFrame.isFullscreen = true + fullscreenWindow.show() + } + } + } + Window { + id: fullscreenWindow + color: "#2d2d2d" + width: tableLayout.totalTableWidth + 30 + 16 + 20 + height: tableLayout.totalTableHeight + 10 + 16 + 20 + title: attribute ? attribute.label : "" + palette.window: tableLayout.appPalette.window + palette.windowText: tableLayout.appPalette.windowText + palette.base: tableLayout.appPalette.base + palette.alternateBase: tableLayout.appPalette.alternateBase + palette.text: tableLayout.appPalette.text + palette.button: tableLayout.appPalette.button + palette.buttonText: tableLayout.appPalette.buttonText + palette.highlight: tableLayout.appPalette.highlight + palette.highlightedText: tableLayout.appPalette.highlightedText + palette.mid: tableLayout.appPalette.mid + palette.dark: tableLayout.appPalette.dark + palette.light: tableLayout.appPalette.light + palette.midlight: tableLayout.appPalette.midlight + palette.shadow: tableLayout.appPalette.shadow + palette.toolTipBase: tableLayout.appPalette.toolTipBase + palette.toolTipText: tableLayout.appPalette.toolTipText + Item { + id: fullscreenContent + anchors.fill: parent + } + onClosing: { + outerFrame.anchors.fill = undefined + outerFrame.anchors.top = undefined + outerFrame.anchors.left = undefined + outerFrame.anchors.right = undefined + outerFrame.anchors.bottom = undefined + outerFrame.width = undefined + outerFrame.height = undefined + outerFrame.parent = tableLayout + outerFrame.isFullscreen = false + outerFrame.Layout.fillWidth = true + outerFrame.Layout.preferredHeight = Qt.binding(function() { + return tableLayout.expanded + ? Math.min(tableLayout.totalTableHeight + 40, 330) + : 0 + }) + outerFrame.visible = Qt.binding(function() { + return tableLayout.expanded + }) + } } FontMetrics { id: fontMetrics @@ -906,9 +980,9 @@ RowLayout { var heights = [] if (attribute && attribute.value) { for (var r = 0; r < attribute.value.count; r++) { - var rowAttr = attribute.value.at(r) // instance of a group attribute + var rowAttr = attribute.value.at(r) if (!rowAttr || !rowAttr.value) continue - for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { // group member + for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { var cell = rowAttr.value.at(c) var cellText = cell ? String(cell.value) : "" var cw = fontMetrics.advanceWidth(cellText) + 20 @@ -923,31 +997,36 @@ RowLayout { Component.onCompleted: tableLayout.initSizes() Connections { target: attribute ? attribute.value : null - function onCountChanged() { tableLayout.initSizes() } - function onModelReset() { tableLayout.initSizes() } - function onRowsInserted() { tableLayout.initSizes() } - function onDataChanged() { tableLayout.initSizes() } + function onCountChanged() { tableLayout.initSizes() } + function onModelReset() { tableLayout.initSizes() } + function onRowsInserted() { tableLayout.initSizes() } + function onDataChanged() { tableLayout.initSizes() } } Item { id: outerFrame Layout.fillWidth: true visible: tableLayout.expanded Layout.preferredHeight: tableLayout.expanded - ? Math.min(tableLayout.totalTableHeight + 30, 330) + ? Math.min(tableLayout.totalTableHeight + 40, 330) : 0 + property bool isFullscreen: false ScrollBar { id: hBar anchors.left: fixedStrip.right anchors.right: outerFrame.right anchors.bottom: outerFrame.bottom anchors.rightMargin: vBar.width - orientation: Qt.Horizontal - policy: ScrollBar.AlwaysOn - size: flickable.width / Math.max(tableLayout.totalTableWidth, 1) - position: flickable.contentX / Math.max(tableLayout.totalTableWidth - flickable.width, 1) + orientation: Qt.Horizontal + policy: flickable.contentWidth > flickable.width + ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + size: Math.min(1.0, flickable.width / Math.max(flickable.contentWidth, 1)) + position: (flickable.contentX / Math.max(flickable.contentWidth - flickable.width, 1)) + * (1.0 - size) onPositionChanged: { if (!pressed) return - flickable.contentX = position * Math.max(tableLayout.totalTableWidth - flickable.width, 1) + var maxPos = 1.0 - size + var ratio = maxPos > 0 ? position / maxPos : 0 + flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) } } ScrollBar { @@ -956,13 +1035,75 @@ RowLayout { anchors.bottom: outerFrame.bottom anchors.right: outerFrame.right anchors.bottomMargin: hBar.height - orientation: Qt.Vertical - policy: ScrollBar.AlwaysOn - size: flickable.height / Math.max(tableLayout.totalTableHeight + 30, 1) - position: flickable.contentY / Math.max(tableLayout.totalTableHeight + 30 - flickable.height, 1) + orientation: Qt.Vertical + policy: flickable.contentHeight > flickable.height + ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + size: Math.min(1.0, flickable.height / Math.max(flickable.contentHeight, 1)) + position: (flickable.contentY / Math.max(flickable.contentHeight - flickable.height, 1)) + * (1.0 - size) onPositionChanged: { if (!pressed) return - flickable.contentY = position * Math.max(tableLayout.totalTableHeight + 30 - flickable.height, 1) + var maxPos = 1.0 - size + var ratio = maxPos > 0 ? position / maxPos : 0 + flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) + } + } + Item { + id: fixedHeader + anchors.left: fixedStrip.right + anchors.right: outerFrame.right + anchors.top: outerFrame.top + anchors.rightMargin: vBar.width + height: 30 + clip: true + Row { + spacing: 1 + x: -flickable.contentX + Repeater { + model: tableLayout.columnNames + delegate: Item { + id: headerCell + required property int index + required property string modelData + width: tableLayout.columnWidths[index] || 100 + height: 30 + Rectangle { + anchors.fill: parent + color: "#2d2d2d" + border.color: "#1d1d1d" + Text { + anchors.fill: parent + text: headerCell.modelData + color: "#aaaaaa" + font.bold: false + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + MouseArea { + width: 6 + height: parent.height + anchors.right: parent.right + cursorShape: Qt.SizeHorCursor + property real startX: 0 + property real startW: 0 + onPressed: function(mouse) { + var p = mapToItem(tableLayout, mouse.x, mouse.y) + startX = p.x + startW = tableLayout.columnWidths[headerCell.index] + } + onPositionChanged: function(mouse) { + if (!pressed) return + var p = mapToItem(tableLayout, mouse.x, mouse.y) + var newW = Math.max(40, startW + (p.x - startX)) + var arr = tableLayout.columnWidths.slice() + arr[headerCell.index] = newW + tableLayout.columnWidths = arr + } + } + } + } } } Item { @@ -970,13 +1111,14 @@ RowLayout { anchors.left: outerFrame.left anchors.top: outerFrame.top anchors.bottom: outerFrame.bottom + anchors.topMargin: 30 anchors.bottomMargin: hBar.height width: 30 clip: true Column { spacing: 1 width: parent.width - y: 31 - flickable.contentY + y: -flickable.contentY Repeater { model: attribute ? attribute.value : null delegate: Item { @@ -992,9 +1134,16 @@ RowLayout { font.family: MaterialIcons.fontFamily font.pointSize: 11 padding: 2 - ToolTip.text: "Remove Element" - ToolTip.visible: hovered - onClicked: _currentScene.removeAttribute(removeDelegate.object) + ToolTip.text: "Remove Element" + ToolTip.visible: hovered + contentItem: Text { + text: parent.text + font: parent.font + color: "#aaaaaa" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: _currentScene.removeAttribute(removeDelegate.object) } } } @@ -1006,12 +1155,13 @@ RowLayout { anchors.top: outerFrame.top anchors.right: outerFrame.right anchors.bottom: outerFrame.bottom + anchors.topMargin: 30 anchors.rightMargin: vBar.width anchors.bottomMargin: hBar.height - clip: true + clip: true contentWidth: tableLayout.totalTableWidth - contentHeight: tableLayout.totalTableHeight + 30 - interactive: true + contentHeight: tableLayout.totalTableHeight + interactive: true WheelHandler { onWheel: function(event) { if (event.modifiers & Qt.ControlModifier) { @@ -1026,127 +1176,343 @@ RowLayout { event.accepted = true } } - Item { - id: content - width: tableLayout.totalTableWidth - height: tableLayout.totalTableHeight + 30 - Row { - id: headerRow - spacing: 1 - Repeater { - model: tableLayout.columnNames - delegate: Item { - id: headerCell - required property int index - required property string modelData - width: tableLayout.columnWidths[index] || 100 - height: 30 - Rectangle { - anchors.fill: parent - color: "#2d2d2d" - border.color: "#1d1d1d" - Text { - anchors.fill: parent - text: headerCell.modelData - color: "#aaaaaa" - font.bold: false - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - } - MouseArea { - width: 6 - height: parent.height - anchors.right: parent.right - cursorShape: Qt.SizeHorCursor - property real startX: 0 - property real startW: 0 - onPressed: function(mouse) { - var stablePoint = mapToItem(tableLayout, mouse.x, mouse.y) - startX = stablePoint.x - startW = tableLayout.columnWidths[headerCell.index] - } - onPositionChanged: function(mouse) { - if (!pressed) return - var stablePoint = mapToItem(tableLayout, mouse.x, mouse.y) - var newW = Math.max(40, startW + (stablePoint.x - startX)) - var arr = tableLayout.columnWidths.slice() - arr[headerCell.index] = newW - tableLayout.columnWidths = arr - } - } - } - } - } - Column { - spacing: 1 - anchors.top: headerRow.bottom - anchors.topMargin: 1 - Repeater { - model: attribute ? attribute.value : null - delegate: Item { - id: rowItem - required property int index - required property var object - width: tableLayout.totalTableWidth - height: tableLayout.rowHeights[index] || 30 - Row { - spacing: 1 - anchors.fill: parent - Repeater { - model: rowItem.object && rowItem.object.value - ? rowItem.object.value.count - : 0 - delegate: Rectangle { - id: cellRect - required property int index - width: tableLayout.columnWidths[index] || 100 - height: rowItem.height - color: rowItem.index % 2 === 0 ? "#2d2d2d" : "#333333" - border.color: cellInput.activeFocus ? "#5599ff" : "#1d1d1d" - clip: true - TextInput { - id: cellInput - property var cell: rowItem.object.value.at(index) - anchors.fill: parent - anchors.margins: 4 - verticalAlignment: TextInput.AlignVCenter - horizontalAlignment: TextInput.AlignHCenter - color: "#aaaaaa" - selectionColor: "#5599ff" - clip: true - text: cell ? String(cell.value) : "" - onEditingFinished: { - if (cell && text != String(cell.value)) { - _currentScene.setAttribute(cell, text) + Column { + spacing: 1 + Repeater { + model: attribute ? attribute.value : null + delegate: Item { + id: rowItem + required property int index + required property var object + width: tableLayout.totalTableWidth + height: tableLayout.rowHeights[index] || 30 + Row { + spacing: 1 + anchors.fill: parent + Repeater { + model: rowItem.object && rowItem.object.value + ? rowItem.object.value.count : 0 + delegate: Rectangle { + id: cellRect + required property int index + width: tableLayout.columnWidths[index] || 100 + height: rowItem.height + color: rowItem.index % 2 === 0 ? "#2d2d2d" : "#333333" + border.color: cellFocused ? "#5599ff" : "#1d1d1d" + clip: true + property bool cellFocused: { + var item = cellLoader.item + if (!item) return false + return item.activeFocus || + (item.children && item.children.length > 0 && + item.children[0] && item.children[0].activeFocus) + } + property var cell: rowItem.object.value.at(index) + function choiceValues(attr) { + if (!attr) return [] + if (attr.desc && Array.isArray(attr.desc.values)) + return attr.desc.values + if (attr.values && Array.isArray(attr.values)) + return attr.values + if (attr.values && typeof attr.values.count === "number") { + var out = [] + for (var i = 0; i < attr.values.count; i++) + out.push(attr.values.at(i).value) + return out + } + return [] + } + Rectangle { + anchors.centerIn: parent + width: cellLoader.width + 8 + height: cellLoader.height + 4 + radius: 3 + color: "#444444" + visible: cellRect.cell && + (cellRect.cell.type === "BoolParam" || + cellRect.cell.type === "ChoiceParam") + } + Loader { + id: cellLoader + anchors.centerIn: parent + width: parent.width + height: parent.height + property var attribute: cellRect.cell + sourceComponent: { + var attr = cellRect.cell + if (!attr) return null + switch (attr.type) { + case "PushButtonParam": + return cellPushButtonComponent + case "ChoiceParam": + return (attr.desc && attr.desc.exclusive) + ? cellChoiceComponent + : cellChoiceMultiComponent + case "IntParam": + return cellSliderComponent + case "FloatParam": + return (attr.desc && attr.desc.semantic === "color/hue") + ? cellColorHueComponent + : cellSliderComponent + case "BoolParam": + return cellCheckboxComponent + case "StringParam": + return (attr.desc && attr.desc.semantic && + attr.desc.semantic.includes("multiline")) + ? cellTextAreaComponent + : cellTextFieldComponent + case "ColorParam": + return cellColorComponent + default: + return cellTextFieldComponent + } + } + Component { + id: cellChoiceComponent + AttributeControls.Choice { + value: cellLoader.attribute ? cellLoader.attribute.value : "" + values: cellLoader.attribute ? cellLoader.attribute.values : [] + enabled: root.editable + Component.onCompleted: { + if (typeof popup !== "undefined" && popup !== null) { + popup.margins = -1 + } + } + onEditingFinished: function(value) { + if (cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, value) + } + } + } + Component { + id: cellChoiceMultiComponent + AttributeControls.ChoiceMulti { + value: cellLoader.attribute ? cellLoader.attribute.value : [] + values: cellLoader.attribute ? cellLoader.attribute.values : [] + enabled: root.editable + customValueColor: Colors.orange + onToggled: function(value, checked) { + if (!cellLoader.attribute) return + var cur = cellLoader.attribute.value.slice() + if (!checked) { + var idx = cur.indexOf(value) + if (idx !== -1) cur.splice(idx, 1) + } else { + cur.push(value) + } + _currentScene.setAttribute(cellLoader.attribute, cur) + } + } + } + Component { + id: cellSliderComponent + RowLayout { + spacing: 2 + TextField { + id: cellNumField + Layout.fillWidth: !cellSliderLoader.active + implicitWidth: 70 + enabled: root.editable + selectByMouse: true + horizontalAlignment: TextInput.AlignRight + text: { + if (cellSliderLoader.active && cellSliderLoader.item && + cellSliderLoader.item.pressed) + return String(cellSliderLoader.item.formattedValue) + return cellLoader.attribute ? String(cellLoader.attribute.value) : "" + } + background: Rectangle { color: "#3c3c3c"; radius: 2 } + color: "#cccccc" + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? parseInt(text) : parseFloat(text)) + } + // Mouse-wheel scroll to increment/decrement + WheelHandler { + onWheel: function(event) { + if (!root.editable || !cellLoader.attribute) return + var step = 1 + if (cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3) + step = cellLoader.attribute.desc.range[2] + var dir = event.angleDelta.y > 0 ? 1 : -1 + var v = Number(cellLoader.attribute.value) + dir * step + if (cellLoader.attribute.desc && cellLoader.attribute.desc.range) { + v = Math.max(cellLoader.attribute.desc.range[0], + Math.min(cellLoader.attribute.desc.range[1], v)) + } + _currentScene.setAttribute(cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? Math.round(v) : v) + event.accepted = true + } + } + } + Loader { + id: cellSliderLoader + Layout.fillWidth: true + active: cellLoader.attribute && + cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3 + sourceComponent: Slider { + readonly property int stepDecimalCount: stepSize < 1 + ? String(stepSize).split(".").pop().length : 0 + readonly property real formattedValue: value.toFixed(stepDecimalCount) + enabled: root.editable + value: cellLoader.attribute ? cellLoader.attribute.value : 0 + from: cellLoader.attribute.desc.range[0] + to: cellLoader.attribute.desc.range[1] + stepSize: cellLoader.attribute.desc.range[2] + snapMode: Slider.SnapAlways + onPressedChanged: { + if (!pressed && cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, + formattedValue) + } + } + } + } + } + Component { + id: cellCheckboxComponent + CheckBox { + enabled: root.editable + checked: cellLoader.attribute + ? cellLoader.attribute.value : false + onToggled: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, checked) + } + } + } + Component { + id: cellTextFieldComponent + TextField { + enabled: root.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + color: "#3c3c3c" + radius: 2 + } + color: "#cccccc" + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) + } + } + } + Component { + id: cellTextAreaComponent + TextField { + enabled: root.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + color: "#3c3c3c" + radius: 2 + } + color: "#cccccc" + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) + } + } + } + Component { + id: cellColorComponent + TextField { + enabled: root.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + color: "#3c3c3c" + radius: 2 + } + color: "#cccccc" + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text) + } + } + } + Component { + id: cellPushButtonComponent + Button { + text: cellLoader.attribute + ? cellLoader.attribute.label : "" + enabled: root.editable + onClicked: { + if (cellLoader.attribute) + cellLoader.attribute.clicked() + } + } + } + Component { + id: cellColorHueComponent + RowLayout { + Slider { + id: cellHueSlider + Layout.fillWidth: true + enabled: root.editable + value: cellLoader.attribute + ? cellLoader.attribute.value : 0 + from: 0 + to: 1 + stepSize: 0.01 + snapMode: Slider.SnapAlways + onPressedChanged: { + if (!pressed && cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, + value.toFixed(2)) + } + } + Rectangle { + width: 16 + height: 16 + color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) } } } } } } - MouseArea { - width: parent.width - height: 6 - anchors.bottom: parent.bottom - cursorShape: Qt.SizeVerCursor - // Prevent Flickable from stealing the drag - preventStealing: true - property real lastY: 0 - onPressed: function(mouse) { - lastY = mapToGlobal(mouse.x, mouse.y).y - } - onPositionChanged: function(mouse) { - if (!pressed) return - var globalY = mapToGlobal(mouse.x, mouse.y).y - var delta = globalY - lastY - lastY = globalY - var newH = Math.max(20, tableLayout.rowHeights[rowItem.index] + delta) - var arr = tableLayout.rowHeights.slice() - arr[rowItem.index] = newH - tableLayout.rowHeights = arr - } + } + MouseArea { + width: parent.width + height: 6 + anchors.bottom: parent.bottom + cursorShape: Qt.SizeVerCursor + preventStealing: true + property real lastY: 0 + onPressed: function(mouse) { + lastY = mapToGlobal(mouse.x, mouse.y).y + } + onPositionChanged: function(mouse) { + if (!pressed) return + var globalY = mapToGlobal(mouse.x, mouse.y).y + var delta = globalY - lastY + lastY = globalY + var newH = Math.max(20, + tableLayout.rowHeights[rowItem.index] + delta) + var arr = tableLayout.rowHeights.slice() + arr[rowItem.index] = newH + tableLayout.rowHeights = arr } } } From 0c58d858f5ff46acd530c19ef140a6c3f88ea6e2 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:07:08 +0200 Subject: [PATCH 09/26] Simplified the palette, removed intermediate smaller viariables, responsible table view, add row button added to the window --- .../qml/GraphEditor/AttributeItemDelegate.qml | 157 ++++++++++++------ 1 file changed, 108 insertions(+), 49 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index e8e2506694..fecf6c4e97 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -838,7 +838,6 @@ RowLayout { ColumnLayout { id: tableLayout spacing: 0 - width: parent ? parent.width : 400 property var columnNames: { if (!attribute || !attribute.value || attribute.value.count === 0) return [] var firstRow = attribute.value.at(0) @@ -853,21 +852,51 @@ RowLayout { property var columnWidths: [] property var rowHeights: [] property real totalTableWidth: { - var w = columnWidths - if (!w || w.length === 0) return 0 + if (!columnWidths || columnWidths.length === 0) return 0 var t = 0 - for (var i = 0; i < w.length; i++) t += w[i] - t += Math.max(0, w.length - 1) + for (var i = 0; i < columnWidths.length; i++) t += columnWidths[i] + t += Math.max(0, columnWidths.length - 1) return t } property real totalTableHeight: { - var h = rowHeights - if (!h || h.length === 0) return 0 + if (!rowHeights || rowHeights.length === 0) return 0 var t = 0 - for (var i = 0; i < h.length; i++) t += h[i] - t += Math.max(0, h.length - 1) + for (var i = 0; i < rowHeights.length; i++) t += rowHeights[i] + t += Math.max(0, rowHeights.length - 1) return t } + property var scaledColumnWidths: [] + property real scaledTableWidth: 0 + property var scaledRowHeights: [] + property real scaledTableHeight: 0 + property real availableW: outerFrame.width - fixedStrip.width - vBar.width + property real availableH: outerFrame.height - fixedHeader.height - hBar.height + function updateScaledWidths() { + if (!columnWidths || columnWidths.length === 0) { + scaledColumnWidths = [] + scaledTableWidth = 0 + return + } + var scaleFactor = Math.max(1.0, availableW / Math.max(totalTableWidth, 1)) + var result = [] + for (var i = 0; i < columnWidths.length; i++) + result.push(columnWidths[i] * scaleFactor) + scaledColumnWidths = result + scaledTableWidth = Math.max(totalTableWidth, availableW) + } + function updateScaledHeights() { + if (!rowHeights || rowHeights.length === 0) { + scaledRowHeights = [] + scaledTableHeight = 0 + return + } + var scaleFactor = Math.max(1.0, availableH / Math.max(totalTableHeight, 1)) + var result = [] + for (var i = 0; i < rowHeights.length; i++) + result.push(rowHeights[i] * scaleFactor) + scaledRowHeights = result + scaledTableHeight = Math.max(totalTableHeight, availableH) + } property bool expanded: false property var appPalette: palette RowLayout { @@ -904,7 +933,8 @@ RowLayout { outerFrame.anchors.right = undefined outerFrame.anchors.bottom = undefined outerFrame.Layout.fillWidth = false - outerFrame.Layout.preferredHeight = 0 + outerFrame.Layout.preferredWidth = fullscreenWindow.width + outerFrame.Layout.preferredHeight = fullscreenWindow.height outerFrame.visible = true outerFrame.parent = fullscreenContent outerFrame.x = 0 @@ -922,22 +952,7 @@ RowLayout { width: tableLayout.totalTableWidth + 30 + 16 + 20 height: tableLayout.totalTableHeight + 10 + 16 + 20 title: attribute ? attribute.label : "" - palette.window: tableLayout.appPalette.window - palette.windowText: tableLayout.appPalette.windowText - palette.base: tableLayout.appPalette.base - palette.alternateBase: tableLayout.appPalette.alternateBase - palette.text: tableLayout.appPalette.text - palette.button: tableLayout.appPalette.button - palette.buttonText: tableLayout.appPalette.buttonText - palette.highlight: tableLayout.appPalette.highlight - palette.highlightedText: tableLayout.appPalette.highlightedText - palette.mid: tableLayout.appPalette.mid - palette.dark: tableLayout.appPalette.dark - palette.light: tableLayout.appPalette.light - palette.midlight: tableLayout.appPalette.midlight - palette.shadow: tableLayout.appPalette.shadow - palette.toolTipBase: tableLayout.appPalette.toolTipBase - palette.toolTipText: tableLayout.appPalette.toolTipText + palette: tableLayout.appPalette Item { id: fullscreenContent anchors.fill: parent @@ -950,6 +965,7 @@ RowLayout { outerFrame.anchors.bottom = undefined outerFrame.width = undefined outerFrame.height = undefined + outerFrame.Layout.preferredWidth = -1 outerFrame.parent = tableLayout outerFrame.isFullscreen = false outerFrame.Layout.fillWidth = true @@ -994,14 +1010,18 @@ RowLayout { tableLayout.columnWidths = widths tableLayout.rowHeights = heights } - Component.onCompleted: tableLayout.initSizes() + Component.onCompleted: { + tableLayout.initSizes() + } Connections { target: attribute ? attribute.value : null - function onCountChanged() { tableLayout.initSizes() } - function onModelReset() { tableLayout.initSizes() } - function onRowsInserted() { tableLayout.initSizes() } - function onDataChanged() { tableLayout.initSizes() } + function onCountChanged() {tableLayout.initSizes(); tableLayout.updateScaledWidths(); tableLayout.updateScaledHeights()} + function onModelReset() {tableLayout.initSizes(); tableLayout.updateScaledWidths(); tableLayout.updateScaledHeights()} + function onRowsInserted() {tableLayout.initSizes(); tableLayout.updateScaledWidths(); tableLayout.updateScaledHeights()} + function onDataChanged() {tableLayout.initSizes(); tableLayout.updateScaledWidths(); tableLayout.updateScaledHeights()} } + onAvailableWChanged: tableLayout.updateScaledWidths() + onAvailableHChanged: tableLayout.updateScaledHeights() Item { id: outerFrame Layout.fillWidth: true @@ -1065,7 +1085,7 @@ RowLayout { id: headerCell required property int index required property string modelData - width: tableLayout.columnWidths[index] || 100 + width: tableLayout.scaledColumnWidths[index] || 100 height: 30 Rectangle { anchors.fill: parent @@ -1089,10 +1109,14 @@ RowLayout { property real startX: 0 property real startW: 0 onPressed: function(mouse) { + grabMouse() var p = mapToItem(tableLayout, mouse.x, mouse.y) startX = p.x startW = tableLayout.columnWidths[headerCell.index] } + onReleased: function(mouse) { + ungrabMouse() + } onPositionChanged: function(mouse) { if (!pressed) return var p = mapToItem(tableLayout, mouse.x, mouse.y) @@ -1100,6 +1124,7 @@ RowLayout { var arr = tableLayout.columnWidths.slice() arr[headerCell.index] = newW tableLayout.columnWidths = arr + tableLayout.updateScaledWidths() } } } @@ -1126,7 +1151,7 @@ RowLayout { required property int index required property var object width: fixedStrip.width - height: tableLayout.rowHeights[index] || 30 + height: tableLayout.scaledRowHeights[index] || 30 ToolButton { anchors.centerIn: parent enabled: root.editable @@ -1149,6 +1174,37 @@ RowLayout { } } } + Item { + id: cornerCell + anchors.left: outerFrame.left + anchors.top: outerFrame.top + width: fixedStrip.width + height: fixedHeader.height + visible: outerFrame.isFullscreen + Rectangle { + anchors.fill: parent + color: "#2d2d2d" + border.color: "#1d1d1d" + } + ToolButton { + anchors.centerIn: parent + text: MaterialIcons.add_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + enabled: root.editable + ToolTip.text: "Add Element" + ToolTip.visible: hovered + contentItem: Text { + text: parent.text + font: parent.font + color: "#aaaaaa" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: _currentScene.appendAttribute(attribute, undefined) + } + } Flickable { id: flickable anchors.left: fixedStrip.right @@ -1159,8 +1215,8 @@ RowLayout { anchors.rightMargin: vBar.width anchors.bottomMargin: hBar.height clip: true - contentWidth: tableLayout.totalTableWidth - contentHeight: tableLayout.totalTableHeight + contentWidth: tableLayout.scaledTableWidth + contentHeight: tableLayout.scaledTableHeight interactive: true WheelHandler { onWheel: function(event) { @@ -1184,8 +1240,8 @@ RowLayout { id: rowItem required property int index required property var object - width: tableLayout.totalTableWidth - height: tableLayout.rowHeights[index] || 30 + width: tableLayout.scaledTableWidth + height: tableLayout.scaledRowHeights[index] || 30 Row { spacing: 1 anchors.fill: parent @@ -1195,7 +1251,7 @@ RowLayout { delegate: Rectangle { id: cellRect required property int index - width: tableLayout.columnWidths[index] || 100 + width: tableLayout.scaledColumnWidths[index] || 100 height: rowItem.height color: rowItem.index % 2 === 0 ? "#2d2d2d" : "#333333" border.color: cellFocused ? "#5599ff" : "#1d1d1d" @@ -1239,26 +1295,25 @@ RowLayout { height: parent.height property var attribute: cellRect.cell sourceComponent: { - var attr = cellRect.cell - if (!attr) return null - switch (attr.type) { + if (!attribute) return null + switch (attribute.type) { case "PushButtonParam": return cellPushButtonComponent case "ChoiceParam": - return (attr.desc && attr.desc.exclusive) + return (attribute.desc && attribute.desc.exclusive) ? cellChoiceComponent : cellChoiceMultiComponent case "IntParam": return cellSliderComponent case "FloatParam": - return (attr.desc && attr.desc.semantic === "color/hue") + return (attribute.desc && attribute.desc.semantic === "color/hue") ? cellColorHueComponent : cellSliderComponent case "BoolParam": return cellCheckboxComponent case "StringParam": - return (attr.desc && attr.desc.semantic && - attr.desc.semantic.includes("multiline")) + return (attribute.desc && attribute.desc.semantic && + attribute.desc.semantic.includes("multiline")) ? cellTextAreaComponent : cellTextFieldComponent case "ColorParam": @@ -1500,19 +1555,23 @@ RowLayout { cursorShape: Qt.SizeVerCursor preventStealing: true property real lastY: 0 - onPressed: function(mouse) { + onPressed: function(mouse) { + grabMouse() lastY = mapToGlobal(mouse.x, mouse.y).y } - onPositionChanged: function(mouse) { + onReleased: function(mouse) { + ungrabMouse() + } + onPositionChanged: function(mouse) { if (!pressed) return var globalY = mapToGlobal(mouse.x, mouse.y).y var delta = globalY - lastY lastY = globalY - var newH = Math.max(20, - tableLayout.rowHeights[rowItem.index] + delta) + var newH = Math.max(20, tableLayout.rowHeights[rowItem.index] + delta) var arr = tableLayout.rowHeights.slice() arr[rowItem.index] = newH tableLayout.rowHeights = arr + tableLayout.updateScaledHeights() } } } From d86729eb1f2a414e1bcdb6d541b99af3d51c3e8c Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:43:22 +0200 Subject: [PATCH 10/26] added TableView.qml --- .../AttributeControls/TableView.qml | 420 ++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml new file mode 100644 index 0000000000..a3aebaaa82 --- /dev/null +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml @@ -0,0 +1,420 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window + +import MaterialIcons 2.2 +import Utils 1.0 + +ColumnLayout { + id: root + spacing: 0 + property bool editable: true + required property var attribute + property var columnNames: { + if (!attribute || !attribute.value || attribute.value.count === 0) return [] + var firstRow = attribute.value.at(0) + if (!firstRow || !firstRow.value) return [] + var names = [] + for (var i = 0; i < firstRow.value.count; i++) { + var child = firstRow.value.at(i) + if (child) names.push(child.label) + } + return names + } + property var columnWidths: [] + property var rowHeights: [] + property real totalTableWidth: { + if (!columnWidths || columnWidths.length === 0) return 0 + var t = 0 + for (var i = 0; i < columnWidths.length; i++) t += columnWidths[i] + t += Math.max(0, columnWidths.length - 1) + return t + } + property real totalTableHeight: { + if (!rowHeights || rowHeights.length === 0) return 0 + var t = 0 + for (var i = 0; i < rowHeights.length; i++) t += rowHeights[i] + t += Math.max(0, rowHeights.length - 1) + return t + } + property var scaledColumnWidths: [] + property real scaledTableWidth: 0 + property var scaledRowHeights: [] + property real scaledTableHeight: 0 + property real availableW: outerFrame.width > 0 ? outerFrame.width - fixedStrip.width - vBar.width : 600 + property real availableH: outerFrame.height > 0 ? outerFrame.height - hBar.height : 400 + function updateScaledWidths() { + if (!columnWidths || columnWidths.length === 0) { + scaledColumnWidths = [] + scaledTableWidth = 0 + return + } + var scaleFactor = Math.max(1.0, availableW / Math.max(totalTableWidth, 1)) + var result = [] + for (var i = 0; i < columnWidths.length; i++) + result.push(columnWidths[i] * scaleFactor) + scaledColumnWidths = result + scaledTableWidth = Math.max(totalTableWidth, availableW) + } + function updateScaledHeights() { + if (!rowHeights || rowHeights.length === 0) { + scaledRowHeights = [] + scaledTableHeight = 0 + return + } + var scaleFactor = Math.max(1.0, availableH / Math.max(totalTableHeight, 1)) + var result = [] + for (var i = 0; i < rowHeights.length; i++) + result.push(rowHeights[i] * scaleFactor) + scaledRowHeights = result + scaledTableHeight = Math.max(totalTableHeight, availableH) + } + property bool expanded: false + property var appPalette: palette + RowLayout { + spacing: 4 + ToolButton { + text: root.expanded ? MaterialIcons.keyboard_arrow_down + : MaterialIcons.keyboard_arrow_right + font.family: MaterialIcons.fontFamily + onClicked: root.expanded = !root.expanded + } + Label { + Layout.alignment: Qt.AlignVCenter + text: attribute.value.count + " elements" + } + ToolButton { + text: MaterialIcons.add_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + enabled: root.editable + onClicked: _currentScene.appendAttribute(attribute, undefined) + } + ToolButton { + text: MaterialIcons.fullscreen + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + ToolTip.text: "Open in fullscreen" + ToolTip.visible: hovered + onClicked: { + outerFrame.Layout.preferredWidth = fullscreenWindow.width + outerFrame.Layout.preferredHeight = fullscreenWindow.height + outerFrame.visible = true + outerFrame.parent = fullscreenContent + outerFrame.x = 0 + outerFrame.y = 0 + outerFrame.width = Qt.binding(function() { return fullscreenWindow.width }) + outerFrame.height = Qt.binding(function() { return fullscreenWindow.height }) + outerFrame.isFullscreen = true + fullscreenWindow.show() + } + } + } + Window { + id: fullscreenWindow + color: "#2d2d2d" + width: root.totalTableWidth + 30 + 16 + 20 + height: root.totalTableHeight + 10 + 16 + 20 + title: attribute ? attribute.label : "" + palette: root.appPalette + Item { + id: fullscreenContent + anchors.fill: parent + } + onClosing: { + outerFrame.width = undefined + outerFrame.height = undefined + outerFrame.Layout.preferredWidth = -1 + outerFrame.parent = root + outerFrame.isFullscreen = false + outerFrame.Layout.fillWidth = true + outerFrame.Layout.preferredHeight = Qt.binding(function() { + return root.expanded + ? Math.min(root.totalTableHeight + 40, 330) + : 0 + }) + outerFrame.visible = Qt.binding(function() { + return root.expanded + }) + } + } + FontMetrics { + id: fontMetrics + font.bold: false + } + function initSizes() { + var names = root.columnNames + if (!names || names.length === 0) { + root.columnWidths = [] + root.rowHeights = [] + return + } + var widths = [] + for (var i = 0; i < names.length; i++) + widths.push(fontMetrics.advanceWidth(names[i]) + 20) + var heights = [] + if (attribute && attribute.value) { + for (var r = 0; r < attribute.value.count; r++) { + var rowAttr = attribute.value.at(r) + if (!rowAttr || !rowAttr.value) continue + for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { + var cell = rowAttr.value.at(c) + var cellText = cell ? String(cell.value) : "" + var cw = fontMetrics.advanceWidth(cellText) + 20 + if (cw > widths[c]) widths[c] = cw + } + heights.push(30) + } + } + root.columnWidths = widths + root.rowHeights = heights + } + Component.onCompleted: { + root.initSizes() + } + Connections { + target: attribute ? attribute.value : null + function onCountChanged() {root.initSizes(); root.updateScaledWidths(); root.updateScaledHeights()} + function onModelReset() {root.initSizes(); root.updateScaledWidths(); root.updateScaledHeights()} + function onRowsInserted() {root.initSizes(); root.updateScaledWidths(); root.updateScaledHeights()} + function onDataChanged() {root.initSizes(); root.updateScaledWidths(); root.updateScaledHeights()} + } + onAvailableWChanged: root.updateScaledWidths() + onAvailableHChanged: root.updateScaledHeights() + Item { + id: outerFrame + Layout.fillWidth: true + visible: root.expanded + Layout.preferredHeight: root.expanded + ? Math.min(root.totalTableHeight + 40, 330) + : 0 + property bool isFullscreen: false + ScrollBar { + id: hBar + anchors.left: fixedStrip.right + anchors.right: outerFrame.right + anchors.bottom: outerFrame.bottom + anchors.rightMargin: vBar.width + orientation: Qt.Horizontal + policy: flickable.contentWidth > flickable.width + ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + size: Math.min(1.0, flickable.width / Math.max(flickable.contentWidth, 1)) + position: (flickable.contentX / Math.max(flickable.contentWidth - flickable.width, 1)) + * (1.0 - size) + onPositionChanged: { + if (!pressed) return + var maxPos = 1.0 - size + var ratio = maxPos > 0 ? position / maxPos : 0 + flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) + } + } + ScrollBar { + id: vBar + anchors.top: outerFrame.top + anchors.bottom: outerFrame.bottom + anchors.right: outerFrame.right + anchors.bottomMargin: hBar.height + orientation: Qt.Vertical + policy: flickable.contentHeight > flickable.height + ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + size: Math.min(1.0, flickable.height / Math.max(flickable.contentHeight, 1)) + position: (flickable.contentY / Math.max(flickable.contentHeight - flickable.height, 1)) + * (1.0 - size) + onPositionChanged: { + if (!pressed) return + var maxPos = 1.0 - size + var ratio = maxPos > 0 ? position / maxPos : 0 + flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) + } + } + Item { + id: fixedHeader + anchors.left: fixedStrip.right + anchors.right: outerFrame.right + anchors.top: outerFrame.top + anchors.rightMargin: vBar.width + height: 30 + clip: true + Row { + spacing: 1 + x: -flickable.contentX + Repeater { + model: root.columnNames + delegate: Item { + id: headerCell + required property int index + required property string modelData + width: root.scaledColumnWidths[index] || 100 + height: 30 + Rectangle { + anchors.fill: parent + color: "#2d2d2d" + border.color: "#1d1d1d" + Text { + anchors.fill: parent + text: headerCell.modelData + color: "#aaaaaa" + font.bold: false + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + MouseArea { + width: 6 + height: parent.height + anchors.right: parent.right + cursorShape: Qt.SizeHorCursor + property real startX: 0 + property real startW: 0 + onPressed: function(mouse) { + grabMouse() + var p = mapToItem(root, mouse.x, mouse.y) + startX = p.x + startW = root.columnWidths[headerCell.index] + } + onReleased: function(mouse) { + ungrabMouse() + } + onPositionChanged: function(mouse) { + if (!pressed) return + var p = mapToItem(root, mouse.x, mouse.y) + var newW = Math.max(40, startW + (p.x - startX)) + var arr = root.columnWidths.slice() + arr[headerCell.index] = newW + root.columnWidths = arr + root.updateScaledWidths() + } + } + } + } + } + } + Item { + id: fixedStrip + anchors.left: outerFrame.left + anchors.top: outerFrame.top + anchors.bottom: outerFrame.bottom + anchors.topMargin: 30 + anchors.bottomMargin: hBar.height + width: 30 + clip: true + Column { + spacing: 1 + width: parent.width + y: -flickable.contentY + Repeater { + model: attribute ? attribute.value : null + delegate: Item { + id: removeDelegate + required property int index + required property var object + width: fixedStrip.width + height: root.scaledRowHeights[index] || 30 + ToolButton { + anchors.centerIn: parent + enabled: root.editable + text: MaterialIcons.remove_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + ToolTip.text: "Remove Element" + ToolTip.visible: hovered + contentItem: Text { + text: parent.text + font: parent.font + color: "#aaaaaa" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: _currentScene.removeAttribute(removeDelegate.object) + } + } + } + } + } + Item { + id: cornerCell + anchors.left: outerFrame.left + anchors.top: outerFrame.top + width: fixedStrip.width + height: fixedHeader.height + visible: outerFrame.isFullscreen + Rectangle { + anchors.fill: parent + color: "#2d2d2d" + border.color: "#1d1d1d" + } + ToolButton { + anchors.centerIn: parent + text: MaterialIcons.add_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + enabled: root.editable + ToolTip.text: "Add Element" + ToolTip.visible: hovered + contentItem: Text { + text: parent.text + font: parent.font + color: "#aaaaaa" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: _currentScene.appendAttribute(attribute, undefined) + } + } + Flickable { + id: flickable + anchors.left: fixedStrip.right + anchors.top: outerFrame.top + anchors.right: outerFrame.right + anchors.bottom: outerFrame.bottom + anchors.topMargin: 30 + anchors.rightMargin: vBar.width + anchors.bottomMargin: hBar.height + clip: true + contentWidth: root.scaledTableWidth + contentHeight: root.scaledTableHeight + interactive: true + WheelHandler { + onWheel: function(event) { + if (event.modifiers & Qt.ControlModifier) { + flickable.contentX = Math.max(0, + Math.min(flickable.contentWidth - flickable.width, + flickable.contentX - event.angleDelta.y / 120 * 40)) + } else { + flickable.contentY = Math.max(0, + Math.min(flickable.contentHeight - flickable.height, + flickable.contentY - event.angleDelta.y / 120 * 40)) + } + event.accepted = true + } + } + Column { + spacing: 1 + Repeater { + id: rowRepeater + model: attribute ? attribute.value : null + delegate: TableViewRowDelegate { + rowIndex: index + rowObject: object + rowHeight: root.scaledRowHeights[index] || 30 + tableWidth: root.scaledTableWidth + scaledColumnWidths: root.scaledColumnWidths + editable: root.editable + onRowResized: function(rowIndex, newH) { + var arr = root.rowHeights.slice() + arr[rowIndex] = newH + root.rowHeights = arr + root.updateScaledHeights() + } + } + } + } + } + } +} From af2173515775520ac36b55b13e9c91821f79bee5 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:43:46 +0200 Subject: [PATCH 11/26] added TableViewRowDelegate.qml --- .../TableViewRowDelegate.qml | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml new file mode 100644 index 0000000000..63d1a9cb82 --- /dev/null +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Controls + +Item { + id: root + property int rowIndex: 0 + property var rowObject: null + property real rowHeight: 30 + property real tableWidth: 100 + property var scaledColumnWidths: [] + property bool editable: true + signal rowResized(int rowIndex, real newHeight) + width: tableWidth + height: rowHeight + Row { + spacing: 1 + anchors.fill: parent + Repeater { + model: root.rowObject && root.rowObject.value + ? root.rowObject.value.count : 0 + delegate: TableViewCellDelegate { + cellIndex: index + rowObject: root.rowObject + rowIndex: root.rowIndex + cellWidth: (root.scaledColumnWidths && root.scaledColumnWidths.length > index) + ? root.scaledColumnWidths[index] : 100 + cellHeight: root.height + } + } + } + MouseArea { + id: colResizeHandle + width: parent.width + height: 6 + anchors.bottom: parent.bottom + cursorShape: Qt.SizeVerCursor + preventStealing: true + property real lastY: 0 + onPressed: function(mouse) { + colResizeHandle.grabMouse(); + lastY = mapToGlobal(mouse.x, mouse.y).y + } + onReleased: function(mouse) { colResizeHandle.ungrabMouse() } + onPositionChanged: function(mouse) { + if (!pressed) return + var globalY = mapToGlobal(mouse.x, mouse.y).y + var delta = globalY - lastY + lastY = globalY + root.rowResized(root.rowIndex, Math.max(20, root.rowHeight + delta)) + } + } +} From b062f66e19a82427a1de5d5b647177c887f9042e Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:44:07 +0200 Subject: [PATCH 12/26] added TableViewCellDelegate.qml --- .../TableViewCellDelegate.qml | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml new file mode 100644 index 0000000000..3d2e41f701 --- /dev/null +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml @@ -0,0 +1,287 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import MaterialIcons 2.2 +import Utils 1.0 + +Rectangle { + id: cellRect + property int cellIndex: 0 + property var rowObject: null + property int rowIndex: 0 + property real cellWidth + property real cellHeight + property bool editable: true + width : cellWidth + height : cellHeight + color : rowIndex % 2 === 0 ? "#2d2d2d" : "#333333" + border.color : cellFocused ? "#5599ff" : "#1d1d1d" + clip : true + property var cell: rowObject.value.at(cellIndex) + property bool cellFocused: { + var item = cellLoader.item + if (!item) return false + return item.activeFocus || + (item.children && item.children.length > 0 && + item.children[0] && item.children[0].activeFocus) + } + Rectangle { + anchors.centerIn: parent + width: cellLoader.width + 8 + height: cellLoader.height + 4 + radius: 3 + color: "#444444" + visible: cellRect.cell && + (cellRect.cell.type === "BoolParam" || + cellRect.cell.type === "ChoiceParam") + } + Loader { + id: cellLoader + anchors.centerIn: parent + width: parent.width + height: parent.height + property var attribute: cellRect.cell + sourceComponent: { + if (!attribute) return null + switch (attribute.type) { + case "PushButtonParam": return cellPushButtonComponent + case "ChoiceParam": + return (attribute.desc && attribute.desc.exclusive) + ? cellChoiceComponent : cellChoiceMultiComponent + case "IntParam": return cellSliderComponent + case "FloatParam": + return (attribute.desc && attribute.desc.semantic === "color/hue") + ? cellColorHueComponent : cellSliderComponent + case "BoolParam": return cellCheckboxComponent + case "StringParam": + return (attribute.desc && attribute.desc.semantic && + attribute.desc.semantic.includes("multiline")) + ? cellTextAreaComponent : cellTextFieldComponent + case "ColorParam": return cellColorComponent + default: return cellTextFieldComponent + } + } + Component { + id: cellChoiceComponent + Choice { + value: cellLoader.attribute ? cellLoader.attribute.value : "" + values: cellLoader.attribute ? cellLoader.attribute.values : [] + enabled: cellRect.editable + Component.onCompleted: { + if (typeof popup !== "undefined" && popup !== null) { + popup.margins = -1 + } + } + onEditingFinished: function(value) { + if (cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, value) + } + } + } + Component { + id: cellChoiceMultiComponent + ChoiceMulti { + value: cellLoader.attribute ? cellLoader.attribute.value : [] + values: cellLoader.attribute ? cellLoader.attribute.values : [] + enabled: cellRect.editable + customValueColor: Colors.orange + onToggled: function(value, checked) { + if (!cellLoader.attribute) return + var cur = cellLoader.attribute.value.slice() + if (!checked) { + var idx = cur.indexOf(value) + if (idx !== -1) cur.splice(idx, 1) + } else { + cur.push(value) + } + _currentScene.setAttribute(cellLoader.attribute, cur) + } + } + } + Component { + id: cellSliderComponent + RowLayout { + spacing: 2 + TextField { + id: cellNumField + Layout.fillWidth: !cellSliderLoader.active + implicitWidth: 70 + enabled: cellRect.editable + selectByMouse: true + horizontalAlignment: TextInput.AlignRight + text: { + if (cellSliderLoader.active && cellSliderLoader.item && + cellSliderLoader.item.pressed) + return String(cellSliderLoader.item.formattedValue) + return cellLoader.attribute ? String(cellLoader.attribute.value) : "" + } + background: Rectangle { color: "#3c3c3c"; radius: 2 } + color: "#cccccc" + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? parseInt(text) : parseFloat(text)) + } + WheelHandler { + onWheel: function(event) { + if (!cellRect.editable || !cellLoader.attribute) return + var step = 1 + if (cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3) + step = cellLoader.attribute.desc.range[2] + var dir = event.angleDelta.y > 0 ? 1 : -1 + var v = Number(cellLoader.attribute.value) + dir * step + if (cellLoader.attribute.desc && cellLoader.attribute.desc.range) { + v = Math.max(cellLoader.attribute.desc.range[0], + Math.min(cellLoader.attribute.desc.range[1], v)) + } + _currentScene.setAttribute(cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? Math.round(v) : v) + event.accepted = true + } + } + } + Loader { + id: cellSliderLoader + Layout.fillWidth: true + active: cellLoader.attribute && + cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3 + sourceComponent: Slider { + readonly property int stepDecimalCount: stepSize < 1 + ? String(stepSize).split(".").pop().length : 0 + readonly property real formattedValue: value.toFixed(stepDecimalCount) + enabled: cellRect.editable + value: cellLoader.attribute ? cellLoader.attribute.value : 0 + from: cellLoader.attribute.desc.range[0] + to: cellLoader.attribute.desc.range[1] + stepSize: cellLoader.attribute.desc.range[2] + snapMode: Slider.SnapAlways + onPressedChanged: { + if (!pressed && cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, + formattedValue) + } + } + } + } + } + Component { + id: cellCheckboxComponent + CheckBox { + enabled: cellRect.editable + checked: cellLoader.attribute + ? cellLoader.attribute.value : false + onToggled: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, checked) + } + } + } + Component { + id: cellTextFieldComponent + TextField { + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + color: "#3c3c3c" + radius: 2 + } + color: "#cccccc" + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) + } + } + } + Component { + id: cellTextAreaComponent + TextField { + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + color: "#3c3c3c" + radius: 2 + } + color: "#cccccc" + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) + } + } + } + Component { + id: cellColorComponent + TextField { + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + color: "#3c3c3c" + radius: 2 + } + color: "#cccccc" + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text) + } + } + } + Component { + id: cellPushButtonComponent + Button { + text: cellLoader.attribute + ? cellLoader.attribute.label : "" + enabled: cellRect.editable + onClicked: { + if (cellLoader.attribute) + cellLoader.attribute.clicked() + } + } + } + Component { + id: cellColorHueComponent + RowLayout { + Slider { + id: cellHueSlider + Layout.fillWidth: true + enabled: cellRect.editable + value: cellLoader.attribute + ? cellLoader.attribute.value : 0 + from: 0 + to: 1 + stepSize: 0.01 + snapMode: Slider.SnapAlways + onPressedChanged: { + if (!pressed && cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, + value.toFixed(2)) + } + } + Rectangle { + width: 16 + height: 16 + color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) + } + } + } + } +} From 41a0d95cf00cf8718bde8dc7da35764c51a42a02 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:44:55 +0200 Subject: [PATCH 13/26] calls the tableWiew components in AttributeItemDelegate.qml --- .../qml/GraphEditor/AttributeItemDelegate.qml | 746 +----------------- 1 file changed, 2 insertions(+), 744 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml index fecf6c4e97..b75a7025c6 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml @@ -835,750 +835,8 @@ RowLayout { Component { id: tableViewAttributeComponent - ColumnLayout { - id: tableLayout - spacing: 0 - property var columnNames: { - if (!attribute || !attribute.value || attribute.value.count === 0) return [] - var firstRow = attribute.value.at(0) - if (!firstRow || !firstRow.value) return [] - var names = [] - for (var i = 0; i < firstRow.value.count; i++) { - var child = firstRow.value.at(i) - if (child) names.push(child.label) - } - return names - } - property var columnWidths: [] - property var rowHeights: [] - property real totalTableWidth: { - if (!columnWidths || columnWidths.length === 0) return 0 - var t = 0 - for (var i = 0; i < columnWidths.length; i++) t += columnWidths[i] - t += Math.max(0, columnWidths.length - 1) - return t - } - property real totalTableHeight: { - if (!rowHeights || rowHeights.length === 0) return 0 - var t = 0 - for (var i = 0; i < rowHeights.length; i++) t += rowHeights[i] - t += Math.max(0, rowHeights.length - 1) - return t - } - property var scaledColumnWidths: [] - property real scaledTableWidth: 0 - property var scaledRowHeights: [] - property real scaledTableHeight: 0 - property real availableW: outerFrame.width - fixedStrip.width - vBar.width - property real availableH: outerFrame.height - fixedHeader.height - hBar.height - function updateScaledWidths() { - if (!columnWidths || columnWidths.length === 0) { - scaledColumnWidths = [] - scaledTableWidth = 0 - return - } - var scaleFactor = Math.max(1.0, availableW / Math.max(totalTableWidth, 1)) - var result = [] - for (var i = 0; i < columnWidths.length; i++) - result.push(columnWidths[i] * scaleFactor) - scaledColumnWidths = result - scaledTableWidth = Math.max(totalTableWidth, availableW) - } - function updateScaledHeights() { - if (!rowHeights || rowHeights.length === 0) { - scaledRowHeights = [] - scaledTableHeight = 0 - return - } - var scaleFactor = Math.max(1.0, availableH / Math.max(totalTableHeight, 1)) - var result = [] - for (var i = 0; i < rowHeights.length; i++) - result.push(rowHeights[i] * scaleFactor) - scaledRowHeights = result - scaledTableHeight = Math.max(totalTableHeight, availableH) - } - property bool expanded: false - property var appPalette: palette - RowLayout { - spacing: 4 - ToolButton { - text: tableLayout.expanded ? MaterialIcons.keyboard_arrow_down - : MaterialIcons.keyboard_arrow_right - font.family: MaterialIcons.fontFamily - onClicked: tableLayout.expanded = !tableLayout.expanded - } - Label { - Layout.alignment: Qt.AlignVCenter - text: attribute.value.count + " elements" - } - ToolButton { - text: MaterialIcons.add_circle_outline - font.family: MaterialIcons.fontFamily - font.pointSize: 11 - padding: 2 - enabled: root.editable - onClicked: _currentScene.appendAttribute(attribute, undefined) - } - ToolButton { - text: MaterialIcons.fullscreen - font.family: MaterialIcons.fontFamily - font.pointSize: 11 - padding: 2 - ToolTip.text: "Open in fullscreen" - ToolTip.visible: hovered - onClicked: { - outerFrame.anchors.fill = undefined - outerFrame.anchors.top = undefined - outerFrame.anchors.left = undefined - outerFrame.anchors.right = undefined - outerFrame.anchors.bottom = undefined - outerFrame.Layout.fillWidth = false - outerFrame.Layout.preferredWidth = fullscreenWindow.width - outerFrame.Layout.preferredHeight = fullscreenWindow.height - outerFrame.visible = true - outerFrame.parent = fullscreenContent - outerFrame.x = 0 - outerFrame.y = 0 - outerFrame.width = Qt.binding(function() { return fullscreenWindow.width }) - outerFrame.height = Qt.binding(function() { return fullscreenWindow.height }) - outerFrame.isFullscreen = true - fullscreenWindow.show() - } - } - } - Window { - id: fullscreenWindow - color: "#2d2d2d" - width: tableLayout.totalTableWidth + 30 + 16 + 20 - height: tableLayout.totalTableHeight + 10 + 16 + 20 - title: attribute ? attribute.label : "" - palette: tableLayout.appPalette - Item { - id: fullscreenContent - anchors.fill: parent - } - onClosing: { - outerFrame.anchors.fill = undefined - outerFrame.anchors.top = undefined - outerFrame.anchors.left = undefined - outerFrame.anchors.right = undefined - outerFrame.anchors.bottom = undefined - outerFrame.width = undefined - outerFrame.height = undefined - outerFrame.Layout.preferredWidth = -1 - outerFrame.parent = tableLayout - outerFrame.isFullscreen = false - outerFrame.Layout.fillWidth = true - outerFrame.Layout.preferredHeight = Qt.binding(function() { - return tableLayout.expanded - ? Math.min(tableLayout.totalTableHeight + 40, 330) - : 0 - }) - outerFrame.visible = Qt.binding(function() { - return tableLayout.expanded - }) - } - } - FontMetrics { - id: fontMetrics - font.bold: false - } - function initSizes() { - var names = tableLayout.columnNames - if (!names || names.length === 0) { - tableLayout.columnWidths = [] - tableLayout.rowHeights = [] - return - } - var widths = [] - for (var i = 0; i < names.length; i++) - widths.push(fontMetrics.advanceWidth(names[i]) + 20) - var heights = [] - if (attribute && attribute.value) { - for (var r = 0; r < attribute.value.count; r++) { - var rowAttr = attribute.value.at(r) - if (!rowAttr || !rowAttr.value) continue - for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { - var cell = rowAttr.value.at(c) - var cellText = cell ? String(cell.value) : "" - var cw = fontMetrics.advanceWidth(cellText) + 20 - if (cw > widths[c]) widths[c] = cw - } - heights.push(30) - } - } - tableLayout.columnWidths = widths - tableLayout.rowHeights = heights - } - Component.onCompleted: { - tableLayout.initSizes() - } - Connections { - target: attribute ? attribute.value : null - function onCountChanged() {tableLayout.initSizes(); tableLayout.updateScaledWidths(); tableLayout.updateScaledHeights()} - function onModelReset() {tableLayout.initSizes(); tableLayout.updateScaledWidths(); tableLayout.updateScaledHeights()} - function onRowsInserted() {tableLayout.initSizes(); tableLayout.updateScaledWidths(); tableLayout.updateScaledHeights()} - function onDataChanged() {tableLayout.initSizes(); tableLayout.updateScaledWidths(); tableLayout.updateScaledHeights()} - } - onAvailableWChanged: tableLayout.updateScaledWidths() - onAvailableHChanged: tableLayout.updateScaledHeights() - Item { - id: outerFrame - Layout.fillWidth: true - visible: tableLayout.expanded - Layout.preferredHeight: tableLayout.expanded - ? Math.min(tableLayout.totalTableHeight + 40, 330) - : 0 - property bool isFullscreen: false - ScrollBar { - id: hBar - anchors.left: fixedStrip.right - anchors.right: outerFrame.right - anchors.bottom: outerFrame.bottom - anchors.rightMargin: vBar.width - orientation: Qt.Horizontal - policy: flickable.contentWidth > flickable.width - ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff - size: Math.min(1.0, flickable.width / Math.max(flickable.contentWidth, 1)) - position: (flickable.contentX / Math.max(flickable.contentWidth - flickable.width, 1)) - * (1.0 - size) - onPositionChanged: { - if (!pressed) return - var maxPos = 1.0 - size - var ratio = maxPos > 0 ? position / maxPos : 0 - flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) - } - } - ScrollBar { - id: vBar - anchors.top: outerFrame.top - anchors.bottom: outerFrame.bottom - anchors.right: outerFrame.right - anchors.bottomMargin: hBar.height - orientation: Qt.Vertical - policy: flickable.contentHeight > flickable.height - ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff - size: Math.min(1.0, flickable.height / Math.max(flickable.contentHeight, 1)) - position: (flickable.contentY / Math.max(flickable.contentHeight - flickable.height, 1)) - * (1.0 - size) - onPositionChanged: { - if (!pressed) return - var maxPos = 1.0 - size - var ratio = maxPos > 0 ? position / maxPos : 0 - flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) - } - } - Item { - id: fixedHeader - anchors.left: fixedStrip.right - anchors.right: outerFrame.right - anchors.top: outerFrame.top - anchors.rightMargin: vBar.width - height: 30 - clip: true - Row { - spacing: 1 - x: -flickable.contentX - Repeater { - model: tableLayout.columnNames - delegate: Item { - id: headerCell - required property int index - required property string modelData - width: tableLayout.scaledColumnWidths[index] || 100 - height: 30 - Rectangle { - anchors.fill: parent - color: "#2d2d2d" - border.color: "#1d1d1d" - Text { - anchors.fill: parent - text: headerCell.modelData - color: "#aaaaaa" - font.bold: false - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - } - MouseArea { - width: 6 - height: parent.height - anchors.right: parent.right - cursorShape: Qt.SizeHorCursor - property real startX: 0 - property real startW: 0 - onPressed: function(mouse) { - grabMouse() - var p = mapToItem(tableLayout, mouse.x, mouse.y) - startX = p.x - startW = tableLayout.columnWidths[headerCell.index] - } - onReleased: function(mouse) { - ungrabMouse() - } - onPositionChanged: function(mouse) { - if (!pressed) return - var p = mapToItem(tableLayout, mouse.x, mouse.y) - var newW = Math.max(40, startW + (p.x - startX)) - var arr = tableLayout.columnWidths.slice() - arr[headerCell.index] = newW - tableLayout.columnWidths = arr - tableLayout.updateScaledWidths() - } - } - } - } - } - } - Item { - id: fixedStrip - anchors.left: outerFrame.left - anchors.top: outerFrame.top - anchors.bottom: outerFrame.bottom - anchors.topMargin: 30 - anchors.bottomMargin: hBar.height - width: 30 - clip: true - Column { - spacing: 1 - width: parent.width - y: -flickable.contentY - Repeater { - model: attribute ? attribute.value : null - delegate: Item { - id: removeDelegate - required property int index - required property var object - width: fixedStrip.width - height: tableLayout.scaledRowHeights[index] || 30 - ToolButton { - anchors.centerIn: parent - enabled: root.editable - text: MaterialIcons.remove_circle_outline - font.family: MaterialIcons.fontFamily - font.pointSize: 11 - padding: 2 - ToolTip.text: "Remove Element" - ToolTip.visible: hovered - contentItem: Text { - text: parent.text - font: parent.font - color: "#aaaaaa" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: _currentScene.removeAttribute(removeDelegate.object) - } - } - } - } - } - Item { - id: cornerCell - anchors.left: outerFrame.left - anchors.top: outerFrame.top - width: fixedStrip.width - height: fixedHeader.height - visible: outerFrame.isFullscreen - Rectangle { - anchors.fill: parent - color: "#2d2d2d" - border.color: "#1d1d1d" - } - ToolButton { - anchors.centerIn: parent - text: MaterialIcons.add_circle_outline - font.family: MaterialIcons.fontFamily - font.pointSize: 11 - padding: 2 - enabled: root.editable - ToolTip.text: "Add Element" - ToolTip.visible: hovered - contentItem: Text { - text: parent.text - font: parent.font - color: "#aaaaaa" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: _currentScene.appendAttribute(attribute, undefined) - } - } - Flickable { - id: flickable - anchors.left: fixedStrip.right - anchors.top: outerFrame.top - anchors.right: outerFrame.right - anchors.bottom: outerFrame.bottom - anchors.topMargin: 30 - anchors.rightMargin: vBar.width - anchors.bottomMargin: hBar.height - clip: true - contentWidth: tableLayout.scaledTableWidth - contentHeight: tableLayout.scaledTableHeight - interactive: true - WheelHandler { - onWheel: function(event) { - if (event.modifiers & Qt.ControlModifier) { - flickable.contentX = Math.max(0, - Math.min(flickable.contentWidth - flickable.width, - flickable.contentX - event.angleDelta.y / 120 * 40)) - } else { - flickable.contentY = Math.max(0, - Math.min(flickable.contentHeight - flickable.height, - flickable.contentY - event.angleDelta.y / 120 * 40)) - } - event.accepted = true - } - } - Column { - spacing: 1 - Repeater { - model: attribute ? attribute.value : null - delegate: Item { - id: rowItem - required property int index - required property var object - width: tableLayout.scaledTableWidth - height: tableLayout.scaledRowHeights[index] || 30 - Row { - spacing: 1 - anchors.fill: parent - Repeater { - model: rowItem.object && rowItem.object.value - ? rowItem.object.value.count : 0 - delegate: Rectangle { - id: cellRect - required property int index - width: tableLayout.scaledColumnWidths[index] || 100 - height: rowItem.height - color: rowItem.index % 2 === 0 ? "#2d2d2d" : "#333333" - border.color: cellFocused ? "#5599ff" : "#1d1d1d" - clip: true - property bool cellFocused: { - var item = cellLoader.item - if (!item) return false - return item.activeFocus || - (item.children && item.children.length > 0 && - item.children[0] && item.children[0].activeFocus) - } - property var cell: rowItem.object.value.at(index) - function choiceValues(attr) { - if (!attr) return [] - if (attr.desc && Array.isArray(attr.desc.values)) - return attr.desc.values - if (attr.values && Array.isArray(attr.values)) - return attr.values - if (attr.values && typeof attr.values.count === "number") { - var out = [] - for (var i = 0; i < attr.values.count; i++) - out.push(attr.values.at(i).value) - return out - } - return [] - } - Rectangle { - anchors.centerIn: parent - width: cellLoader.width + 8 - height: cellLoader.height + 4 - radius: 3 - color: "#444444" - visible: cellRect.cell && - (cellRect.cell.type === "BoolParam" || - cellRect.cell.type === "ChoiceParam") - } - Loader { - id: cellLoader - anchors.centerIn: parent - width: parent.width - height: parent.height - property var attribute: cellRect.cell - sourceComponent: { - if (!attribute) return null - switch (attribute.type) { - case "PushButtonParam": - return cellPushButtonComponent - case "ChoiceParam": - return (attribute.desc && attribute.desc.exclusive) - ? cellChoiceComponent - : cellChoiceMultiComponent - case "IntParam": - return cellSliderComponent - case "FloatParam": - return (attribute.desc && attribute.desc.semantic === "color/hue") - ? cellColorHueComponent - : cellSliderComponent - case "BoolParam": - return cellCheckboxComponent - case "StringParam": - return (attribute.desc && attribute.desc.semantic && - attribute.desc.semantic.includes("multiline")) - ? cellTextAreaComponent - : cellTextFieldComponent - case "ColorParam": - return cellColorComponent - default: - return cellTextFieldComponent - } - } - Component { - id: cellChoiceComponent - AttributeControls.Choice { - value: cellLoader.attribute ? cellLoader.attribute.value : "" - values: cellLoader.attribute ? cellLoader.attribute.values : [] - enabled: root.editable - Component.onCompleted: { - if (typeof popup !== "undefined" && popup !== null) { - popup.margins = -1 - } - } - onEditingFinished: function(value) { - if (cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, value) - } - } - } - Component { - id: cellChoiceMultiComponent - AttributeControls.ChoiceMulti { - value: cellLoader.attribute ? cellLoader.attribute.value : [] - values: cellLoader.attribute ? cellLoader.attribute.values : [] - enabled: root.editable - customValueColor: Colors.orange - onToggled: function(value, checked) { - if (!cellLoader.attribute) return - var cur = cellLoader.attribute.value.slice() - if (!checked) { - var idx = cur.indexOf(value) - if (idx !== -1) cur.splice(idx, 1) - } else { - cur.push(value) - } - _currentScene.setAttribute(cellLoader.attribute, cur) - } - } - } - Component { - id: cellSliderComponent - RowLayout { - spacing: 2 - TextField { - id: cellNumField - Layout.fillWidth: !cellSliderLoader.active - implicitWidth: 70 - enabled: root.editable - selectByMouse: true - horizontalAlignment: TextInput.AlignRight - text: { - if (cellSliderLoader.active && cellSliderLoader.item && - cellSliderLoader.item.pressed) - return String(cellSliderLoader.item.formattedValue) - return cellLoader.attribute ? String(cellLoader.attribute.value) : "" - } - background: Rectangle { color: "#3c3c3c"; radius: 2 } - color: "#cccccc" - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, - cellLoader.attribute.type === "IntParam" - ? parseInt(text) : parseFloat(text)) - } - // Mouse-wheel scroll to increment/decrement - WheelHandler { - onWheel: function(event) { - if (!root.editable || !cellLoader.attribute) return - var step = 1 - if (cellLoader.attribute.desc && - cellLoader.attribute.desc.range && - cellLoader.attribute.desc.range.length === 3) - step = cellLoader.attribute.desc.range[2] - var dir = event.angleDelta.y > 0 ? 1 : -1 - var v = Number(cellLoader.attribute.value) + dir * step - if (cellLoader.attribute.desc && cellLoader.attribute.desc.range) { - v = Math.max(cellLoader.attribute.desc.range[0], - Math.min(cellLoader.attribute.desc.range[1], v)) - } - _currentScene.setAttribute(cellLoader.attribute, - cellLoader.attribute.type === "IntParam" - ? Math.round(v) : v) - event.accepted = true - } - } - } - Loader { - id: cellSliderLoader - Layout.fillWidth: true - active: cellLoader.attribute && - cellLoader.attribute.desc && - cellLoader.attribute.desc.range && - cellLoader.attribute.desc.range.length === 3 - sourceComponent: Slider { - readonly property int stepDecimalCount: stepSize < 1 - ? String(stepSize).split(".").pop().length : 0 - readonly property real formattedValue: value.toFixed(stepDecimalCount) - enabled: root.editable - value: cellLoader.attribute ? cellLoader.attribute.value : 0 - from: cellLoader.attribute.desc.range[0] - to: cellLoader.attribute.desc.range[1] - stepSize: cellLoader.attribute.desc.range[2] - snapMode: Slider.SnapAlways - onPressedChanged: { - if (!pressed && cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, - formattedValue) - } - } - } - } - } - Component { - id: cellCheckboxComponent - CheckBox { - enabled: root.editable - checked: cellLoader.attribute - ? cellLoader.attribute.value : false - onToggled: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, checked) - } - } - } - Component { - id: cellTextFieldComponent - TextField { - enabled: root.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - selectByMouse: true - background: Rectangle { - color: "#3c3c3c" - radius: 2 - } - color: "#cccccc" - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text.trim()) - } - } - } - Component { - id: cellTextAreaComponent - TextField { - enabled: root.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - selectByMouse: true - background: Rectangle { - color: "#3c3c3c" - radius: 2 - } - color: "#cccccc" - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text.trim()) - } - } - } - Component { - id: cellColorComponent - TextField { - enabled: root.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - selectByMouse: true - background: Rectangle { - color: "#3c3c3c" - radius: 2 - } - color: "#cccccc" - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text) - } - } - } - Component { - id: cellPushButtonComponent - Button { - text: cellLoader.attribute - ? cellLoader.attribute.label : "" - enabled: root.editable - onClicked: { - if (cellLoader.attribute) - cellLoader.attribute.clicked() - } - } - } - Component { - id: cellColorHueComponent - RowLayout { - Slider { - id: cellHueSlider - Layout.fillWidth: true - enabled: root.editable - value: cellLoader.attribute - ? cellLoader.attribute.value : 0 - from: 0 - to: 1 - stepSize: 0.01 - snapMode: Slider.SnapAlways - onPressedChanged: { - if (!pressed && cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, - value.toFixed(2)) - } - } - Rectangle { - width: 16 - height: 16 - color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) - } - } - } - } - } - } - } - MouseArea { - width: parent.width - height: 6 - anchors.bottom: parent.bottom - cursorShape: Qt.SizeVerCursor - preventStealing: true - property real lastY: 0 - onPressed: function(mouse) { - grabMouse() - lastY = mapToGlobal(mouse.x, mouse.y).y - } - onReleased: function(mouse) { - ungrabMouse() - } - onPositionChanged: function(mouse) { - if (!pressed) return - var globalY = mapToGlobal(mouse.x, mouse.y).y - var delta = globalY - lastY - lastY = globalY - var newH = Math.max(20, tableLayout.rowHeights[rowItem.index] + delta) - var arr = tableLayout.rowHeights.slice() - arr[rowItem.index] = newH - tableLayout.rowHeights = arr - tableLayout.updateScaledHeights() - } - } - } - } - } - } - } + AttributeControls.TableView { + attribute: root.attribute } } From 362869022941648260d3f8e90d54ef4884b7b888 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:32:39 +0200 Subject: [PATCH 14/26] tableView : fixed syntax issues, rowHeight is now a fixed value --- .../AttributeControls/TableView.qml | 662 ++++++++---------- 1 file changed, 280 insertions(+), 382 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml index a3aebaaa82..2efbadfc44 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml @@ -1,419 +1,317 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import QtQuick.Window import MaterialIcons 2.2 -import Utils 1.0 +import Utils 1.0 -ColumnLayout { - id: root - spacing: 0 +Rectangle { + id: cellRect + property int cellIndex: 0 + property var rowObject: null + property int rowIndex: 0 + property real cellWidth: 100 + property real cellHeight: 24 property bool editable: true - required property var attribute - property var columnNames: { - if (!attribute || !attribute.value || attribute.value.count === 0) return [] - var firstRow = attribute.value.at(0) - if (!firstRow || !firstRow.value) return [] - var names = [] - for (var i = 0; i < firstRow.value.count; i++) { - var child = firstRow.value.at(i) - if (child) names.push(child.label) - } - return names - } - property var columnWidths: [] - property var rowHeights: [] - property real totalTableWidth: { - if (!columnWidths || columnWidths.length === 0) return 0 - var t = 0 - for (var i = 0; i < columnWidths.length; i++) t += columnWidths[i] - t += Math.max(0, columnWidths.length - 1) - return t - } - property real totalTableHeight: { - if (!rowHeights || rowHeights.length === 0) return 0 - var t = 0 - for (var i = 0; i < rowHeights.length; i++) t += rowHeights[i] - t += Math.max(0, rowHeights.length - 1) - return t - } - property var scaledColumnWidths: [] - property real scaledTableWidth: 0 - property var scaledRowHeights: [] - property real scaledTableHeight: 0 - property real availableW: outerFrame.width > 0 ? outerFrame.width - fixedStrip.width - vBar.width : 600 - property real availableH: outerFrame.height > 0 ? outerFrame.height - hBar.height : 400 - function updateScaledWidths() { - if (!columnWidths || columnWidths.length === 0) { - scaledColumnWidths = [] - scaledTableWidth = 0 - return - } - var scaleFactor = Math.max(1.0, availableW / Math.max(totalTableWidth, 1)) - var result = [] - for (var i = 0; i < columnWidths.length; i++) - result.push(columnWidths[i] * scaleFactor) - scaledColumnWidths = result - scaledTableWidth = Math.max(totalTableWidth, availableW) - } - function updateScaledHeights() { - if (!rowHeights || rowHeights.length === 0) { - scaledRowHeights = [] - scaledTableHeight = 0 - return - } - var scaleFactor = Math.max(1.0, availableH / Math.max(totalTableHeight, 1)) - var result = [] - for (var i = 0; i < rowHeights.length; i++) - result.push(rowHeights[i] * scaleFactor) - scaledRowHeights = result - scaledTableHeight = Math.max(totalTableHeight, availableH) - } - property bool expanded: false + width : cellWidth + height : cellHeight property var appPalette: palette - RowLayout { - spacing: 4 - ToolButton { - text: root.expanded ? MaterialIcons.keyboard_arrow_down - : MaterialIcons.keyboard_arrow_right - font.family: MaterialIcons.fontFamily - onClicked: root.expanded = !root.expanded - } - Label { - Layout.alignment: Qt.AlignVCenter - text: attribute.value.count + " elements" - } - ToolButton { - text: MaterialIcons.add_circle_outline - font.family: MaterialIcons.fontFamily - font.pointSize: 11 - padding: 2 - enabled: root.editable - onClicked: _currentScene.appendAttribute(attribute, undefined) - } - ToolButton { - text: MaterialIcons.fullscreen - font.family: MaterialIcons.fontFamily - font.pointSize: 11 - padding: 2 - ToolTip.text: "Open in fullscreen" - ToolTip.visible: hovered - onClicked: { - outerFrame.Layout.preferredWidth = fullscreenWindow.width - outerFrame.Layout.preferredHeight = fullscreenWindow.height - outerFrame.visible = true - outerFrame.parent = fullscreenContent - outerFrame.x = 0 - outerFrame.y = 0 - outerFrame.width = Qt.binding(function() { return fullscreenWindow.width }) - outerFrame.height = Qt.binding(function() { return fullscreenWindow.height }) - outerFrame.isFullscreen = true - fullscreenWindow.show() - } - } + color : palette.window + border.color : cellFocused ? palette.highlight : palette.mid + clip : true + property var cell: rowObject.value.at(cellIndex) + property bool cellFocused: { + var item = cellLoader.item + if (!item) + return false + return item.activeFocus || + (item.children && item.children.length > 0 && + item.children[0] && item.children[0].activeFocus) } - Window { - id: fullscreenWindow - color: "#2d2d2d" - width: root.totalTableWidth + 30 + 16 + 20 - height: root.totalTableHeight + 10 + 16 + 20 - title: attribute ? attribute.label : "" - palette: root.appPalette - Item { - id: fullscreenContent - anchors.fill: parent - } - onClosing: { - outerFrame.width = undefined - outerFrame.height = undefined - outerFrame.Layout.preferredWidth = -1 - outerFrame.parent = root - outerFrame.isFullscreen = false - outerFrame.Layout.fillWidth = true - outerFrame.Layout.preferredHeight = Qt.binding(function() { - return root.expanded - ? Math.min(root.totalTableHeight + 40, 330) - : 0 - }) - outerFrame.visible = Qt.binding(function() { - return root.expanded - }) - } - } - FontMetrics { - id: fontMetrics - font.bold: false + Rectangle { + anchors.centerIn: parent + width: cellLoader.width + 8 + height: cellLoader.height + 4 + radius: 3 + color: palette.base + visible: cellRect.cell && + (cellRect.cell.type === "BoolParam" || + cellRect.cell.type === "ChoiceParam") } - function initSizes() { - var names = root.columnNames - if (!names || names.length === 0) { - root.columnWidths = [] - root.rowHeights = [] - return - } - var widths = [] - for (var i = 0; i < names.length; i++) - widths.push(fontMetrics.advanceWidth(names[i]) + 20) - var heights = [] - if (attribute && attribute.value) { - for (var r = 0; r < attribute.value.count; r++) { - var rowAttr = attribute.value.at(r) - if (!rowAttr || !rowAttr.value) continue - for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { - var cell = rowAttr.value.at(c) - var cellText = cell ? String(cell.value) : "" - var cw = fontMetrics.advanceWidth(cellText) + 20 - if (cw > widths[c]) widths[c] = cw - } - heights.push(30) + Loader { + id: cellLoader + anchors.centerIn: parent + width: parent.width + height: parent.height + property var attribute: cellRect.cell + sourceComponent: { + if (!attribute) + return null + switch (attribute.type) { + case "PushButtonParam": return cellPushButtonComponent + case "ChoiceParam": + return (attribute.desc && attribute.desc.exclusive) + ? cellChoiceComponent + : cellChoiceMultiComponent + case "IntParam": return cellSliderComponent + case "FloatParam": + return (attribute.desc && attribute.desc.semantic === "color/hue") + ? cellColorHueComponent + : cellSliderComponent + case "BoolParam": return cellCheckboxComponent + case "StringParam": + return (attribute.desc && attribute.desc.semantic && + attribute.desc.semantic.includes("multiline")) + ? cellTextAreaComponent + : cellTextFieldComponent + case "ColorParam": return cellColorComponent + default: return cellTextFieldComponent } } - root.columnWidths = widths - root.rowHeights = heights - } - Component.onCompleted: { - root.initSizes() - } - Connections { - target: attribute ? attribute.value : null - function onCountChanged() {root.initSizes(); root.updateScaledWidths(); root.updateScaledHeights()} - function onModelReset() {root.initSizes(); root.updateScaledWidths(); root.updateScaledHeights()} - function onRowsInserted() {root.initSizes(); root.updateScaledWidths(); root.updateScaledHeights()} - function onDataChanged() {root.initSizes(); root.updateScaledWidths(); root.updateScaledHeights()} - } - onAvailableWChanged: root.updateScaledWidths() - onAvailableHChanged: root.updateScaledHeights() - Item { - id: outerFrame - Layout.fillWidth: true - visible: root.expanded - Layout.preferredHeight: root.expanded - ? Math.min(root.totalTableHeight + 40, 330) - : 0 - property bool isFullscreen: false - ScrollBar { - id: hBar - anchors.left: fixedStrip.right - anchors.right: outerFrame.right - anchors.bottom: outerFrame.bottom - anchors.rightMargin: vBar.width - orientation: Qt.Horizontal - policy: flickable.contentWidth > flickable.width - ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff - size: Math.min(1.0, flickable.width / Math.max(flickable.contentWidth, 1)) - position: (flickable.contentX / Math.max(flickable.contentWidth - flickable.width, 1)) - * (1.0 - size) - onPositionChanged: { - if (!pressed) return - var maxPos = 1.0 - size - var ratio = maxPos > 0 ? position / maxPos : 0 - flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) + Component { + id: cellChoiceComponent + Choice { + value: cellLoader.attribute + ? cellLoader.attribute.value + : "" + values: cellLoader.attribute + ? cellLoader.attribute.values + : [] + enabled: cellRect.editable + Component.onCompleted: { + if (typeof popup !== "undefined" && popup !== null) { + popup.margins = -1 + } + } + onEditingFinished: function(value) { + if (cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, value) + } } } - ScrollBar { - id: vBar - anchors.top: outerFrame.top - anchors.bottom: outerFrame.bottom - anchors.right: outerFrame.right - anchors.bottomMargin: hBar.height - orientation: Qt.Vertical - policy: flickable.contentHeight > flickable.height - ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff - size: Math.min(1.0, flickable.height / Math.max(flickable.contentHeight, 1)) - position: (flickable.contentY / Math.max(flickable.contentHeight - flickable.height, 1)) - * (1.0 - size) - onPositionChanged: { - if (!pressed) return - var maxPos = 1.0 - size - var ratio = maxPos > 0 ? position / maxPos : 0 - flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) + Component { + id: cellChoiceMultiComponent + ChoiceMulti { + value: cellLoader.attribute + ? cellLoader.attribute.value + : [] + values: cellLoader.attribute + ? cellLoader.attribute.values + : [] + enabled: cellRect.editable + customValueColor: Colors.orange + onToggled: function(value, checked) { + if (!cellLoader.attribute) + return + var cur = cellLoader.attribute.value.slice() + if (!checked) { + var idx = cur.indexOf(value) + if (idx !== -1) cur.splice(idx, 1) + } else { + cur.push(value) + } + _currentScene.setAttribute(cellLoader.attribute, cur) + } } } - Item { - id: fixedHeader - anchors.left: fixedStrip.right - anchors.right: outerFrame.right - anchors.top: outerFrame.top - anchors.rightMargin: vBar.width - height: 30 - clip: true - Row { - spacing: 1 - x: -flickable.contentX - Repeater { - model: root.columnNames - delegate: Item { - id: headerCell - required property int index - required property string modelData - width: root.scaledColumnWidths[index] || 100 - height: 30 - Rectangle { - anchors.fill: parent - color: "#2d2d2d" - border.color: "#1d1d1d" - Text { - anchors.fill: parent - text: headerCell.modelData - color: "#aaaaaa" - font.bold: false - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + Component { + id: cellSliderComponent + RowLayout { + spacing: 2 + TextField { + id: cellNumField + Layout.fillWidth: !cellSliderLoader.active + implicitWidth: 70 + enabled: cellRect.editable + selectByMouse: true + horizontalAlignment: TextInput.AlignRight + text: { + if (cellSliderLoader.active && cellSliderLoader.item && + cellSliderLoader.item.pressed) + return String(cellSliderLoader.item.formattedValue) + return cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + } + background: Rectangle { color: Qt.darker(palette.window, 1.2); radius: 2 } + color: palette.text + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? parseInt(text) + : parseFloat(text)) + } + WheelHandler { + onWheel: function(event) { + if (!cellRect.editable || !cellLoader.attribute) + return + var step = 1 + if (cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3) + step = cellLoader.attribute.desc.range[2] + var dir = event.angleDelta.y > 0 + ? 1 + : -1 + var v = Number(cellLoader.attribute.value) + dir * step + if (cellLoader.attribute.desc && cellLoader.attribute.desc.range) { + v = Math.max(cellLoader.attribute.desc.range[0], + Math.min(cellLoader.attribute.desc.range[1], v)) } + _currentScene.setAttribute(cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? Math.round(v) + : v) + event.accepted = true } - MouseArea { - width: 6 - height: parent.height - anchors.right: parent.right - cursorShape: Qt.SizeHorCursor - property real startX: 0 - property real startW: 0 - onPressed: function(mouse) { - grabMouse() - var p = mapToItem(root, mouse.x, mouse.y) - startX = p.x - startW = root.columnWidths[headerCell.index] - } - onReleased: function(mouse) { - ungrabMouse() - } - onPositionChanged: function(mouse) { - if (!pressed) return - var p = mapToItem(root, mouse.x, mouse.y) - var newW = Math.max(40, startW + (p.x - startX)) - var arr = root.columnWidths.slice() - arr[headerCell.index] = newW - root.columnWidths = arr - root.updateScaledWidths() - } + } + } + Loader { + id: cellSliderLoader + Layout.fillWidth: true + active: cellLoader.attribute && + cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3 + sourceComponent: Slider { + readonly property int stepDecimalCount: stepSize < 1 + ? String(stepSize).split(".").pop().length + : 0 + readonly property real formattedValue: value.toFixed(stepDecimalCount) + enabled: cellRect.editable + value: cellLoader.attribute + ? cellLoader.attribute.value + : 0 + from: cellLoader.attribute.desc.range[0] + to: cellLoader.attribute.desc.range[1] + stepSize: cellLoader.attribute.desc.range[2] + snapMode: Slider.SnapAlways + onPressedChanged: { + if (!pressed && cellLoader.attribute) + _currentScene.setAttribute(cellLoader.attribute, + formattedValue) } } } } } - Item { - id: fixedStrip - anchors.left: outerFrame.left - anchors.top: outerFrame.top - anchors.bottom: outerFrame.bottom - anchors.topMargin: 30 - anchors.bottomMargin: hBar.height - width: 30 - clip: true - Column { - spacing: 1 - width: parent.width - y: -flickable.contentY - Repeater { - model: attribute ? attribute.value : null - delegate: Item { - id: removeDelegate - required property int index - required property var object - width: fixedStrip.width - height: root.scaledRowHeights[index] || 30 - ToolButton { - anchors.centerIn: parent - enabled: root.editable - text: MaterialIcons.remove_circle_outline - font.family: MaterialIcons.fontFamily - font.pointSize: 11 - padding: 2 - ToolTip.text: "Remove Element" - ToolTip.visible: hovered - contentItem: Text { - text: parent.text - font: parent.font - color: "#aaaaaa" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: _currentScene.removeAttribute(removeDelegate.object) - } - } + Component { + id: cellCheckboxComponent + CheckBox { + enabled: cellRect.editable + checked: cellLoader.attribute + ? cellLoader.attribute.value + : false + onToggled: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, checked) } + background: Rectangle { color:palette.window; radius: 2 } } } - Item { - id: cornerCell - anchors.left: outerFrame.left - anchors.top: outerFrame.top - width: fixedStrip.width - height: fixedHeader.height - visible: outerFrame.isFullscreen - Rectangle { - anchors.fill: parent - color: "#2d2d2d" - border.color: "#1d1d1d" + Component { + id: cellTextFieldComponent + TextField { + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + placeholderText: cellLoader.attribute.isMandatory ? "This field is required" : "" + placeholderTextColor: "gray" + selectByMouse: true + background: Rectangle { + color: Qt.darker(palette.window, 1.2) + radius: 2 + } + color: palette.text + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) + } } - ToolButton { - anchors.centerIn: parent - text: MaterialIcons.add_circle_outline - font.family: MaterialIcons.fontFamily - font.pointSize: 11 - padding: 2 - enabled: root.editable - ToolTip.text: "Add Element" - ToolTip.visible: hovered - contentItem: Text { - text: parent.text - font: parent.font - color: "#aaaaaa" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + } + Component { + id: cellTextAreaComponent + TextField { + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + color: palette.base + radius: 2 + } + color: palette.text + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) } - onClicked: _currentScene.appendAttribute(attribute, undefined) } } - Flickable { - id: flickable - anchors.left: fixedStrip.right - anchors.top: outerFrame.top - anchors.right: outerFrame.right - anchors.bottom: outerFrame.bottom - anchors.topMargin: 30 - anchors.rightMargin: vBar.width - anchors.bottomMargin: hBar.height - clip: true - contentWidth: root.scaledTableWidth - contentHeight: root.scaledTableHeight - interactive: true - WheelHandler { - onWheel: function(event) { - if (event.modifiers & Qt.ControlModifier) { - flickable.contentX = Math.max(0, - Math.min(flickable.contentWidth - flickable.width, - flickable.contentX - event.angleDelta.y / 120 * 40)) - } else { - flickable.contentY = Math.max(0, - Math.min(flickable.contentHeight - flickable.height, - flickable.contentY - event.angleDelta.y / 120 * 40)) - } - event.accepted = true + Component { + id: cellColorComponent + TextField { + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + color: Qt.darker(palette.window, 1.2) + radius: 2 + } + color: palette.text + onEditingFinished: { + if (cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, text) } } - Column { - spacing: 1 - Repeater { - id: rowRepeater - model: attribute ? attribute.value : null - delegate: TableViewRowDelegate { - rowIndex: index - rowObject: object - rowHeight: root.scaledRowHeights[index] || 30 - tableWidth: root.scaledTableWidth - scaledColumnWidths: root.scaledColumnWidths - editable: root.editable - onRowResized: function(rowIndex, newH) { - var arr = root.rowHeights.slice() - arr[rowIndex] = newH - root.rowHeights = arr - root.updateScaledHeights() - } + } + Component { + id: cellPushButtonComponent + Button { + text: cellLoader.attribute + ? cellLoader.attribute.label + : "" + enabled: cellRect.editable + onClicked: { + if (cellLoader.attribute) + cellLoader.attribute.clicked() + } + } + } + Component { + id: cellColorHueComponent + RowLayout { + Slider { + id: cellHueSlider + Layout.fillWidth: true + enabled: cellRect.editable + value: cellLoader.attribute + ? cellLoader.attribute.value + : 0 + from: 0 + to: 1 + stepSize: 0.01 + snapMode: Slider.SnapAlways + onPressedChanged: { + if (!pressed && cellLoader.attribute) + _currentScene.setAttribute( + cellLoader.attribute, + value.toFixed(2)) } } + Rectangle { + width: 16 + height: 16 + color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) + } } } } From 72b9d3e175afc66cd809d2ce7af65ee886ca5335 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:38:06 +0200 Subject: [PATCH 15/26] Fixed typo --- .../AttributeControls/TableView.qml | 654 ++++++++++-------- 1 file changed, 374 insertions(+), 280 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml index 2efbadfc44..8d83a496da 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml @@ -1,317 +1,411 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Window import MaterialIcons 2.2 -import Utils 1.0 +import Utils 1.0 -Rectangle { - id: cellRect - property int cellIndex: 0 - property var rowObject: null - property int rowIndex: 0 - property real cellWidth: 100 - property real cellHeight: 24 +ColumnLayout { + id: root + spacing: 0 property bool editable: true - width : cellWidth - height : cellHeight - property var appPalette: palette - color : palette.window - border.color : cellFocused ? palette.highlight : palette.mid - clip : true - property var cell: rowObject.value.at(cellIndex) - property bool cellFocused: { - var item = cellLoader.item - if (!item) - return false - return item.activeFocus || - (item.children && item.children.length > 0 && - item.children[0] && item.children[0].activeFocus) + required property var attribute + property real stdHeight: 24 + property var columnNames: { + if (!attribute || !attribute.value || attribute.value.count === 0) + return [] + var firstRow = attribute.value.at(0) + if (!firstRow || !firstRow.value) return [] + var names = [] + for (var i = 0; i < firstRow.value.count; i++) { + var child = firstRow.value.at(i) + if (child) names.push(child.label) + } + return names } - Rectangle { - anchors.centerIn: parent - width: cellLoader.width + 8 - height: cellLoader.height + 4 - radius: 3 - color: palette.base - visible: cellRect.cell && - (cellRect.cell.type === "BoolParam" || - cellRect.cell.type === "ChoiceParam") + property var columnWidths: [] + property real totalTableWidth: { + if (!columnWidths || columnWidths.length === 0) + return 0 + var t = 0 + for (var i = 0; i < columnWidths.length; i++) t += columnWidths[i] + t += Math.max(0, columnWidths.length - 1) + return t + } + property real totalTableHeight: attribute && attribute.value + ? attribute.value.count * 31 + : 0 + property var scaledColumnWidths: [] + property real scaledTableWidth: 0 + property real availableW: outerFrame.width > 0 + ? outerFrame.width - fixedStrip.width - vBar.width + : 600 + function updateScaledWidths() { + if (!columnWidths || columnWidths.length === 0) { + scaledColumnWidths = [] + scaledTableWidth = 0 + return + } + var scaleFactor = Math.max(1.0, availableW / Math.max(totalTableWidth, 1)) + var result = [] + for (var i = 0; i < columnWidths.length; i++) + result.push(columnWidths[i] * scaleFactor) + scaledColumnWidths = result + scaledTableWidth = Math.max(totalTableWidth, availableW) } - Loader { - id: cellLoader - anchors.centerIn: parent - width: parent.width - height: parent.height - property var attribute: cellRect.cell - sourceComponent: { - if (!attribute) - return null - switch (attribute.type) { - case "PushButtonParam": return cellPushButtonComponent - case "ChoiceParam": - return (attribute.desc && attribute.desc.exclusive) - ? cellChoiceComponent - : cellChoiceMultiComponent - case "IntParam": return cellSliderComponent - case "FloatParam": - return (attribute.desc && attribute.desc.semantic === "color/hue") - ? cellColorHueComponent - : cellSliderComponent - case "BoolParam": return cellCheckboxComponent - case "StringParam": - return (attribute.desc && attribute.desc.semantic && - attribute.desc.semantic.includes("multiline")) - ? cellTextAreaComponent - : cellTextFieldComponent - case "ColorParam": return cellColorComponent - default: return cellTextFieldComponent + property bool expanded: false + property var appPalette: palette + RowLayout { + spacing: 4 + ToolButton { + text: root.expanded + ? MaterialIcons.keyboard_arrow_down + : MaterialIcons.keyboard_arrow_right + font.family: MaterialIcons.fontFamily + onClicked: root.expanded = !root.expanded + } + Label { + Layout.alignment: Qt.AlignVCenter + text: attribute.value.count + " elements" + } + ToolButton { + text: MaterialIcons.add_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + enabled: root.editable + onClicked: _currentScene.appendAttribute(attribute, undefined) + } + ToolButton { + text: MaterialIcons.fullscreen + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + ToolTip.text: "Open in fullscreen" + ToolTip.visible: hovered + onClicked: { + outerFrame.Layout.preferredWidth = fullscreenWindow.width + outerFrame.Layout.preferredHeight = fullscreenWindow.height + outerFrame.visible = true + outerFrame.parent = fullscreenContent + outerFrame.x = 0 + outerFrame.y = 0 + outerFrame.width = Qt.binding(function() { return fullscreenWindow.width }) + outerFrame.height = Qt.binding(function() { return fullscreenWindow.height }) + outerFrame.isFullscreen = true + fullscreenWindow.show() } } - Component { - id: cellChoiceComponent - Choice { - value: cellLoader.attribute - ? cellLoader.attribute.value - : "" - values: cellLoader.attribute - ? cellLoader.attribute.values - : [] - enabled: cellRect.editable - Component.onCompleted: { - if (typeof popup !== "undefined" && popup !== null) { - popup.margins = -1 - } - } - onEditingFinished: function(value) { - if (cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, value) + } + Window { + id: fullscreenWindow + width: root.totalTableWidth + 2*stdHeight + height: root.totalTableHeight + 1.5*stdHeight + title: attribute + ? attribute.label + : "" + palette: root.appPalette + color: palette.window + Item { + id: fullscreenContent + anchors.fill: parent + } + onClosing: { + outerFrame.width = undefined + outerFrame.height = undefined + outerFrame.Layout.preferredWidth = -1 + outerFrame.parent = root + outerFrame.isFullscreen = false + outerFrame.Layout.fillWidth = true + outerFrame.Layout.preferredHeight = Qt.binding(function() { + return root.expanded + ? Math.min(root.totalTableHeight + 40, 330) + : 0 + }) + outerFrame.visible = Qt.binding(function() { + return root.expanded + }) + } + } + FontMetrics { + id: fontMetrics + font.bold: true + } + function initSizes() { + var names = root.columnNames + if (!names || names.length === 0) { + root.columnWidths = [] + return + } + var widths = [] + for (var i = 0; i < names.length; i++) + widths.push(fontMetrics.advanceWidth(names[i]) + 20) + if (attribute && attribute.value) { + for (var r = 0; r < attribute.value.count; r++) { + var rowAttr = attribute.value.at(r) + if (!rowAttr || !rowAttr.value) continue + for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { + var cell = rowAttr.value.at(c) + var cellText = cell ? String(cell.value) : "" + var cw = fontMetrics.advanceWidth(cellText) + 20 + if (cw > widths[c]) widths[c] = cw } } } - Component { - id: cellChoiceMultiComponent - ChoiceMulti { - value: cellLoader.attribute - ? cellLoader.attribute.value - : [] - values: cellLoader.attribute - ? cellLoader.attribute.values - : [] - enabled: cellRect.editable - customValueColor: Colors.orange - onToggled: function(value, checked) { - if (!cellLoader.attribute) - return - var cur = cellLoader.attribute.value.slice() - if (!checked) { - var idx = cur.indexOf(value) - if (idx !== -1) cur.splice(idx, 1) - } else { - cur.push(value) - } - _currentScene.setAttribute(cellLoader.attribute, cur) - } + root.columnWidths = widths + } + Component.onCompleted: { + root.initSizes() + } + Connections { + target: attribute + ? attribute.value + : null + function onCountChanged() {root.initSizes(); root.updateScaledWidths()} + function onModelReset() {root.initSizes(); root.updateScaledWidths()} + function onRowsInserted() {root.initSizes(); root.updateScaledWidths()} + function onDataChanged() {root.initSizes(); root.updateScaledWidths()} + } + onAvailableWChanged: root.updateScaledWidths() + Item { + id: outerFrame + Layout.fillWidth: true + visible: root.expanded + Layout.preferredHeight: root.expanded + ? Math.min(root.totalTableHeight + 40, 330) + : 0 + property bool isFullscreen: false + ScrollBar { + id: hBar + anchors.left: fixedStrip.right + anchors.right: outerFrame.right + anchors.bottom: outerFrame.bottom + anchors.rightMargin: vBar.width + orientation: Qt.Horizontal + policy: flickable.contentWidth > flickable.width + ? ScrollBar.AlwaysOn + : ScrollBar.AlwaysOff + size: Math.min(1.0, flickable.width / Math.max(flickable.contentWidth, 1)) + position: (flickable.contentX / Math.max(flickable.contentWidth - flickable.width, 1)) + * (1.0 - size) + onPositionChanged: { + if (!pressed) + return + var maxPos = 1.0 - size + var ratio = maxPos > 0 + ? position / maxPos + : 0 + flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) } } - Component { - id: cellSliderComponent - RowLayout { - spacing: 2 - TextField { - id: cellNumField - Layout.fillWidth: !cellSliderLoader.active - implicitWidth: 70 - enabled: cellRect.editable - selectByMouse: true - horizontalAlignment: TextInput.AlignRight - text: { - if (cellSliderLoader.active && cellSliderLoader.item && - cellSliderLoader.item.pressed) - return String(cellSliderLoader.item.formattedValue) - return cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - } - background: Rectangle { color: Qt.darker(palette.window, 1.2); radius: 2 } - color: palette.text - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, - cellLoader.attribute.type === "IntParam" - ? parseInt(text) - : parseFloat(text)) - } - WheelHandler { - onWheel: function(event) { - if (!cellRect.editable || !cellLoader.attribute) - return - var step = 1 - if (cellLoader.attribute.desc && - cellLoader.attribute.desc.range && - cellLoader.attribute.desc.range.length === 3) - step = cellLoader.attribute.desc.range[2] - var dir = event.angleDelta.y > 0 - ? 1 - : -1 - var v = Number(cellLoader.attribute.value) + dir * step - if (cellLoader.attribute.desc && cellLoader.attribute.desc.range) { - v = Math.max(cellLoader.attribute.desc.range[0], - Math.min(cellLoader.attribute.desc.range[1], v)) + ScrollBar { + id: vBar + anchors.top: outerFrame.top + anchors.bottom: outerFrame.bottom + anchors.right: outerFrame.right + anchors.bottomMargin: hBar.height + orientation: Qt.Vertical + policy: flickable.contentHeight > flickable.height + ? ScrollBar.AlwaysOn + : ScrollBar.AlwaysOff + size: Math.min(1.0, flickable.height / Math.max(flickable.contentHeight, 1)) + position: (flickable.contentY / Math.max(flickable.contentHeight - flickable.height, 1)) + * (1.0 - size) + onPositionChanged: { + if (!pressed) + return + var maxPos = 1.0 - size + var ratio = maxPos > 0 + ? position / maxPos + : 0 + flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) + } + } + Item { + id: fixedHeader + anchors.left: fixedStrip.right + anchors.right: outerFrame.right + anchors.top: outerFrame.top + anchors.rightMargin: vBar.width + height: stdHeight + clip: true + Row { + spacing: 1 + x: -flickable.contentX + Repeater { + model: root.columnNames + delegate: Item { + id: headerCell + required property int index + required property string modelData + width: root.scaledColumnWidths[index] || 100 + height: stdHeight + Rectangle { + anchors.fill: parent + color: Qt.darker(palette.window, 1.2) + border.color: palette.mid + Text { + anchors.fill: parent + text: headerCell.modelData + color: palette.text + font.bold: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight } - _currentScene.setAttribute(cellLoader.attribute, - cellLoader.attribute.type === "IntParam" - ? Math.round(v) - : v) - event.accepted = true } - } - } - Loader { - id: cellSliderLoader - Layout.fillWidth: true - active: cellLoader.attribute && - cellLoader.attribute.desc && - cellLoader.attribute.desc.range && - cellLoader.attribute.desc.range.length === 3 - sourceComponent: Slider { - readonly property int stepDecimalCount: stepSize < 1 - ? String(stepSize).split(".").pop().length - : 0 - readonly property real formattedValue: value.toFixed(stepDecimalCount) - enabled: cellRect.editable - value: cellLoader.attribute - ? cellLoader.attribute.value - : 0 - from: cellLoader.attribute.desc.range[0] - to: cellLoader.attribute.desc.range[1] - stepSize: cellLoader.attribute.desc.range[2] - snapMode: Slider.SnapAlways - onPressedChanged: { - if (!pressed && cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, - formattedValue) + MouseArea { + id: colResizeHandle + width: 6 + height: parent.height + anchors.right: parent.right + cursorShape: Qt.SizeHorCursor + property real startX: 0 + property real startW: 0 + onPressed: function(mouse) { + colResizeHandle.grabMouse() + var p = mapToItem(root, mouse.x, mouse.y) + startX = p.x + startW = root.columnWidths[headerCell.index] + } + onReleased: function(mouse) { + colResizeHandle.ungrabMouse() + } + onPositionChanged: function(mouse) { + if (!pressed) + return + var p = mapToItem(root, mouse.x, mouse.y) + var newW = Math.max(40, startW + (p.x - startX)) + var arr = root.columnWidths.slice() + arr[headerCell.index] = newW + root.columnWidths = arr + root.updateScaledWidths() + } } } } } } - Component { - id: cellCheckboxComponent - CheckBox { - enabled: cellRect.editable - checked: cellLoader.attribute - ? cellLoader.attribute.value - : false - onToggled: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, checked) - } - background: Rectangle { color:palette.window; radius: 2 } - } - } - Component { - id: cellTextFieldComponent - TextField { - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - placeholderText: cellLoader.attribute.isMandatory ? "This field is required" : "" - placeholderTextColor: "gray" - selectByMouse: true - background: Rectangle { - color: Qt.darker(palette.window, 1.2) - radius: 2 - } - color: palette.text - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text.trim()) + Item { + id: fixedStrip + anchors.left: outerFrame.left + anchors.top: outerFrame.top + anchors.bottom: outerFrame.bottom + anchors.topMargin: stdHeight + anchors.bottomMargin: hBar.height + width: stdHeight + clip: true + Column { + spacing: 1 + width: parent.width + y: -flickable.contentY + Repeater { + model: attribute + ? attribute.value + : null + delegate: Item { + id: removeDelegate + required property int index + required property var object + width: fixedStrip.width + height: stdHeight + ToolButton { + anchors.centerIn: parent + enabled: root.editable + text: MaterialIcons.remove_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + ToolTip.text: "Remove Element" + ToolTip.visible: hovered + contentItem: Text { + text: parent.text + font: parent.font + color: palette.text + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: _currentScene.removeAttribute(removeDelegate.object) + } + } } } } - Component { - id: cellTextAreaComponent - TextField { - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - selectByMouse: true - background: Rectangle { - color: palette.base - radius: 2 - } - color: palette.text - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text.trim()) - } + Item { + id: cornerCell + anchors.left: outerFrame.left + anchors.top: outerFrame.top + width: fixedStrip.width + height: fixedHeader.height + visible: outerFrame.isFullscreen + Rectangle { + anchors.fill: parent + color: Qt.darker(palette.window, 1.2) + border.color: palette.mid } - } - Component { - id: cellColorComponent - TextField { - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - selectByMouse: true - background: Rectangle { - color: Qt.darker(palette.window, 1.2) - radius: 2 - } - color: palette.text - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text) + ToolButton { + anchors.centerIn: parent + text: MaterialIcons.add_circle_outline + font.family: MaterialIcons.fontFamily + font.pointSize: 11 + padding: 2 + enabled: root.editable + ToolTip.text: "Add Element" + ToolTip.visible: hovered + contentItem: Text { + text: parent.text + font: parent.font + color: palette.text + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter } + onClicked: _currentScene.appendAttribute(attribute, undefined) } } - Component { - id: cellPushButtonComponent - Button { - text: cellLoader.attribute - ? cellLoader.attribute.label - : "" - enabled: cellRect.editable - onClicked: { - if (cellLoader.attribute) - cellLoader.attribute.clicked() + Flickable { + id: flickable + anchors.left: fixedStrip.right + anchors.top: outerFrame.top + anchors.right: outerFrame.right + anchors.bottom: outerFrame.bottom + anchors.topMargin: stdHeight + anchors.rightMargin: vBar.width + anchors.bottomMargin: hBar.height + clip: true + contentWidth: root.scaledTableWidth + contentHeight: root.totalTableHeight + interactive: true + WheelHandler { + onWheel: function(event) { + if (event.modifiers & Qt.ControlModifier) { + flickable.contentX = Math.max(0, + Math.min(flickable.contentWidth - flickable.width, + flickable.contentX - event.angleDelta.y / 120 * 40)) + } else { + flickable.contentY = Math.max(0, + Math.min(flickable.contentHeight - flickable.height, + flickable.contentY - event.angleDelta.y / 120 * 40)) + } + event.accepted = true } } - } - Component { - id: cellColorHueComponent - RowLayout { - Slider { - id: cellHueSlider - Layout.fillWidth: true - enabled: cellRect.editable - value: cellLoader.attribute - ? cellLoader.attribute.value - : 0 - from: 0 - to: 1 - stepSize: 0.01 - snapMode: Slider.SnapAlways - onPressedChanged: { - if (!pressed && cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, - value.toFixed(2)) + Column { + spacing: 1 + Repeater { + id: rowRepeater + model: attribute + ? attribute.value + : null + delegate: TableViewRowDelegate { + rowIndex: index + rowObject: object + rowHeight: stdHeight + tableWidth: root.scaledTableWidth + scaledColumnWidths: root.scaledColumnWidths + editable: root.editable } } - Rectangle { - width: 16 - height: 16 - color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) - } } } } From 23b61296e8fcd5dc8d4ebfe3292b99ba49987365 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:38:31 +0200 Subject: [PATCH 16/26] Fixed syntax issue --- .../TableViewCellDelegate.qml | 177 ++++++++++-------- 1 file changed, 104 insertions(+), 73 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml index 3d2e41f701..2efbadfc44 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml @@ -7,31 +7,33 @@ import Utils 1.0 Rectangle { id: cellRect - property int cellIndex: 0 - property var rowObject: null - property int rowIndex: 0 - property real cellWidth - property real cellHeight + property int cellIndex: 0 + property var rowObject: null + property int rowIndex: 0 + property real cellWidth: 100 + property real cellHeight: 24 property bool editable: true width : cellWidth height : cellHeight - color : rowIndex % 2 === 0 ? "#2d2d2d" : "#333333" - border.color : cellFocused ? "#5599ff" : "#1d1d1d" + property var appPalette: palette + color : palette.window + border.color : cellFocused ? palette.highlight : palette.mid clip : true property var cell: rowObject.value.at(cellIndex) property bool cellFocused: { var item = cellLoader.item - if (!item) return false + if (!item) + return false return item.activeFocus || (item.children && item.children.length > 0 && item.children[0] && item.children[0].activeFocus) } Rectangle { anchors.centerIn: parent - width: cellLoader.width + 8 - height: cellLoader.height + 4 - radius: 3 - color: "#444444" + width: cellLoader.width + 8 + height: cellLoader.height + 4 + radius: 3 + color: palette.base visible: cellRect.cell && (cellRect.cell.type === "BoolParam" || cellRect.cell.type === "ChoiceParam") @@ -39,34 +41,42 @@ Rectangle { Loader { id: cellLoader anchors.centerIn: parent - width: parent.width + width: parent.width height: parent.height property var attribute: cellRect.cell sourceComponent: { - if (!attribute) return null + if (!attribute) + return null switch (attribute.type) { - case "PushButtonParam": return cellPushButtonComponent + case "PushButtonParam": return cellPushButtonComponent case "ChoiceParam": return (attribute.desc && attribute.desc.exclusive) - ? cellChoiceComponent : cellChoiceMultiComponent - case "IntParam": return cellSliderComponent + ? cellChoiceComponent + : cellChoiceMultiComponent + case "IntParam": return cellSliderComponent case "FloatParam": return (attribute.desc && attribute.desc.semantic === "color/hue") - ? cellColorHueComponent : cellSliderComponent - case "BoolParam": return cellCheckboxComponent + ? cellColorHueComponent + : cellSliderComponent + case "BoolParam": return cellCheckboxComponent case "StringParam": return (attribute.desc && attribute.desc.semantic && attribute.desc.semantic.includes("multiline")) - ? cellTextAreaComponent : cellTextFieldComponent - case "ColorParam": return cellColorComponent - default: return cellTextFieldComponent + ? cellTextAreaComponent + : cellTextFieldComponent + case "ColorParam": return cellColorComponent + default: return cellTextFieldComponent } } Component { id: cellChoiceComponent Choice { - value: cellLoader.attribute ? cellLoader.attribute.value : "" - values: cellLoader.attribute ? cellLoader.attribute.values : [] + value: cellLoader.attribute + ? cellLoader.attribute.value + : "" + values: cellLoader.attribute + ? cellLoader.attribute.values + : [] enabled: cellRect.editable Component.onCompleted: { if (typeof popup !== "undefined" && popup !== null) { @@ -82,12 +92,17 @@ Rectangle { Component { id: cellChoiceMultiComponent ChoiceMulti { - value: cellLoader.attribute ? cellLoader.attribute.value : [] - values: cellLoader.attribute ? cellLoader.attribute.values : [] - enabled: cellRect.editable + value: cellLoader.attribute + ? cellLoader.attribute.value + : [] + values: cellLoader.attribute + ? cellLoader.attribute.values + : [] + enabled: cellRect.editable customValueColor: Colors.orange onToggled: function(value, checked) { - if (!cellLoader.attribute) return + if (!cellLoader.attribute) + return var cur = cellLoader.attribute.value.slice() if (!checked) { var idx = cur.indexOf(value) @@ -106,41 +121,48 @@ Rectangle { TextField { id: cellNumField Layout.fillWidth: !cellSliderLoader.active - implicitWidth: 70 - enabled: cellRect.editable - selectByMouse: true + implicitWidth: 70 + enabled: cellRect.editable + selectByMouse: true horizontalAlignment: TextInput.AlignRight text: { if (cellSliderLoader.active && cellSliderLoader.item && cellSliderLoader.item.pressed) return String(cellSliderLoader.item.formattedValue) - return cellLoader.attribute ? String(cellLoader.attribute.value) : "" + return cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" } - background: Rectangle { color: "#3c3c3c"; radius: 2 } - color: "#cccccc" + background: Rectangle { color: Qt.darker(palette.window, 1.2); radius: 2 } + color: palette.text onEditingFinished: { if (cellLoader.attribute) _currentScene.setAttribute(cellLoader.attribute, cellLoader.attribute.type === "IntParam" - ? parseInt(text) : parseFloat(text)) + ? parseInt(text) + : parseFloat(text)) } WheelHandler { onWheel: function(event) { - if (!cellRect.editable || !cellLoader.attribute) return + if (!cellRect.editable || !cellLoader.attribute) + return var step = 1 if (cellLoader.attribute.desc && cellLoader.attribute.desc.range && cellLoader.attribute.desc.range.length === 3) step = cellLoader.attribute.desc.range[2] - var dir = event.angleDelta.y > 0 ? 1 : -1 - var v = Number(cellLoader.attribute.value) + dir * step + var dir = event.angleDelta.y > 0 + ? 1 + : -1 + var v = Number(cellLoader.attribute.value) + dir * step if (cellLoader.attribute.desc && cellLoader.attribute.desc.range) { v = Math.max(cellLoader.attribute.desc.range[0], Math.min(cellLoader.attribute.desc.range[1], v)) } _currentScene.setAttribute(cellLoader.attribute, cellLoader.attribute.type === "IntParam" - ? Math.round(v) : v) + ? Math.round(v) + : v) event.accepted = true } } @@ -153,13 +175,16 @@ Rectangle { cellLoader.attribute.desc.range && cellLoader.attribute.desc.range.length === 3 sourceComponent: Slider { - readonly property int stepDecimalCount: stepSize < 1 - ? String(stepSize).split(".").pop().length : 0 + readonly property int stepDecimalCount: stepSize < 1 + ? String(stepSize).split(".").pop().length + : 0 readonly property real formattedValue: value.toFixed(stepDecimalCount) - enabled: cellRect.editable - value: cellLoader.attribute ? cellLoader.attribute.value : 0 - from: cellLoader.attribute.desc.range[0] - to: cellLoader.attribute.desc.range[1] + enabled: cellRect.editable + value: cellLoader.attribute + ? cellLoader.attribute.value + : 0 + from: cellLoader.attribute.desc.range[0] + to: cellLoader.attribute.desc.range[1] stepSize: cellLoader.attribute.desc.range[2] snapMode: Slider.SnapAlways onPressedChanged: { @@ -176,27 +201,31 @@ Rectangle { CheckBox { enabled: cellRect.editable checked: cellLoader.attribute - ? cellLoader.attribute.value : false + ? cellLoader.attribute.value + : false onToggled: { if (cellLoader.attribute) _currentScene.setAttribute( cellLoader.attribute, checked) } + background: Rectangle { color:palette.window; radius: 2 } } } Component { id: cellTextFieldComponent TextField { - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + placeholderText: cellLoader.attribute.isMandatory ? "This field is required" : "" + placeholderTextColor: "gray" selectByMouse: true background: Rectangle { - color: "#3c3c3c" + color: Qt.darker(palette.window, 1.2) radius: 2 } - color: "#cccccc" + color: palette.text onEditingFinished: { if (cellLoader.attribute) _currentScene.setAttribute( @@ -207,16 +236,16 @@ Rectangle { Component { id: cellTextAreaComponent TextField { - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" selectByMouse: true background: Rectangle { - color: "#3c3c3c" + color: palette.base radius: 2 } - color: "#cccccc" + color: palette.text onEditingFinished: { if (cellLoader.attribute) _currentScene.setAttribute( @@ -227,16 +256,16 @@ Rectangle { Component { id: cellColorComponent TextField { - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" selectByMouse: true background: Rectangle { - color: "#3c3c3c" + color: Qt.darker(palette.window, 1.2) radius: 2 } - color: "#cccccc" + color: palette.text onEditingFinished: { if (cellLoader.attribute) _currentScene.setAttribute( @@ -247,8 +276,9 @@ Rectangle { Component { id: cellPushButtonComponent Button { - text: cellLoader.attribute - ? cellLoader.attribute.label : "" + text: cellLoader.attribute + ? cellLoader.attribute.label + : "" enabled: cellRect.editable onClicked: { if (cellLoader.attribute) @@ -262,11 +292,12 @@ Rectangle { Slider { id: cellHueSlider Layout.fillWidth: true - enabled: cellRect.editable - value: cellLoader.attribute - ? cellLoader.attribute.value : 0 - from: 0 - to: 1 + enabled: cellRect.editable + value: cellLoader.attribute + ? cellLoader.attribute.value + : 0 + from: 0 + to: 1 stepSize: 0.01 snapMode: Slider.SnapAlways onPressedChanged: { @@ -277,9 +308,9 @@ Rectangle { } } Rectangle { - width: 16 + width: 16 height: 16 - color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) + color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) } } } From 1565b289bc72bd0d1e7b446ea226f8bbac0c315b Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:38:58 +0200 Subject: [PATCH 17/26] Fixed syntax issues --- .../TableViewRowDelegate.qml | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml index 63d1a9cb82..7f41c39862 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml @@ -3,27 +3,30 @@ import QtQuick.Controls Item { id: root - property int rowIndex: 0 - property var rowObject: null - property real rowHeight: 30 + property int rowIndex: 0 + property var rowObject: null + property real rowHeight: 24 property real tableWidth: 100 - property var scaledColumnWidths: [] + property var scaledColumnWidths: [] property bool editable: true + property var appPalette: palette signal rowResized(int rowIndex, real newHeight) - width: tableWidth + width: tableWidth height: rowHeight Row { - spacing: 1 + spacing: 1 anchors.fill: parent Repeater { model: root.rowObject && root.rowObject.value - ? root.rowObject.value.count : 0 + ? root.rowObject.value.count + : 0 delegate: TableViewCellDelegate { - cellIndex: index - rowObject: root.rowObject - rowIndex: root.rowIndex - cellWidth: (root.scaledColumnWidths && root.scaledColumnWidths.length > index) - ? root.scaledColumnWidths[index] : 100 + cellIndex: index + rowObject: root.rowObject + rowIndex: root.rowIndex + cellWidth: (root.scaledColumnWidths && root.scaledColumnWidths.length > index) + ? root.scaledColumnWidths[index] + : 100 cellHeight: root.height } } @@ -32,17 +35,18 @@ Item { id: colResizeHandle width: parent.width height: 6 - anchors.bottom: parent.bottom + anchors.bottom: parent.bottom cursorShape: Qt.SizeVerCursor preventStealing: true property real lastY: 0 - onPressed: function(mouse) { - colResizeHandle.grabMouse(); + onPressed: function(mouse) { + colResizeHandle.grabMouse() lastY = mapToGlobal(mouse.x, mouse.y).y } onReleased: function(mouse) { colResizeHandle.ungrabMouse() } onPositionChanged: function(mouse) { - if (!pressed) return + if (!pressed) + return var globalY = mapToGlobal(mouse.x, mouse.y).y var delta = globalY - lastY lastY = globalY From c0ae997de063e4ae143b981a754970ced001b12e Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:51:26 +0200 Subject: [PATCH 18/26] tableView capped column sizes and always visible if applicable hbar --- .../AttributeControls/TableView.qml | 134 +++++++++++------- 1 file changed, 85 insertions(+), 49 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml index 8d83a496da..5a8744e12a 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml @@ -39,20 +39,71 @@ ColumnLayout { property var scaledColumnWidths: [] property real scaledTableWidth: 0 property real availableW: outerFrame.width > 0 - ? outerFrame.width - fixedStrip.width - vBar.width + ? outerFrame.width - fixedStrip.width - vBar.width : 600 + function computeMinColumnWidths() { + var firstRow = rowRepeater.itemAt(0) + if (!firstRow) + return new Array(root.columnNames.length).fill(60) + return firstRow.minColumnWidths + } + function computeMaxColumnWidths() { + var firstRow = rowRepeater.itemAt(0) + var n = root.columnWidths.length + if (!firstRow || !firstRow.maxColumnWidths || firstRow.maxColumnWidths.length !== n) + return new Array(n).fill(Infinity) + return firstRow.maxColumnWidths.map(function(v, i) { + return v === Infinity ? Infinity : root.columnWidths[i] * 1.2 + }) + } function updateScaledWidths() { - if (!columnWidths || columnWidths.length === 0) { - scaledColumnWidths = [] - scaledTableWidth = 0 + if (!root.columnWidths || root.columnWidths.length === 0) return + var mins = computeMinColumnWidths() + var maxs = computeMaxColumnWidths() + var n = root.columnWidths.length + var total = 0 + var widths = [] + for (var i = 0; i < n; i++) { + var w = Math.max(root.columnWidths[i], mins[i] !== undefined ? mins[i] : 60) + widths.push(w) + total += w } - var scaleFactor = Math.max(1.0, availableW / Math.max(totalTableWidth, 1)) - var result = [] - for (var i = 0; i < columnWidths.length; i++) - result.push(columnWidths[i] * scaleFactor) - scaledColumnWidths = result - scaledTableWidth = Math.max(totalTableWidth, availableW) + var leftover = root.availableW - total + if (leftover > 0) { + var eligible = [] + for (var j = 0; j < n; j++) { + if (maxs[j] === undefined || maxs[j] === Infinity || widths[j] < maxs[j]) + eligible.push(j) + } + while (leftover > 0.5 && eligible.length > 0) { + var extra = leftover / eligible.length + var stillEligible = [] + var consumed = 0 + for (var k = 0; k < eligible.length; k++) { + var idx = eligible[k] + var cap = (maxs[idx] === undefined || maxs[idx] === Infinity) + ? Infinity + : maxs[idx] + var room = (cap === Infinity) ? Infinity : cap - widths[idx] + if (cap !== Infinity && room <= extra) { + widths[idx] = cap + consumed += room + } else { + widths[idx] += extra + consumed += extra + stillEligible.push(idx) + } + } + leftover -= consumed + eligible = stillEligible + } + } + total = 0 + for (var l = 0; l < n; l++) + total += widths[l] + root.scaledColumnWidths = widths + root.scaledTableWidth = total } property bool expanded: false property var appPalette: palette @@ -85,23 +136,20 @@ ColumnLayout { ToolTip.text: "Open in fullscreen" ToolTip.visible: hovered onClicked: { - outerFrame.Layout.preferredWidth = fullscreenWindow.width - outerFrame.Layout.preferredHeight = fullscreenWindow.height + outerFrame.Layout.preferredWidth = -1 + outerFrame.Layout.preferredHeight = -1 outerFrame.visible = true outerFrame.parent = fullscreenContent - outerFrame.x = 0 - outerFrame.y = 0 - outerFrame.width = Qt.binding(function() { return fullscreenWindow.width }) - outerFrame.height = Qt.binding(function() { return fullscreenWindow.height }) + outerFrame.anchors.fill = fullscreenContent outerFrame.isFullscreen = true + fullscreenWindow.width = root.scaledTableWidth + fixedStrip.width + vBar.width + 2*stdHeight + fullscreenWindow.height = root.totalTableHeight + hBar.height + 0.75*stdHeight fullscreenWindow.show() } } } Window { id: fullscreenWindow - width: root.totalTableWidth + 2*stdHeight - height: root.totalTableHeight + 1.5*stdHeight title: attribute ? attribute.label : "" @@ -112,6 +160,7 @@ ColumnLayout { anchors.fill: parent } onClosing: { + outerFrame.anchors.fill = undefined outerFrame.width = undefined outerFrame.height = undefined outerFrame.Layout.preferredWidth = -1 @@ -157,6 +206,7 @@ ColumnLayout { } Component.onCompleted: { root.initSizes() + root.updateScaledWidths() } Connections { target: attribute @@ -190,12 +240,9 @@ ColumnLayout { position: (flickable.contentX / Math.max(flickable.contentWidth - flickable.width, 1)) * (1.0 - size) onPositionChanged: { - if (!pressed) - return + if (!pressed) return var maxPos = 1.0 - size - var ratio = maxPos > 0 - ? position / maxPos - : 0 + var ratio = maxPos > 0 ? position / maxPos : 0 flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) } } @@ -213,12 +260,9 @@ ColumnLayout { position: (flickable.contentY / Math.max(flickable.contentHeight - flickable.height, 1)) * (1.0 - size) onPositionChanged: { - if (!pressed) - return + if (!pressed) return var maxPos = 1.0 - size - var ratio = maxPos > 0 - ? position / maxPos - : 0 + var ratio = maxPos > 0 ? position / maxPos : 0 flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) } } @@ -261,25 +305,23 @@ ColumnLayout { height: parent.height anchors.right: parent.right cursorShape: Qt.SizeHorCursor + preventStealing: true property real startX: 0 property real startW: 0 onPressed: function(mouse) { - colResizeHandle.grabMouse() var p = mapToItem(root, mouse.x, mouse.y) startX = p.x startW = root.columnWidths[headerCell.index] } - onReleased: function(mouse) { - colResizeHandle.ungrabMouse() - } + onReleased: function(mouse) { } onPositionChanged: function(mouse) { - if (!pressed) - return + if (!pressed) return var p = mapToItem(root, mouse.x, mouse.y) - var newW = Math.max(40, startW + (p.x - startX)) - var arr = root.columnWidths.slice() - arr[headerCell.index] = newW - root.columnWidths = arr + var minW = Math.max(40, computeMinColumnWidths()[headerCell.index] || 60) + var newW = Math.max(minW, startW + (p.x - startX)) + var widthArray = root.columnWidths.slice() + widthArray[headerCell.index] = newW + root.columnWidths = widthArray root.updateScaledWidths() } } @@ -291,19 +333,16 @@ ColumnLayout { id: fixedStrip anchors.left: outerFrame.left anchors.top: outerFrame.top - anchors.bottom: outerFrame.bottom + anchors.bottom: hBar.top anchors.topMargin: stdHeight - anchors.bottomMargin: hBar.height width: stdHeight - clip: true + clip: true Column { spacing: 1 width: parent.width y: -flickable.contentY Repeater { - model: attribute - ? attribute.value - : null + model: attribute ? attribute.value : null delegate: Item { id: removeDelegate required property int index @@ -368,10 +407,9 @@ ColumnLayout { anchors.left: fixedStrip.right anchors.top: outerFrame.top anchors.right: outerFrame.right - anchors.bottom: outerFrame.bottom + anchors.bottom: hBar.top anchors.topMargin: stdHeight anchors.rightMargin: vBar.width - anchors.bottomMargin: hBar.height clip: true contentWidth: root.scaledTableWidth contentHeight: root.totalTableHeight @@ -394,9 +432,7 @@ ColumnLayout { spacing: 1 Repeater { id: rowRepeater - model: attribute - ? attribute.value - : null + model: attribute ? attribute.value : null delegate: TableViewRowDelegate { rowIndex: index rowObject: object From 5f33c35fa35310d060c92654ab8e53eb8ce42b23 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:52:04 +0200 Subject: [PATCH 19/26] TableViewRowDelegate capped column widths --- .../TableViewRowDelegate.qml | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml index 7f41c39862..df319ffca3 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml @@ -8,15 +8,31 @@ Item { property real rowHeight: 24 property real tableWidth: 100 property var scaledColumnWidths: [] + readonly property var maxColumnWidths: { + var result = [] + for (var i = 0; i < cellRepeater.count; i++) { + var item = cellRepeater.itemAt(i) + result.push(item ? item.maxCellWidth : Infinity) + } + return result + } property bool editable: true property var appPalette: palette - signal rowResized(int rowIndex, real newHeight) + readonly property var minColumnWidths: { + var result = [] + for (var i = 0; i < cellRepeater.count; i++) { + var cellItem = cellRepeater.itemAt(i) + result.push(cellItem ? cellItem.minCellWidth : 60) + } + return result +} width: tableWidth height: rowHeight Row { spacing: 1 anchors.fill: parent Repeater { + id: cellRepeater model: root.rowObject && root.rowObject.value ? root.rowObject.value.count : 0 @@ -28,29 +44,10 @@ Item { ? root.scaledColumnWidths[index] : 100 cellHeight: root.height + maxWidth: (root.maxColumnWidths && root.maxColumnWidths.length > index) + ? root.maxColumnWidths[index] + : 100 } } } - MouseArea { - id: colResizeHandle - width: parent.width - height: 6 - anchors.bottom: parent.bottom - cursorShape: Qt.SizeVerCursor - preventStealing: true - property real lastY: 0 - onPressed: function(mouse) { - colResizeHandle.grabMouse() - lastY = mapToGlobal(mouse.x, mouse.y).y - } - onReleased: function(mouse) { colResizeHandle.ungrabMouse() } - onPositionChanged: function(mouse) { - if (!pressed) - return - var globalY = mapToGlobal(mouse.x, mouse.y).y - var delta = globalY - lastY - lastY = globalY - root.rowResized(root.rowIndex, Math.max(20, root.rowHeight + delta)) - } - } } From 14d6226f94867ea1a2407a0e64f6377469068189 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:53:22 +0200 Subject: [PATCH 20/26] TableViewCellDelegate capped columns widths --- .../TableViewCellDelegate.qml | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml index 2efbadfc44..a5b758b502 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml @@ -10,9 +10,19 @@ Rectangle { property int cellIndex: 0 property var rowObject: null property int rowIndex: 0 - property real cellWidth: 100 + property real cellWidth: 100 + property real maxWidth: cellWidth property real cellHeight: 24 property bool editable: true + readonly property real minCellWidth: cellLoader.item && cellLoader.item.minWidth !== undefined + ? cellLoader.item.minWidth + : 60 + readonly property real maxCellWidth: { + var item = cellLoader.item + if (!item || item.maxWidth === Infinity) + return Infinity + return -1 + } width : cellWidth height : cellHeight property var appPalette: palette @@ -71,6 +81,8 @@ Rectangle { Component { id: cellChoiceComponent Choice { + property real minWidth: 80 + property real maxWidth: -1 value: cellLoader.attribute ? cellLoader.attribute.value : "" @@ -92,6 +104,8 @@ Rectangle { Component { id: cellChoiceMultiComponent ChoiceMulti { + property real minWidth: 80 + property real maxWidth: -1 value: cellLoader.attribute ? cellLoader.attribute.value : [] @@ -117,6 +131,8 @@ Rectangle { Component { id: cellSliderComponent RowLayout { + property real minWidth: cellNumField.implicitWidth + property real maxWidth: Infinity spacing: 2 TextField { id: cellNumField @@ -199,6 +215,8 @@ Rectangle { Component { id: cellCheckboxComponent CheckBox { + property real minWidth: 40 + property real maxWidth: -1 enabled: cellRect.editable checked: cellLoader.attribute ? cellLoader.attribute.value @@ -214,6 +232,8 @@ Rectangle { Component { id: cellTextFieldComponent TextField { + property real minWidth: 120 + property real maxWidth: -1 enabled: cellRect.editable text: cellLoader.attribute ? String(cellLoader.attribute.value) @@ -236,6 +256,8 @@ Rectangle { Component { id: cellTextAreaComponent TextField { + property real minWidth: 120 + property real maxWidth: -1 enabled: cellRect.editable text: cellLoader.attribute ? String(cellLoader.attribute.value) @@ -256,6 +278,8 @@ Rectangle { Component { id: cellColorComponent TextField { + property real minWidth: 60 + property real maxWidth: -1 enabled: cellRect.editable text: cellLoader.attribute ? String(cellLoader.attribute.value) @@ -276,6 +300,8 @@ Rectangle { Component { id: cellPushButtonComponent Button { + property real minWidth: 80 + property real maxWidth: -1 text: cellLoader.attribute ? cellLoader.attribute.label : "" @@ -289,6 +315,8 @@ Rectangle { Component { id: cellColorHueComponent RowLayout { + property real minWidth: 96 + property real maxWidth: -1 Slider { id: cellHueSlider Layout.fillWidth: true From fb189851133fcf629a344a7aeec5b0eadbb36760 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:32:53 +0200 Subject: [PATCH 21/26] Fixed highlights and focus erros, added a format for real values --- .../TableViewCellDelegate.qml | 538 ++++++++++++------ 1 file changed, 350 insertions(+), 188 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml index a5b758b502..dfe248e4ba 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml @@ -11,33 +11,19 @@ Rectangle { property var rowObject: null property int rowIndex: 0 property real cellWidth: 100 - property real maxWidth: cellWidth property real cellHeight: 24 property bool editable: true - readonly property real minCellWidth: cellLoader.item && cellLoader.item.minWidth !== undefined - ? cellLoader.item.minWidth - : 60 - readonly property real maxCellWidth: { - var item = cellLoader.item - if (!item || item.maxWidth === Infinity) - return Infinity - return -1 - } - width : cellWidth - height : cellHeight - property var appPalette: palette - color : palette.window - border.color : cellFocused ? palette.highlight : palette.mid - clip : true - property var cell: rowObject.value.at(cellIndex) - property bool cellFocused: { - var item = cellLoader.item - if (!item) - return false - return item.activeFocus || - (item.children && item.children.length > 0 && - item.children[0] && item.children[0].activeFocus) - } + property bool cellReady: false + property real minCellWidth: 60 + signal loaderReady() + width: cellWidth + height: cellHeight + color: palette.window + border.color: palette.mid + clip: true + property var cell: rowObject + ? rowObject.value.at(cellIndex) + : null Rectangle { anchors.centerIn: parent width: cellLoader.width + 8 @@ -50,163 +36,275 @@ Rectangle { } Loader { id: cellLoader - anchors.centerIn: parent - width: parent.width - height: parent.height + anchors.fill: parent property var attribute: cellRect.cell + onStatusChanged: { + if (status !== Loader.Ready) + return + cellRect.minCellWidth = (item && item.minWidth !== undefined) + ? item.minWidth + : 60 + cellRect.cellReady = true + cellRect.loaderReady() + } sourceComponent: { if (!attribute) return null switch (attribute.type) { - case "PushButtonParam": return cellPushButtonComponent + case "PushButtonParam": + return cellPushButtonComponent case "ChoiceParam": return (attribute.desc && attribute.desc.exclusive) ? cellChoiceComponent : cellChoiceMultiComponent - case "IntParam": return cellSliderComponent + case "IntParam": + return cellSliderComponent case "FloatParam": - return (attribute.desc && attribute.desc.semantic === "color/hue") - ? cellColorHueComponent - : cellSliderComponent - case "BoolParam": return cellCheckboxComponent + return (attribute.desc && + attribute.desc.semantic === "color/hue") + ? cellColorHueComponent : + cellSliderComponent + case "BoolParam": + return cellCheckboxComponent case "StringParam": return (attribute.desc && attribute.desc.semantic && attribute.desc.semantic.includes("multiline")) - ? cellTextAreaComponent - : cellTextFieldComponent - case "ColorParam": return cellColorComponent - default: return cellTextFieldComponent + ? cellTextAreaComponent : + cellTextFieldComponent + case "ColorParam": + return cellColorComponent + default: + return cellTextFieldComponent } } Component { id: cellChoiceComponent - Choice { + Item { property real minWidth: 80 - property real maxWidth: -1 - value: cellLoader.attribute - ? cellLoader.attribute.value - : "" - values: cellLoader.attribute - ? cellLoader.attribute.values - : [] - enabled: cellRect.editable - Component.onCompleted: { - if (typeof popup !== "undefined" && popup !== null) { - popup.margins = -1 + Choice { + id: innerChoice + anchors.fill: parent + value: cellLoader.attribute + ? cellLoader.attribute.value + : "" + values: cellLoader.attribute + ? cellLoader.attribute.values + : [] + enabled: cellRect.editable + Component.onCompleted: { + if (typeof popup !== "undefined" && popup !== null) + popup.margins = -1 + } + onEditingFinished: function(v) { + if (!cellLoader.attribute) + return + _currentScene.setAttribute(cellLoader.attribute, v) } } - onEditingFinished: function(value) { - if (cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, value) + Rectangle { + anchors.fill: innerChoice + color: "transparent" + radius: 3 + z: 10 + enabled: false + border.width: innerChoice.activeFocus + ? 1 + : 0 + border.color: palette.highlight } } } Component { id: cellChoiceMultiComponent - ChoiceMulti { + Item { property real minWidth: 80 - property real maxWidth: -1 - value: cellLoader.attribute - ? cellLoader.attribute.value - : [] - values: cellLoader.attribute - ? cellLoader.attribute.values - : [] - enabled: cellRect.editable - customValueColor: Colors.orange - onToggled: function(value, checked) { - if (!cellLoader.attribute) - return - var cur = cellLoader.attribute.value.slice() - if (!checked) { - var idx = cur.indexOf(value) - if (idx !== -1) cur.splice(idx, 1) - } else { - cur.push(value) + ChoiceMulti { + id: innerChoiceMulti + anchors.fill: parent + value: cellLoader.attribute + ? cellLoader.attribute.value + : [] + values: cellLoader.attribute + ? cellLoader.attribute.values + : [] + enabled: cellRect.editable + customValueColor: Colors.orange + onToggled: function(value, checked) { + if (!cellLoader.attribute) + return + var cur = cellLoader.attribute.value.slice() + if (!checked) { + var idx = cur.indexOf(value) + if (idx !== -1) cur.splice(idx, 1) + } else { + cur.push(value) + } + _currentScene.setAttribute(cellLoader.attribute, cur) } - _currentScene.setAttribute(cellLoader.attribute, cur) + } + Rectangle { + anchors.fill: innerChoiceMulti + color: "transparent" + radius: 3 + z: 10 + enabled: false + border.width: innerChoiceMulti.activeFocus + ? 1 + : 0 + border.color: palette.highlight } } } Component { id: cellSliderComponent - RowLayout { + FocusScope { + id: sliderScope property real minWidth: cellNumField.implicitWidth - property real maxWidth: Infinity - spacing: 2 - TextField { - id: cellNumField - Layout.fillWidth: !cellSliderLoader.active - implicitWidth: 70 - enabled: cellRect.editable - selectByMouse: true - horizontalAlignment: TextInput.AlignRight - text: { - if (cellSliderLoader.active && cellSliderLoader.item && + readonly property int decimals: { + if (!cellLoader.attribute) + return 0 + if (cellLoader.attribute.type === "IntParam") + return 0 + var step = (cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3) + ? cellLoader.attribute.desc.range[2] + : 0.01 + if (step <= 0 || step >= 1) + return 2 + return String(step).split(".").pop().length + } + Rectangle { + anchors.fill: parent + color: "transparent" + radius: 3 + z: 10 + enabled: false + border.width: sliderScope.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + RowLayout { + anchors.fill: parent + spacing: 2 + TextField { + id: cellNumField + Layout.fillWidth: !cellSliderLoader.active + implicitWidth: 70 + enabled: cellRect.editable + selectByMouse: true + horizontalAlignment: TextInput.AlignRight + text: { + if (cellSliderLoader.active && + cellSliderLoader.item && cellSliderLoader.item.pressed) - return String(cellSliderLoader.item.formattedValue) - return cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - } - background: Rectangle { color: Qt.darker(palette.window, 1.2); radius: 2 } - color: palette.text - onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, + return cellSliderLoader.item.value + .toFixed(sliderScope.decimals) + if (!cellLoader.attribute) return "" + var v = Number(cellLoader.attribute.value) + return isNaN(v) + ? String(cellLoader.attribute.value) + : v.toFixed(sliderScope.decimals) + } + background: Rectangle { + color: Qt.darker(palette.window, 1.2) + radius: 2 + border.width: cellNumField.activeFocus + ? 1 : + 0 + border.color: palette.highlight + } + color: palette.text + onEditingFinished: { + if (!cellLoader.attribute || !cellNumField.activeFocus) + return + _currentScene.setAttribute( + cellLoader.attribute, cellLoader.attribute.type === "IntParam" ? parseInt(text) : parseFloat(text)) - } - WheelHandler { - onWheel: function(event) { - if (!cellRect.editable || !cellLoader.attribute) - return - var step = 1 - if (cellLoader.attribute.desc && + } + WheelHandler { + enabled: cellNumField.activeFocus + onWheel: function(event) { + if (!cellRect.editable || !cellLoader.attribute) + return + var step = 1 + if (cellLoader.attribute.desc && cellLoader.attribute.desc.range && cellLoader.attribute.desc.range.length === 3) - step = cellLoader.attribute.desc.range[2] - var dir = event.angleDelta.y > 0 - ? 1 - : -1 - var v = Number(cellLoader.attribute.value) + dir * step - if (cellLoader.attribute.desc && cellLoader.attribute.desc.range) { - v = Math.max(cellLoader.attribute.desc.range[0], - Math.min(cellLoader.attribute.desc.range[1], v)) + step = cellLoader.attribute.desc.range[2] + var dir = event.angleDelta.y > 0 + ? 1 + : -1 + var v = Number(cellLoader.attribute.value) + + dir * step + if (cellLoader.attribute.desc && + cellLoader.attribute.desc.range) { + v = Math.max(cellLoader.attribute.desc.range[0], + Math.min(cellLoader.attribute.desc.range[1], v)) + } + _currentScene.setAttribute( + cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? Math.round(v) + : parseFloat(v.toFixed(sliderScope.decimals))) + event.accepted = true } - _currentScene.setAttribute(cellLoader.attribute, - cellLoader.attribute.type === "IntParam" - ? Math.round(v) - : v) - event.accepted = true } } - } - Loader { - id: cellSliderLoader - Layout.fillWidth: true - active: cellLoader.attribute && - cellLoader.attribute.desc && - cellLoader.attribute.desc.range && - cellLoader.attribute.desc.range.length === 3 - sourceComponent: Slider { - readonly property int stepDecimalCount: stepSize < 1 - ? String(stepSize).split(".").pop().length - : 0 - readonly property real formattedValue: value.toFixed(stepDecimalCount) - enabled: cellRect.editable - value: cellLoader.attribute - ? cellLoader.attribute.value - : 0 - from: cellLoader.attribute.desc.range[0] - to: cellLoader.attribute.desc.range[1] - stepSize: cellLoader.attribute.desc.range[2] - snapMode: Slider.SnapAlways - onPressedChanged: { - if (!pressed && cellLoader.attribute) - _currentScene.setAttribute(cellLoader.attribute, - formattedValue) + Loader { + id: cellSliderLoader + Layout.fillWidth: true + active: cellLoader.attribute && + cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3 + sourceComponent: Slider { + id: innerSlider + enabled: cellRect.editable + from: cellLoader.attribute.desc.range[0] + to: cellLoader.attribute.desc.range[1] + stepSize: cellLoader.attribute.desc.range[2] + snapMode: Slider.SnapAlways + value: { + if (!cellLoader.attribute) + return from + var v = Number(cellLoader.attribute.value) + if (isNaN(v)) + return from + return Math.max(from, Math.min(to, v)) + } + background: Rectangle { + x: innerSlider.leftPadding + y: innerSlider.topPadding + + innerSlider.availableHeight / 2 - height / 2 + width: innerSlider.availableWidth + height: 4 + radius: 2 + color: Qt.darker(palette.window, 1.4) + border.width: innerSlider.activeFocus + ? 1 + : 0 + border.color: palette.highlight + Rectangle { + width: innerSlider.visualPosition * parent.width + height: parent.height + color: palette.highlight + radius: 2 + } + } + onPressedChanged: { + if (!cellLoader.attribute || pressed) + return + var v = parseFloat( + value.toFixed(sliderScope.decimals)) + if (isNaN(v)) + return + _currentScene.setAttribute( + cellLoader.attribute, v) + } } } } @@ -214,131 +312,195 @@ Rectangle { } Component { id: cellCheckboxComponent - CheckBox { + Item { property real minWidth: 40 - property real maxWidth: -1 - enabled: cellRect.editable - checked: cellLoader.attribute - ? cellLoader.attribute.value - : false - onToggled: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, checked) + CheckBox { + id: innerCheckBox + anchors.centerIn: parent + enabled: cellRect.editable + checked: cellLoader.attribute + ? cellLoader.attribute.value + : false + onToggled: { + if (!cellLoader.attribute) + return + _currentScene.setAttribute(cellLoader.attribute, checked) + } + } + Rectangle { + anchors.fill: innerCheckBox + color: "transparent" + radius: 2 + border.width: innerCheckBox.activeFocus ? 2 : 0 + border.color: root.appPalette.highlight + z: 10 + enabled: false } - background: Rectangle { color:palette.window; radius: 2 } } } Component { id: cellTextFieldComponent TextField { + id: innerTextField property real minWidth: 120 - property real maxWidth: -1 + anchors.fill: parent enabled: cellRect.editable text: cellLoader.attribute ? String(cellLoader.attribute.value) : "" - placeholderText: cellLoader.attribute.isMandatory ? "This field is required" : "" + placeholderText: cellLoader.attribute && + cellLoader.attribute.isMandatory + ? "This field is required" + : "" placeholderTextColor: "gray" selectByMouse: true background: Rectangle { + anchors.fill: innerTextField color: Qt.darker(palette.window, 1.2) radius: 2 + border.width: innerTextField.activeFocus + ? 1 + : 0 + border.color: palette.highlight } color: palette.text onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text.trim()) + if (!cellLoader.attribute || !activeFocus) + return + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) } } } Component { id: cellTextAreaComponent TextField { + id: innerTextArea property real minWidth: 120 - property real maxWidth: -1 + anchors.fill: parent enabled: cellRect.editable text: cellLoader.attribute ? String(cellLoader.attribute.value) : "" selectByMouse: true background: Rectangle { + anchors.fill: innerTextArea color: palette.base radius: 2 + border.width: innerTextArea.activeFocus + ? 1 + : 0 + border.color: palette.highlight } color: palette.text onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text.trim()) + if (!cellLoader.attribute || !activeFocus) + return + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) } } } Component { id: cellColorComponent TextField { + id: innerColor property real minWidth: 60 - property real maxWidth: -1 + anchors.fill: parent enabled: cellRect.editable text: cellLoader.attribute ? String(cellLoader.attribute.value) : "" selectByMouse: true background: Rectangle { + anchors.fill: innerColor color: Qt.darker(palette.window, 1.2) radius: 2 + border.width: innerColor.activeFocus + ? 1 + : 0 + border.color: palette.highlight } color: palette.text onEditingFinished: { - if (cellLoader.attribute) - _currentScene.setAttribute( - cellLoader.attribute, text) + if (!cellLoader.attribute || !activeFocus) + return + _currentScene.setAttribute(cellLoader.attribute, text) } } } Component { id: cellPushButtonComponent Button { + id: innerButton property real minWidth: 80 - property real maxWidth: -1 + anchors.fill: parent text: cellLoader.attribute ? cellLoader.attribute.label : "" + background: Rectangle { + anchors.fill: innerButton + color: palette.button + radius: 3 + border.width: innerButton.activeFocus + ? 1 + : 0 + border.color: innerButton.activeFocus + ? palette.highlight + : palette.mid + } enabled: cellRect.editable onClicked: { - if (cellLoader.attribute) - cellLoader.attribute.clicked() + if (!cellLoader.attribute) + return + cellLoader.attribute.clicked() } } } Component { id: cellColorHueComponent - RowLayout { + FocusScope { + id: hueScope property real minWidth: 96 - property real maxWidth: -1 - Slider { - id: cellHueSlider - Layout.fillWidth: true - enabled: cellRect.editable - value: cellLoader.attribute - ? cellLoader.attribute.value - : 0 - from: 0 - to: 1 - stepSize: 0.01 - snapMode: Slider.SnapAlways - onPressedChanged: { - if (!pressed && cellLoader.attribute) + Rectangle { + anchors.fill: parent + color: "transparent" + radius: 3 + z: 10 + enabled: false + border.width: hueScope.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + RowLayout { + anchors.fill: parent + spacing: 4 + Slider { + id: cellHueSlider + Layout.fillWidth: true + enabled: cellRect.editable + from: 0; to: 1; stepSize: 0.01 + snapMode: Slider.SnapAlways + value: cellLoader.attribute + ? Number(cellLoader.attribute.value) + : 0 + onPressedChanged: { + if (!cellLoader.attribute || pressed) + return _currentScene.setAttribute( cellLoader.attribute, - value.toFixed(2)) + parseFloat(value.toFixed(2))) + } + } + Rectangle { + width: 16 + height: 16 + radius: 2 + color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) + border.width: 1 + border.color: palette.mid } - } - Rectangle { - width: 16 - height: 16 - color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) } } } From 3ce6ed21b92d3365ba1d47db8ec613afbf2349b1 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:33:23 +0200 Subject: [PATCH 22/26] fixed focus errors --- .../TableViewRowDelegate.qml | 541 ++++++++++++++++-- 1 file changed, 498 insertions(+), 43 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml index df319ffca3..dfe248e4ba 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml @@ -1,52 +1,507 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts -Item { - id: root - property int rowIndex: 0 +import MaterialIcons 2.2 +import Utils 1.0 + +Rectangle { + id: cellRect + property int cellIndex: 0 property var rowObject: null - property real rowHeight: 24 - property real tableWidth: 100 - property var scaledColumnWidths: [] - readonly property var maxColumnWidths: { - var result = [] - for (var i = 0; i < cellRepeater.count; i++) { - var item = cellRepeater.itemAt(i) - result.push(item ? item.maxCellWidth : Infinity) - } - return result - } + property int rowIndex: 0 + property real cellWidth: 100 + property real cellHeight: 24 property bool editable: true - property var appPalette: palette - readonly property var minColumnWidths: { - var result = [] - for (var i = 0; i < cellRepeater.count; i++) { - var cellItem = cellRepeater.itemAt(i) - result.push(cellItem ? cellItem.minCellWidth : 60) - } - return result -} - width: tableWidth - height: rowHeight - Row { - spacing: 1 + property bool cellReady: false + property real minCellWidth: 60 + signal loaderReady() + width: cellWidth + height: cellHeight + color: palette.window + border.color: palette.mid + clip: true + property var cell: rowObject + ? rowObject.value.at(cellIndex) + : null + Rectangle { + anchors.centerIn: parent + width: cellLoader.width + 8 + height: cellLoader.height + 4 + radius: 3 + color: palette.base + visible: cellRect.cell && + (cellRect.cell.type === "BoolParam" || + cellRect.cell.type === "ChoiceParam") + } + Loader { + id: cellLoader anchors.fill: parent - Repeater { - id: cellRepeater - model: root.rowObject && root.rowObject.value - ? root.rowObject.value.count - : 0 - delegate: TableViewCellDelegate { - cellIndex: index - rowObject: root.rowObject - rowIndex: root.rowIndex - cellWidth: (root.scaledColumnWidths && root.scaledColumnWidths.length > index) - ? root.scaledColumnWidths[index] - : 100 - cellHeight: root.height - maxWidth: (root.maxColumnWidths && root.maxColumnWidths.length > index) - ? root.maxColumnWidths[index] - : 100 + property var attribute: cellRect.cell + onStatusChanged: { + if (status !== Loader.Ready) + return + cellRect.minCellWidth = (item && item.minWidth !== undefined) + ? item.minWidth + : 60 + cellRect.cellReady = true + cellRect.loaderReady() + } + sourceComponent: { + if (!attribute) + return null + switch (attribute.type) { + case "PushButtonParam": + return cellPushButtonComponent + case "ChoiceParam": + return (attribute.desc && attribute.desc.exclusive) + ? cellChoiceComponent + : cellChoiceMultiComponent + case "IntParam": + return cellSliderComponent + case "FloatParam": + return (attribute.desc && + attribute.desc.semantic === "color/hue") + ? cellColorHueComponent : + cellSliderComponent + case "BoolParam": + return cellCheckboxComponent + case "StringParam": + return (attribute.desc && attribute.desc.semantic && + attribute.desc.semantic.includes("multiline")) + ? cellTextAreaComponent : + cellTextFieldComponent + case "ColorParam": + return cellColorComponent + default: + return cellTextFieldComponent + } + } + Component { + id: cellChoiceComponent + Item { + property real minWidth: 80 + Choice { + id: innerChoice + anchors.fill: parent + value: cellLoader.attribute + ? cellLoader.attribute.value + : "" + values: cellLoader.attribute + ? cellLoader.attribute.values + : [] + enabled: cellRect.editable + Component.onCompleted: { + if (typeof popup !== "undefined" && popup !== null) + popup.margins = -1 + } + onEditingFinished: function(v) { + if (!cellLoader.attribute) + return + _currentScene.setAttribute(cellLoader.attribute, v) + } + } + Rectangle { + anchors.fill: innerChoice + color: "transparent" + radius: 3 + z: 10 + enabled: false + border.width: innerChoice.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + } + } + Component { + id: cellChoiceMultiComponent + Item { + property real minWidth: 80 + ChoiceMulti { + id: innerChoiceMulti + anchors.fill: parent + value: cellLoader.attribute + ? cellLoader.attribute.value + : [] + values: cellLoader.attribute + ? cellLoader.attribute.values + : [] + enabled: cellRect.editable + customValueColor: Colors.orange + onToggled: function(value, checked) { + if (!cellLoader.attribute) + return + var cur = cellLoader.attribute.value.slice() + if (!checked) { + var idx = cur.indexOf(value) + if (idx !== -1) cur.splice(idx, 1) + } else { + cur.push(value) + } + _currentScene.setAttribute(cellLoader.attribute, cur) + } + } + Rectangle { + anchors.fill: innerChoiceMulti + color: "transparent" + radius: 3 + z: 10 + enabled: false + border.width: innerChoiceMulti.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + } + } + Component { + id: cellSliderComponent + FocusScope { + id: sliderScope + property real minWidth: cellNumField.implicitWidth + readonly property int decimals: { + if (!cellLoader.attribute) + return 0 + if (cellLoader.attribute.type === "IntParam") + return 0 + var step = (cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3) + ? cellLoader.attribute.desc.range[2] + : 0.01 + if (step <= 0 || step >= 1) + return 2 + return String(step).split(".").pop().length + } + Rectangle { + anchors.fill: parent + color: "transparent" + radius: 3 + z: 10 + enabled: false + border.width: sliderScope.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + RowLayout { + anchors.fill: parent + spacing: 2 + TextField { + id: cellNumField + Layout.fillWidth: !cellSliderLoader.active + implicitWidth: 70 + enabled: cellRect.editable + selectByMouse: true + horizontalAlignment: TextInput.AlignRight + text: { + if (cellSliderLoader.active && + cellSliderLoader.item && + cellSliderLoader.item.pressed) + return cellSliderLoader.item.value + .toFixed(sliderScope.decimals) + if (!cellLoader.attribute) return "" + var v = Number(cellLoader.attribute.value) + return isNaN(v) + ? String(cellLoader.attribute.value) + : v.toFixed(sliderScope.decimals) + } + background: Rectangle { + color: Qt.darker(palette.window, 1.2) + radius: 2 + border.width: cellNumField.activeFocus + ? 1 : + 0 + border.color: palette.highlight + } + color: palette.text + onEditingFinished: { + if (!cellLoader.attribute || !cellNumField.activeFocus) + return + _currentScene.setAttribute( + cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? parseInt(text) + : parseFloat(text)) + } + WheelHandler { + enabled: cellNumField.activeFocus + onWheel: function(event) { + if (!cellRect.editable || !cellLoader.attribute) + return + var step = 1 + if (cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3) + step = cellLoader.attribute.desc.range[2] + var dir = event.angleDelta.y > 0 + ? 1 + : -1 + var v = Number(cellLoader.attribute.value) + + dir * step + if (cellLoader.attribute.desc && + cellLoader.attribute.desc.range) { + v = Math.max(cellLoader.attribute.desc.range[0], + Math.min(cellLoader.attribute.desc.range[1], v)) + } + _currentScene.setAttribute( + cellLoader.attribute, + cellLoader.attribute.type === "IntParam" + ? Math.round(v) + : parseFloat(v.toFixed(sliderScope.decimals))) + event.accepted = true + } + } + } + Loader { + id: cellSliderLoader + Layout.fillWidth: true + active: cellLoader.attribute && + cellLoader.attribute.desc && + cellLoader.attribute.desc.range && + cellLoader.attribute.desc.range.length === 3 + sourceComponent: Slider { + id: innerSlider + enabled: cellRect.editable + from: cellLoader.attribute.desc.range[0] + to: cellLoader.attribute.desc.range[1] + stepSize: cellLoader.attribute.desc.range[2] + snapMode: Slider.SnapAlways + value: { + if (!cellLoader.attribute) + return from + var v = Number(cellLoader.attribute.value) + if (isNaN(v)) + return from + return Math.max(from, Math.min(to, v)) + } + background: Rectangle { + x: innerSlider.leftPadding + y: innerSlider.topPadding + + innerSlider.availableHeight / 2 - height / 2 + width: innerSlider.availableWidth + height: 4 + radius: 2 + color: Qt.darker(palette.window, 1.4) + border.width: innerSlider.activeFocus + ? 1 + : 0 + border.color: palette.highlight + Rectangle { + width: innerSlider.visualPosition * parent.width + height: parent.height + color: palette.highlight + radius: 2 + } + } + onPressedChanged: { + if (!cellLoader.attribute || pressed) + return + var v = parseFloat( + value.toFixed(sliderScope.decimals)) + if (isNaN(v)) + return + _currentScene.setAttribute( + cellLoader.attribute, v) + } + } + } + } + } + } + Component { + id: cellCheckboxComponent + Item { + property real minWidth: 40 + CheckBox { + id: innerCheckBox + anchors.centerIn: parent + enabled: cellRect.editable + checked: cellLoader.attribute + ? cellLoader.attribute.value + : false + onToggled: { + if (!cellLoader.attribute) + return + _currentScene.setAttribute(cellLoader.attribute, checked) + } + } + Rectangle { + anchors.fill: innerCheckBox + color: "transparent" + radius: 2 + border.width: innerCheckBox.activeFocus ? 2 : 0 + border.color: root.appPalette.highlight + z: 10 + enabled: false + } + } + } + Component { + id: cellTextFieldComponent + TextField { + id: innerTextField + property real minWidth: 120 + anchors.fill: parent + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + placeholderText: cellLoader.attribute && + cellLoader.attribute.isMandatory + ? "This field is required" + : "" + placeholderTextColor: "gray" + selectByMouse: true + background: Rectangle { + anchors.fill: innerTextField + color: Qt.darker(palette.window, 1.2) + radius: 2 + border.width: innerTextField.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + color: palette.text + onEditingFinished: { + if (!cellLoader.attribute || !activeFocus) + return + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) + } + } + } + Component { + id: cellTextAreaComponent + TextField { + id: innerTextArea + property real minWidth: 120 + anchors.fill: parent + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + anchors.fill: innerTextArea + color: palette.base + radius: 2 + border.width: innerTextArea.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + color: palette.text + onEditingFinished: { + if (!cellLoader.attribute || !activeFocus) + return + _currentScene.setAttribute( + cellLoader.attribute, text.trim()) + } + } + } + Component { + id: cellColorComponent + TextField { + id: innerColor + property real minWidth: 60 + anchors.fill: parent + enabled: cellRect.editable + text: cellLoader.attribute + ? String(cellLoader.attribute.value) + : "" + selectByMouse: true + background: Rectangle { + anchors.fill: innerColor + color: Qt.darker(palette.window, 1.2) + radius: 2 + border.width: innerColor.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + color: palette.text + onEditingFinished: { + if (!cellLoader.attribute || !activeFocus) + return + _currentScene.setAttribute(cellLoader.attribute, text) + } + } + } + Component { + id: cellPushButtonComponent + Button { + id: innerButton + property real minWidth: 80 + anchors.fill: parent + text: cellLoader.attribute + ? cellLoader.attribute.label + : "" + background: Rectangle { + anchors.fill: innerButton + color: palette.button + radius: 3 + border.width: innerButton.activeFocus + ? 1 + : 0 + border.color: innerButton.activeFocus + ? palette.highlight + : palette.mid + } + enabled: cellRect.editable + onClicked: { + if (!cellLoader.attribute) + return + cellLoader.attribute.clicked() + } + } + } + Component { + id: cellColorHueComponent + FocusScope { + id: hueScope + property real minWidth: 96 + Rectangle { + anchors.fill: parent + color: "transparent" + radius: 3 + z: 10 + enabled: false + border.width: hueScope.activeFocus + ? 1 + : 0 + border.color: palette.highlight + } + RowLayout { + anchors.fill: parent + spacing: 4 + Slider { + id: cellHueSlider + Layout.fillWidth: true + enabled: cellRect.editable + from: 0; to: 1; stepSize: 0.01 + snapMode: Slider.SnapAlways + value: cellLoader.attribute + ? Number(cellLoader.attribute.value) + : 0 + onPressedChanged: { + if (!cellLoader.attribute || pressed) + return + _currentScene.setAttribute( + cellLoader.attribute, + parseFloat(value.toFixed(2))) + } + } + Rectangle { + width: 16 + height: 16 + radius: 2 + color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) + border.width: 1 + border.color: palette.mid + } + } } } } From 7ffbb3708a5122b369fd5955d2c6f04d4515ae47 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:34:03 +0200 Subject: [PATCH 23/26] fixed typo --- .../TableViewRowDelegate.qml | 537 ++---------------- 1 file changed, 40 insertions(+), 497 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml index dfe248e4ba..7ab5bc6f20 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewRowDelegate.qml @@ -1,508 +1,51 @@ import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import MaterialIcons 2.2 -import Utils 1.0 - -Rectangle { - id: cellRect - property int cellIndex: 0 - property var rowObject: null +Item { + id: rowRoot property int rowIndex: 0 - property real cellWidth: 100 - property real cellHeight: 24 + property var rowObject: null + property real rowHeight: 24 + property real tableWidth: 100 + property var scaledColumnWidths: [] property bool editable: true - property bool cellReady: false - property real minCellWidth: 60 - signal loaderReady() - width: cellWidth - height: cellHeight - color: palette.window - border.color: palette.mid - clip: true - property var cell: rowObject - ? rowObject.value.at(cellIndex) - : null - Rectangle { - anchors.centerIn: parent - width: cellLoader.width + 8 - height: cellLoader.height + 4 - radius: 3 - color: palette.base - visible: cellRect.cell && - (cellRect.cell.type === "BoolParam" || - cellRect.cell.type === "ChoiceParam") - } - Loader { - id: cellLoader + property var minColumnWidths: [] + width: tableWidth + height: rowHeight + Row { + spacing: 1 anchors.fill: parent - property var attribute: cellRect.cell - onStatusChanged: { - if (status !== Loader.Ready) - return - cellRect.minCellWidth = (item && item.minWidth !== undefined) - ? item.minWidth - : 60 - cellRect.cellReady = true - cellRect.loaderReady() - } - sourceComponent: { - if (!attribute) - return null - switch (attribute.type) { - case "PushButtonParam": - return cellPushButtonComponent - case "ChoiceParam": - return (attribute.desc && attribute.desc.exclusive) - ? cellChoiceComponent - : cellChoiceMultiComponent - case "IntParam": - return cellSliderComponent - case "FloatParam": - return (attribute.desc && - attribute.desc.semantic === "color/hue") - ? cellColorHueComponent : - cellSliderComponent - case "BoolParam": - return cellCheckboxComponent - case "StringParam": - return (attribute.desc && attribute.desc.semantic && - attribute.desc.semantic.includes("multiline")) - ? cellTextAreaComponent : - cellTextFieldComponent - case "ColorParam": - return cellColorComponent - default: - return cellTextFieldComponent - } - } - Component { - id: cellChoiceComponent - Item { - property real minWidth: 80 - Choice { - id: innerChoice - anchors.fill: parent - value: cellLoader.attribute - ? cellLoader.attribute.value - : "" - values: cellLoader.attribute - ? cellLoader.attribute.values - : [] - enabled: cellRect.editable - Component.onCompleted: { - if (typeof popup !== "undefined" && popup !== null) - popup.margins = -1 - } - onEditingFinished: function(v) { - if (!cellLoader.attribute) - return - _currentScene.setAttribute(cellLoader.attribute, v) - } - } - Rectangle { - anchors.fill: innerChoice - color: "transparent" - radius: 3 - z: 10 - enabled: false - border.width: innerChoice.activeFocus - ? 1 - : 0 - border.color: palette.highlight - } - } - } - Component { - id: cellChoiceMultiComponent - Item { - property real minWidth: 80 - ChoiceMulti { - id: innerChoiceMulti - anchors.fill: parent - value: cellLoader.attribute - ? cellLoader.attribute.value - : [] - values: cellLoader.attribute - ? cellLoader.attribute.values - : [] - enabled: cellRect.editable - customValueColor: Colors.orange - onToggled: function(value, checked) { - if (!cellLoader.attribute) - return - var cur = cellLoader.attribute.value.slice() - if (!checked) { - var idx = cur.indexOf(value) - if (idx !== -1) cur.splice(idx, 1) - } else { - cur.push(value) - } - _currentScene.setAttribute(cellLoader.attribute, cur) - } - } - Rectangle { - anchors.fill: innerChoiceMulti - color: "transparent" - radius: 3 - z: 10 - enabled: false - border.width: innerChoiceMulti.activeFocus - ? 1 - : 0 - border.color: palette.highlight - } - } - } - Component { - id: cellSliderComponent - FocusScope { - id: sliderScope - property real minWidth: cellNumField.implicitWidth - readonly property int decimals: { - if (!cellLoader.attribute) - return 0 - if (cellLoader.attribute.type === "IntParam") - return 0 - var step = (cellLoader.attribute.desc && - cellLoader.attribute.desc.range && - cellLoader.attribute.desc.range.length === 3) - ? cellLoader.attribute.desc.range[2] - : 0.01 - if (step <= 0 || step >= 1) - return 2 - return String(step).split(".").pop().length - } - Rectangle { - anchors.fill: parent - color: "transparent" - radius: 3 - z: 10 - enabled: false - border.width: sliderScope.activeFocus - ? 1 - : 0 - border.color: palette.highlight - } - RowLayout { - anchors.fill: parent - spacing: 2 - TextField { - id: cellNumField - Layout.fillWidth: !cellSliderLoader.active - implicitWidth: 70 - enabled: cellRect.editable - selectByMouse: true - horizontalAlignment: TextInput.AlignRight - text: { - if (cellSliderLoader.active && - cellSliderLoader.item && - cellSliderLoader.item.pressed) - return cellSliderLoader.item.value - .toFixed(sliderScope.decimals) - if (!cellLoader.attribute) return "" - var v = Number(cellLoader.attribute.value) - return isNaN(v) - ? String(cellLoader.attribute.value) - : v.toFixed(sliderScope.decimals) - } - background: Rectangle { - color: Qt.darker(palette.window, 1.2) - radius: 2 - border.width: cellNumField.activeFocus - ? 1 : - 0 - border.color: palette.highlight - } - color: palette.text - onEditingFinished: { - if (!cellLoader.attribute || !cellNumField.activeFocus) - return - _currentScene.setAttribute( - cellLoader.attribute, - cellLoader.attribute.type === "IntParam" - ? parseInt(text) - : parseFloat(text)) - } - WheelHandler { - enabled: cellNumField.activeFocus - onWheel: function(event) { - if (!cellRect.editable || !cellLoader.attribute) - return - var step = 1 - if (cellLoader.attribute.desc && - cellLoader.attribute.desc.range && - cellLoader.attribute.desc.range.length === 3) - step = cellLoader.attribute.desc.range[2] - var dir = event.angleDelta.y > 0 - ? 1 - : -1 - var v = Number(cellLoader.attribute.value) + - dir * step - if (cellLoader.attribute.desc && - cellLoader.attribute.desc.range) { - v = Math.max(cellLoader.attribute.desc.range[0], - Math.min(cellLoader.attribute.desc.range[1], v)) - } - _currentScene.setAttribute( - cellLoader.attribute, - cellLoader.attribute.type === "IntParam" - ? Math.round(v) - : parseFloat(v.toFixed(sliderScope.decimals))) - event.accepted = true - } - } - } - Loader { - id: cellSliderLoader - Layout.fillWidth: true - active: cellLoader.attribute && - cellLoader.attribute.desc && - cellLoader.attribute.desc.range && - cellLoader.attribute.desc.range.length === 3 - sourceComponent: Slider { - id: innerSlider - enabled: cellRect.editable - from: cellLoader.attribute.desc.range[0] - to: cellLoader.attribute.desc.range[1] - stepSize: cellLoader.attribute.desc.range[2] - snapMode: Slider.SnapAlways - value: { - if (!cellLoader.attribute) - return from - var v = Number(cellLoader.attribute.value) - if (isNaN(v)) - return from - return Math.max(from, Math.min(to, v)) - } - background: Rectangle { - x: innerSlider.leftPadding - y: innerSlider.topPadding + - innerSlider.availableHeight / 2 - height / 2 - width: innerSlider.availableWidth - height: 4 - radius: 2 - color: Qt.darker(palette.window, 1.4) - border.width: innerSlider.activeFocus - ? 1 - : 0 - border.color: palette.highlight - Rectangle { - width: innerSlider.visualPosition * parent.width - height: parent.height - color: palette.highlight - radius: 2 - } - } - onPressedChanged: { - if (!cellLoader.attribute || pressed) - return - var v = parseFloat( - value.toFixed(sliderScope.decimals)) - if (isNaN(v)) - return - _currentScene.setAttribute( - cellLoader.attribute, v) - } - } - } - } - } - } - Component { - id: cellCheckboxComponent - Item { - property real minWidth: 40 - CheckBox { - id: innerCheckBox - anchors.centerIn: parent - enabled: cellRect.editable - checked: cellLoader.attribute - ? cellLoader.attribute.value - : false - onToggled: { - if (!cellLoader.attribute) - return - _currentScene.setAttribute(cellLoader.attribute, checked) - } - } - Rectangle { - anchors.fill: innerCheckBox - color: "transparent" - radius: 2 - border.width: innerCheckBox.activeFocus ? 2 : 0 - border.color: root.appPalette.highlight - z: 10 - enabled: false - } - } - } - Component { - id: cellTextFieldComponent - TextField { - id: innerTextField - property real minWidth: 120 - anchors.fill: parent - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - placeholderText: cellLoader.attribute && - cellLoader.attribute.isMandatory - ? "This field is required" - : "" - placeholderTextColor: "gray" - selectByMouse: true - background: Rectangle { - anchors.fill: innerTextField - color: Qt.darker(palette.window, 1.2) - radius: 2 - border.width: innerTextField.activeFocus - ? 1 - : 0 - border.color: palette.highlight - } - color: palette.text - onEditingFinished: { - if (!cellLoader.attribute || !activeFocus) - return - _currentScene.setAttribute( - cellLoader.attribute, text.trim()) - } - } - } - Component { - id: cellTextAreaComponent - TextField { - id: innerTextArea - property real minWidth: 120 - anchors.fill: parent - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - selectByMouse: true - background: Rectangle { - anchors.fill: innerTextArea - color: palette.base - radius: 2 - border.width: innerTextArea.activeFocus - ? 1 - : 0 - border.color: palette.highlight - } - color: palette.text - onEditingFinished: { - if (!cellLoader.attribute || !activeFocus) - return - _currentScene.setAttribute( - cellLoader.attribute, text.trim()) - } - } - } - Component { - id: cellColorComponent - TextField { - id: innerColor - property real minWidth: 60 - anchors.fill: parent - enabled: cellRect.editable - text: cellLoader.attribute - ? String(cellLoader.attribute.value) - : "" - selectByMouse: true - background: Rectangle { - anchors.fill: innerColor - color: Qt.darker(palette.window, 1.2) - radius: 2 - border.width: innerColor.activeFocus - ? 1 - : 0 - border.color: palette.highlight - } - color: palette.text - onEditingFinished: { - if (!cellLoader.attribute || !activeFocus) - return - _currentScene.setAttribute(cellLoader.attribute, text) - } - } - } - Component { - id: cellPushButtonComponent - Button { - id: innerButton - property real minWidth: 80 - anchors.fill: parent - text: cellLoader.attribute - ? cellLoader.attribute.label - : "" - background: Rectangle { - anchors.fill: innerButton - color: palette.button - radius: 3 - border.width: innerButton.activeFocus - ? 1 - : 0 - border.color: innerButton.activeFocus - ? palette.highlight - : palette.mid - } - enabled: cellRect.editable - onClicked: { - if (!cellLoader.attribute) - return - cellLoader.attribute.clicked() - } - } - } - Component { - id: cellColorHueComponent - FocusScope { - id: hueScope - property real minWidth: 96 - Rectangle { - anchors.fill: parent - color: "transparent" - radius: 3 - z: 10 - enabled: false - border.width: hueScope.activeFocus - ? 1 - : 0 - border.color: palette.highlight - } - RowLayout { - anchors.fill: parent - spacing: 4 - Slider { - id: cellHueSlider - Layout.fillWidth: true - enabled: cellRect.editable - from: 0; to: 1; stepSize: 0.01 - snapMode: Slider.SnapAlways - value: cellLoader.attribute - ? Number(cellLoader.attribute.value) - : 0 - onPressedChanged: { - if (!cellLoader.attribute || pressed) - return - _currentScene.setAttribute( - cellLoader.attribute, - parseFloat(value.toFixed(2))) - } - } - Rectangle { - width: 16 - height: 16 - radius: 2 - color: Qt.hsla(cellHueSlider.value, 1, 0.5, 1) - border.width: 1 - border.color: palette.mid - } + Repeater { + id: cellRepeater + model: rowRoot.rowObject && rowRoot.rowObject.value + ? rowRoot.rowObject.value.count + : 0 + onItemAdded: function(index, item) { + rowRoot.refreshColumnWidths() + } + delegate: TableViewCellDelegate { + cellIndex: index + rowObject: rowRoot.rowObject + rowIndex: rowRoot.rowIndex + cellWidth: (rowRoot.scaledColumnWidths && + rowRoot.scaledColumnWidths.length > index) + ? rowRoot.scaledColumnWidths[index] + : 100 + cellHeight: rowRoot.rowHeight + editable: rowRoot.editable + onLoaderReady: { + rowRoot.refreshColumnWidths() } } } } + function refreshColumnWidths() { + var mins = [] + for (var i = 0; i < cellRepeater.count; i++) { + var c = cellRepeater.itemAt(i) + var ready = c && c.cellReady + mins.push (ready ? c.minCellWidth : 60) + } + rowRoot.minColumnWidths = mins + } } From a5b2b92c285562c27f200692fb81088066abb7e5 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:34:31 +0200 Subject: [PATCH 24/26] Fixed focus errors --- .../AttributeControls/TableView.qml | 301 ++++++++---------- 1 file changed, 140 insertions(+), 161 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml index 5a8744e12a..df05085667 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts import QtQuick.Window import MaterialIcons 2.2 -import Utils 1.0 +import Utils 1.0 ColumnLayout { id: root @@ -25,94 +25,86 @@ ColumnLayout { return names } property var columnWidths: [] - property real totalTableWidth: { - if (!columnWidths || columnWidths.length === 0) - return 0 - var t = 0 - for (var i = 0; i < columnWidths.length; i++) t += columnWidths[i] - t += Math.max(0, columnWidths.length - 1) - return t - } property real totalTableHeight: attribute && attribute.value ? attribute.value.count * 31 : 0 - property var scaledColumnWidths: [] + property var scaledColumnWidths: [] property real scaledTableWidth: 0 - property real availableW: outerFrame.width > 0 + property real availableW: outerFrame.width > 0 ? outerFrame.width - fixedStrip.width - vBar.width : 600 + property bool expanded: false + property var appPalette: palette function computeMinColumnWidths() { var firstRow = rowRepeater.itemAt(0) - if (!firstRow) + if (!firstRow || firstRow.minColumnWidths.length === 0) return new Array(root.columnNames.length).fill(60) return firstRow.minColumnWidths } - function computeMaxColumnWidths() { - var firstRow = rowRepeater.itemAt(0) - var n = root.columnWidths.length - if (!firstRow || !firstRow.maxColumnWidths || firstRow.maxColumnWidths.length !== n) - return new Array(n).fill(Infinity) - return firstRow.maxColumnWidths.map(function(v, i) { - return v === Infinity ? Infinity : root.columnWidths[i] * 1.2 - }) - } function updateScaledWidths() { if (!root.columnWidths || root.columnWidths.length === 0) return - var mins = computeMinColumnWidths() - var maxs = computeMaxColumnWidths() var n = root.columnWidths.length - var total = 0 - var widths = [] + var mins = computeMinColumnWidths() + var widths = [], total = 0 for (var i = 0; i < n; i++) { - var w = Math.max(root.columnWidths[i], mins[i] !== undefined ? mins[i] : 60) + var minW = (mins[i] !== undefined ? mins[i] : 60) + var w = Math.max(root.columnWidths[i], minW) widths.push(w) total += w } var leftover = root.availableW - total - if (leftover > 0) { - var eligible = [] - for (var j = 0; j < n; j++) { - if (maxs[j] === undefined || maxs[j] === Infinity || widths[j] < maxs[j]) - eligible.push(j) - } - while (leftover > 0.5 && eligible.length > 0) { - var extra = leftover / eligible.length - var stillEligible = [] - var consumed = 0 - for (var k = 0; k < eligible.length; k++) { - var idx = eligible[k] - var cap = (maxs[idx] === undefined || maxs[idx] === Infinity) - ? Infinity - : maxs[idx] - var room = (cap === Infinity) ? Infinity : cap - widths[idx] - if (cap !== Infinity && room <= extra) { - widths[idx] = cap - consumed += room - } else { - widths[idx] += extra - consumed += extra - stillEligible.push(idx) - } - } - leftover -= consumed - eligible = stillEligible - } + if (leftover > 0.5 && n > 0) { + var share = leftover / n + for (var j = 0; j < n; j++) widths[j] += share } total = 0 - for (var l = 0; l < n; l++) - total += widths[l] + for (var l = 0; l < n; l++) total += widths[l] root.scaledColumnWidths = widths root.scaledTableWidth = total } - property bool expanded: false - property var appPalette: palette + FontMetrics { id: fontMetrics; font.bold: true } + function initSizes() { + var names = root.columnNames + if (!names || names.length === 0) { + root.columnWidths = [] + return + } + var widths = [] + for (var i = 0; i < names.length; i++) + widths.push(fontMetrics.advanceWidth(names[i]) + 20) + if (attribute && attribute.value) { + for (var r = 0; r < attribute.value.count; r++) { + var rowAttr = attribute.value.at(r) + if (!rowAttr || !rowAttr.value) continue + for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { + var ca = rowAttr.value.at(c) + var cw = fontMetrics.advanceWidth(ca ? String(ca.value) : "") + 20 + if (cw > widths[c]) widths[c] = cw + } + } + } + root.columnWidths = widths + } + Component.onCompleted: { + initSizes() + updateScaledWidths() + } + Connections { + target: attribute + ? attribute.value + : null + function onCountChanged() { root.initSizes(); root.updateScaledWidths() } + function onModelReset() { root.initSizes(); root.updateScaledWidths() } + function onRowsInserted() { root.initSizes(); root.updateScaledWidths() } + function onDataChanged() { root.initSizes(); root.updateScaledWidths() } + } + onAvailableWChanged: root.updateScaledWidths() RowLayout { spacing: 4 ToolButton { - text: root.expanded - ? MaterialIcons.keyboard_arrow_down - : MaterialIcons.keyboard_arrow_right + text: root.expanded ? MaterialIcons.keyboard_arrow_down + : MaterialIcons.keyboard_arrow_right font.family: MaterialIcons.fontFamily onClicked: root.expanded = !root.expanded } @@ -136,25 +128,23 @@ ColumnLayout { ToolTip.text: "Open in fullscreen" ToolTip.visible: hovered onClicked: { - outerFrame.Layout.preferredWidth = -1 - outerFrame.Layout.preferredHeight = -1 - outerFrame.visible = true outerFrame.parent = fullscreenContent outerFrame.anchors.fill = fullscreenContent outerFrame.isFullscreen = true - fullscreenWindow.width = root.scaledTableWidth + fixedStrip.width + vBar.width + 2*stdHeight - fullscreenWindow.height = root.totalTableHeight + hBar.height + 0.75*stdHeight + outerFrame.visible = true + fullscreenWindow.width = root.scaledTableWidth + fixedStrip.width + vBar.width + 2 * stdHeight + fullscreenWindow.height = root.totalTableHeight + hBar.height + 0.15 * stdHeight fullscreenWindow.show() } } } Window { id: fullscreenWindow - title: attribute - ? attribute.label - : "" + title: attribute + ? attribute.label + : "" palette: root.appPalette - color: palette.window + color: palette.window Item { id: fullscreenContent anchors.fill: parent @@ -163,61 +153,17 @@ ColumnLayout { outerFrame.anchors.fill = undefined outerFrame.width = undefined outerFrame.height = undefined - outerFrame.Layout.preferredWidth = -1 - outerFrame.parent = root outerFrame.isFullscreen = false + outerFrame.parent = root outerFrame.Layout.fillWidth = true outerFrame.Layout.preferredHeight = Qt.binding(function() { return root.expanded ? Math.min(root.totalTableHeight + 40, 330) : 0 }) - outerFrame.visible = Qt.binding(function() { - return root.expanded - }) - } - } - FontMetrics { - id: fontMetrics - font.bold: true - } - function initSizes() { - var names = root.columnNames - if (!names || names.length === 0) { - root.columnWidths = [] - return - } - var widths = [] - for (var i = 0; i < names.length; i++) - widths.push(fontMetrics.advanceWidth(names[i]) + 20) - if (attribute && attribute.value) { - for (var r = 0; r < attribute.value.count; r++) { - var rowAttr = attribute.value.at(r) - if (!rowAttr || !rowAttr.value) continue - for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { - var cell = rowAttr.value.at(c) - var cellText = cell ? String(cell.value) : "" - var cw = fontMetrics.advanceWidth(cellText) + 20 - if (cw > widths[c]) widths[c] = cw - } - } + outerFrame.visible = Qt.binding(function() { return root.expanded }) } - root.columnWidths = widths - } - Component.onCompleted: { - root.initSizes() - root.updateScaledWidths() } - Connections { - target: attribute - ? attribute.value - : null - function onCountChanged() {root.initSizes(); root.updateScaledWidths()} - function onModelReset() {root.initSizes(); root.updateScaledWidths()} - function onRowsInserted() {root.initSizes(); root.updateScaledWidths()} - function onDataChanged() {root.initSizes(); root.updateScaledWidths()} - } - onAvailableWChanged: root.updateScaledWidths() Item { id: outerFrame Layout.fillWidth: true @@ -228,50 +174,65 @@ ColumnLayout { property bool isFullscreen: false ScrollBar { id: hBar - anchors.left: fixedStrip.right - anchors.right: outerFrame.right - anchors.bottom: outerFrame.bottom - anchors.rightMargin: vBar.width + anchors { + left: fixedStrip.right + right: outerFrame.right + bottom: outerFrame.bottom + rightMargin: vBar.width + } orientation: Qt.Horizontal policy: flickable.contentWidth > flickable.width ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff size: Math.min(1.0, flickable.width / Math.max(flickable.contentWidth, 1)) - position: (flickable.contentX / Math.max(flickable.contentWidth - flickable.width, 1)) + position: (flickable.contentX / + Math.max(flickable.contentWidth - flickable.width, 1)) * (1.0 - size) onPositionChanged: { - if (!pressed) return + if (!pressed) + return var maxPos = 1.0 - size - var ratio = maxPos > 0 ? position / maxPos : 0 - flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) + var ratio = maxPos > 0 + ? position / maxPos + : 0 + flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) } } ScrollBar { id: vBar - anchors.top: outerFrame.top - anchors.bottom: outerFrame.bottom - anchors.right: outerFrame.right - anchors.bottomMargin: hBar.height + anchors { + top: outerFrame.top + bottom: outerFrame.bottom + right: outerFrame.right + bottomMargin: hBar.height + } orientation: Qt.Vertical policy: flickable.contentHeight > flickable.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff - size: Math.min(1.0, flickable.height / Math.max(flickable.contentHeight, 1)) - position: (flickable.contentY / Math.max(flickable.contentHeight - flickable.height, 1)) + size: Math.min(1.0, flickable.height / + Math.max(flickable.contentHeight, 1)) + position: (flickable.contentY / + Math.max(flickable.contentHeight - flickable.height, 1)) * (1.0 - size) onPositionChanged: { - if (!pressed) return + if (!pressed) + return var maxPos = 1.0 - size - var ratio = maxPos > 0 ? position / maxPos : 0 - flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) + var ratio = maxPos > 0 + ? position / maxPos + : 0 + flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) } } Item { id: fixedHeader - anchors.left: fixedStrip.right - anchors.right: outerFrame.right - anchors.top: outerFrame.top - anchors.rightMargin: vBar.width + anchors { + left: fixedStrip.right + right: outerFrame.right + top: outerFrame.top + rightMargin: vBar.width + } height: stdHeight clip: true Row { @@ -311,17 +272,23 @@ ColumnLayout { onPressed: function(mouse) { var p = mapToItem(root, mouse.x, mouse.y) startX = p.x - startW = root.columnWidths[headerCell.index] + startW = root.columnWidths[headerCell.index] || 100 } - onReleased: function(mouse) { } onPositionChanged: function(mouse) { - if (!pressed) return + if (!pressed) + return var p = mapToItem(root, mouse.x, mouse.y) - var minW = Math.max(40, computeMinColumnWidths()[headerCell.index] || 60) - var newW = Math.max(minW, startW + (p.x - startX)) - var widthArray = root.columnWidths.slice() - widthArray[headerCell.index] = newW - root.columnWidths = widthArray + var delta = p.x - startX + var newW = startW + delta + var mins = root.computeMinColumnWidths() + var minW = Math.max(40, + mins[headerCell.index] !== undefined + ? mins[headerCell.index] + : 60) + newW = Math.max(minW, newW) + var wa = root.columnWidths.slice() + wa[headerCell.index] = newW + root.columnWidths = wa root.updateScaledWidths() } } @@ -331,10 +298,12 @@ ColumnLayout { } Item { id: fixedStrip - anchors.left: outerFrame.left - anchors.top: outerFrame.top - anchors.bottom: hBar.top - anchors.topMargin: stdHeight + anchors { + left: outerFrame.left + top: outerFrame.top + bottom: hBar.top + topMargin: stdHeight + } width: stdHeight clip: true Column { @@ -342,7 +311,9 @@ ColumnLayout { width: parent.width y: -flickable.contentY Repeater { - model: attribute ? attribute.value : null + model: attribute + ? attribute.value + : null delegate: Item { id: removeDelegate required property int index @@ -373,8 +344,10 @@ ColumnLayout { } Item { id: cornerCell - anchors.left: outerFrame.left - anchors.top: outerFrame.top + anchors { + left: outerFrame.left + top: outerFrame.top + } width: fixedStrip.width height: fixedHeader.height visible: outerFrame.isFullscreen @@ -404,12 +377,14 @@ ColumnLayout { } Flickable { id: flickable - anchors.left: fixedStrip.right - anchors.top: outerFrame.top - anchors.right: outerFrame.right - anchors.bottom: hBar.top - anchors.topMargin: stdHeight - anchors.rightMargin: vBar.width + anchors { + left: fixedStrip.right + right: outerFrame.right + top: outerFrame.top + bottom: hBar.top + topMargin: stdHeight + rightMargin: vBar.width + } clip: true contentWidth: root.scaledTableWidth contentHeight: root.totalTableHeight @@ -419,11 +394,13 @@ ColumnLayout { if (event.modifiers & Qt.ControlModifier) { flickable.contentX = Math.max(0, Math.min(flickable.contentWidth - flickable.width, - flickable.contentX - event.angleDelta.y / 120 * 40)) + flickable.contentX - + event.angleDelta.y / 120 * 40)) } else { flickable.contentY = Math.max(0, Math.min(flickable.contentHeight - flickable.height, - flickable.contentY - event.angleDelta.y / 120 * 40)) + flickable.contentY - + event.angleDelta.y / 120 * 40)) } event.accepted = true } @@ -432,7 +409,9 @@ ColumnLayout { spacing: 1 Repeater { id: rowRepeater - model: attribute ? attribute.value : null + model: attribute + ? attribute.value + : null delegate: TableViewRowDelegate { rowIndex: index rowObject: object From cd3bf1275911b643039c0e6ad9600653491edbff Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:40:14 +0200 Subject: [PATCH 25/26] Fixed typos --- .../AttributeControls/TableView.qml | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml index df05085667..2fd6199e2d 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableView.qml @@ -16,11 +16,13 @@ ColumnLayout { if (!attribute || !attribute.value || attribute.value.count === 0) return [] var firstRow = attribute.value.at(0) - if (!firstRow || !firstRow.value) return [] + if (!firstRow || !firstRow.value) + return [] var names = [] for (var i = 0; i < firstRow.value.count; i++) { var child = firstRow.value.at(i) - if (child) names.push(child.label) + if (child) + names.push(child.label) } return names } @@ -56,10 +58,12 @@ ColumnLayout { var leftover = root.availableW - total if (leftover > 0.5 && n > 0) { var share = leftover / n - for (var j = 0; j < n; j++) widths[j] += share + for (var j = 0; j < n; j++) + widths[j] += share } total = 0 - for (var l = 0; l < n; l++) total += widths[l] + for (var l = 0; l < n; l++) + total += widths[l] root.scaledColumnWidths = widths root.scaledTableWidth = total } @@ -76,11 +80,13 @@ ColumnLayout { if (attribute && attribute.value) { for (var r = 0; r < attribute.value.count; r++) { var rowAttr = attribute.value.at(r) - if (!rowAttr || !rowAttr.value) continue + if (!rowAttr || !rowAttr.value) + continue for (var c = 0; c < rowAttr.value.count && c < widths.length; c++) { var ca = rowAttr.value.at(c) var cw = fontMetrics.advanceWidth(ca ? String(ca.value) : "") + 20 - if (cw > widths[c]) widths[c] = cw + if (cw > widths[c]) + widths[c] = cw } } } @@ -195,7 +201,7 @@ ColumnLayout { var ratio = maxPos > 0 ? position / maxPos : 0 - flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) + flickable.contentX = ratio * Math.max(flickable.contentWidth - flickable.width, 1) } } ScrollBar { @@ -222,7 +228,7 @@ ColumnLayout { var ratio = maxPos > 0 ? position / maxPos : 0 - flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) + flickable.contentY = ratio * Math.max(flickable.contentHeight - flickable.height, 1) } } Item { From a3321af63395abff167aca767aa201941ef382c6 Mon Sep 17 00:00:00 2001 From: raphaelKoskas <64128722+raphaelKoskas@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:41:26 +0200 Subject: [PATCH 26/26] Fixed typos --- .../GraphEditor/AttributeControls/TableViewCellDelegate.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml index dfe248e4ba..479656673b 100644 --- a/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml +++ b/meshroom/ui/qml/GraphEditor/AttributeControls/TableViewCellDelegate.qml @@ -135,7 +135,8 @@ Rectangle { var cur = cellLoader.attribute.value.slice() if (!checked) { var idx = cur.indexOf(value) - if (idx !== -1) cur.splice(idx, 1) + if (idx !== -1) + cur.splice(idx, 1) } else { cur.push(value) } @@ -201,7 +202,8 @@ Rectangle { cellSliderLoader.item.pressed) return cellSliderLoader.item.value .toFixed(sliderScope.decimals) - if (!cellLoader.attribute) return "" + if (!cellLoader.attribute) + return "" var v = Number(cellLoader.attribute.value) return isNaN(v) ? String(cellLoader.attribute.value)