From 7bc456f4b86dc5090e94414f1b2b20d91d6c56d6 Mon Sep 17 00:00:00 2001
From: Piotr Rozyczko
Date: Wed, 5 Nov 2025 14:38:06 +0100
Subject: [PATCH 01/26] updated python to 3.11 (#31)
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 17baea82..aae0d8f4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,7 +19,7 @@ classifiers = [
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX :: Linux'
]
-requires-python = '>=3.10'
+requires-python = '>=3.11'
dependencies = [
'PySide6>=6.8,<6.9' # Issue with TableView formatting in 6.9
]
From dfef95a881c7f517a8f18de160500750f7a9b8fc Mon Sep 17 00:00:00 2001
From: Piotr Rozyczko
Date: Thu, 6 Nov 2025 10:35:08 +0100
Subject: [PATCH 02/26] Unpinned pyside6 (#32)
* initital commit
* fixed the table alignment issue in PySide> 6.8
* replace custom attribute with class name check
---
pyproject.toml | 2 +-
src/EasyApp/Gui/Components/TableView.qml | 16 +++++++++++-----
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index aae0d8f4..7385333c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,7 +21,7 @@ classifiers = [
]
requires-python = '>=3.11'
dependencies = [
- 'PySide6>=6.8,<6.9' # Issue with TableView formatting in 6.9
+ 'PySide6'
]
[project.urls]
diff --git a/src/EasyApp/Gui/Components/TableView.qml b/src/EasyApp/Gui/Components/TableView.qml
index 87431e84..c3d538ec 100644
--- a/src/EasyApp/Gui/Components/TableView.qml
+++ b/src/EasyApp/Gui/Components/TableView.qml
@@ -127,11 +127,17 @@ ListView {
function setAllColumnsWidthAndAlignment() {
for (let item of contentItem.children) {
- if (item instanceof TableViewDelegate) {
- for (let columnIndex in item.children[0].children) {
- item.children[0].children[columnIndex].width = headerLabelItems[columnIndex].width
- if (typeof item.children[0].children[columnIndex].horizontalAlignment !== 'undefined') {
- item.children[0].children[columnIndex].horizontalAlignment = headerLabelItems[columnIndex].horizontalAlignment
+ // Check for TableViewDelegate using explicit property
+ if (item.toString().startsWith('TableViewDelegate_QMLTYPE')) {
+ const rowElement = item.children[0]
+ if (rowElement && rowElement.children) {
+ for (let columnIndex in rowElement.children) {
+ if (columnIndex < headerLabelItems.length) {
+ rowElement.children[columnIndex].width = headerLabelItems[columnIndex].width
+ if (typeof rowElement.children[columnIndex].horizontalAlignment !== 'undefined') {
+ rowElement.children[columnIndex].horizontalAlignment = headerLabelItems[columnIndex].horizontalAlignment
+ }
+ }
}
}
}
From 082e33a4882d92accf2aabcc320a38deec91716f Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Mon, 16 Mar 2026 12:07:16 +0100
Subject: [PATCH 03/26] Added a minor theme accent for selections
---
src/EasyApp/Gui/Style/Colors.qml | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/EasyApp/Gui/Style/Colors.qml b/src/EasyApp/Gui/Style/Colors.qml
index b826ecf1..2923e3f3 100644
--- a/src/EasyApp/Gui/Style/Colors.qml
+++ b/src/EasyApp/Gui/Style/Colors.qml
@@ -40,6 +40,7 @@ QtObject {
onIsDarkPaletteChanged: console.debug(`Is dark palette: ${isDarkPalette}`)
property color themeAccent: isDarkPalette ? "#4ec1ef": "#00a3e3"
+ property color themeAccentMinor: isDarkPalette ? "#4d9dbd" : "#8ad6ed"
property color themePrimary: isDarkPalette ? "#111" : "#bbb"
property color themeBackground: isDarkPalette ? "#303030" : "#e9e9e9"
From e98091939dbd8f9e38ecbc87b61498fda676765c Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Mon, 16 Mar 2026 12:15:42 +0100
Subject: [PATCH 04/26] Added a content width fix for DialogButtonBox in order
to show all 'standardButton's listed in Dialogs instead of just one
---
src/EasyApp/Gui/Elements/DialogButtonBox.qml | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/EasyApp/Gui/Elements/DialogButtonBox.qml b/src/EasyApp/Gui/Elements/DialogButtonBox.qml
index 84b56918..0b985c82 100644
--- a/src/EasyApp/Gui/Elements/DialogButtonBox.qml
+++ b/src/EasyApp/Gui/Elements/DialogButtonBox.qml
@@ -29,6 +29,7 @@ T.DialogButtonBox {
delegate: EaElements.Button { highlighted: true }
contentItem: ListView {
+ implicitWidth: contentWidth
model: control.contentModel
spacing: control.spacing
orientation: ListView.Horizontal
From 270bd5eef87451fc1536306724bbd7870c79b147 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Mon, 16 Mar 2026 16:42:37 +0100
Subject: [PATCH 05/26] Changed the behviour of TableViewTextInput to loose
focus on enter/return pressed
---
src/EasyApp/Gui/Components/TableViewTextInput.qml | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/EasyApp/Gui/Components/TableViewTextInput.qml b/src/EasyApp/Gui/Components/TableViewTextInput.qml
index fecc3d9e..902aaa89 100644
--- a/src/EasyApp/Gui/Components/TableViewTextInput.qml
+++ b/src/EasyApp/Gui/Components/TableViewTextInput.qml
@@ -8,4 +8,13 @@ EaElements.TextInput {
height: parent.height
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
+
+ Keys.onReturnPressed: {
+ accepted()
+ focus = false
+ }
+ Keys.onEnterPressed: {
+ accepted()
+ focus = false
+ }
}
From 0b5fec2cea5e425c1dfab79e74826060645cb2ec Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Tue, 17 Mar 2026 14:39:44 +0100
Subject: [PATCH 06/26] Moved the logic of toggling focus to TextField and
TextInput
---
src/EasyApp/Gui/Components/TableViewTextInput.qml | 9 ---------
src/EasyApp/Gui/Elements/TextField.qml | 10 ++++++++++
src/EasyApp/Gui/Elements/TextInput.qml | 10 ++++++++++
3 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/src/EasyApp/Gui/Components/TableViewTextInput.qml b/src/EasyApp/Gui/Components/TableViewTextInput.qml
index 902aaa89..fecc3d9e 100644
--- a/src/EasyApp/Gui/Components/TableViewTextInput.qml
+++ b/src/EasyApp/Gui/Components/TableViewTextInput.qml
@@ -8,13 +8,4 @@ EaElements.TextInput {
height: parent.height
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
-
- Keys.onReturnPressed: {
- accepted()
- focus = false
- }
- Keys.onEnterPressed: {
- accepted()
- focus = false
- }
}
diff --git a/src/EasyApp/Gui/Elements/TextField.qml b/src/EasyApp/Gui/Elements/TextField.qml
index bebbb9ba..c4d3adab 100644
--- a/src/EasyApp/Gui/Elements/TextField.qml
+++ b/src/EasyApp/Gui/Elements/TextField.qml
@@ -80,6 +80,16 @@ T.TextField {
Behavior on border.color { EaAnimations.ThemeChange {} }
}
+ // Visual feedback for the user that editing finish was accepted
+ Keys.onReturnPressed: {
+ accepted()
+ focus = false
+ }
+ Keys.onEnterPressed: {
+ accepted()
+ focus = false
+ }
+
//Mouse area to react on click events
MouseArea {
id: mouseArea
diff --git a/src/EasyApp/Gui/Elements/TextInput.qml b/src/EasyApp/Gui/Elements/TextInput.qml
index 0d8314e0..320e2a96 100644
--- a/src/EasyApp/Gui/Elements/TextInput.qml
+++ b/src/EasyApp/Gui/Elements/TextInput.qml
@@ -66,6 +66,16 @@ T.TextField {
color: 'transparent'
}
+ // Visual feedback for the user that editing finish was accepted
+ Keys.onReturnPressed: {
+ accepted()
+ focus = false
+ }
+ Keys.onEnterPressed: {
+ accepted()
+ focus = false
+ }
+
//Mouse area to react on click events
MouseArea {
id: mouseArea
From ce1086ee2da47136fcb808693526438bb1681ed1 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau <72985238+seventil@users.noreply.github.com>
Date: Mon, 23 Mar 2026 11:53:26 +0100
Subject: [PATCH 07/26] Fixed reference error in slider tooltip (#36)
---
src/EasyApp/Gui/Elements/Slider.qml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/EasyApp/Gui/Elements/Slider.qml b/src/EasyApp/Gui/Elements/Slider.qml
index 9eac32ed..9a180e15 100644
--- a/src/EasyApp/Gui/Elements/Slider.qml
+++ b/src/EasyApp/Gui/Elements/Slider.qml
@@ -31,7 +31,7 @@ T.Slider {
EaElements.ToolTip {
id: toolTip
- visible: slider.pressed || slider.hovered
+ visible: control.pressed || control.hovered
//text: EaLogic.Utils.toDefaultPrecision(control.value)
}
}
From b114e1e0d346479dae797f62ebf6a0f2ae2562a3 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Tue, 24 Mar 2026 12:05:03 +0100
Subject: [PATCH 08/26] fixes to include validation logic
---
src/EasyApp/Gui/Elements/TextField.qml | 16 +++++++++++-----
src/EasyApp/Gui/Elements/TextInput.qml | 16 +++++++++++-----
2 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/src/EasyApp/Gui/Elements/TextField.qml b/src/EasyApp/Gui/Elements/TextField.qml
index c4d3adab..9f62cbfc 100644
--- a/src/EasyApp/Gui/Elements/TextField.qml
+++ b/src/EasyApp/Gui/Elements/TextField.qml
@@ -81,15 +81,21 @@ T.TextField {
}
// Visual feedback for the user that editing finish was accepted
- Keys.onReturnPressed: {
- accepted()
- focus = false
- }
- Keys.onEnterPressed: {
+ function _commit(event) {
+ if (!acceptableInput) {
+ warned = true
+ event.accepted = true
+ return
+ }
+ warned = false
accepted()
focus = false
+ event.accepted = true
}
+ Keys.onReturnPressed: (event) => _commit(event)
+ Keys.onEnterPressed: (event) => _commit(event)
+
//Mouse area to react on click events
MouseArea {
id: mouseArea
diff --git a/src/EasyApp/Gui/Elements/TextInput.qml b/src/EasyApp/Gui/Elements/TextInput.qml
index 320e2a96..64c54fac 100644
--- a/src/EasyApp/Gui/Elements/TextInput.qml
+++ b/src/EasyApp/Gui/Elements/TextInput.qml
@@ -67,15 +67,21 @@ T.TextField {
}
// Visual feedback for the user that editing finish was accepted
- Keys.onReturnPressed: {
- accepted()
- focus = false
- }
- Keys.onEnterPressed: {
+ function _commit(event) {
+ if (!acceptableInput) {
+ warned = true
+ event.accepted = true
+ return
+ }
+ warned = false
accepted()
focus = false
+ event.accepted = true
}
+ Keys.onReturnPressed: (event) => _commit(event)
+ Keys.onEnterPressed: (event) => _commit(event)
+
//Mouse area to react on click events
MouseArea {
id: mouseArea
From a539122cbcd474fa8a0adcec63d90835c6bdea2a Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Tue, 24 Mar 2026 14:18:37 +0100
Subject: [PATCH 09/26] introduced quick fixes for the EaElements tableview
---
src/EasyApp/Gui/Components/TableView.qml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/EasyApp/Gui/Components/TableView.qml b/src/EasyApp/Gui/Components/TableView.qml
index c3d538ec..69262220 100644
--- a/src/EasyApp/Gui/Components/TableView.qml
+++ b/src/EasyApp/Gui/Components/TableView.qml
@@ -53,10 +53,14 @@ ListView {
// Empty content rows
//delegate: EaComponents.TableViewDelegate {}
+ // fixes an issue of clicks not registering right after scroll
+ pressDelay: 10
+
// Table border
Rectangle {
anchors.fill: listView
color: "transparent"
+ antialiasing: true
border.color: EaStyle.Colors.appBarComboBoxBorder
Behavior on border.color { EaAnimations.ThemeChange {} }
}
From e87f9c921571a8da1467fc40fecbafc9e2ef2101 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Tue, 24 Mar 2026 15:56:53 +0100
Subject: [PATCH 10/26] scrollbar and scrollindicator size and color updates
---
src/EasyApp/Gui/Elements/ScrollBar.qml | 22 +++++++++++++-------
src/EasyApp/Gui/Elements/ScrollIndicator.qml | 9 ++++++--
2 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/src/EasyApp/Gui/Elements/ScrollBar.qml b/src/EasyApp/Gui/Elements/ScrollBar.qml
index 1384ecda..8460a467 100644
--- a/src/EasyApp/Gui/Elements/ScrollBar.qml
+++ b/src/EasyApp/Gui/Elements/ScrollBar.qml
@@ -2,6 +2,8 @@ import QtQuick
import QtQuick.Templates as T
import QtQuick.Controls.Material
+import EasyApp.Gui.Style as EaStyle
+
T.ScrollBar {
id: control
@@ -10,25 +12,29 @@ T.ScrollBar {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
- padding: control.interactive ? 1 : 2
+
+ property int _padding: control.interactive ? 1 : 2
+ padding: _padding
+ topInset: parent.showHeader ? parent.tableRowHeight : 0
+ topPadding: parent.showHeader ? parent.tableRowHeight + _padding : 0
visible: control.policy !== T.ScrollBar.AlwaysOff
minimumSize: orientation === Qt.Horizontal ? height / width : width / height
contentItem: Rectangle {
- implicitWidth: control.interactive ? 13 : 4
- implicitHeight: control.interactive ? 13 : 4
+ implicitWidth: control.interactive ? 6 : 4
+ implicitHeight: control.interactive ? 6 : 4
color: control.pressed ?
- control.Material.scrollBarPressedColor :
+ EaStyle.Colors.themeAccent :
control.interactive && control.hovered ?
- control.Material.scrollBarHoveredColor :
- control.Material.scrollBarColor
+ EaStyle.Colors.themeForegroundMinor :
+ EaStyle.Colors.themeForegroundDisabled
opacity: 0.0
}
background: Rectangle {
- implicitWidth: control.interactive ? 16 : 4
- implicitHeight: control.interactive ? 16 : 4
+ implicitWidth: control.interactive ? 8 : 4
+ implicitHeight: control.interactive ? 8 : 4
color: "#0e000000"
opacity: 0.0
visible: control.interactive
diff --git a/src/EasyApp/Gui/Elements/ScrollIndicator.qml b/src/EasyApp/Gui/Elements/ScrollIndicator.qml
index 0e99a83f..8f8a767b 100644
--- a/src/EasyApp/Gui/Elements/ScrollIndicator.qml
+++ b/src/EasyApp/Gui/Elements/ScrollIndicator.qml
@@ -1,6 +1,8 @@
import QtQuick
import QtQuick.Templates as T
+import EasyApp.Gui.Style as EaStyle
+
T.ScrollIndicator {
id: control
@@ -9,13 +11,16 @@ T.ScrollIndicator {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
- padding: 2
+ property int _padding: 2
+ padding: _padding
+ topInset: parent.showHeader ? parent.tableRowHeight : 0
+ topPadding: parent.showHeader ? parent.tableRowHeight + _padding : 0
contentItem: Rectangle {
implicitWidth: 4
implicitHeight: 4
- ///color: control.Material.scrollBarColor
+ color: EaStyle.Colors.themeForegroundDisabled
visible: control.size < 1.0
opacity: 0.0
From e73f6ad6499aa0fca9ffdb05d00ca57fd9e37359 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Tue, 24 Mar 2026 16:17:25 +0100
Subject: [PATCH 11/26] added radius and a flag to snap to header
---
src/EasyApp/Gui/Elements/ScrollBar.qml | 15 ++++++++-------
src/EasyApp/Gui/Elements/ScrollIndicator.qml | 1 +
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/src/EasyApp/Gui/Elements/ScrollBar.qml b/src/EasyApp/Gui/Elements/ScrollBar.qml
index 8460a467..5fad1c09 100644
--- a/src/EasyApp/Gui/Elements/ScrollBar.qml
+++ b/src/EasyApp/Gui/Elements/ScrollBar.qml
@@ -12,17 +12,18 @@ T.ScrollBar {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
-
+ property bool snapToHeader: false
property int _padding: control.interactive ? 1 : 2
padding: _padding
- topInset: parent.showHeader ? parent.tableRowHeight : 0
- topPadding: parent.showHeader ? parent.tableRowHeight + _padding : 0
+ topInset: snapToHeader && parent.showHeader ? parent.tableRowHeight : 0
+ topPadding: snapToHeader && parent.showHeader ? parent.tableRowHeight + _padding : 0
visible: control.policy !== T.ScrollBar.AlwaysOff
minimumSize: orientation === Qt.Horizontal ? height / width : width / height
contentItem: Rectangle {
- implicitWidth: control.interactive ? 6 : 4
- implicitHeight: control.interactive ? 6 : 4
+ implicitWidth: 4
+ implicitHeight: 4
+ radius: width / 4
color: control.pressed ?
EaStyle.Colors.themeAccent :
@@ -33,8 +34,8 @@ T.ScrollBar {
}
background: Rectangle {
- implicitWidth: control.interactive ? 8 : 4
- implicitHeight: control.interactive ? 8 : 4
+ implicitWidth: control.interactive ? 7 : 4
+ implicitHeight: control.interactive ? 7 : 4
color: "#0e000000"
opacity: 0.0
visible: control.interactive
diff --git a/src/EasyApp/Gui/Elements/ScrollIndicator.qml b/src/EasyApp/Gui/Elements/ScrollIndicator.qml
index 8f8a767b..321662f0 100644
--- a/src/EasyApp/Gui/Elements/ScrollIndicator.qml
+++ b/src/EasyApp/Gui/Elements/ScrollIndicator.qml
@@ -19,6 +19,7 @@ T.ScrollIndicator {
contentItem: Rectangle {
implicitWidth: 4
implicitHeight: 4
+ radius: width / 4
color: EaStyle.Colors.themeForegroundDisabled
visible: control.size < 1.0
From 5333641d6422ed8dfbd4bb0bea690a675e863392 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Thu, 9 Apr 2026 14:25:40 +0200
Subject: [PATCH 12/26] Added expanding width for scrollbar on hover
---
src/EasyApp/Gui/Elements/ScrollBar.qml | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/EasyApp/Gui/Elements/ScrollBar.qml b/src/EasyApp/Gui/Elements/ScrollBar.qml
index 5fad1c09..8e9f4b5d 100644
--- a/src/EasyApp/Gui/Elements/ScrollBar.qml
+++ b/src/EasyApp/Gui/Elements/ScrollBar.qml
@@ -21,8 +21,8 @@ T.ScrollBar {
minimumSize: orientation === Qt.Horizontal ? height / width : width / height
contentItem: Rectangle {
- implicitWidth: 4
- implicitHeight: 4
+ implicitWidth: control.hovered ? 8 : 4
+ implicitHeight: control.hovered ? 8 : 4
radius: width / 4
color: control.pressed ?
@@ -31,14 +31,28 @@ T.ScrollBar {
EaStyle.Colors.themeForegroundMinor :
EaStyle.Colors.themeForegroundDisabled
opacity: 0.0
+
+ Behavior on implicitWidth {
+ NumberAnimation { duration: 150; easing.type: Easing.InOutQuad }
+ }
+ Behavior on implicitHeight {
+ NumberAnimation { duration: 150; easing.type: Easing.InOutQuad }
+ }
}
background: Rectangle {
- implicitWidth: control.interactive ? 7 : 4
- implicitHeight: control.interactive ? 7 : 4
+ implicitWidth: control.hovered ? 14 : (control.interactive ? 7 : 4)
+ implicitHeight: control.hovered ? 14 : (control.interactive ? 7 : 4)
color: "#0e000000"
opacity: 0.0
visible: control.interactive
+
+ Behavior on implicitWidth {
+ NumberAnimation { duration: 150; easing.type: Easing.InOutQuad }
+ }
+ Behavior on implicitHeight {
+ NumberAnimation { duration: 150; easing.type: Easing.InOutQuad }
+ }
}
states: State {
From bc42ba240d80919c3dd6465393b04403b6c46865 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Thu, 9 Apr 2026 14:37:11 +0200
Subject: [PATCH 13/26] Added a width fix on pressed as well, cleaned up a bit
---
src/EasyApp/Gui/Elements/ScrollBar.qml | 13 ++-----------
1 file changed, 2 insertions(+), 11 deletions(-)
diff --git a/src/EasyApp/Gui/Elements/ScrollBar.qml b/src/EasyApp/Gui/Elements/ScrollBar.qml
index 8e9f4b5d..f785aa22 100644
--- a/src/EasyApp/Gui/Elements/ScrollBar.qml
+++ b/src/EasyApp/Gui/Elements/ScrollBar.qml
@@ -21,8 +21,6 @@ T.ScrollBar {
minimumSize: orientation === Qt.Horizontal ? height / width : width / height
contentItem: Rectangle {
- implicitWidth: control.hovered ? 8 : 4
- implicitHeight: control.hovered ? 8 : 4
radius: width / 4
color: control.pressed ?
@@ -31,18 +29,11 @@ T.ScrollBar {
EaStyle.Colors.themeForegroundMinor :
EaStyle.Colors.themeForegroundDisabled
opacity: 0.0
-
- Behavior on implicitWidth {
- NumberAnimation { duration: 150; easing.type: Easing.InOutQuad }
- }
- Behavior on implicitHeight {
- NumberAnimation { duration: 150; easing.type: Easing.InOutQuad }
- }
}
background: Rectangle {
- implicitWidth: control.hovered ? 14 : (control.interactive ? 7 : 4)
- implicitHeight: control.hovered ? 14 : (control.interactive ? 7 : 4)
+ implicitWidth: (control.hovered || control.pressed) ? 12 : (control.interactive ? 7 : 4)
+ implicitHeight: (control.hovered || control.pressed) ? 12 : (control.interactive ? 7 : 4)
color: "#0e000000"
opacity: 0.0
visible: control.interactive
From 1d9d2e55267a02f76a1545344ebea0f3e2bd3d05 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Fri, 10 Apr 2026 14:47:14 +0200
Subject: [PATCH 14/26] Replaced defocus with a color flash
---
src/EasyApp/Gui/Elements/TextField.qml | 27 +++++++++++++-------------
src/EasyApp/Gui/Elements/TextInput.qml | 27 +++++++++++++-------------
2 files changed, 26 insertions(+), 28 deletions(-)
diff --git a/src/EasyApp/Gui/Elements/TextField.qml b/src/EasyApp/Gui/Elements/TextField.qml
index 9f62cbfc..ddebe6e1 100644
--- a/src/EasyApp/Gui/Elements/TextField.qml
+++ b/src/EasyApp/Gui/Elements/TextField.qml
@@ -11,6 +11,7 @@ T.TextField {
id: control
property bool warned: false
+ property bool enterFlash: false
implicitWidth: implicitBackgroundWidth + leftInset + rightInset
|| Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding
@@ -31,7 +32,9 @@ T.TextField {
font.pixelSize: EaStyle.Sizes.fontPixelSize
//font.bold: control.activeFocus ? true : false
- color: warned ?
+ color: enterFlash ?
+ EaStyle.Colors.themeForeground :
+ warned ?
EaStyle.Colors.red :
!enabled ?
EaStyle.Colors.themeForegroundDisabled :
@@ -80,21 +83,17 @@ T.TextField {
Behavior on border.color { EaAnimations.ThemeChange {} }
}
- // Visual feedback for the user that editing finish was accepted
- function _commit(event) {
- if (!acceptableInput) {
- warned = true
- event.accepted = true
- return
- }
- warned = false
- accepted()
- focus = false
- event.accepted = true
+ onAccepted: {
+ control.enterFlash = true
+ enterFlashTimer.start()
}
- Keys.onReturnPressed: (event) => _commit(event)
- Keys.onEnterPressed: (event) => _commit(event)
+ // Visual feedback for the user that editing finish was accepted
+ Timer {
+ id: enterFlashTimer
+ interval: 180
+ onTriggered: control.enterFlash = false
+ }
//Mouse area to react on click events
MouseArea {
diff --git a/src/EasyApp/Gui/Elements/TextInput.qml b/src/EasyApp/Gui/Elements/TextInput.qml
index 64c54fac..54a91ebf 100644
--- a/src/EasyApp/Gui/Elements/TextInput.qml
+++ b/src/EasyApp/Gui/Elements/TextInput.qml
@@ -14,6 +14,7 @@ T.TextField {
property bool warned: false
property bool selected: false
property bool minored: false
+ property bool enterFlash: false
implicitWidth: implicitBackgroundWidth + leftInset + rightInset ||
Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding
@@ -27,7 +28,9 @@ T.TextField {
font.pixelSize: EaStyle.Sizes.fontPixelSize
font.bold: control.activeFocus ? true : false
- color: warned ?
+ color: enterFlash ?
+ EaStyle.Colors.themeForeground :
+ warned ?
EaStyle.Colors.red :
!enabled || readOnly || minored ?
EaStyle.Colors.themeForegroundMinor :
@@ -66,21 +69,17 @@ T.TextField {
color: 'transparent'
}
- // Visual feedback for the user that editing finish was accepted
- function _commit(event) {
- if (!acceptableInput) {
- warned = true
- event.accepted = true
- return
- }
- warned = false
- accepted()
- focus = false
- event.accepted = true
+ onAccepted: {
+ control.enterFlash = true
+ enterFlashTimer.start()
}
- Keys.onReturnPressed: (event) => _commit(event)
- Keys.onEnterPressed: (event) => _commit(event)
+ // Visual feedback for the user that editing finish was accepted
+ Timer {
+ id: enterFlashTimer
+ interval: 180
+ onTriggered: control.enterFlash = false
+ }
//Mouse area to react on click events
MouseArea {
From bff51a8632f331086025b843416d90973a935c87 Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Fri, 10 Apr 2026 15:19:02 +0200
Subject: [PATCH 15/26] removed parent-coupled header tracking in ScrollBar
---
src/EasyApp/Gui/Elements/ScrollBar.qml | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/src/EasyApp/Gui/Elements/ScrollBar.qml b/src/EasyApp/Gui/Elements/ScrollBar.qml
index f785aa22..0520f2ad 100644
--- a/src/EasyApp/Gui/Elements/ScrollBar.qml
+++ b/src/EasyApp/Gui/Elements/ScrollBar.qml
@@ -12,16 +12,11 @@ T.ScrollBar {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
- property bool snapToHeader: false
- property int _padding: control.interactive ? 1 : 2
- padding: _padding
- topInset: snapToHeader && parent.showHeader ? parent.tableRowHeight : 0
- topPadding: snapToHeader && parent.showHeader ? parent.tableRowHeight + _padding : 0
+ padding: control.interactive ? 1 : 2
visible: control.policy !== T.ScrollBar.AlwaysOff
minimumSize: orientation === Qt.Horizontal ? height / width : width / height
contentItem: Rectangle {
- radius: width / 4
color: control.pressed ?
EaStyle.Colors.themeAccent :
From 564ede4d76d6fef562161204ad433046bec5866d Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Fri, 10 Apr 2026 15:20:12 +0200
Subject: [PATCH 16/26] removed parent-coupled header tracking in
ScrollIndicator
---
src/EasyApp/Gui/Elements/ScrollIndicator.qml | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/EasyApp/Gui/Elements/ScrollIndicator.qml b/src/EasyApp/Gui/Elements/ScrollIndicator.qml
index 321662f0..d7218624 100644
--- a/src/EasyApp/Gui/Elements/ScrollIndicator.qml
+++ b/src/EasyApp/Gui/Elements/ScrollIndicator.qml
@@ -11,15 +11,11 @@ T.ScrollIndicator {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
- property int _padding: 2
- padding: _padding
- topInset: parent.showHeader ? parent.tableRowHeight : 0
- topPadding: parent.showHeader ? parent.tableRowHeight + _padding : 0
+ padding: control.interactive ? 1 : 2
contentItem: Rectangle {
implicitWidth: 4
implicitHeight: 4
- radius: width / 4
color: EaStyle.Colors.themeForegroundDisabled
visible: control.size < 1.0
From 1e578558682d587e6c5a0823df75bf14305dd77e Mon Sep 17 00:00:00 2001
From: Ales Kutsepau
Date: Fri, 10 Apr 2026 15:23:02 +0200
Subject: [PATCH 17/26] changed indicator padding to just 2 as it is not
interactive anyway
---
src/EasyApp/Gui/Elements/ScrollIndicator.qml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/EasyApp/Gui/Elements/ScrollIndicator.qml b/src/EasyApp/Gui/Elements/ScrollIndicator.qml
index d7218624..0f10c22c 100644
--- a/src/EasyApp/Gui/Elements/ScrollIndicator.qml
+++ b/src/EasyApp/Gui/Elements/ScrollIndicator.qml
@@ -11,7 +11,7 @@ T.ScrollIndicator {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
- padding: control.interactive ? 1 : 2
+ padding: 2
contentItem: Rectangle {
implicitWidth: 4
From d6796a6945f0b34bdd7b3c1ac94b37162b99e3d5 Mon Sep 17 00:00:00 2001
From: Andrew Sazonov
Date: Fri, 17 Apr 2026 12:51:51 +0200
Subject: [PATCH 18/26] Add QtGraphs-based analysis page to AdvancedPy example
(#39)
* Add qtgraphs example
* Remove unused file
* Add analysis QML files to AdvancedPy.pyproject
* Fix point count and X range in mock analysis data generator
* Validate analysis point count input before updating backend
* Rename group on analysis page
* Update comments regarding methods on the analysis page
* Replace TextEdit with non-editable Text for axis labels
* Get rid of memoryview
* Add exception when generating new data
---
examples/AdvancedPy/src/AdvancedPy.pyproject | 6 +
.../src/AdvancedPy/Backends/MockBackend.qml | 3 +-
.../AdvancedPy/Backends/MockQml/Analysis.qml | 48 +
.../src/AdvancedPy/Backends/MockQml/qmldir | 1 +
.../src/AdvancedPy/Backends/real_backend.py | 6 +
.../AdvancedPy/Backends/real_py/analysis.py | 132 +++
.../src/AdvancedPy/Gui/ApplicationWindow.qml | 14 +
.../AdvancedPy/Gui/Globals/BackendWrapper.qml | 20 +-
.../src/AdvancedPy/Gui/Globals/References.qml | 15 +
.../AdvancedPy/Gui/Pages/Analysis/Layout.qml | 49 +
.../Gui/Pages/Analysis/MainArea/Chart.qml | 161 +++
.../Sidebar/Basic/Groups/GenerateData.qml | 60 ++
.../Pages/Analysis/Sidebar/Basic/Layout.qml | 22 +
.../AdvancedPy/Gui/Pages/Project/Layout.qml | 4 +-
pixi.lock | 968 ++++++++++++++++++
pixi.toml | 64 ++
src/EasyApp/Gui/Elements/Slider.qml | 2 +-
17 files changed, 1569 insertions(+), 6 deletions(-)
create mode 100644 examples/AdvancedPy/src/AdvancedPy/Backends/MockQml/Analysis.qml
create mode 100644 examples/AdvancedPy/src/AdvancedPy/Backends/real_py/analysis.py
create mode 100644 examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Layout.qml
create mode 100644 examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/MainArea/Chart.qml
create mode 100644 examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Groups/GenerateData.qml
create mode 100644 examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml
create mode 100644 pixi.lock
create mode 100644 pixi.toml
diff --git a/examples/AdvancedPy/src/AdvancedPy.pyproject b/examples/AdvancedPy/src/AdvancedPy.pyproject
index b0ef8565..73922d3d 100644
--- a/examples/AdvancedPy/src/AdvancedPy.pyproject
+++ b/examples/AdvancedPy/src/AdvancedPy.pyproject
@@ -12,6 +12,10 @@
"AdvancedPy/Gui/Globals/References.qml",
"AdvancedPy/Gui/Pages/Home/Content.qml",
"AdvancedPy/Gui/Pages/Home/Popups/About.qml",
+ "AdvancedPy/Gui/Pages/Analysis/Layout.qml",
+ "AdvancedPy/Gui/Pages/Analysis/MainArea/Chart.qml",
+ "AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml",
+ "AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Groups/GenerateData.qml",
"AdvancedPy/Gui/Pages/Project/Layout.qml",
"AdvancedPy/Gui/Pages/Project/MainArea/Description.qml",
"AdvancedPy/Gui/Pages/Project/Sidebar/Basic/Layout.qml",
@@ -31,12 +35,14 @@
"AdvancedPy/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml",
"AdvancedPy/Backends/qmldir",
"AdvancedPy/Backends/MockBackend.qml",
+ "AdvancedPy/Backends/MockQml/Analysis.qml",
"AdvancedPy/Backends/MockQml/Project.qml",
"AdvancedPy/Backends/MockQml/Report.qml",
"AdvancedPy/Backends/MockQml/Status.qml",
"AdvancedPy/Backends/MockQml/qmldir",
"AdvancedPy/Backends/real_backend.py",
"AdvancedPy/Backends/real_py/project.py",
+ "AdvancedPy/Backends/real_py/analysis.py",
"AdvancedPy/Backends/real_py/report.py",
"AdvancedPy/Backends/real_py/status.py",
"AdvancedPy/Backends/real_py/logic/helpers.py"
diff --git a/examples/AdvancedPy/src/AdvancedPy/Backends/MockBackend.qml b/examples/AdvancedPy/src/AdvancedPy/Backends/MockBackend.qml
index 44da4805..f9110280 100644
--- a/examples/AdvancedPy/src/AdvancedPy/Backends/MockBackend.qml
+++ b/examples/AdvancedPy/src/AdvancedPy/Backends/MockBackend.qml
@@ -12,9 +12,8 @@ import Backends.MockQml as MockLogic
QtObject {
property var project: MockLogic.Project
+ property var analysis: MockLogic.Analysis
property var status: MockLogic.Status
property var report: MockLogic.Report
}
-
-
diff --git a/examples/AdvancedPy/src/AdvancedPy/Backends/MockQml/Analysis.qml b/examples/AdvancedPy/src/AdvancedPy/Backends/MockQml/Analysis.qml
new file mode 100644
index 00000000..44ea3793
--- /dev/null
+++ b/examples/AdvancedPy/src/AdvancedPy/Backends/MockQml/Analysis.qml
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: 2024 EasyApp contributors
+// SPDX-License-Identifier: BSD-3-Clause
+// © 2024 Contributors to the EasyApp project
+
+pragma Singleton
+
+import QtQuick
+import QtGraphs
+
+import Gui.Globals as Globals
+
+
+QtObject {
+
+ property int dataSize: 50
+ property var axesRanges: {
+ "xmin": 0.0,
+ "xmax": 180.0,
+ "ymin": 0.0,
+ "ymax": 100.0,
+ }
+
+ signal dataPointsChanged(var points)
+
+ function generateData() {
+ console.debug(`* Generating ${dataSize} data points...`)
+ const xmin = axesRanges.xmin
+ const xmax = axesRanges.xmax
+ const ymin = axesRanges.ymin
+ const ymax = axesRanges.ymax
+
+ const pointCount = Math.max(1, dataSize)
+ const stepSize = pointCount > 1 ? (xmax - xmin) / (pointCount - 1) : 0
+
+ let dataPoints = []
+ for (let i = 0; i < pointCount; i++) {
+ const x = xmin + i * stepSize
+ const y = ymin + Math.random() * (ymax - ymin)
+ dataPoints.push(Qt.point(x, y))
+ }
+ console.debug(" Data generation completed.")
+
+ console.debug(`* Sending ${pointCount} data points to series...`)
+ dataPointsChanged(dataPoints)
+ console.debug(" Data update signal emitted.")
+ }
+
+}
diff --git a/examples/AdvancedPy/src/AdvancedPy/Backends/MockQml/qmldir b/examples/AdvancedPy/src/AdvancedPy/Backends/MockQml/qmldir
index 7fb05624..7ee148eb 100644
--- a/examples/AdvancedPy/src/AdvancedPy/Backends/MockQml/qmldir
+++ b/examples/AdvancedPy/src/AdvancedPy/Backends/MockQml/qmldir
@@ -1,5 +1,6 @@
module MockQml
singleton Project Project.qml
+singleton Analysis Analysis.qml
singleton Report Report.qml
singleton Status Status.qml
diff --git a/examples/AdvancedPy/src/AdvancedPy/Backends/real_backend.py b/examples/AdvancedPy/src/AdvancedPy/Backends/real_backend.py
index ad3c2cea..4cb2ac7f 100644
--- a/examples/AdvancedPy/src/AdvancedPy/Backends/real_backend.py
+++ b/examples/AdvancedPy/src/AdvancedPy/Backends/real_backend.py
@@ -7,6 +7,7 @@
from EasyApp.Logic.Logging import LoggerLevelHandler
from .real_py.project import Project
+from .real_py.analysis import Analysis
from .real_py.status import Status
from .real_py.report import Report
@@ -21,6 +22,7 @@ def __init__(self):
# Individual Backend objects
self._project = Project()
+ self._analysis = Analysis()
self._status = Status()
self._report = Report()
@@ -47,6 +49,10 @@ def __init__(self):
def project(self):
return self._project
+ @Property('QVariant', constant=True)
+ def analysis(self):
+ return self._analysis
+
@Property('QVariant', constant=True)
def status(self):
return self._status
diff --git a/examples/AdvancedPy/src/AdvancedPy/Backends/real_py/analysis.py b/examples/AdvancedPy/src/AdvancedPy/Backends/real_py/analysis.py
new file mode 100644
index 00000000..d966a754
--- /dev/null
+++ b/examples/AdvancedPy/src/AdvancedPy/Backends/real_py/analysis.py
@@ -0,0 +1,132 @@
+# SPDX-FileCopyrightText: 2024 EasyApp contributors
+# SPDX-License-Identifier: BSD-3-Clause
+# © 2024 Contributors to the EasyApp project
+
+import numpy as np
+from PySide6.QtCore import QObject, Signal, Slot, Property, QPointF
+
+from EasyApp.Logic.Logging import console
+
+
+class Analysis(QObject):
+ """
+ Backend object that generates synthetic diffraction-like data
+ and exposes it to QML as a list of QPointF values plus axis ranges.
+ """
+
+ # Signals
+ dataSizeChanged = Signal()
+ dataPointsChanged = Signal("QVariantList") # Emitted with list
+ axesRangesChanged = Signal() # Emitted when range dict updates
+
+ def __init__(self):
+ super().__init__()
+
+ self._dataSize = 10000
+ self._axesRanges = {
+ "xmin": 0.0,
+ "xmax": 180.0,
+ "ymin": 0.0,
+ "ymax": 100.0,
+ }
+
+ # ------------------------------------------------------------------
+ # QML-accessible Properties
+ # ------------------------------------------------------------------
+
+ @Property(int, notify=dataSizeChanged)
+ def dataSize(self):
+ """Number of X/Y data points to generate."""
+ return self._dataSize
+
+ @dataSize.setter
+ def dataSize(self, value):
+ value = int(value)
+ if self._dataSize == value:
+ return
+ self._dataSize = value
+ self.dataSizeChanged.emit()
+
+ @Property("QVariantMap", notify=axesRangesChanged)
+ def axesRanges(self):
+ """
+ Axis ranges used by the graph:
+ { "xmin": float, "xmax": float, "ymin": float, "ymax": float }
+ Access in QML using: axisX.min: analysis.axesRanges["xmin"]
+ """
+ return self._axesRanges
+
+ # ------------------------------------------------------------------
+ # Public Slot Called from QML
+ # ------------------------------------------------------------------
+
+ @Slot()
+ def generateData(self):
+ """Generate new synthetic data and notify QML."""
+ console.debug(f"* Generating {self.dataSize} data points...")
+ try:
+ x, y = self._generate_data(n_points=self.dataSize)
+ console.debug(" Data generation completed.")
+
+ console.debug(f"* Converting and sending {self.dataSize} data points to series...")
+ self.dataPointsChanged.emit(self._ndarrays_to_qpoints(x, y))
+ console.debug(" Data update signal emitted.")
+
+ self._updateAxesRanges(x.min(), x.max(), y.min(), y.max())
+ except Exception as exception:
+ console.error(f"Failed to generate analysis data: {exception}")
+
+ # ------------------------------------------------------------------
+ # Internal Helpers
+ # ------------------------------------------------------------------
+
+ def _updateAxesRanges(self, xmin, xmax, ymin, ymax):
+ """Store axis ranges and notify QML."""
+ vmargin = 10.0
+ self._axesRanges["xmin"] = float(xmin)
+ self._axesRanges["xmax"] = float(xmax)
+ self._axesRanges["ymin"] = max(0, float(ymin) - vmargin)
+ self._axesRanges["ymax"] = float(ymax) + vmargin
+ self.axesRangesChanged.emit()
+
+ @staticmethod
+ def _ndarrays_to_qpoints(x: np.ndarray, y: np.ndarray):
+ """
+ Convert NumPy X/Y arrays to list[QPointF].
+ """
+ return [QPointF(xi, yi) for xi, yi in zip(x, y, strict=True)]
+
+ @staticmethod
+ def _generate_data(
+ n_points=2000,
+ n_peaks=100,
+ x_range=(0.0, 180.0),
+ intensity_range=(0, 100),
+ width_range=(0.05, 0.5),
+ noise_level=1.0,
+ background=20.0,
+ ):
+ """
+ Generate synthetic diffraction-like pattern from sum of random Gaussians.
+ Returns (x, y) NumPy arrays.
+ """
+ # Sample x grid
+ x = np.linspace(*x_range, n_points)
+ y = np.zeros_like(x)
+
+ # Random peak positions, intensities, widths
+ positions = np.random.uniform(*x_range, n_peaks)
+ amplitudes = np.random.uniform(*intensity_range, n_peaks)
+ widths = np.random.uniform(*width_range, n_peaks)
+
+ # Gaussian peak contributions
+ for pos, amp, width in zip(positions, amplitudes, widths):
+ y += amp * np.exp(-0.5 * ((x - pos) / width) ** 2)
+
+ # Noise
+ y += np.random.normal(scale=noise_level, size=n_points)
+
+ # Background
+ y += background
+
+ return x, y
diff --git a/examples/AdvancedPy/src/AdvancedPy/Gui/ApplicationWindow.qml b/examples/AdvancedPy/src/AdvancedPy/Gui/ApplicationWindow.qml
index 835e886f..2073b0fa 100644
--- a/examples/AdvancedPy/src/AdvancedPy/Gui/ApplicationWindow.qml
+++ b/examples/AdvancedPy/src/AdvancedPy/Gui/ApplicationWindow.qml
@@ -71,6 +71,19 @@ EaComponents.ApplicationWindow {
},
// Project page
+ // Analysis page
+ EaElements.AppBarTabButton {
+ id: analysisButton
+ enabled: false
+ fontIcon: 'microscope'
+ text: qsTr('Analysis')
+ ToolTip.text: qsTr('Calculation and fitting page')
+ Component.onCompleted: {
+ Globals.References.applicationWindow.appBarCentralTabs.analysisButton = analysisButton
+ }
+ },
+ // Analysis page
+
// Summary page
EaElements.AppBarTabButton {
id: summaryButton
@@ -93,6 +106,7 @@ EaComponents.ApplicationWindow {
contentArea: [
Loader { source: 'Pages/Home/Content.qml' },
Loader { source: 'Pages/Project/Layout.qml' },
+ Loader { source: 'Pages/Analysis/Layout.qml' },
Loader { source: 'Pages/Report/Layout.qml' }
]
diff --git a/examples/AdvancedPy/src/AdvancedPy/Gui/Globals/BackendWrapper.qml b/examples/AdvancedPy/src/AdvancedPy/Gui/Globals/BackendWrapper.qml
index c0d36666..19acbd7c 100644
--- a/examples/AdvancedPy/src/AdvancedPy/Gui/Globals/BackendWrapper.qml
+++ b/examples/AdvancedPy/src/AdvancedPy/Gui/Globals/BackendWrapper.qml
@@ -7,7 +7,7 @@ pragma Singleton
import QtQuick
// This module is registered in the main.py file and allows access to the properties
-// and backend methods of the singleton object of the ‘PyBackend’ class.
+// and backend methods of the singleton object of the ‘PyBackend’ class.
// If ‘PyBackend’ is not defined, then 'MockBackend' from directory 'Backends' is used.
// It is needed to run the GUI frontend via the qml runtime tool without any Python backend.
import Backends as Backends
@@ -56,6 +56,24 @@ QtObject {
function projectSave() { activeBackend.project.save() }
function projectEditInfo(path, new_value) { activeBackend.project.editInfo(path, new_value) }
+ ////////////////
+ // Analysis page
+ ////////////////
+
+ // All properties and methods related to the analysis page, unlike other pages,
+ // are accessed directly via the `activeBackend` object, without an intermediate
+ // wrapper in this file.
+ //
+ // They are defined in the following files:
+ // - Backends/MockQml/Analysis.qml
+ // - Backends/real_py/analysis.py
+ //
+ // This approach is used to reduce duplication of objects between the backend
+ // and frontend, and to minimize the number of automatically generated signals/slots.
+ //
+ // TODO: Profile performance and memory usage, and decide whether to use this
+ // direct access approach or keep a wrapper layer.
+
///////////////
// Summary page
///////////////
diff --git a/examples/AdvancedPy/src/AdvancedPy/Gui/Globals/References.qml b/examples/AdvancedPy/src/AdvancedPy/Gui/Globals/References.qml
index 0d9d7812..fa19e85c 100644
--- a/examples/AdvancedPy/src/AdvancedPy/Gui/Globals/References.qml
+++ b/examples/AdvancedPy/src/AdvancedPy/Gui/Globals/References.qml
@@ -17,6 +17,7 @@ QtObject {
'appBarCentralTabs': {
'homeButton': null,
'projectButton': null,
+ 'analysisButton': null,
'summaryButton': null,
}
}
@@ -31,6 +32,20 @@ QtObject {
}
}
}
+ },
+ 'analysis': {
+ 'sidebar': {
+ 'basic': {
+ 'slider': null
+ }
+ },
+ 'mainarea': {
+ 'description': {
+ 'graph': {
+ 'lineseries': null
+ }
+ }
+ }
}
}
diff --git a/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Layout.qml b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Layout.qml
new file mode 100644
index 00000000..4c9d5468
--- /dev/null
+++ b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Layout.qml
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2024 EasyApp contributors
+// SPDX-License-Identifier: BSD-3-Clause
+// © 2024 Contributors to the EasyApp project
+
+import QtQuick
+import QtQuick.Controls
+
+import EasyApp.Gui.Style as EaStyle
+import EasyApp.Gui.Globals as EaGlobals
+import EasyApp.Gui.Elements as EaElements
+import EasyApp.Gui.Components as EaComponents
+
+import Gui.Globals as Globals
+
+
+EaComponents.ContentPage {
+
+ mainView: EaComponents.MainContent {
+ tabs: [
+ EaElements.TabButton { text: qsTr('Chart') }
+ ]
+
+ items: [
+ Loader { source: 'MainArea/Chart.qml' }
+ ]
+ }
+
+ sideBar: EaComponents.SideBar {
+ tabs: [
+ EaElements.TabButton { text: qsTr('Basic controls') }
+ ]
+
+ items: [
+ Loader { source: 'Sidebar/Basic/Layout.qml' }
+ ]
+
+ continueButton.text: qsTr('Continue')
+
+ continueButton.onClicked: {
+ console.debug(`Clicking '${continueButton.text}' button ::: ${this}`)
+ Globals.References.applicationWindow.appBarCentralTabs.summaryButton.enabled = true
+ Globals.References.applicationWindow.appBarCentralTabs.summaryButton.toggle()
+ }
+ }
+
+ Component.onCompleted: console.debug(`Analysis page loaded ::: ${this}`)
+ Component.onDestruction: console.debug(`Analysis page destroyed ::: ${this}`)
+
+}
diff --git a/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/MainArea/Chart.qml b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/MainArea/Chart.qml
new file mode 100644
index 00000000..3afca366
--- /dev/null
+++ b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/MainArea/Chart.qml
@@ -0,0 +1,161 @@
+// SPDX-FileCopyrightText: 2024 EasyApp contributors
+// SPDX-License-Identifier: BSD-3-Clause
+// © 2024 Contributors to the EasyApp project
+
+import QtQuick
+import QtGraphs
+
+import EasyApp.Gui.Style as EaStyle
+import EasyApp.Gui.Elements as EaElements
+
+import Gui.Globals as Globals
+
+
+Rectangle {
+
+ // graph
+ GraphsView {
+
+ id: graph
+ anchors.fill: parent
+
+ marginTop: EaStyle.Sizes.fontPixelSize * 2
+ marginBottom: EaStyle.Sizes.fontPixelSize * 2
+ marginLeft: EaStyle.Sizes.fontPixelSize
+ marginRight: EaStyle.Sizes.fontPixelSize * 2
+
+ zoomAreaEnabled: true
+
+ // theme
+ theme: GraphsTheme {
+ backgroundColor: EaStyle.Colors.chartBackground
+ plotAreaBackgroundColor: EaStyle.Colors.chartBackground
+
+ axisX.mainColor: EaStyle.Colors.chartGridLine
+ axisX.mainWidth: 0
+ //axisX.labelTextColor: EaStyle.Colors.chartLabels
+
+ axisY.mainColor: EaStyle.Colors.chartGridLine
+ axisY.mainWidth: 0
+ //axisY.labelTextColor: EaStyle.Colors.chartLabels
+
+ gridVisible: true
+ grid.mainWidth: 1
+ grid.subWidth: 0
+ grid.mainColor: EaStyle.Colors.chartGridLine
+ grid.subColor: EaStyle.Colors.chartMinorGridLine
+
+ labelFont.family: EaStyle.Fonts.fontFamily
+ labelFont.pixelSize: EaStyle.Sizes.fontPixelSize
+ labelTextColor: EaStyle.Colors.chartLabels
+ }
+ // theme
+
+ // axisX
+ axisX: ValueAxis {
+ //visible: true
+ //gridVisible: false
+ //subGridVisible: true
+ //labelsVisible: true
+ //subTickCount: 1
+
+ labelDelegate: Text {
+ horizontalAlignment: TextInput.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ bottomPadding: EaStyle.Sizes.fontPixelSize
+ color: EaStyle.Colors.chartLabels
+ }
+
+
+ titleText: 'x'
+ min: Globals.BackendWrapper.activeBackend.analysis.axesRanges["xmin"]
+ max: Globals.BackendWrapper.activeBackend.analysis.axesRanges["xmax"]
+ }
+ // axisX
+
+ // axisY
+ axisY: ValueAxis {
+ //visible: true
+ //gridVisible: false
+ //subGridVisible: true
+ //labelsVisible: true
+ //subTickCount: 1
+
+ labelDelegate: Text {
+ horizontalAlignment: TextInput.AlignRight
+ verticalAlignment: Text.AlignVCenter
+ rightPadding: -EaStyle.Sizes.fontPixelSize
+ color: EaStyle.Colors.chartLabels
+ }
+
+
+ titleText: 'y'
+ min: Globals.BackendWrapper.activeBackend.analysis.axesRanges["ymin"]
+ max: Globals.BackendWrapper.activeBackend.analysis.axesRanges["ymax"]
+ }
+ // axisY
+
+ // areaSeries
+ AreaSeries {
+
+ borderWidth: EaStyle.Sizes.measuredLineWidth
+ borderColor: EaStyle.Colors.chartForegrounds[0]
+ color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.15)
+
+ upperSeries: LineSeries {
+ id: upperSeries
+
+ // Default points for Live Mode in Design
+ XYPoint { x: 0; y: 2 }
+ XYPoint { x: 40; y: 9.5 }
+ XYPoint { x: 100; y: 3.8 }
+
+ // Update series data when backend emits new points
+ Connections {
+ target: Globals.BackendWrapper.activeBackend.analysis
+ function onDataPointsChanged(points) {
+ upperSeries.replace(points)
+ }
+ }
+
+ }
+
+ }
+ // areaSeries
+
+ Component.onCompleted: {
+ Globals.BackendWrapper.activeBackend.analysis.generateData()
+ }
+ }
+ // graph
+
+ // plotAreaBorder
+ Rectangle {
+ id: plotAreaBorder
+ color: "transparent"
+ border.color: EaStyle.Colors.chartGridLine
+ border.width: 1
+
+ x: graph.plotArea.x
+ y: graph.plotArea.y
+ width: graph.plotArea.width
+ height: graph.plotArea.height
+ }
+ // plotAreaBorder
+
+ // mouseArea
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton
+ propagateComposedEvents: true
+
+ // Right-click → zoom out (reset view)
+ onClicked: {
+ graph.axisX.zoom = 1.0
+ graph.axisY.zoom = 1.0
+ graph.axisX.pan = 0.0
+ graph.axisY.pan = 0.0
+ }
+ }
+ // mouseArea
+}
diff --git a/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Groups/GenerateData.qml b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Groups/GenerateData.qml
new file mode 100644
index 00000000..b6efe476
--- /dev/null
+++ b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Groups/GenerateData.qml
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: 2024 EasyApp contributors
+// SPDX-License-Identifier: BSD-3-Clause
+// © 2024 Contributors to the EasyApp project
+
+import QtQuick
+import QtQuick.Controls
+
+import EasyApp.Gui.Globals as EaGlobals
+import EasyApp.Gui.Style as EaStyle
+import EasyApp.Gui.Elements as EaElements
+import EasyApp.Gui.Components as EaComponents
+import EasyApp.Gui.Logic as EaLogic
+
+import Gui.Globals as Globals
+
+EaElements.GroupColumn {
+
+ // 1st row
+ EaElements.GroupRow {
+ spacing: EaStyle.Sizes.fontPixelSize
+
+ // button
+ EaElements.SideBarButton {
+ id: generateDataButton
+
+ fontIcon: 'plus-circle'
+ text: qsTr('Generate new data')
+
+ onClicked: {
+ console.debug(`Clicking '${text}' button ::: ${this}`)
+ Globals.BackendWrapper.activeBackend.analysis.generateData()
+ }
+ }
+ // button
+
+ // text input
+ EaElements.ParamTextField {
+ height: generateDataButton.height
+
+ inputMethodHints: Qt.ImhDigitsOnly
+ validator: IntValidator {
+ bottom: 2
+ top: 100000
+ }
+
+ value: Globals.BackendWrapper.activeBackend.analysis.dataSize
+ units: 'points'
+
+ onTextChanged: {
+ if (acceptableInput) {
+ Globals.BackendWrapper.activeBackend.analysis.dataSize = text
+ } else {
+ console.warn("Input value must belong to [2, 100'000]")
+ }
+ }
+ }
+ // text input
+ }
+ // 1st row
+}
diff --git a/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml
new file mode 100644
index 00000000..e1c91b59
--- /dev/null
+++ b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: 2024 EasyApp contributors
+// SPDX-License-Identifier: BSD-3-Clause
+// © 2024 Contributors to the EasyApp project
+
+import QtQuick
+import QtQuick.Controls
+
+import EasyApp.Gui.Elements as EaElements
+import EasyApp.Gui.Components as EaComponents
+
+import Gui.Globals as Globals
+
+
+EaComponents.SideBarColumn {
+
+ EaElements.GroupBox {
+ collapsible: false
+
+ Loader { source: 'Groups/GenerateData.qml' }
+ }
+
+}
diff --git a/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Project/Layout.qml b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Project/Layout.qml
index 6c03c65c..66ae62e8 100644
--- a/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Project/Layout.qml
+++ b/examples/AdvancedPy/src/AdvancedPy/Gui/Pages/Project/Layout.qml
@@ -48,8 +48,8 @@ EaComponents.ContentPage {
continueButton.onClicked: {
console.debug(`Clicking '${continueButton.text}' button ::: ${this}`)
- Globals.References.applicationWindow.appBarCentralTabs.summaryButton.enabled = true
- Globals.References.applicationWindow.appBarCentralTabs.summaryButton.toggle()
+ Globals.References.applicationWindow.appBarCentralTabs.analysisButton.enabled = true
+ Globals.References.applicationWindow.appBarCentralTabs.analysisButton.toggle()
}
}
diff --git a/pixi.lock b/pixi.lock
new file mode 100644
index 00000000..cbcfe22e
--- /dev/null
+++ b/pixi.lock
@@ -0,0 +1,968 @@
+version: 6
+environments:
+ default:
+ channels:
+ - url: https://conda.anaconda.org/conda-forge/
+ indexes:
+ - https://pypi.org/simple
+ packages:
+ linux-64:
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-hc97d973_101_cp313.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda
+ - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/34/0e/1e9841cc46196c55ac3eac0b8e08044a88cc70c8cc29e9dc1e33b2ced2b7/pyside6-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/17/fe/d5c67665f866b8859d02aa1a859f101a1b2fd348cb61746a3e16fd98fb20/pyside6_addons-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/85/e8/9396cf11a60f80175bb3c5c1d498d84e87b7af653ab4ea001acf821a3981/pyside6_essentials-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/be/82/c1c6932f9849bc5e75c93c38a29419505a6e3e0037261e28f3e7ecbf2751/shiboken6-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl
+ - pypi: ./
+ osx-64:
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h17c18a5_101_cp313.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
+ - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/98/84b16f78b5d92dd234fb1eb9890a350a5b0c83d985bb8c44a92f813a2d02/pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ - pypi: https://files.pythonhosted.org/packages/47/23/9fbdec2ce16244ac3fe28e6d44c39c70465c93a03325939a792fd00fde7f/pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ - pypi: https://files.pythonhosted.org/packages/e5/55/bad02ab890c8b8101abef0db4a2e5304be78a69e23a438e4d8555b664467/pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ - pypi: https://files.pythonhosted.org/packages/fd/78/3e730aea82089dd82b1e092bc265778bda329459e6ad9b7134eec5fff3f2/shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ - pypi: ./
+ osx-arm64:
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.9-hfc2f54d_101_cp313.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
+ - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/98/84b16f78b5d92dd234fb1eb9890a350a5b0c83d985bb8c44a92f813a2d02/pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ - pypi: https://files.pythonhosted.org/packages/47/23/9fbdec2ce16244ac3fe28e6d44c39c70465c93a03325939a792fd00fde7f/pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ - pypi: https://files.pythonhosted.org/packages/e5/55/bad02ab890c8b8101abef0db4a2e5304be78a69e23a438e4d8555b664467/pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ - pypi: https://files.pythonhosted.org/packages/fd/78/3e730aea82089dd82b1e092bc265778bda329459e6ad9b7134eec5fff3f2/shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ - pypi: ./
+ win-64:
+ - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-h09917c8_101_cp313.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda
+ - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda
+ - pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/43/67/94794ebaf198bbdb35cb77f19f38370f9b323b036ab149874bc33c38faab/pyside6-6.10.0-cp39-abi3-win_amd64.whl
+ - pypi: https://files.pythonhosted.org/packages/7d/5d/a3c32f85ac7f905c95679967c0ddda0ba043c273b75623cc90d8185064e4/pyside6_addons-6.10.0-cp39-abi3-win_amd64.whl
+ - pypi: https://files.pythonhosted.org/packages/bb/3a/d8211d17e6ca70f641c6ebd309f08ef18930acda60e74082c75875a274da/pyside6_essentials-6.10.0-cp39-abi3-win_amd64.whl
+ - pypi: https://files.pythonhosted.org/packages/11/30/e4624a7e3f0dc9796b701079b77defcce0d32d1afc86bb1d0df04bc3d9e2/shiboken6-6.10.0-cp39-abi3-win_amd64.whl
+ - pypi: ./
+packages:
+- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
+ sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726
+ md5: d7c89558ba9fa0495403155b64376d81
+ license: None
+ purls: []
+ size: 2562
+ timestamp: 1578324546067
+- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2
+ build_number: 16
+ sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22
+ md5: 73aaf86a425cc6e73fcf236a5a46396d
+ depends:
+ - _libgcc_mutex 0.1 conda_forge
+ - libgomp >=7.5.0
+ constrains:
+ - openmp_impl 9999
+ license: BSD-3-Clause
+ license_family: BSD
+ purls: []
+ size: 23621
+ timestamp: 1650670423406
+- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda
+ sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5
+ md5: 51a19bba1b8ebfb60df25cde030b7ebc
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ license: bzip2-1.0.6
+ license_family: BSD
+ purls: []
+ size: 260341
+ timestamp: 1757437258798
+- conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda
+ sha256: 8f50b58efb29c710f3cecf2027a8d7325ba769ab10c746eff75cea3ac050b10c
+ md5: 97c4b3bd8a90722104798175a1bdddbf
+ depends:
+ - __osx >=10.13
+ license: bzip2-1.0.6
+ license_family: BSD
+ purls: []
+ size: 132607
+ timestamp: 1757437730085
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda
+ sha256: b456200636bd5fecb2bec63f7e0985ad2097cf1b83d60ce0b6968dffa6d02aa1
+ md5: 58fd217444c2a5701a44244faf518206
+ depends:
+ - __osx >=11.0
+ license: bzip2-1.0.6
+ license_family: BSD
+ purls: []
+ size: 125061
+ timestamp: 1757437486465
+- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda
+ sha256: d882712855624641f48aa9dc3f5feea2ed6b4e6004585d3616386a18186fe692
+ md5: 1077e9333c41ff0be8edd1a5ec0ddace
+ depends:
+ - ucrt >=10.0.20348.0
+ - vc >=14.3,<15
+ - vc14_runtime >=14.44.35208
+ license: bzip2-1.0.6
+ license_family: BSD
+ purls: []
+ size: 55977
+ timestamp: 1757437738856
+- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda
+ sha256: bfb7f9f242f441fdcd80f1199edd2ecf09acea0f2bcef6f07d7cbb1a8131a345
+ md5: e54200a1cd1fe33d61c9df8d3b00b743
+ depends:
+ - __win
+ license: ISC
+ purls: []
+ size: 156354
+ timestamp: 1759649104842
+- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda
+ sha256: 3b5ad78b8bb61b6cdc0978a6a99f8dfb2cc789a451378d054698441005ecbdb6
+ md5: f9e5fbc24009179e8b0409624691758a
+ depends:
+ - __unix
+ license: ISC
+ purls: []
+ size: 155907
+ timestamp: 1759649036195
+- pypi: ./
+ name: easyapp
+ version: 0.8.0
+ sha256: 9a70274e50d805916093ff0760fe747728bce9023b76cb802b1feba354b24fd6
+ requires_dist:
+ - pyside6
+ requires_python: '>=3.10'
+ editable: true
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda
+ sha256: 9ba12c93406f3df5ab0a43db8a4b4ef67a5871dfd401010fbe29b218b2cbe620
+ md5: 5eb22c1d7b3fc4abb50d92d621583137
+ depends:
+ - __osx >=11.0
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 11857802
+ timestamp: 1720853997952
+- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda
+ sha256: 96b6900ca0489d9e5d0318a6b49f8eff43fd85fef6e07cb0c25344ee94cd7a3a
+ md5: c94ab6ff54ba5172cf1c58267005670f
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - zstd >=1.5.7,<1.6.0a0
+ constrains:
+ - binutils_impl_linux-64 2.44
+ license: GPL-3.0-only
+ license_family: GPL
+ purls: []
+ size: 742501
+ timestamp: 1761335175964
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda
+ sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2
+ md5: 4211416ecba1866fab0c6470986c22d6
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ constrains:
+ - expat 2.7.1.*
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 74811
+ timestamp: 1752719572741
+- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda
+ sha256: 689862313571b62ee77ee01729dc093f2bf25a2f99415fcfe51d3a6cd31cce7b
+ md5: 9fdeae0b7edda62e989557d645769515
+ depends:
+ - __osx >=10.13
+ constrains:
+ - expat 2.7.1.*
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 72450
+ timestamp: 1752719744781
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda
+ sha256: 8fbb17a56f51e7113ed511c5787e0dec0d4b10ef9df921c4fd1cccca0458f648
+ md5: b1ca5f21335782f71a8bd69bdc093f67
+ depends:
+ - __osx >=11.0
+ constrains:
+ - expat 2.7.1.*
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 65971
+ timestamp: 1752719657566
+- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda
+ sha256: 8432ca842bdf8073ccecf016ccc9140c41c7114dc4ec77ca754551c01f780845
+ md5: 3608ffde260281fa641e70d6e34b1b96
+ depends:
+ - ucrt >=10.0.20348.0
+ - vc >=14.3,<15
+ - vc14_runtime >=14.44.35208
+ constrains:
+ - expat 2.7.1.*
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 141322
+ timestamp: 1752719767870
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda
+ sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54
+ md5: 35f29eec58405aaf55e01cb470d8c26a
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 57821
+ timestamp: 1760295480630
+- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda
+ sha256: 277dc89950f5d97f1683f26e362d6dca3c2efa16cb2f6fdb73d109effa1cd3d0
+ md5: d214916b24c625bcc459b245d509f22e
+ depends:
+ - __osx >=10.13
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 52573
+ timestamp: 1760295626449
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda
+ sha256: 9b8acdf42df61b7bfe8bdc545c016c29e61985e79748c64ad66df47dbc2e295f
+ md5: 411ff7cd5d1472bba0f55c0faf04453b
+ depends:
+ - __osx >=11.0
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 40251
+ timestamp: 1760295839166
+- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda
+ sha256: ddff25aaa4f0aa535413f5d831b04073789522890a4d8626366e43ecde1534a3
+ md5: ba4ad812d2afc22b9a34ce8327a0930f
+ depends:
+ - ucrt >=10.0.20348.0
+ - vc >=14.3,<15
+ - vc14_runtime >=14.44.35208
+ license: MIT
+ license_family: MIT
+ purls: []
+ size: 44866
+ timestamp: 1760295760649
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda
+ sha256: 08f9b87578ab981c7713e4e6a7d935e40766e10691732bba376d4964562bcb45
+ md5: c0374badb3a5d4b1372db28d19462c53
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - _openmp_mutex >=4.5
+ constrains:
+ - libgomp 15.2.0 h767d61c_7
+ - libgcc-ng ==15.2.0=*_7
+ license: GPL-3.0-only WITH GCC-exception-3.1
+ license_family: GPL
+ purls: []
+ size: 822552
+ timestamp: 1759968052178
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda
+ sha256: e9fb1c258c8e66ee278397b5822692527c5f5786d372fe7a869b900853f3f5ca
+ md5: f7b4d76975aac7e5d9e6ad13845f92fe
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ license: GPL-3.0-only WITH GCC-exception-3.1
+ license_family: GPL
+ purls: []
+ size: 447919
+ timestamp: 1759967942498
+- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda
+ sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8
+ md5: 1a580f7796c7bf6393fddb8bbbde58dc
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=13
+ constrains:
+ - xz 5.8.1.*
+ license: 0BSD
+ purls: []
+ size: 112894
+ timestamp: 1749230047870
+- conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda
+ sha256: 7e22fd1bdb8bf4c2be93de2d4e718db5c548aa082af47a7430eb23192de6bb36
+ md5: 8468beea04b9065b9807fc8b9cdc5894
+ depends:
+ - __osx >=10.13
+ constrains:
+ - xz 5.8.1.*
+ license: 0BSD
+ purls: []
+ size: 104826
+ timestamp: 1749230155443
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda
+ sha256: 0cb92a9e026e7bd4842f410a5c5c665c89b2eb97794ffddba519a626b8ce7285
+ md5: d6df911d4564d77c4374b02552cb17d1
+ depends:
+ - __osx >=11.0
+ constrains:
+ - xz 5.8.1.*
+ license: 0BSD
+ purls: []
+ size: 92286
+ timestamp: 1749230283517
+- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda
+ sha256: 55764956eb9179b98de7cc0e55696f2eff8f7b83fc3ebff5e696ca358bca28cc
+ md5: c15148b2e18da456f5108ccb5e411446
+ depends:
+ - ucrt >=10.0.20348.0
+ - vc >=14.2,<15
+ - vc14_runtime >=14.29.30139
+ constrains:
+ - xz 5.8.1.*
+ license: 0BSD
+ purls: []
+ size: 104935
+ timestamp: 1749230611612
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda
+ sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee
+ md5: c7e925f37e3b40d893459e625f6a53f1
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=13
+ license: BSD-2-Clause
+ license_family: BSD
+ purls: []
+ size: 91183
+ timestamp: 1748393666725
+- conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda
+ sha256: 98299c73c7a93cd4f5ff8bb7f43cd80389f08b5a27a296d806bdef7841cc9b9e
+ md5: 18b81186a6adb43f000ad19ed7b70381
+ depends:
+ - __osx >=10.13
+ license: BSD-2-Clause
+ license_family: BSD
+ purls: []
+ size: 77667
+ timestamp: 1748393757154
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda
+ sha256: 0a1875fc1642324ebd6c4ac864604f3f18f57fbcf558a8264f6ced028a3c75b2
+ md5: 85ccccb47823dd9f7a99d2c7f530342f
+ depends:
+ - __osx >=11.0
+ license: BSD-2-Clause
+ license_family: BSD
+ purls: []
+ size: 71829
+ timestamp: 1748393749336
+- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda
+ sha256: fc529fc82c7caf51202cc5cec5bb1c2e8d90edbac6d0a4602c966366efe3c7bf
+ md5: 74860100b2029e2523cf480804c76b9b
+ depends:
+ - ucrt >=10.0.20348.0
+ - vc >=14.2,<15
+ - vc14_runtime >=14.29.30139
+ license: BSD-2-Clause
+ license_family: BSD
+ purls: []
+ size: 88657
+ timestamp: 1723861474602
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda
+ sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da
+ md5: 0b367fad34931cb79e0d6b7e5c06bb1c
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ - libzlib >=1.3.1,<2.0a0
+ license: blessing
+ purls: []
+ size: 932581
+ timestamp: 1753948484112
+- conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda
+ sha256: 466366b094c3eb4b1d77320530cbf5400e7a10ab33e4824c200147488eebf7a6
+ md5: 156bfb239b6a67ab4a01110e6718cbc4
+ depends:
+ - __osx >=10.13
+ - libzlib >=1.3.1,<2.0a0
+ license: blessing
+ purls: []
+ size: 980121
+ timestamp: 1753948554003
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda
+ sha256: 802ebe62e6bc59fc26b26276b793e0542cfff2d03c086440aeaf72fb8bbcec44
+ md5: 1dcb0468f5146e38fae99aef9656034b
+ depends:
+ - __osx >=11.0
+ - icu >=75.1,<76.0a0
+ - libzlib >=1.3.1,<2.0a0
+ license: blessing
+ purls: []
+ size: 902645
+ timestamp: 1753948599139
+- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda
+ sha256: 5dc4f07b2d6270ac0c874caec53c6984caaaa84bc0d3eb593b0edf3dc8492efa
+ md5: ccb20d946040f86f0c05b644d5eadeca
+ depends:
+ - ucrt >=10.0.20348.0
+ - vc >=14.3,<15
+ - vc14_runtime >=14.44.35208
+ license: blessing
+ purls: []
+ size: 1288499
+ timestamp: 1753948889360
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda
+ sha256: 1b981647d9775e1cdeb2fab0a4dd9cd75a6b0de2963f6c3953dbd712f78334b3
+ md5: 5b767048b1b3ee9a954b06f4084f93dc
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc 15.2.0 h767d61c_7
+ constrains:
+ - libstdcxx-ng ==15.2.0=*_7
+ license: GPL-3.0-only WITH GCC-exception-3.1
+ license_family: GPL
+ purls: []
+ size: 3898269
+ timestamp: 1759968103436
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda
+ sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808
+ md5: 80c07c68d2f6870250959dcc95b209d1
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ license: BSD-3-Clause
+ license_family: BSD
+ purls: []
+ size: 37135
+ timestamp: 1758626800002
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda
+ sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4
+ md5: edb0dca6bc32e4f4789199455a1dbeb8
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=13
+ constrains:
+ - zlib 1.3.1 *_2
+ license: Zlib
+ license_family: Other
+ purls: []
+ size: 60963
+ timestamp: 1727963148474
+- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda
+ sha256: 8412f96504fc5993a63edf1e211d042a1fd5b1d51dedec755d2058948fcced09
+ md5: 003a54a4e32b02f7355b50a837e699da
+ depends:
+ - __osx >=10.13
+ constrains:
+ - zlib 1.3.1 *_2
+ license: Zlib
+ license_family: Other
+ purls: []
+ size: 57133
+ timestamp: 1727963183990
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda
+ sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b
+ md5: 369964e85dc26bfe78f41399b366c435
+ depends:
+ - __osx >=11.0
+ constrains:
+ - zlib 1.3.1 *_2
+ license: Zlib
+ license_family: Other
+ purls: []
+ size: 46438
+ timestamp: 1727963202283
+- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda
+ sha256: ba945c6493449bed0e6e29883c4943817f7c79cbff52b83360f7b341277c6402
+ md5: 41fbfac52c601159df6c01f875de31b9
+ depends:
+ - ucrt >=10.0.20348.0
+ - vc >=14.2,<15
+ - vc14_runtime >=14.29.30139
+ constrains:
+ - zlib 1.3.1 *_2
+ license: Zlib
+ license_family: Other
+ purls: []
+ size: 55476
+ timestamp: 1727963768015
+- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda
+ sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586
+ md5: 47e340acb35de30501a76c7c799c41d7
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=13
+ license: X11 AND BSD-3-Clause
+ purls: []
+ size: 891641
+ timestamp: 1738195959188
+- conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda
+ sha256: ea4a5d27ded18443749aefa49dc79f6356da8506d508b5296f60b8d51e0c4bd9
+ md5: ced34dd9929f491ca6dab6a2927aff25
+ depends:
+ - __osx >=10.13
+ license: X11 AND BSD-3-Clause
+ purls: []
+ size: 822259
+ timestamp: 1738196181298
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda
+ sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733
+ md5: 068d497125e4bf8a66bf707254fff5ae
+ depends:
+ - __osx >=11.0
+ license: X11 AND BSD-3-Clause
+ purls: []
+ size: 797030
+ timestamp: 1738196177597
+- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda
+ sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549
+ md5: 14edad12b59ccbfa3910d42c72adc2a0
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - ca-certificates
+ - libgcc >=14
+ license: Apache-2.0
+ license_family: Apache
+ purls: []
+ size: 3119624
+ timestamp: 1759324353651
+- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda
+ sha256: 3ce8467773b2472b2919412fd936413f05a9b10c42e52c27bbddc923ef5da78a
+ md5: 075eaad78f96bbf5835952afbe44466e
+ depends:
+ - __osx >=10.13
+ - ca-certificates
+ license: Apache-2.0
+ license_family: Apache
+ purls: []
+ size: 2747108
+ timestamp: 1759326402264
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda
+ sha256: f0512629f9589392c2fb9733d11e753d0eab8fc7602f96e4d7f3bd95c783eb07
+ md5: 71118318f37f717eefe55841adb172fd
+ depends:
+ - __osx >=11.0
+ - ca-certificates
+ license: Apache-2.0
+ license_family: Apache
+ purls: []
+ size: 3067808
+ timestamp: 1759324763146
+- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda
+ sha256: 5ddc1e39e2a8b72db2431620ad1124016f3df135f87ebde450d235c212a61994
+ md5: f28ffa510fe055ab518cbd9d6ddfea23
+ depends:
+ - ca-certificates
+ - ucrt >=10.0.20348.0
+ - vc >=14.3,<15
+ - vc14_runtime >=14.44.35208
+ license: Apache-2.0
+ license_family: Apache
+ purls: []
+ size: 9218823
+ timestamp: 1759326176247
+- pypi: https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl
+ name: pip
+ version: '25.3'
+ sha256: 9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/34/0e/1e9841cc46196c55ac3eac0b8e08044a88cc70c8cc29e9dc1e33b2ced2b7/pyside6-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl
+ name: pyside6
+ version: 6.9.3
+ sha256: 6485aebec8eba4e55d1ec1cebe68ca1413589880cc8ccd8a49acae852ec6cfb3
+ requires_dist:
+ - shiboken6==6.9.3
+ - pyside6-essentials==6.9.3
+ - pyside6-addons==6.9.3
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/43/67/94794ebaf198bbdb35cb77f19f38370f9b323b036ab149874bc33c38faab/pyside6-6.10.0-cp39-abi3-win_amd64.whl
+ name: pyside6
+ version: 6.10.0
+ sha256: 70a8bcc73ea8d6baab70bba311eac77b9a1d31f658d0b418e15eb6ea36c97e6f
+ requires_dist:
+ - shiboken6==6.10.0
+ - pyside6-essentials==6.10.0
+ - pyside6-addons==6.10.0
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/e6/98/84b16f78b5d92dd234fb1eb9890a350a5b0c83d985bb8c44a92f813a2d02/pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ name: pyside6
+ version: 6.10.0
+ sha256: c2cbc5dc2a164e3c7c51b3435e24203e90e5edd518c865466afccbd2e5872bb0
+ requires_dist:
+ - shiboken6==6.10.0
+ - pyside6-essentials==6.10.0
+ - pyside6-addons==6.10.0
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/17/fe/d5c67665f866b8859d02aa1a859f101a1b2fd348cb61746a3e16fd98fb20/pyside6_addons-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl
+ name: pyside6-addons
+ version: 6.9.3
+ sha256: 68932327e1c33d729d79b2b94242f97b77601efe0427e757cd3fd588939ea479
+ requires_dist:
+ - shiboken6==6.9.3
+ - pyside6-essentials==6.9.3
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/47/23/9fbdec2ce16244ac3fe28e6d44c39c70465c93a03325939a792fd00fde7f/pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ name: pyside6-addons
+ version: 6.10.0
+ sha256: 88e61e21ee4643cdd9efb39ec52f4dc1ac74c0b45c5b7fa453d03c094f0a8a5c
+ requires_dist:
+ - shiboken6==6.10.0
+ - pyside6-essentials==6.10.0
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/7d/5d/a3c32f85ac7f905c95679967c0ddda0ba043c273b75623cc90d8185064e4/pyside6_addons-6.10.0-cp39-abi3-win_amd64.whl
+ name: pyside6-addons
+ version: 6.10.0
+ sha256: 99d93a32c17c5f6d797c3b90dd58f2a8bae13abde81e85802c34ceafaee11859
+ requires_dist:
+ - shiboken6==6.10.0
+ - pyside6-essentials==6.10.0
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/85/e8/9396cf11a60f80175bb3c5c1d498d84e87b7af653ab4ea001acf821a3981/pyside6_essentials-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl
+ name: pyside6-essentials
+ version: 6.9.3
+ sha256: c70d5544e892b201a677b615156fab6a0fef865e7fc287f55a0eae00a682e83f
+ requires_dist:
+ - shiboken6==6.9.3
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/bb/3a/d8211d17e6ca70f641c6ebd309f08ef18930acda60e74082c75875a274da/pyside6_essentials-6.10.0-cp39-abi3-win_amd64.whl
+ name: pyside6-essentials
+ version: 6.10.0
+ sha256: fc167eb211dd1580e20ba90d299e74898e7a5a1306d832421e879641fc03b6fe
+ requires_dist:
+ - shiboken6==6.10.0
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/e5/55/bad02ab890c8b8101abef0db4a2e5304be78a69e23a438e4d8555b664467/pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ name: pyside6-essentials
+ version: 6.10.0
+ sha256: 003e871effe1f3e5b876bde715c15a780d876682005a6e989d89f48b8b93e93a
+ requires_dist:
+ - shiboken6==6.10.0
+ requires_python: '>=3.9,<3.14'
+- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-hc97d973_101_cp313.conda
+ build_number: 101
+ sha256: e89da062abd0d3e76c8d3b35d3cafc5f0d05914339dcb238f9e3675f2a58d883
+ md5: 4780fe896e961722d0623fa91d0d3378
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - bzip2 >=1.0.8,<2.0a0
+ - ld_impl_linux-64 >=2.36.1
+ - libexpat >=2.7.1,<3.0a0
+ - libffi >=3.5.2,<3.6.0a0
+ - libgcc >=14
+ - liblzma >=5.8.1,<6.0a0
+ - libmpdec >=4.0.0,<5.0a0
+ - libsqlite >=3.50.4,<4.0a0
+ - libuuid >=2.41.2,<3.0a0
+ - libzlib >=1.3.1,<2.0a0
+ - ncurses >=6.5,<7.0a0
+ - openssl >=3.5.4,<4.0a0
+ - python_abi 3.13.* *_cp313
+ - readline >=8.2,<9.0a0
+ - tk >=8.6.13,<8.7.0a0
+ - tzdata
+ license: Python-2.0
+ purls: []
+ size: 37174029
+ timestamp: 1761178179147
+ python_site_packages_path: lib/python3.13/site-packages
+- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h17c18a5_101_cp313.conda
+ build_number: 101
+ sha256: b56484229cf83f6c84e8b138dc53f7f2fa9ee850f42bf1f6d6fa1c03c044c2d3
+ md5: fb1e51574ce30d2a4d5e4facb9b2cbd5
+ depends:
+ - __osx >=10.13
+ - bzip2 >=1.0.8,<2.0a0
+ - libexpat >=2.7.1,<3.0a0
+ - libffi >=3.5.2,<3.6.0a0
+ - liblzma >=5.8.1,<6.0a0
+ - libmpdec >=4.0.0,<5.0a0
+ - libsqlite >=3.50.4,<4.0a0
+ - libzlib >=1.3.1,<2.0a0
+ - ncurses >=6.5,<7.0a0
+ - openssl >=3.5.4,<4.0a0
+ - python_abi 3.13.* *_cp313
+ - readline >=8.2,<9.0a0
+ - tk >=8.6.13,<8.7.0a0
+ - tzdata
+ license: Python-2.0
+ purls: []
+ size: 17521522
+ timestamp: 1761177097697
+ python_site_packages_path: lib/python3.13/site-packages
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.9-hfc2f54d_101_cp313.conda
+ build_number: 101
+ sha256: 516229f780b98783a5ef4112a5a4b5e5647d4f0177c4621e98aa60bb9bc32f98
+ md5: a4241bce59eecc74d4d2396e108c93b8
+ depends:
+ - __osx >=11.0
+ - bzip2 >=1.0.8,<2.0a0
+ - libexpat >=2.7.1,<3.0a0
+ - libffi >=3.5.2,<3.6.0a0
+ - liblzma >=5.8.1,<6.0a0
+ - libmpdec >=4.0.0,<5.0a0
+ - libsqlite >=3.50.4,<4.0a0
+ - libzlib >=1.3.1,<2.0a0
+ - ncurses >=6.5,<7.0a0
+ - openssl >=3.5.4,<4.0a0
+ - python_abi 3.13.* *_cp313
+ - readline >=8.2,<9.0a0
+ - tk >=8.6.13,<8.7.0a0
+ - tzdata
+ license: Python-2.0
+ purls: []
+ size: 11915380
+ timestamp: 1761176793936
+ python_site_packages_path: lib/python3.13/site-packages
+- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-h09917c8_101_cp313.conda
+ build_number: 101
+ sha256: bc855b513197637c2083988d5cbdcc407a23151cdecff381bd677df33d516a01
+ md5: 89d992b9d4b9e88ed54346c9c4a24c1c
+ depends:
+ - bzip2 >=1.0.8,<2.0a0
+ - libexpat >=2.7.1,<3.0a0
+ - libffi >=3.5.2,<3.6.0a0
+ - liblzma >=5.8.1,<6.0a0
+ - libmpdec >=4.0.0,<5.0a0
+ - libsqlite >=3.50.4,<4.0a0
+ - libzlib >=1.3.1,<2.0a0
+ - openssl >=3.5.4,<4.0a0
+ - python_abi 3.13.* *_cp313
+ - tk >=8.6.13,<8.7.0a0
+ - tzdata
+ - ucrt >=10.0.20348.0
+ - vc >=14.3,<15
+ - vc14_runtime >=14.44.35208
+ license: Python-2.0
+ purls: []
+ size: 16613183
+ timestamp: 1761175050438
+ python_site_packages_path: Lib/site-packages
+- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda
+ build_number: 8
+ sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7
+ md5: 94305520c52a4aa3f6c2b1ff6008d9f8
+ constrains:
+ - python 3.13.* *_cp313
+ license: BSD-3-Clause
+ license_family: BSD
+ purls: []
+ size: 7002
+ timestamp: 1752805902938
+- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda
+ sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c
+ md5: 283b96675859b20a825f8fa30f311446
+ depends:
+ - libgcc >=13
+ - ncurses >=6.5,<7.0a0
+ license: GPL-3.0-only
+ license_family: GPL
+ purls: []
+ size: 282480
+ timestamp: 1740379431762
+- conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda
+ sha256: 53017e80453c4c1d97aaf78369040418dea14cf8f46a2fa999f31bd70b36c877
+ md5: 342570f8e02f2f022147a7f841475784
+ depends:
+ - ncurses >=6.5,<7.0a0
+ license: GPL-3.0-only
+ license_family: GPL
+ purls: []
+ size: 256712
+ timestamp: 1740379577668
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda
+ sha256: 7db04684d3904f6151eff8673270922d31da1eea7fa73254d01c437f49702e34
+ md5: 63ef3f6e6d6d5c589e64f11263dc5676
+ depends:
+ - ncurses >=6.5,<7.0a0
+ license: GPL-3.0-only
+ license_family: GPL
+ purls: []
+ size: 252359
+ timestamp: 1740379663071
+- pypi: https://files.pythonhosted.org/packages/be/82/c1c6932f9849bc5e75c93c38a29419505a6e3e0037261e28f3e7ecbf2751/shiboken6-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl
+ name: shiboken6
+ version: 6.9.3
+ sha256: f3f5337a3a8fc660ba1462265bd9a2bdda9588f8d90fbc3d5ac4ce3134c11e59
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/11/30/e4624a7e3f0dc9796b701079b77defcce0d32d1afc86bb1d0df04bc3d9e2/shiboken6-6.10.0-cp39-abi3-win_amd64.whl
+ name: shiboken6
+ version: 6.10.0
+ sha256: 0bc5631c1bf150cbef768a17f5f289aae1cb4db6c6b0c19b2421394e27783717
+ requires_python: '>=3.9,<3.14'
+- pypi: https://files.pythonhosted.org/packages/fd/78/3e730aea82089dd82b1e092bc265778bda329459e6ad9b7134eec5fff3f2/shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl
+ name: shiboken6
+ version: 6.10.0
+ sha256: 7a5f5f400ebfb3a13616030815708289c2154e701a60b9db7833b843e0bee543
+ requires_python: '>=3.9,<3.14'
+- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda
+ sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1
+ md5: a0116df4f4ed05c303811a837d5b39d8
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=13
+ - libzlib >=1.3.1,<2.0a0
+ license: TCL
+ license_family: BSD
+ purls: []
+ size: 3285204
+ timestamp: 1748387766691
+- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda
+ sha256: b24468006a96b71a5f4372205ea7ec4b399b0f2a543541e86f883de54cd623fc
+ md5: 9864891a6946c2fe037c02fca7392ab4
+ depends:
+ - __osx >=10.13
+ - libzlib >=1.3.1,<2.0a0
+ license: TCL
+ license_family: BSD
+ purls: []
+ size: 3259809
+ timestamp: 1748387843735
+- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda
+ sha256: cb86c522576fa95c6db4c878849af0bccfd3264daf0cc40dd18e7f4a7bfced0e
+ md5: 7362396c170252e7b7b0c8fb37fe9c78
+ depends:
+ - __osx >=11.0
+ - libzlib >=1.3.1,<2.0a0
+ license: TCL
+ license_family: BSD
+ purls: []
+ size: 3125538
+ timestamp: 1748388189063
+- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda
+ sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896
+ md5: ebd0e761de9aa879a51d22cc721bd095
+ depends:
+ - ucrt >=10.0.20348.0
+ - vc >=14.2,<15
+ - vc14_runtime >=14.29.30139
+ license: TCL
+ license_family: BSD
+ purls: []
+ size: 3466348
+ timestamp: 1748388121356
+- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
+ sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192
+ md5: 4222072737ccff51314b5ece9c7d6f5a
+ license: LicenseRef-Public-Domain
+ purls: []
+ size: 122968
+ timestamp: 1742727099393
+- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda
+ sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5
+ md5: 71b24316859acd00bdb8b38f5e2ce328
+ constrains:
+ - vc14_runtime >=14.29.30037
+ - vs2015_runtime >=14.29.30037
+ license: LicenseRef-MicrosoftWindowsSDK10
+ purls: []
+ size: 694692
+ timestamp: 1756385147981
+- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda
+ sha256: 82250af59af9ff3c6a635dd4c4764c631d854feb334d6747d356d949af44d7cf
+ md5: ef02bbe151253a72b8eda264a935db66
+ depends:
+ - vc14_runtime >=14.42.34433
+ track_features:
+ - vc14
+ license: BSD-3-Clause
+ license_family: BSD
+ purls: []
+ size: 18861
+ timestamp: 1760418772353
+- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda
+ sha256: e3a3656b70d1202e0d042811ceb743bd0d9f7e00e2acdf824d231b044ef6c0fd
+ md5: 378d5dcec45eaea8d303da6f00447ac0
+ depends:
+ - ucrt >=10.0.20348.0
+ - vcomp14 14.44.35208 h818238b_32
+ constrains:
+ - vs2015_runtime 14.44.35208.* *_32
+ license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime
+ license_family: Proprietary
+ purls: []
+ size: 682706
+ timestamp: 1760418629729
+- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda
+ sha256: f3790c88fbbdc55874f41de81a4237b1b91eab75e05d0e58661518ff04d2a8a1
+ md5: 58f67b437acbf2764317ba273d731f1d
+ depends:
+ - ucrt >=10.0.20348.0
+ constrains:
+ - vs2015_runtime 14.44.35208.* *_32
+ license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime
+ license_family: Proprietary
+ purls: []
+ size: 114846
+ timestamp: 1760418593847
+- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda
+ sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb
+ md5: 6432cb5d4ac0046c3ac0a8a0f95842f9
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=13
+ - libstdcxx >=13
+ - libzlib >=1.3.1,<2.0a0
+ license: BSD-3-Clause
+ license_family: BSD
+ purls: []
+ size: 567578
+ timestamp: 1742433379869
diff --git a/pixi.toml b/pixi.toml
new file mode 100644
index 00000000..0faecf02
--- /dev/null
+++ b/pixi.toml
@@ -0,0 +1,64 @@
+#######################
+# ENVIRONMENT VARIABLES
+#######################
+
+# Platform-independent
+
+[activation.env]
+PYTHONIOENCODING = "utf-8"
+
+# Platform-specific
+
+# Ensures the main package is used from the source code during
+# development, even if main package is installed in editable mode
+# via uv. This is important because `pixi update` might replace
+# the installed version with the latest released version.
+
+# Unix/macOS
+[target.unix.activation.env]
+PYTHONPATH = "${PIXI_PROJECT_ROOT}/src${PYTHONPATH:+:${PYTHONPATH}}"
+
+# Windows
+[target.win.activation.env]
+PYTHONPATH = "${PIXI_PROJECT_ROOT}/src;%PYTHONPATH%"
+
+###########
+# WORKSPACE
+###########
+
+[workspace]
+# Supported platforms for the lock file (pixi.lock)
+platforms = ['win-64', 'linux-64', 'osx-64', 'osx-arm64']
+
+# Channels for fetching packages
+channels = ['conda-forge']
+
+##########
+# FEATURES
+##########
+
+[dependencies] # == [feature.default.dependencies]
+python = '3.13.*'
+
+[pypi-dependencies] # == [feature.default.pypi-dependencies]
+pip = '*'
+easyapp = { path = ".", editable = true }
+
+
+##############
+# ENVIRONMENTS
+##############
+
+[environments]
+
+# The `default` feature is always included in all environments.
+# Additional features can be specified per environment.
+
+# The `default` environment is always created and includes the `default` feature.
+# It does not need to be specified explicitly unless non-default features are included.
+
+#######
+# TASKS
+#######
+
+#[tasks]
diff --git a/src/EasyApp/Gui/Elements/Slider.qml b/src/EasyApp/Gui/Elements/Slider.qml
index 9a180e15..955f1893 100644
--- a/src/EasyApp/Gui/Elements/Slider.qml
+++ b/src/EasyApp/Gui/Elements/Slider.qml
@@ -32,7 +32,7 @@ T.Slider {
id: toolTip
visible: control.pressed || control.hovered
- //text: EaLogic.Utils.toDefaultPrecision(control.value)
+ text: EaLogic.Utils.toDefaultPrecision(control.value)
}
}
From d82c56ca0cffdd806117a6620f28dd2c1c778c28 Mon Sep 17 00:00:00 2001
From: Oleksandr Koshchii
Date: Mon, 20 Apr 2026 10:27:00 +0200
Subject: [PATCH 19/26] Add basic templates of visualisation charts utilized in
EasyTexture (#30)
* Fix reference to EaGlobals.Vars in TableViewLabelControl
* Upload new templates for visualisations
* Upload js file needed for visualisations to work
* Add colorbar title setting to 2d and 3d plots
* Add 1d barplots
* Incorporate new templates for 1d, 2d, and 3d visualisations
* Implement additional possibility to highlight surface regions in 3d and 2d plots using rectangular patches
* Implement code review suggestions
* Fix wrong function call in Plotly2dHeatmap.qml and marker lims calculation in Plotly2dPolarHeatmap.html
---------
Co-authored-by: Andrew Sazonov
---
src/EasyApp/Gui/Charts/Plotly1dBarPlot.qml | 82 +++++++++
src/EasyApp/Gui/Charts/Plotly1dLine.qml | 17 +-
src/EasyApp/Gui/Charts/Plotly2dHeatmap.qml | 58 ++++++-
.../Gui/Charts/Plotly2dPolarHeatmap.qml | 65 +++++++
src/EasyApp/Gui/Charts/Plotly3dSurface.qml | 51 ++++--
src/EasyApp/Gui/Charts/qmldir | 2 +
.../Gui/Components/TableViewLabelControl.qml | 2 +-
src/EasyApp/Gui/Html/Plotly1dBarPlot.html | 148 ++++++++++++++++
src/EasyApp/Gui/Html/Plotly1dLine.html | 15 +-
src/EasyApp/Gui/Html/Plotly2dHeatmap.html | 33 +++-
.../Gui/Html/Plotly2dPolarHeatmap.html | 158 ++++++++++++++++++
src/EasyApp/Gui/Html/Plotly3dSurface.html | 99 +++++++----
src/EasyApp/Gui/Html/plotly-2.18.0.min.js | 2 +-
13 files changed, 656 insertions(+), 76 deletions(-)
create mode 100644 src/EasyApp/Gui/Charts/Plotly1dBarPlot.qml
create mode 100644 src/EasyApp/Gui/Charts/Plotly2dPolarHeatmap.qml
create mode 100644 src/EasyApp/Gui/Html/Plotly1dBarPlot.html
create mode 100644 src/EasyApp/Gui/Html/Plotly2dPolarHeatmap.html
diff --git a/src/EasyApp/Gui/Charts/Plotly1dBarPlot.qml b/src/EasyApp/Gui/Charts/Plotly1dBarPlot.qml
new file mode 100644
index 00000000..78aac480
--- /dev/null
+++ b/src/EasyApp/Gui/Charts/Plotly1dBarPlot.qml
@@ -0,0 +1,82 @@
+import QtQuick
+import QtQuick.Controls
+import QtWebEngine
+
+import EasyApp.Gui.Style as EaStyle
+import Gui.Globals as Globals
+
+WebEngineView {
+ id: chartView
+
+ property bool loadSucceededStatus: false
+ property string xAxisTitle: ''
+ property string yAxisTitle: ''
+
+ property var plotData: ({})
+
+ width: parent.width
+ height: parent.height
+
+ url: Qt.resolvedUrl('../Html/Plotly1dBarPlot.html')
+
+ onLoadSucceededStatusChanged: {
+ if (loadSucceededStatus) {
+ setXAxisTitle()
+ setYAxisTitle()
+ setXyData()
+ redrawPlot()
+ }
+ }
+
+ onLoadingChanged: {
+ // Bug 'loadRequest' is not declared - https://bugreports.qt.io/browse/QTBUG-84746
+ //if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
+ if (loadProgress === 100) {
+ loadSucceededStatus = true
+ }
+ }
+
+ onXAxisTitleChanged: {
+ if (loadSucceededStatus) {
+ setXAxisTitle()
+ redrawPlot()
+ }
+ }
+
+ onYAxisTitleChanged: {
+ if (loadSucceededStatus) {
+ setYAxisTitle()
+ redrawPlot()
+ }
+ }
+
+ onPlotDataChanged: {
+ if (loadSucceededStatus) {
+ setXyData()
+ redrawPlot()
+ }
+ }
+
+ // Logic
+
+ function redrawPlot() {
+ chartView.runJavaScript(`redrawPlot()`)
+ }
+
+ function setXAxisTitle() {
+ runJavaScript(`setXAxisTitle(${JSON.stringify(xAxisTitle)})`)
+ }
+
+ function setYAxisTitle() {
+ runJavaScript(`setYAxisTitle(${JSON.stringify(yAxisTitle)})`)
+ }
+
+ function setXyData() {
+ runJavaScript(`setXyData(${JSON.stringify(plotData)})`)
+ }
+
+ function redrawPlotWithAnimation() {
+ runJavaScript(`redrawPlotWithAnimation(${JSON.stringify(plotData)})`)
+ }
+
+}
diff --git a/src/EasyApp/Gui/Charts/Plotly1dLine.qml b/src/EasyApp/Gui/Charts/Plotly1dLine.qml
index a3371d81..b6c6e4f5 100644
--- a/src/EasyApp/Gui/Charts/Plotly1dLine.qml
+++ b/src/EasyApp/Gui/Charts/Plotly1dLine.qml
@@ -6,16 +6,16 @@ WebEngineView {
id: chartView
property bool loadSucceededStatus: false
-
property string xAxisTitle: ''
property string yAxisTitle: ''
- property var xyData: ({})
+ property var plotData: ({})
+ property var fullData: ({})
width: parent.width
height: parent.height
- url: Qt.resolvedUrl("Plotly1dLine.html")
+ url: Qt.resolvedUrl('../Html/Plotly1dLine.html')
onLoadSucceededStatusChanged: {
if (loadSucceededStatus) {
@@ -27,7 +27,7 @@ WebEngineView {
}
onLoadingChanged: {
- // Bug "loadRequest" is not declared - https://bugreports.qt.io/browse/QTBUG-84746
+ // Bug 'loadRequest' is not declared - https://bugreports.qt.io/browse/QTBUG-84746
//if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
if (loadProgress === 100) {
loadSucceededStatus = true
@@ -48,7 +48,7 @@ WebEngineView {
}
}
- onXyDataChanged: {
+ onPlotDataChanged: {
if (loadSucceededStatus) {
setXyData()
redrawPlot()
@@ -70,13 +70,10 @@ WebEngineView {
}
function setXyData() {
- //runJavaScript(`setXyData(${JSON.stringify(xyData)})`, function(result) { console.log(JSON.stringify(result)); })
- runJavaScript(`setXyData(${JSON.stringify(xyData)})`)
+ runJavaScript(`setXyData(${JSON.stringify(plotData)})`)
}
function redrawPlotWithAnimation() {
- runJavaScript(`redrawPlotWithAnimation(${JSON.stringify(xyData)})`)
-
+ runJavaScript(`redrawPlotWithAnimation(${JSON.stringify(plotData)})`)
}
-
}
diff --git a/src/EasyApp/Gui/Charts/Plotly2dHeatmap.qml b/src/EasyApp/Gui/Charts/Plotly2dHeatmap.qml
index 80545db9..091420d0 100644
--- a/src/EasyApp/Gui/Charts/Plotly2dHeatmap.qml
+++ b/src/EasyApp/Gui/Charts/Plotly2dHeatmap.qml
@@ -8,16 +8,23 @@ WebEngineView {
property bool loadSucceededStatus: false
property string xAxisTitle: ''
property string yAxisTitle: ''
+ property string colorbarTitle: ''
+
+ property var plotData: ({})
+ property var shapes: ([{}])
width: parent.width
height: parent.height
- url: Qt.resolvedUrl('../Html/Plotly2dHeatmap.html')
+ url: Qt.resolvedUrl('../Html/Plotly2dHeatmap.html')
onLoadSucceededStatusChanged: {
if (loadSucceededStatus) {
- setXAxisTitle(xAxisTitle)
- setYAxisTitle(yAxisTitle)
+ setXAxisTitle()
+ setYAxisTitle()
+ setColorbarTitle()
+ setShape()
+ setXyzData()
redrawPlot()
}
}
@@ -30,16 +37,37 @@ WebEngineView {
}
}
+ onColorbarTitleChanged: {
+ if (loadSucceededStatus) {
+ setColorbarTitle()
+ redrawPlot()
+ }
+ }
+
onXAxisTitleChanged: {
if (loadSucceededStatus) {
- setXAxisTitle(newTitle)
+ setXAxisTitle()
redrawPlot()
}
}
onYAxisTitleChanged: {
if (loadSucceededStatus) {
- setYAxisTitle(newTitle)
+ setYAxisTitle()
+ redrawPlot()
+ }
+ }
+
+ onPlotDataChanged: {
+ if (loadSucceededStatus) {
+ setXyzData()
+ redrawPlot()
+ }
+ }
+
+ onShapesChanged: {
+ if (loadSucceededStatus) {
+ setShape()
redrawPlot()
}
}
@@ -50,12 +78,24 @@ WebEngineView {
chartView.runJavaScript(`redrawPlot()`)
}
- function setXAxisTitle(newTitle) {
- runJavaScript(`setXAxisTitle(${JSON.stringify(newTitle)})`)
+ function setXAxisTitle() {
+ runJavaScript(`setXAxisTitle(${JSON.stringify(xAxisTitle)})`)
+ }
+
+ function setYAxisTitle() {
+ runJavaScript(`setYAxisTitle(${JSON.stringify(yAxisTitle)})`)
+ }
+
+ function setShape() {
+ runJavaScript(`setShape(${JSON.stringify(shapes)})`)
+ }
+
+ function setColorbarTitle() {
+ runJavaScript(`setColorbarTitle(${JSON.stringify(colorbarTitle)})`)
}
- function setYAxisTitle(newTitle) {
- runJavaScript(`setYAxisTitle(${JSON.stringify(newTitle)})`)
+ function setXyzData() {
+ runJavaScript(`setXyzData(${JSON.stringify(plotData)})`)
}
}
diff --git a/src/EasyApp/Gui/Charts/Plotly2dPolarHeatmap.qml b/src/EasyApp/Gui/Charts/Plotly2dPolarHeatmap.qml
new file mode 100644
index 00000000..c69a27c7
--- /dev/null
+++ b/src/EasyApp/Gui/Charts/Plotly2dPolarHeatmap.qml
@@ -0,0 +1,65 @@
+import QtQuick
+import QtQuick.Controls
+import QtWebEngine
+
+import Gui.Globals as Globals
+
+WebEngineView {
+ id: chartView
+
+ property bool loadSucceededStatus: false
+ property string colorbarTitle: ''
+
+ property var plotData: ({})
+ property var fullData: ({})
+
+ width: parent.width
+ height: parent.height
+
+ url: Qt.resolvedUrl('../Html/Plotly2dPolarHeatmap.html')
+
+ onLoadSucceededStatusChanged: {
+ if (loadSucceededStatus) {
+ setColorbarTitle()
+ setXyzData()
+ redrawPlot()
+ }
+ }
+
+ onLoadingChanged: {
+ // Bug "loadRequest" is not declared - https://bugreports.qt.io/browse/QTBUG-84746
+ //if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
+ if (loadProgress === 100) {
+ loadSucceededStatus = true
+ }
+ }
+
+ onColorbarTitleChanged: {
+ if (loadSucceededStatus) {
+ setColorbarTitle()
+ redrawPlot()
+ }
+ }
+
+ onPlotDataChanged: {
+ if (loadSucceededStatus) {
+ setXyzData()
+ redrawPlot()
+ }
+ }
+
+ // Logic
+
+ function redrawPlot() {
+ chartView.runJavaScript(`redrawPlot()`)
+ }
+
+ function setColorbarTitle() {
+ runJavaScript(`setColorbarTitle(${JSON.stringify(colorbarTitle)})`)
+ }
+
+ function setXyzData() {
+ runJavaScript(`setXyzData(${JSON.stringify(plotData)})`)
+ }
+
+}
diff --git a/src/EasyApp/Gui/Charts/Plotly3dSurface.qml b/src/EasyApp/Gui/Charts/Plotly3dSurface.qml
index 1d0cc34a..54787888 100644
--- a/src/EasyApp/Gui/Charts/Plotly3dSurface.qml
+++ b/src/EasyApp/Gui/Charts/Plotly3dSurface.qml
@@ -6,9 +6,12 @@ WebEngineView {
id: chartView
property bool loadSucceededStatus: false
- property string xAxisTitle: ''
- property string yAxisTitle: ''
- property string zAxisTitle: ''
+ property string colorbarTitle: ''
+
+ property var scene: ({})
+
+ property var plotData: ({})
+ property var patchData: ({})
width: parent.width
height: parent.height
@@ -17,9 +20,10 @@ WebEngineView {
onLoadSucceededStatusChanged: {
if (loadSucceededStatus) {
- setXAxisTitle(xAxisTitle)
- setYAxisTitle(yAxisTitle)
- setZAxisTitle(zAxisTitle)
+ setColorbarTitle()
+ setScene()
+ setXyzData()
+ setPatchData()
redrawPlot()
}
}
@@ -32,23 +36,30 @@ WebEngineView {
}
}
- onXAxisTitleChanged: {
+ onColorbarTitleChanged: {
if (loadSucceededStatus) {
- setXAxisTitle(newTitle)
+ setColorbarTitle()
redrawPlot()
}
}
- onYAxisTitleChanged: {
+ onSceneChanged: {
if (loadSucceededStatus) {
- setYAxisTitle(newTitle)
+ setScene()
redrawPlot()
}
}
- onZAxisTitleChanged: {
+ onPlotDataChanged: {
if (loadSucceededStatus) {
- setZAxisTitle(newTitle)
+ setXyzData()
+ redrawPlot()
+ }
+ }
+
+ onPatchDataChanged: {
+ if (loadSucceededStatus) {
+ setPatchData()
redrawPlot()
}
}
@@ -59,16 +70,20 @@ WebEngineView {
chartView.runJavaScript(`redrawPlot()`)
}
- function setXAxisTitle(newTitle) {
- runJavaScript(`setXAxisTitle(${JSON.stringify(newTitle)})`)
+ function setColorbarTitle() {
+ runJavaScript(`setColorbarTitle(${JSON.stringify(colorbarTitle)})`)
+ }
+
+ function setScene() {
+ runJavaScript(`setScene(${JSON.stringify(scene)})`)
}
- function setYAxisTitle(newTitle) {
- runJavaScript(`setYAxisTitle(${JSON.stringify(newTitle)})`)
+ function setXyzData() {
+ runJavaScript(`setXyzData(${JSON.stringify(plotData)})`)
}
- function setZAxisTitle(newTitle) {
- runJavaScript(`setZAxisTitle(${JSON.stringify(newTitle)})`)
+ function setPatchData() {
+ runJavaScript(`setPatchData(${JSON.stringify(patchData)})`)
}
}
diff --git a/src/EasyApp/Gui/Charts/qmldir b/src/EasyApp/Gui/Charts/qmldir
index dacfe70a..3253e6d0 100644
--- a/src/EasyApp/Gui/Charts/qmldir
+++ b/src/EasyApp/Gui/Charts/qmldir
@@ -5,10 +5,12 @@ QtCharts1dValueAxis QtCharts1dValueAxis.qml
QtCharts1dBase QtCharts1dBase.qml
QtCharts1dMeasVsCalc QtCharts1dMeasVsCalc.qml
+Plotly1dBarPlot Plotly1dBarPlot.qml
Plotly1dLine Plotly1dLine.qml
Plotly1dMeasVsCalc Plotly1dMeasVsCalc.qml
Plotly2dHeatmap Plotly2dHeatmap.qml
+Plotly2dPolarHeatmap Plotly2dPolarHeatmap.qml
Plotly3dScatter Plotly3dScatter.qml
Plotly3dSurface Plotly3dSurface.qml
diff --git a/src/EasyApp/Gui/Components/TableViewLabelControl.qml b/src/EasyApp/Gui/Components/TableViewLabelControl.qml
index 01030ad7..3846e417 100644
--- a/src/EasyApp/Gui/Components/TableViewLabelControl.qml
+++ b/src/EasyApp/Gui/Components/TableViewLabelControl.qml
@@ -39,7 +39,7 @@ T.Button {
// ToolTip
EaElements.ToolTip {
text: control.ToolTip.text
- visible: label.truncated && control.hovered && EaGlobals.Variables.showToolTips && text !== ""
+ visible: label.truncated && control.hovered && EaGlobals.Vars.showToolTips && text !== ""
}
// Text label
diff --git a/src/EasyApp/Gui/Html/Plotly1dBarPlot.html b/src/EasyApp/Gui/Html/Plotly1dBarPlot.html
new file mode 100644
index 00000000..7cae1eb0
--- /dev/null
+++ b/src/EasyApp/Gui/Html/Plotly1dBarPlot.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/EasyApp/Gui/Html/Plotly1dLine.html b/src/EasyApp/Gui/Html/Plotly1dLine.html
index 87c3b568..b0974ffa 100644
--- a/src/EasyApp/Gui/Html/Plotly1dLine.html
+++ b/src/EasyApp/Gui/Html/Plotly1dLine.html
@@ -73,10 +73,13 @@
// Default trace
let trace = {
+ name: '',
+ type: 'scatter',
x: data.x,
y: data.y,
- type: 'scatter', //'scattergl'
mode: 'lines',
+ customdata: [],
+ hovertemplate: ''
}
// Plot dataset
@@ -139,6 +142,16 @@
function setXyData(newData) {
trace.x = newData.x
trace.y = newData.y
+ trace.customdata = newData.customData
+ trace.hovertemplate = newData.hoverTemplate
+ }
+
+ function getDataFromJson(filename) {
+ let request = new XMLHttpRequest()
+ request.open("GET", filename, false)
+ request.send(null)
+ let data = JSON.parse(request.responseText)
+ return data
}
diff --git a/src/EasyApp/Gui/Html/Plotly2dHeatmap.html b/src/EasyApp/Gui/Html/Plotly2dHeatmap.html
index bcebec92..e1ce435a 100644
--- a/src/EasyApp/Gui/Html/Plotly2dHeatmap.html
+++ b/src/EasyApp/Gui/Html/Plotly2dHeatmap.html
@@ -74,10 +74,15 @@
// Default trace
let trace = {
+ name: '',
x: data.x,
y: data.y,
z: data.z,
- type: 'heatmap'
+ type: 'heatmap',
+ colorbar: {title: {text: ''}},
+ colorscale: 'Portland',
+ hoverongaps: false,
+ hovertemplate: ''
}
// Plot dataset
@@ -99,6 +104,7 @@
zaxis: {
title: 'z-axis',
},
+ shapes: [],
}
// Plot config
@@ -126,7 +132,30 @@
plotLayout.yaxis.title = newTitle
}
-
+ function setShape(newShape) {
+ return plotLayout.shapes = newShape
+ }
+
+ function setColorbarTitle(newTitle){
+ trace.colorbar.title.text = newTitle
+ }
+
+ function setXyzData(newData) {
+ trace.x = newData.x
+ trace.y = newData.y
+ trace.z = newData.z
+ trace.hovertemplate = newData.hoverTemplate
+ }
+
+ function getDataFromJson(filename) {
+ let request = new XMLHttpRequest()
+ request.open("GET", filename, false)
+ request.send(null)
+ let data = JSON.parse(request.responseText)
+ return data
+ }
+
+
+
+
+
+
+
+
diff --git a/src/EasyApp/Gui/Html/Plotly2dPolarHeatmap.html b/src/EasyApp/Gui/Html/Plotly2dPolarHeatmap.html
new file mode 100644
index 00000000..6ddac6d1
--- /dev/null
+++ b/src/EasyApp/Gui/Html/Plotly2dPolarHeatmap.html
@@ -0,0 +1,158 @@
+
+
+
+