Skip to content

Commit c7065a9

Browse files
author
Tato Levicz
committed
feat: theming
1 parent 6db3053 commit c7065a9

7 files changed

Lines changed: 184 additions & 71 deletions

File tree

examples/qml_calculator/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ int main(int argc, char *argv[])
6060
qmlRegisterType(QUrl("qrc:/QtNodes/QML/qml/NodeGraph.qml"), "QtNodes", 1, 0, "NodeGraph");
6161
qmlRegisterType(QUrl("qrc:/QtNodes/QML/qml/Node.qml"), "QtNodes", 1, 0, "Node");
6262
qmlRegisterType(QUrl("qrc:/QtNodes/QML/qml/Connection.qml"), "QtNodes", 1, 0, "Connection");
63+
qmlRegisterType(QUrl("qrc:/QtNodes/QML/qml/NodeGraphStyle.qml"), "QtNodes", 1, 0, "NodeGraphStyle");
6364

6465
auto registry = registerDataModels();
6566
auto graphModel = new QuickGraphModel();

examples/qml_calculator/main.qml

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,40 @@ Window {
1010
height: 800
1111
title: "QML Calculator - Extended"
1212

13-
property QuickGraphModel model: _graphModel
13+
property QuickGraphModel model: _graphModel
14+
property bool darkTheme: true
15+
16+
// Custom dark theme
17+
NodeGraphStyle {
18+
id: darkStyle
19+
canvasBackground: "#1e1e1e"
20+
gridMinorLine: "#2a2a2a"
21+
gridMajorLine: "#0f0f0f"
22+
nodeBackground: "#2d2d2d"
23+
nodeBorder: "#1a1a1a"
24+
nodeSelectedBorder: "#4a9eff"
25+
nodeCaptionColor: "#eeeeee"
26+
nodeContentColor: "#ffffff"
27+
connectionSelectionOutline: "#4a9eff"
28+
selectionRectFill: "#224a9eff"
29+
selectionRectBorder: "#4a9eff"
30+
}
31+
32+
// Custom light theme
33+
NodeGraphStyle {
34+
id: lightStyle
35+
canvasBackground: "#f5f5f5"
36+
gridMinorLine: "#e0e0e0"
37+
gridMajorLine: "#c0c0c0"
38+
nodeBackground: "#ffffff"
39+
nodeBorder: "#cccccc"
40+
nodeSelectedBorder: "#2196F3"
41+
nodeCaptionColor: "#333333"
42+
nodeContentColor: "#333333"
43+
connectionSelectionOutline: "#2196F3"
44+
selectionRectFill: "#222196F3"
45+
selectionRectBorder: "#2196F3"
46+
}
1447

1548
Column {
1649
anchors.fill: parent
@@ -19,16 +52,23 @@ Window {
1952
Rectangle {
2053
width: parent.width
2154
height: 50
22-
color: "#3c3c3c"
55+
color: darkTheme ? "#3c3c3c" : "#e0e0e0"
2356

2457
RowLayout {
2558
anchors.fill: parent
2659
anchors.margins: 5
2760
spacing: 10
2861

62+
Button {
63+
text: darkTheme ? "☀ Light" : "🌙 Dark"
64+
onClicked: darkTheme = !darkTheme
65+
}
66+
67+
Rectangle { width: 1; height: 30; color: "#555" }
68+
2969
Label {
3070
text: "Numbers:"
31-
color: "#aaa"
71+
color: darkTheme ? "#aaa" : "#555"
3272
font.bold: true
3373
}
3474
Button {
@@ -37,23 +77,23 @@ Window {
3777
palette.buttonText: "#4CAF50"
3878
}
3979

40-
Rectangle { width: 1; height: 30; color: "#555" }
80+
Rectangle { width: 1; height: 30; color: darkTheme ? "#555" : "#bbb" }
4181

4282
Label {
4383
text: "Math:"
44-
color: "#aaa"
84+
color: darkTheme ? "#aaa" : "#555"
4585
font.bold: true
4686
}
4787
Button { text: "Add"; onClicked: model.addNode("Addition") }
4888
Button { text: "Subtract"; onClicked: model.addNode("Subtract") }
4989
Button { text: "Multiply"; onClicked: model.addNode("Multiply") }
5090
Button { text: "Divide"; onClicked: model.addNode("Divide") }
5191

52-
Rectangle { width: 1; height: 30; color: "#555" }
92+
Rectangle { width: 1; height: 30; color: darkTheme ? "#555" : "#bbb" }
5393

5494
Label {
5595
text: "String:"
56-
color: "#aaa"
96+
color: darkTheme ? "#aaa" : "#555"
5797
font.bold: true
5898
}
5999
Button {
@@ -67,11 +107,11 @@ Window {
67107
palette.buttonText: "#FF9800"
68108
}
69109

70-
Rectangle { width: 1; height: 30; color: "#555" }
110+
Rectangle { width: 1; height: 30; color: darkTheme ? "#555" : "#bbb" }
71111

72112
Label {
73113
text: "Integer:"
74-
color: "#aaa"
114+
color: darkTheme ? "#aaa" : "#555"
75115
font.bold: true
76116
}
77117
Button {
@@ -85,11 +125,11 @@ Window {
85125
palette.buttonText: "#2196F3"
86126
}
87127

88-
Rectangle { width: 1; height: 30; color: "#555" }
128+
Rectangle { width: 1; height: 30; color: darkTheme ? "#555" : "#bbb" }
89129

90130
Label {
91131
text: "Logic:"
92-
color: "#aaa"
132+
color: darkTheme ? "#aaa" : "#555"
93133
font.bold: true
94134
}
95135
Button {
@@ -98,11 +138,11 @@ Window {
98138
palette.buttonText: "#9C27B0"
99139
}
100140

101-
Rectangle { width: 1; height: 30; color: "#555" }
141+
Rectangle { width: 1; height: 30; color: darkTheme ? "#555" : "#bbb" }
102142

103143
Label {
104144
text: "Display:"
105-
color: "#aaa"
145+
color: darkTheme ? "#aaa" : "#555"
106146
font.bold: true
107147
}
108148
Button {
@@ -126,9 +166,11 @@ Window {
126166
}
127167

128168
NodeGraph {
169+
id: nodeGraph
129170
width: parent.width
130171
height: parent.height - 50
131172
graphModel: model
173+
style: darkTheme ? darkStyle : lightStyle
132174

133175
Component.onCompleted: {
134176
// Create a demo graph: (5 + 3) * 2 = 16, formatted as text
@@ -172,6 +214,7 @@ Window {
172214
Item {
173215
property var delegateModel
174216
property string nodeType
217+
property var contentColor: nodeGraph.style.nodeContentColor
175218

176219
// NumberSource - editable number input
177220
TextField {
@@ -205,7 +248,7 @@ Window {
205248
anchors.centerIn: parent
206249
visible: nodeType === "Addition"
207250
text: "+"
208-
color: "white"
251+
color: contentColor
209252
font.pixelSize: 36
210253
font.bold: true
211254
}
@@ -214,7 +257,7 @@ Window {
214257
anchors.centerIn: parent
215258
visible: nodeType === "Subtract"
216259
text: ""
217-
color: "white"
260+
color: contentColor
218261
font.pixelSize: 36
219262
font.bold: true
220263
}
@@ -223,7 +266,7 @@ Window {
223266
anchors.centerIn: parent
224267
visible: nodeType === "Multiply"
225268
text: "×"
226-
color: "white"
269+
color: contentColor
227270
font.pixelSize: 36
228271
font.bold: true
229272
}
@@ -232,7 +275,7 @@ Window {
232275
anchors.centerIn: parent
233276
visible: nodeType === "Divide"
234277
text: "÷"
235-
color: "white"
278+
color: contentColor
236279
font.pixelSize: 36
237280
font.bold: true
238281
}

resources/qml.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
<file>qml/NodeGraph.qml</file>
44
<file>qml/Node.qml</file>
55
<file>qml/Connection.qml</file>
6+
<file>qml/NodeGraphStyle.qml</file>
67
</qresource>
78
</RCC>

resources/qml/Connection.qml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import QtQuick.Shapes 1.15
44
Item {
55
id: root
66
property var graph
7+
property var style: graph ? graph.style : null
78

89
property int sourceNodeId: -1
910
property int sourcePortIndex: -1
@@ -140,8 +141,8 @@ Item {
140141
visible: root.selected
141142

142143
ShapePath {
143-
strokeWidth: 7
144-
strokeColor: "#4a9eff"
144+
strokeWidth: style.connectionSelectionOutlineWidth
145+
strokeColor: style.connectionSelectionOutline
145146
fillColor: "transparent"
146147

147148
startX: root.startPos.x
@@ -163,7 +164,7 @@ Item {
163164
anchors.fill: parent
164165

165166
ShapePath {
166-
strokeWidth: root.hovered ? 3.5 : 3
167+
strokeWidth: root.hovered ? style.connectionHoverWidth : style.connectionWidth
167168
strokeColor: root.hovered ? Qt.lighter(root.lineColor, 1.3) : root.lineColor
168169
fillColor: "transparent"
169170

resources/qml/Node.qml

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@ Rectangle {
2020
return graph.isNodeSelected(nodeId)
2121
}
2222

23+
// Style shortcut - use direct access for reactivity
24+
property var style: graph ? graph.style : null
25+
2326
x: initialX
2427
y: initialY
2528

26-
width: 150
27-
height: Math.max(Math.max(inPorts, outPorts) * 20 + 40, 50)
29+
width: style.nodeMinWidth
30+
height: Math.max(Math.max(inPorts, outPorts) * (style.portSize + style.nodePortSpacing) + style.nodeHeaderHeight + 5, 50)
2831

29-
color: "#2d2d2d"
30-
border.color: selected ? "#4a9eff" : "black"
31-
border.width: selected ? 3 : 2
32-
radius: 5
32+
color: style.nodeBackground
33+
border.color: selected ? style.nodeSelectedBorder : style.nodeBorder
34+
border.width: selected ? style.nodeSelectedBorderWidth : style.nodeBorderWidth
35+
radius: style.nodeRadius
3336

3437
Component.onCompleted: {
3538
completed = true
@@ -108,17 +111,18 @@ Rectangle {
108111
anchors.top: parent.top
109112
anchors.topMargin: 8
110113
text: caption
111-
color: "#eeeeee"
112-
font.bold: true
114+
color: graph && graph.style ? graph.style.nodeCaptionColor : "#eeeeee"
115+
font.bold: graph && graph.style ? graph.style.nodeCaptionBold : true
116+
font.pixelSize: graph && graph.style ? graph.style.nodeCaptionFontSize : 12
113117
}
114118

115119
Loader {
116120
id: contentLoader
117121
anchors.top: parent.top
118-
anchors.topMargin: 35
122+
anchors.topMargin: style.nodeHeaderHeight
119123
anchors.horizontalCenter: parent.horizontalCenter
120124
width: parent.width - 20
121-
height: parent.height - 50
125+
height: parent.height - style.nodeHeaderHeight - 15
122126
sourceComponent: contentDelegate
123127

124128
onLoaded: {
@@ -146,35 +150,34 @@ Rectangle {
146150
z: 10
147151
anchors.left: parent.left
148152
anchors.top: parent.top
149-
anchors.topMargin: 35
150-
anchors.leftMargin: -5 // Overlap edge
151-
spacing: 10
153+
anchors.topMargin: style.nodeHeaderHeight
154+
anchors.leftMargin: -style.portSize / 2 + 1
155+
spacing: style.nodePortSpacing
152156

153157
Repeater {
154158
id: inRepeater
155159
model: inPorts
156160
delegate: Rectangle {
157161
id: inPortRect
158-
width: 12; height: 12
159-
radius: 6
162+
width: style.portSize; height: style.portSize
163+
radius: style.portSize / 2
160164
property string portTypeId: graph.getPortTypeId(root.nodeId, 0, index)
161165
property bool isCompatible: !graph.isDragging ||
162166
(graph.activeConnectionStart && graph.activeConnectionStart.portType === 1 &&
163167
graph.draftConnectionTypeId === portTypeId)
164168
property bool isDimmed: graph.isDragging && !isCompatible
165169
color: graph.getPortColor(portTypeId)
166-
opacity: isDimmed ? 0.3 : 1.0
167-
border.color: isCompatible && graph.isDragging ? "#ffffff" : "black"
168-
border.width: isCompatible && graph.isDragging ? 2 : 1
170+
opacity: isDimmed ? style.portDimmedOpacity : 1.0
171+
border.color: isCompatible && graph.isDragging ? style.portHighlightBorder : style.portBorderColor
172+
border.width: isCompatible && graph.isDragging ? style.portHighlightBorderWidth : style.portBorderWidth
169173

170174
MouseArea {
171175
anchors.fill: parent
172176
hoverEnabled: true
173177
preventStealing: true
174178
onEntered: {
175-
// Only highlight if not dragging or if we are the target
176179
if (!graph.isDragging) {
177-
parent.scale = 1.2
180+
parent.scale = style.portHoverScale
178181
graph.setActivePort({nodeId: root.nodeId, portType: 0, portIndex: index})
179182
}
180183
}
@@ -185,12 +188,11 @@ Rectangle {
185188
}
186189
}
187190

188-
// Visual feedback based on activePort
189191
property bool isActive: {
190192
var ap = graph.activePort
191193
return ap && ap.nodeId === root.nodeId && ap.portType === 0 && ap.portIndex === index
192194
}
193-
onIsActiveChanged: parent.scale = isActive ? 1.4 : 1.0
195+
onIsActiveChanged: parent.scale = isActive ? style.portActiveScale : 1.0
194196
onPressed: (mouse) => {
195197
var existing = graph.graphModel.getConnectionAtInput(root.nodeId, index)
196198
var mousePos = mapToItem(graph.canvas, mouse.x, mouse.y)
@@ -226,34 +228,34 @@ Rectangle {
226228
z: 10
227229
anchors.right: parent.right
228230
anchors.top: parent.top
229-
anchors.topMargin: 35
230-
anchors.rightMargin: -5
231-
spacing: 10
231+
anchors.topMargin: style.nodeHeaderHeight
232+
anchors.rightMargin: -style.portSize / 2 + 1
233+
spacing: style.nodePortSpacing
232234

233235
Repeater {
234236
id: outRepeater
235237
model: outPorts
236238
delegate: Rectangle {
237239
id: outPortRect
238-
width: 12; height: 12
239-
radius: 6
240+
width: style.portSize; height: style.portSize
241+
radius: style.portSize / 2
240242
property string portTypeId: graph.getPortTypeId(root.nodeId, 1, index)
241243
property bool isCompatible: !graph.isDragging ||
242244
(graph.activeConnectionStart && graph.activeConnectionStart.portType === 0 &&
243245
graph.draftConnectionTypeId === portTypeId)
244246
property bool isDimmed: graph.isDragging && !isCompatible
245247
color: graph.getPortColor(portTypeId)
246-
opacity: isDimmed ? 0.3 : 1.0
247-
border.color: isCompatible && graph.isDragging ? "#ffffff" : "black"
248-
border.width: isCompatible && graph.isDragging ? 2 : 1
248+
opacity: isDimmed ? style.portDimmedOpacity : 1.0
249+
border.color: isCompatible && graph.isDragging ? style.portHighlightBorder : style.portBorderColor
250+
border.width: isCompatible && graph.isDragging ? style.portHighlightBorderWidth : style.portBorderWidth
249251

250252
MouseArea {
251253
anchors.fill: parent
252254
hoverEnabled: true
253255
preventStealing: true
254256
onEntered: {
255257
if (!graph.isDragging) {
256-
parent.scale = 1.2
258+
parent.scale = style.portHoverScale
257259
graph.setActivePort({nodeId: root.nodeId, portType: 1, portIndex: index})
258260
}
259261
}
@@ -268,7 +270,7 @@ Rectangle {
268270
var ap = graph.activePort
269271
return ap && ap.nodeId === root.nodeId && ap.portType === 1 && ap.portIndex === index
270272
}
271-
onIsActiveChanged: parent.scale = isActive ? 1.4 : 1.0
273+
onIsActiveChanged: parent.scale = isActive ? style.portActiveScale : 1.0
272274

273275
onPressed: (mouse) => {
274276
var pos = mapToItem(graph.canvas, width/2, height/2)

0 commit comments

Comments
 (0)