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,222 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import ISC
|
||||
|
||||
// ===== Home Workspace Shell =====
|
||||
Rectangle {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
color: Theme.pageBackground
|
||||
clip: true
|
||||
border.color: Theme.outerFrameBorder
|
||||
border.width: Theme.borderStrong
|
||||
|
||||
// ===== Navigation Model =====
|
||||
// Primary navigation shown in the left rail.
|
||||
property var sideActions: ["DETECT WAFER", "READ MEMORY", "OPEN CSV IN EXCEL", "ERASE MEMORY", "IMPORT DATA", "STORED DATA"]
|
||||
|
||||
// ===== Footer Tab Model =====
|
||||
// Footer tabs drive the active workspace section.
|
||||
property var bottomTabs: ["Status", "Graph", "Data", "Wafer Map", "Compare", "Split", "Settings", "About"]
|
||||
|
||||
// ===== View State =====
|
||||
property int selectedTabIndex: 0
|
||||
property int selectedSideActionIndex: -1 // nothing active on startup
|
||||
|
||||
// ===== Main Two-Column Layout =====
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// ===== Left Action Rail =====
|
||||
// Left control rail.
|
||||
Rectangle {
|
||||
id: sideRail
|
||||
Layout.preferredWidth: Theme.sideRailWidth
|
||||
Layout.fillHeight: true
|
||||
color: Theme.sideRailBackground
|
||||
border.color: Theme.workspaceBorder
|
||||
border.width: Theme.borderThin
|
||||
|
||||
readonly property int actionCount: root.sideActions.length
|
||||
readonly property real computedButtonHeight: Math.min(Theme.sideButtonHeight, (height - (Theme.panelPadding * 2) - (Theme.sideRailSpacing * Math.max(0, actionCount - 1))) / Math.max(1, actionCount))
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.panelPadding
|
||||
spacing: Theme.sideRailSpacing
|
||||
|
||||
Repeater {
|
||||
model: root.sideActions
|
||||
|
||||
Button {
|
||||
id: control
|
||||
text: modelData
|
||||
property bool isActive: index === root.selectedSideActionIndex
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.max(Theme.sideButtonMinHeight, sideRail.computedButtonHeight)
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
root.selectedSideActionIndex = index
|
||||
root.selectedTabIndex = 0 // always jump to Status tab
|
||||
if (index === 0) deviceController.detectWafer()
|
||||
else if (index === 1) deviceController.readMemoryAsync()
|
||||
else if (index === 2) deviceController.openCsvFile()
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: {
|
||||
if (control.down) {
|
||||
return Theme.buttonPressed;
|
||||
}
|
||||
if (control.isActive) {
|
||||
return Theme.sideActiveBackground;
|
||||
}
|
||||
return "transparent";
|
||||
}
|
||||
border.color: control.isActive ? Theme.cardBorder : "transparent"
|
||||
border.width: 1
|
||||
radius: Theme.radiusSm
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: 3
|
||||
visible: control.isActive
|
||||
color: Theme.primaryAccent
|
||||
radius: Theme.radiusXs
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
color: control.isActive ? Theme.headingColor : Theme.bodyColor
|
||||
font.bold: control.isActive
|
||||
font.pixelSize: 18
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main workspace and footer navigation.
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Theme.workspaceBackground
|
||||
border.color: Theme.workspaceBorder
|
||||
border.width: Theme.borderThin
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.mainAreaPadding
|
||||
spacing: Theme.rightPaneGap
|
||||
|
||||
// ===== Active Tab Content Area =====
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 220
|
||||
color: Theme.responseBackground
|
||||
border.color: Theme.responseBorder
|
||||
border.width: 1
|
||||
radius: Theme.radiusMd
|
||||
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
currentIndex: root.selectedTabIndex
|
||||
|
||||
// ===== Tab Content Routing =====
|
||||
Repeater {
|
||||
model: root.bottomTabs
|
||||
|
||||
Item {
|
||||
property string tabName: modelData
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: parent.tabName === "Settings"
|
||||
source: parent.tabName === "Settings" ? "Tabs/SettingsTab.qml" : ""
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: parent.tabName === "Status"
|
||||
source: parent.tabName === "Status" ? "Tabs/StatusTab.qml" : ""
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: parent.tabName === "Data"
|
||||
source: parent.tabName === "Data" ? "Tabs/DataTab.qml" : ""
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
visible: parent.tabName !== "Settings" && parent.tabName !== "Status" && parent.tabName !== "Data"
|
||||
text: parent.tabName + " content"
|
||||
color: Theme.bodyColor
|
||||
font.pixelSize: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the tab strip evenly distributed across the footer.
|
||||
// ===== Footer Tab Strip =====
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Theme.tabBarHeight + (Theme.tabBarPadding * 2)
|
||||
color: Theme.tabBarBackground
|
||||
border.color: Theme.workspaceBorder
|
||||
border.width: Theme.borderThin
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.tabBarPadding
|
||||
spacing: Theme.tabSpacing
|
||||
|
||||
Repeater {
|
||||
model: root.bottomTabs
|
||||
|
||||
Button {
|
||||
id: botTabBtn
|
||||
property bool isActive: index === root.selectedTabIndex
|
||||
|
||||
text: modelData
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Theme.tabBarHeight
|
||||
Layout.minimumWidth: Theme.tabButtonMinWidth
|
||||
onClicked: root.selectedTabIndex = index
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle {
|
||||
color: botTabBtn.isActive ? Theme.tabActiveBackground : (botTabBtn.hovered ? Theme.tabHoverBackground : Theme.tabBackground)
|
||||
border.color: Theme.tabBorder
|
||||
border.width: 1
|
||||
radius: Theme.tabRadius
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: botTabBtn.text
|
||||
color: botTabBtn.isActive ? Theme.tabActiveText : Theme.tabText
|
||||
font.pixelSize: Theme.tabFontSize
|
||||
font.bold: botTabBtn.isActive
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user