Restructure into src/ layout under pygui package
Move all application source under src/pygui/ and rewire imports, build config, and QML module path to match. - Relocate backend/, serialcomm/, and the ISC QML module into src/pygui/; convert main.py into pygui/__main__.py with a main() entry point (run via `python -m pygui` or the new `isc` script) - Rewrite absolute imports: backend.* -> pygui.backend.*, serialcomm.* -> pygui.serialcomm.* (source + tests) - Move app icons (isc.ico/icns) into packaging/ - Update README and ISC.qmlproject to the new paths
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
import ISC
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
|
||||
// ===== CSV metadata dialog (must not live inside RowLayout: implicit size breaks the tab) =====
|
||||
SelectFileDialog {
|
||||
id: selectFileDialog
|
||||
tableModel: file_browser.files
|
||||
onFileChosen: function (path) {
|
||||
root.handleCsvSelected(path);
|
||||
}
|
||||
}
|
||||
|
||||
function handleEditCsvMetadata() {
|
||||
file_browser.refreshFiles();
|
||||
selectFileDialog.open();
|
||||
}
|
||||
|
||||
function handleCsvSelected(filePath) {
|
||||
// Selection confirmed — metadata editing is handled inline inside SelectFileDialog.
|
||||
}
|
||||
|
||||
// ===== Settings Data Helpers =====
|
||||
readonly property var masterFamilies: ["A", "B", "C", "D", "E", "F", "P", "X", "Z"]
|
||||
|
||||
function masterPath(family) {
|
||||
const table = settingsModel.masters;
|
||||
return table && table[family] ? table[family] : "";
|
||||
}
|
||||
|
||||
function statusColor() {
|
||||
if (!settingsModel.isValid) {
|
||||
return Theme.statusWarningColor;
|
||||
}
|
||||
return settingsModel.saveStatus.indexOf("error:") === 0 ? Theme.statusErrorColor : Theme.statusSuccessColor;
|
||||
}
|
||||
|
||||
// ===== Reusable Settings Components =====
|
||||
component SettingsGroupBox: GroupBox {
|
||||
id: settingsGroup
|
||||
|
||||
background: Rectangle {
|
||||
y: settingsGroup.topPadding - settingsGroup.bottomPadding
|
||||
width: settingsGroup.width
|
||||
height: settingsGroup.height - settingsGroup.topPadding + settingsGroup.bottomPadding
|
||||
radius: Theme.radiusMd
|
||||
color: Theme.panelBackground
|
||||
border.color: Theme.innerFrameBorder
|
||||
border.width: Theme.borderThin
|
||||
}
|
||||
|
||||
label: Label {
|
||||
text: settingsGroup.title
|
||||
color: Theme.panelTitleText
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
component SettingsTextInput: TextField {
|
||||
id: textInput
|
||||
color: Theme.fieldText
|
||||
placeholderTextColor: Theme.fieldPlaceholder
|
||||
selectedTextColor: Theme.fieldBackground
|
||||
selectionColor: Theme.fieldText
|
||||
|
||||
background: Rectangle {
|
||||
radius: Theme.radiusXs
|
||||
color: Theme.fieldBackground
|
||||
border.width: textInput.activeFocus ? Theme.borderStrong : Theme.borderThin
|
||||
border.color: textInput.activeFocus ? Theme.fieldBorderFocus : Theme.fieldBorder
|
||||
}
|
||||
}
|
||||
|
||||
component SettingsActionButton: Button {
|
||||
id: actionButton
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle {
|
||||
radius: Theme.radiusSm
|
||||
color: actionButton.down ? Theme.buttonNeutralPressed : (actionButton.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground)
|
||||
border.width: Theme.borderThin
|
||||
border.color: Theme.fieldBorder
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: actionButton.text
|
||||
color: Theme.buttonNeutralText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
component SettingsToggle: CheckBox {
|
||||
id: toggle
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
x: toggle.leftPadding
|
||||
y: parent.height / 2 - height / 2
|
||||
radius: Theme.radiusXs
|
||||
color: toggle.checked ? Theme.primaryAccent : Theme.fieldBackground
|
||||
border.width: Theme.borderThin
|
||||
border.color: toggle.checked ? Theme.primaryAccent : Theme.fieldBorder
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 9
|
||||
height: 9
|
||||
radius: Theme.radiusXs
|
||||
visible: toggle.checked
|
||||
color: Theme.fieldBackground
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: toggle.text
|
||||
color: Theme.checkboxText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: toggle.indicator.width + toggle.spacing
|
||||
}
|
||||
}
|
||||
|
||||
// ===== File Picker Dialog =====
|
||||
FileDialog {
|
||||
id: masterPicker
|
||||
title: "Choose Master CSV"
|
||||
nameFilters: ["CSV files (*.csv)"]
|
||||
property string targetFamily: ""
|
||||
onAccepted: settingsModel.setMaster(targetFamily, String(selectedFile).replace(/^file:\/\//, ""))
|
||||
}
|
||||
|
||||
// ===== Settings Page Layout =====
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
ScrollView {
|
||||
id: settingsScroll
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.settingsOuterMargin
|
||||
|
||||
contentWidth: availableWidth
|
||||
contentHeight: container.implicitHeight
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
parent: settingsScroll
|
||||
anchors.top: settingsScroll.top
|
||||
anchors.right: settingsScroll.right
|
||||
anchors.bottom: settingsScroll.bottom
|
||||
policy: ScrollBar.AsNeeded
|
||||
|
||||
contentItem: Rectangle {
|
||||
implicitWidth: 8
|
||||
implicitHeight: 8
|
||||
radius: Theme.radiusSm
|
||||
color: Theme.trackBackground
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 8
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: container
|
||||
width: Math.min(settingsScroll.availableWidth, Theme.settingsPanelMaxWidth)
|
||||
x: (settingsScroll.availableWidth - width) / 2
|
||||
spacing: Theme.settingsSectionSpacing
|
||||
|
||||
// ===== Page Title =====
|
||||
Label {
|
||||
text: "Settings"
|
||||
font.pixelSize: 30
|
||||
font.bold: true
|
||||
color: Theme.headingColor
|
||||
}
|
||||
|
||||
// ===== Chamber Settings =====
|
||||
SettingsGroupBox {
|
||||
title: "Chamber"
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.settingsRowSpacing
|
||||
|
||||
Label {
|
||||
text: "Chamber ID"
|
||||
color: Theme.headingColor
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
SettingsTextInput {
|
||||
id: chamberField
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: Theme.settingsFieldMinWidth
|
||||
placeholderText: "Enter chamber ID"
|
||||
text: settingsModel.chamberId
|
||||
|
||||
Connections {
|
||||
target: settingsModel
|
||||
function onChamberIdChanged() {
|
||||
if (!chamberField.activeFocus) {
|
||||
chamberField.text = settingsModel.chamberId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsActionButton {
|
||||
text: "Set"
|
||||
Layout.preferredWidth: 90
|
||||
Layout.preferredHeight: Theme.settingsButtonHeight
|
||||
onClicked: {
|
||||
settingsModel.setChamberId(chamberField.text);
|
||||
settingsModel.saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Master CSV Mapping =====
|
||||
SettingsGroupBox {
|
||||
title: "Master Files"
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.settingsGridSpacing
|
||||
|
||||
Repeater {
|
||||
model: root.masterFamilies
|
||||
|
||||
RowLayout {
|
||||
property string family: modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.settingsRowSpacing
|
||||
|
||||
Label {
|
||||
text: family
|
||||
font.bold: true
|
||||
color: Theme.headingColor
|
||||
Layout.preferredWidth: 24
|
||||
}
|
||||
|
||||
SettingsTextInput {
|
||||
Layout.fillWidth: true
|
||||
readOnly: true
|
||||
text: root.masterPath(family)
|
||||
placeholderText: "No master selected"
|
||||
color: Theme.bodyColor
|
||||
}
|
||||
|
||||
SettingsActionButton {
|
||||
text: "Browse"
|
||||
Layout.preferredHeight: Theme.settingsButtonHeight
|
||||
onClicked: {
|
||||
masterPicker.targetFamily = family;
|
||||
masterPicker.open();
|
||||
}
|
||||
}
|
||||
|
||||
SettingsActionButton {
|
||||
text: "Clear"
|
||||
Layout.preferredHeight: Theme.settingsButtonHeight
|
||||
onClicked: settingsModel.clearMaster(family)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Wafer Behavior =====
|
||||
SettingsGroupBox {
|
||||
title: "Wafer Behavior"
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.settingsGridSpacing
|
||||
|
||||
SettingsToggle {
|
||||
text: "Reverse Z Wafer"
|
||||
checked: settingsModel.reverseZWafer
|
||||
onToggled: settingsModel.setReverseZWafer(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Appearance =====
|
||||
SettingsGroupBox {
|
||||
title: "Appearance"
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.settingsRowSpacing
|
||||
|
||||
// Custom pill toggle
|
||||
Rectangle {
|
||||
id: toggleTrack
|
||||
implicitWidth: 52
|
||||
implicitHeight: 28
|
||||
radius: height / 2
|
||||
color: Theme.trackBackground
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: implicitWidth
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 250
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
// Thumb
|
||||
Rectangle {
|
||||
id: toggleThumb
|
||||
width: 22
|
||||
height: 22
|
||||
radius: height / 2
|
||||
color: "white"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: Theme.isDarkMode ? parent.width - width - 3 : 3
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 250
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
// Drop shadow effect
|
||||
layer.enabled: true
|
||||
layer.effect: null // swap for DropShadow if Qt.labs.effects is available
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Theme.isDarkMode = !Theme.isDarkMode
|
||||
}
|
||||
}
|
||||
|
||||
// Label
|
||||
Label {
|
||||
text: Theme.isDarkMode ? "Dark" : "Light"
|
||||
color: Theme.bodyColor
|
||||
font.pixelSize: 13
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Adjustment Entry Point =====
|
||||
RowLayout {
|
||||
id: adjustmentRow
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.settingsRowSpacing
|
||||
|
||||
SettingsActionButton {
|
||||
text: "Edit CSV Metadata"
|
||||
Layout.preferredWidth: Theme.settingsActionWidth
|
||||
Layout.preferredHeight: Theme.settingsButtonHeight
|
||||
onClicked: root.handleEditCsvMetadata()
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom spacer so last group isn't flush with scroll edge
|
||||
Item {
|
||||
Layout.preferredHeight: Theme.settingsSectionSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Scroll hint overlay (fades out when scrolled) =====
|
||||
Rectangle {
|
||||
id: scrollHint
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 48
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: Theme.isDarkMode ? 'rgba(30,30,35,1)' : 'rgba(255,255,255,1)'
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.rgba(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "↓ Scroll for more"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: Theme.bodyColor
|
||||
opacity: (settingsScroll.ScrollBar.vertical.position <= 0 && settingsScroll.contentHeight > settingsScroll.height) ? (scrollHintTimer.running ? 1 : 0) : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Auto-hide scroll hint after first interaction =====
|
||||
Timer {
|
||||
id: scrollHintTimer
|
||||
running: true
|
||||
repeat: false
|
||||
onTriggered: scrollHint.visible = false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user