From 78b9532c0f8efbf4d2575dd953a116b2922a43c8 Mon Sep 17 00:00:00 2001 From: marina Date: Mon, 2 Nov 2026 17:02:39 +0300 Subject: [PATCH] feat(gui): implement animated thread state transition diagram component (TSV-51) --- README.md | 3 +- src/qml.qrc | 1 + src/qml/ThreadStateDiagram.qml | 172 ++++++++ src/qml/main.qml | 731 +++++++++++++++++---------------- 4 files changed, 548 insertions(+), 359 deletions(-) create mode 100644 src/qml/ThreadStateDiagram.qml diff --git a/README.md b/README.md index 55651b8..ff7abd3 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,8 @@ thread-scheduler-visualizer/ │ │ └── ThreadListModel.cpp # Thread list model implementation │ ├── qml/ │ │ ├── BenchmarkWindow.qml # Benchmarking window & QML layout -│ │ └── main.qml # Main application window & QML UI layout +│ │ ├── main.qml # Main application window & QML UI layout +│ │ └── ThreadStateDiagram.qml # Thread state diagram & QML animation │ ├── main.cpp # Entry point / Cross-platform icon loader │ └── qml.qrc # Qt resource bundle for QML assets ├── tests/ diff --git a/src/qml.qrc b/src/qml.qrc index 99321e4..9f9cf49 100644 --- a/src/qml.qrc +++ b/src/qml.qrc @@ -2,5 +2,6 @@ qml/main.qml qml/BenchmarkWindow.qml + qml/ThreadStateDiagram.qml \ No newline at end of file diff --git a/src/qml/ThreadStateDiagram.qml b/src/qml/ThreadStateDiagram.qml new file mode 100644 index 0000000..ec1fb0f --- /dev/null +++ b/src/qml/ThreadStateDiagram.qml @@ -0,0 +1,172 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +Rectangle { + id: stateDiagramRoot + color: "#1e1e1e" + border.color: "#333333" + radius: 5 + + property var threadModel: null + + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 8 + + Text { + text: "Thread State Transition Diagram" + color: "#ffffff" + font.pixelSize: 14 + font.bold: true + } + + GridLayout { + Layout.fillWidth: true + Layout.fillHeight: true + columns: 3 + rows: 2 + rowSpacing: 10 + columnSpacing: 10 + + // 1. NEW State Box + StateBox { + stateName: "NEW" + stateColor: "#616161" + threadModel: stateDiagramRoot.threadModel + filterState: "NEW" + Layout.fillWidth: true + Layout.fillHeight: true + } + + // 2. READY State Box + StateBox { + stateName: "READY" + stateColor: "#0277bd" + threadModel: stateDiagramRoot.threadModel + filterState: "READY" + Layout.fillWidth: true + Layout.fillHeight: true + } + + // 3. RUNNING State Box + StateBox { + stateName: "RUNNING" + stateColor: "#2e7d32" + threadModel: stateDiagramRoot.threadModel + filterState: "RUNNING" + Layout.fillWidth: true + Layout.fillHeight: true + } + + // 4. WAITING State Box + StateBox { + stateName: "WAITING" + stateColor: "#ef6c00" + threadModel: stateDiagramRoot.threadModel + filterState: "WAITING" + Layout.fillWidth: true + Layout.fillHeight: true + } + + // 5. TERMINATED State Box + StateBox { + stateName: "TERMINATED" + stateColor: "#424242" + threadModel: stateDiagramRoot.threadModel + filterState: "TERMINATED" + Layout.fillWidth: true + Layout.fillHeight: true + } + + // Ïóñòàÿ ÿ÷åéêà äëÿ ñåòêè 3x2 + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + // Âíóòðåííèé êîìïîíåíò äëÿ êîíòåéíåðà êîíêðåòíîãî ñîñòîÿíèÿ + component StateBox: GroupBox { + property string stateName: "" + property color stateColor: "#fff" + property var threadModel: null + property string filterState: "" + + title: stateName + + background: Rectangle { + color: "#252526" + border.color: stateColor + border.width: 1 + radius: 4 + } + + label: Label { + text: stateName + color: stateColor + font.bold: true + font.pixelSize: 11 + leftPadding: 4 + } + + ScrollView { + anchors.fill: parent + contentWidth: availableWidth + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + Flow { + width: parent.width + spacing: 6 + + Repeater { + model: threadModel + + delegate: Rectangle { + readonly property bool isTargetState: model.state === filterState + + width: isTargetState ? 80 : 0 + height: isTargetState ? 40 : 0 + visible: width > 0 + + color: model.color || "#333" + radius: 4 + border.color: "#ffffff" + border.width: 1 + + Behavior on width { NumberAnimation { duration: 250; easing.type: Easing.OutBack } } + Behavior on height { NumberAnimation { duration: 250; easing.type: Easing.OutBack } } + + scale: hoverHandler.hovered ? 1.05 : 1.0 + Behavior on scale { NumberAnimation { duration: 150 } } + + HoverHandler { id: hoverHandler } + + ToolTip.visible: hoverHandler.hovered + ToolTip.text: "Thread T" + model.threadId + "\nState: " + model.state + "\nRemaining: " + model.remainingTime + + Column { + anchors.centerIn: parent + spacing: 2 + Text { + text: "T" + model.threadId + color: "#ffffff" + font.bold: true + font.pixelSize: 11 + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + text: model.remainingTime + "/" + model.burstTime + color: "#dddddd" + font.pixelSize: 9 + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/qml/main.qml b/src/qml/main.qml index 1a4c71f..afec1ca 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -7,253 +7,261 @@ import QtQuick.Dialogs 1.3 ApplicationWindow { id: window width: 1024 - height: 750 + height: 900 visible: true title: "Thread Scheduler Visualizer" - ColumnLayout { + ScrollView { anchors.fill: parent - anchors.margins: 10 - spacing: 10 - - // Simulation & Algorithm Controls - GroupBox { - title: "Simulation & Algorithm Controls" - Layout.fillWidth: true - - RowLayout { - spacing: 15 - anchors.fill: parent - - Label { text: "Algorithm:" } - ComboBox { - id: algoSelector - model: [ - "FCFS", - "Round Robin", - "Priority", - "SJF", - "SRTF", - "MLFQ" - ] - currentIndex: 0 - onCurrentTextChanged: { - if (guiController) guiController.currentScheduler = currentText + contentWidth: availableWidth + contentHeight: mainColumn.implicitHeight + 20 + clip: true + + ColumnLayout { + id: mainColumn + width: parent.width + anchors.margins: 10 + spacing: 10 + + // Simulation & Algorithm Controls + GroupBox { + title: "Simulation & Algorithm Controls" + Layout.fillWidth: true + + RowLayout { + spacing: 15 + anchors.fill: parent + + Label { text: "Algorithm:" } + ComboBox { + id: algoSelector + model: [ + "FCFS", + "Round Robin", + "Priority", + "SJF", + "SRTF", + "MLFQ" + ] + currentIndex: 0 + onCurrentTextChanged: { + if (guiController) guiController.currentScheduler = currentText + } } - } - Label { - text: "Quantum:" - visible: algoSelector.currentText === "Round Robin" - } - SpinBox { - id: quantumSpinBox - from: 1 - to: 10 - value: guiController ? guiController.timeQuantum : 1 - visible: algoSelector.currentText === "Round Robin" - onValueChanged: { - if (guiController) guiController.timeQuantum = value + Label { + text: "Quantum:" + visible: algoSelector.currentText === "Round Robin" + } + SpinBox { + id: quantumSpinBox + from: 1 + to: 10 + value: guiController ? guiController.timeQuantum : 1 + visible: algoSelector.currentText === "Round Robin" + onValueChanged: { + if (guiController) guiController.timeQuantum = value + } } - } - Item { Layout.fillWidth: true } // Spacer + Item { Layout.fillWidth: true } // Spacer - Button { - text: "Compare Algorithms" - onClicked: { - if (guiController) guiController.runAlgorithmBenchmark() - benchmarkWindow.open() + Button { + text: "Compare Algorithms" + onClicked: { + if (guiController) guiController.runAlgorithmBenchmark() + benchmarkWindow.open() + } } - } - Button { - text: (guiController && guiController.isRunning) ? "Pause" : "Start" - onClicked: { - if (guiController) { - if (guiController.isRunning) - guiController.pauseSimulation() - else - guiController.startSimulation() + Button { + text: (guiController && guiController.isRunning) ? "Pause" : "Start" + onClicked: { + if (guiController) { + if (guiController.isRunning) + guiController.pauseSimulation() + else + guiController.startSimulation() + } } } - } - Button { - text: "Step" - enabled: !(guiController && guiController.isRunning) - onClicked: { - if (guiController) guiController.stepSimulation() + Button { + text: "Step" + enabled: !(guiController && guiController.isRunning) + onClicked: { + if (guiController) guiController.stepSimulation() + } } - } - Button { - text: "Reset" - onClicked: { - if (guiController) guiController.resetSimulation() + Button { + text: "Reset" + onClicked: { + if (guiController) guiController.resetSimulation() + } } } } - } - // Predefined Workload Scenarios & IO Panel - GroupBox { - title: "Predefined Workload Scenarios & I/O" - Layout.fillWidth: true + // Predefined Workload Scenarios & IO Panel + GroupBox { + title: "Predefined Workload Scenarios & I/O" + Layout.fillWidth: true - RowLayout { - spacing: 15 - anchors.fill: parent - - Label { text: "Scenario:" } - ComboBox { - id: scenarioSelector - model: guiController ? guiController.scenarioNames : [] - Layout.preferredWidth: 180 - } + RowLayout { + spacing: 15 + anchors.fill: parent + + Label { text: "Scenario:" } + ComboBox { + id: scenarioSelector + model: guiController ? guiController.scenarioNames : [] + Layout.preferredWidth: 180 + } - Button { - text: "Load Scenario" - onClicked: { - if (guiController) { - guiController.loadScenario(scenarioSelector.currentIndex) + Button { + text: "Load Scenario" + onClicked: { + if (guiController) { + guiController.loadScenario(scenarioSelector.currentIndex) + } } } - } - Item { width: 10 } + Item { width: 10 } - Button { - text: "Save JSON" - onClicked: saveJsonDialog.open() - } + Button { + text: "Save JSON" + onClicked: saveJsonDialog.open() + } - Button { - text: "Load JSON" - onClicked: loadJsonDialog.open() - } + Button { + text: "Load JSON" + onClicked: loadJsonDialog.open() + } - Button { - text: "Export CSV" - onClicked: exportCsvDialog.open() - } + Button { + text: "Export CSV" + onClicked: exportCsvDialog.open() + } - Item { Layout.fillWidth: true } + Item { Layout.fillWidth: true } + } } - } - // The Flow Addition Panel - GroupBox { - title: "Add New Thread" - Layout.fillWidth: true - - RowLayout { - spacing: 15 - anchors.fill: parent - - Label { text: "ID:" } - SpinBox { - id: threadIdInput - from: 1 - to: 99 - value: 1 - - Connections { - target: guiController ? guiController : null - function onCurrentTimeChanged() { - if (guiController && guiController.currentTime === 0) { - threadIdInput.value = 1 + // The Flow Addition Panel + GroupBox { + title: "Add New Thread" + Layout.fillWidth: true + + RowLayout { + spacing: 15 + anchors.fill: parent + + Label { text: "ID:" } + SpinBox { + id: threadIdInput + from: 1 + to: 99 + value: 1 + + Connections { + target: guiController ? guiController : null + function onCurrentTimeChanged() { + if (guiController && guiController.currentTime === 0) { + threadIdInput.value = 1 + } } } } - } - Label { text: "Burst Time:" } - SpinBox { id: burstTimeInput; from: 1; to: 20; value: 3 } + Label { text: "Burst Time:" } + SpinBox { id: burstTimeInput; from: 1; to: 20; value: 3 } - Label { text: "Priority:" } - SpinBox { id: priorityInput; from: 1; to: 10; value: 1 } + Label { text: "Priority:" } + SpinBox { id: priorityInput; from: 1; to: 10; value: 1 } - Button { - text: "Add Thread" - onClicked: { - if (guiController) { - guiController.addThread(threadIdInput.value, burstTimeInput.value, priorityInput.value) - threadIdInput.value = threadIdInput.value + 1 + Button { + text: "Add Thread" + onClicked: { + if (guiController) { + guiController.addThread(threadIdInput.value, burstTimeInput.value, priorityInput.value) + threadIdInput.value = threadIdInput.value + 1 + } } } - } - Item { Layout.fillWidth: true } + Item { Layout.fillWidth: true } + } } - } - // Ready Queue Panel - Rectangle { - Layout.fillWidth: true - implicitHeight: 90 - color: "#1e1e1e" - border.color: "#333333" - radius: 5 - clip: true - - ColumnLayout { - anchors.fill: parent - anchors.margins: 8 - spacing: 5 - - Text { - text: "Ready Queue (Waiting Threads)" - color: "#ffffff" - font.pixelSize: 14 - font.bold: true - } + // Ready Queue Panel + Rectangle { + Layout.fillWidth: true + implicitHeight: 90 + color: "#1e1e1e" + border.color: "#333333" + radius: 5 + clip: true - ScrollView { - Layout.fillWidth: true - Layout.fillHeight: true - ScrollBar.horizontal.policy: ScrollBar.AsNeeded - ScrollBar.vertical.policy: ScrollBar.AlwaysOff - - Row { - spacing: 8 - - Repeater { - model: guiController ? guiController.readyQueueModel : null - - Rectangle { - width: 90 - height: 50 - color: "#2a2a2a" - border.color: model.color - border.width: 2 - radius: 4 - - scale: hoverHandler.hovered ? 1.03 : 1.0 - Behavior on scale { NumberAnimation { duration: 150 } } - - HoverHandler { - id: hoverHandler - } + ColumnLayout { + anchors.fill: parent + anchors.margins: 8 + spacing: 5 + + Text { + text: "Ready Queue (Waiting Threads)" + color: "#ffffff" + font.pixelSize: 14 + font.bold: true + } - ToolTip.visible: hoverHandler.hovered - ToolTip.text: "Thread T" + model.threadId + "\nPriority: " + model.priority + "\nBurst: " + model.burstTime + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + ScrollBar.horizontal.policy: ScrollBar.AsNeeded + ScrollBar.vertical.policy: ScrollBar.AlwaysOff - Column { - anchors.centerIn: parent - spacing: 2 - Text { - text: "Thread " + model.threadId - color: "#ffffff" - font.bold: true - font.pixelSize: 11 - anchors.horizontalCenter: parent.horizontalCenter + Row { + spacing: 8 + + Repeater { + model: guiController ? guiController.readyQueueModel : null + + Rectangle { + width: 90 + height: 50 + color: "#2a2a2a" + border.color: model.color + border.width: 2 + radius: 4 + + scale: hoverHandler.hovered ? 1.03 : 1.0 + Behavior on scale { NumberAnimation { duration: 150 } } + + HoverHandler { + id: hoverHandler } - Text { - text: "Burst: " + model.burstTime - color: "#aaaaaa" - font.pixelSize: 10 - anchors.horizontalCenter: parent.horizontalCenter + + ToolTip.visible: hoverHandler.hovered + ToolTip.text: "Thread T" + model.threadId + "\nPriority: " + model.priority + "\nBurst: " + model.burstTime + + Column { + anchors.centerIn: parent + spacing: 2 + Text { + text: "Thread " + model.threadId + color: "#ffffff" + font.bold: true + font.pixelSize: 11 + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + text: "Burst: " + model.burstTime + color: "#aaaaaa" + font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + } } } } @@ -261,85 +269,85 @@ ApplicationWindow { } } } - } - // Visualization display area (Gantt Chart) - Rectangle { - Layout.fillWidth: true - Layout.fillHeight: true - color: "#1e1e1e" - border.color: "#333333" - radius: 5 - clip: true + // Visualization display area (Gantt Chart) + Rectangle { + Layout.fillWidth: true + implicitHeight: 140 + color: "#1e1e1e" + border.color: "#333333" + radius: 5 + clip: true - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 - spacing: 10 + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 - RowLayout { - Layout.fillWidth: true - Text { - text: "Gantt Chart Execution Timeline" - color: "#ffffff" - font.pixelSize: 16 - font.bold: true - } - Item { Layout.fillWidth: true } - Text { - text: "Current Time: " + (guiController ? guiController.currentTime : 0) - color: "#aaaaaa" - font.pixelSize: 14 + RowLayout { + Layout.fillWidth: true + Text { + text: "Gantt Chart Execution Timeline" + color: "#ffffff" + font.pixelSize: 16 + font.bold: true + } + Item { Layout.fillWidth: true } + Text { + text: "Current Time: " + (guiController ? guiController.currentTime : 0) + color: "#aaaaaa" + font.pixelSize: 14 + } } - } - ScrollView { - Layout.fillWidth: true - Layout.fillHeight: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOn - ScrollBar.vertical.policy: ScrollBar.AlwaysOff - - Row { - id: ganttRow - spacing: 2 - - Repeater { - model: guiController ? guiController.ganttBlocksModel : null - - Rectangle { - width: model.duration * 40 - height: 70 - color: hoverHandlerGantt.hovered ? Qt.lighter(model.color, 1.2) : model.color - radius: 4 - border.color: "#ffffff" - border.width: 1 - - scale: 0.0 - Component.onCompleted: scale = 1.0 - Behavior on scale { - NumberAnimation { duration: 250; easing.type: Easing.OutBack } - } + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOn + ScrollBar.vertical.policy: ScrollBar.AlwaysOff - HoverHandler { - id: hoverHandlerGantt - } + Row { + id: ganttRow + spacing: 2 + + Repeater { + model: guiController ? guiController.ganttBlocksModel : null - ToolTip.visible: hoverHandlerGantt.hovered - ToolTip.text: "Execution Block: " + model.name + "\nInterval: [" + model.startTime + " - " + (model.startTime + model.duration) + "]" + Rectangle { + width: model.duration * 40 + height: 70 + color: hoverHandlerGantt.hovered ? Qt.lighter(model.color, 1.2) : model.color + radius: 4 + border.color: "#ffffff" + border.width: 1 + + scale: 0.0 + Component.onCompleted: scale = 1.0 + Behavior on scale { + NumberAnimation { duration: 250; easing.type: Easing.OutBack } + } - Column { - anchors.centerIn: parent - Text { - text: model.name - color: "#ffffff" - font.bold: true - anchors.horizontalCenter: parent.horizontalCenter + HoverHandler { + id: hoverHandlerGantt } - Text { - text: "[" + model.startTime + " - " + (model.startTime + model.duration) + "]" - color: "#dddddd" - font.pixelSize: 10 - anchors.horizontalCenter: parent.horizontalCenter + + ToolTip.visible: hoverHandlerGantt.hovered + ToolTip.text: "Execution Block: " + model.name + "\nInterval: [" + model.startTime + " - " + (model.startTime + model.duration) + "]" + + Column { + anchors.centerIn: parent + Text { + text: model.name + color: "#ffffff" + font.bold: true + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + text: "[" + model.startTime + " - " + (model.startTime + model.duration) + "]" + color: "#dddddd" + font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + } } } } @@ -347,119 +355,126 @@ ApplicationWindow { } } } - } - // Status Table Panel - Rectangle { - Layout.fillWidth: true - implicitHeight: 180 - color: "#1e1e1e" - border.color: "#333333" - radius: 5 - clip: true - - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 - spacing: 8 - - Text { - text: "Process / Thread Status Table" - color: "#ffffff" - font.pixelSize: 14 - font.bold: true - } + // Status Table Panel + Rectangle { + Layout.fillWidth: true + implicitHeight: 160 + color: "#1e1e1e" + border.color: "#333333" + radius: 5 + clip: true - Rectangle { - Layout.fillWidth: true - height: 25 - color: "#2d2d2d" - radius: 3 + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 8 - RowLayout { - anchors.fill: parent - anchors.leftMargin: 10 - anchors.rightMargin: 10 - - Text { text: "ID"; color: "#aaa"; font.bold: true; Layout.preferredWidth: 60 } - Text { text: "Priority"; color: "#aaa"; font.bold: true; Layout.preferredWidth: 80 } - Text { text: "State"; color: "#aaa"; font.bold: true; Layout.preferredWidth: 100 } - Text { text: "Remaining / Burst"; color: "#aaa"; font.bold: true; Layout.fillWidth: true } + Text { + text: "Process / Thread Status Table" + color: "#ffffff" + font.pixelSize: 14 + font.bold: true } - } - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - model: guiController ? guiController.threadListModel : null - clip: true - spacing: 4 - - delegate: Rectangle { - width: ListView.view.width - height: 30 - color: "#252526" + Rectangle { + Layout.fillWidth: true + height: 25 + color: "#2d2d2d" radius: 3 - border.color: "#383838" RowLayout { anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 - Row { - Layout.preferredWidth: 60 - spacing: 6 - Rectangle { - width: 10 - height: 10 - radius: 5 - color: model.color - anchors.verticalCenter: parent.verticalCenter + Text { text: "ID"; color: "#aaa"; font.bold: true; Layout.preferredWidth: 60 } + Text { text: "Priority"; color: "#aaa"; font.bold: true; Layout.preferredWidth: 80 } + Text { text: "State"; color: "#aaa"; font.bold: true; Layout.preferredWidth: 100 } + Text { text: "Remaining / Burst"; color: "#aaa"; font.bold: true; Layout.fillWidth: true } + } + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + model: guiController ? guiController.threadListModel : null + clip: true + spacing: 4 + + delegate: Rectangle { + width: ListView.view.width + height: 30 + color: "#252526" + radius: 3 + border.color: "#383838" + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 + + Row { + Layout.preferredWidth: 60 + spacing: 6 + Rectangle { + width: 10 + height: 10 + radius: 5 + color: model.color + anchors.verticalCenter: parent.verticalCenter + } + Text { + text: "T" + model.threadId + color: "#ffffff" + font.bold: true + anchors.verticalCenter: parent.verticalCenter + } } + Text { - text: "T" + model.threadId - color: "#ffffff" - font.bold: true - anchors.verticalCenter: parent.verticalCenter + text: model.priority + color: "#cccccc" + Layout.preferredWidth: 80 } - } - Text { - text: model.priority - color: "#cccccc" - Layout.preferredWidth: 80 - } + Rectangle { + Layout.preferredWidth: 90 + height: 20 + radius: 4 + color: { + if (model.state === "RUNNING") return "#2e7d32" + if (model.state === "READY") return "#0277bd" + if (model.state === "TERMINATED") return "#424242" + return "#616161" + } - Rectangle { - Layout.preferredWidth: 90 - height: 20 - radius: 4 - color: { - if (model.state === "RUNNING") return "#2e7d32" - if (model.state === "READY") return "#0277bd" - if (model.state === "TERMINATED") return "#424242" - return "#616161" + Text { + text: model.state + color: "#ffffff" + font.pixelSize: 11 + font.bold: true + anchors.centerIn: parent + } } Text { - text: model.state - color: "#ffffff" - font.pixelSize: 11 - font.bold: true - anchors.centerIn: parent + text: model.remainingTime + " / " + model.burstTime + " ticks" + color: "#cccccc" + Layout.fillWidth: true } } - - Text { - text: model.remainingTime + " / " + model.burstTime + " ticks" - color: "#cccccc" - Layout.fillWidth: true - } } } } } + + // Thread State Transition Diagram Panel + ThreadStateDiagram { + Layout.fillWidth: true + implicitHeight: 200 + threadModel: guiController ? guiController.threadListModel : null + } } }