diff --git a/src/pygui/ISC/Tabs/components/ReplaceSensorDialog.qml b/src/pygui/ISC/Tabs/components/ReplaceSensorDialog.qml
index cf43116..5c1ea37 100644
--- a/src/pygui/ISC/Tabs/components/ReplaceSensorDialog.qml
+++ b/src/pygui/ISC/Tabs/components/ReplaceSensorDialog.qml
@@ -10,6 +10,10 @@ Dialog {
standardButtons: Dialog.NoButton
width: 300
+ // Anchor to the top-left of the window instead of the default centered position.
+ x: 24
+ y: 24
+
// called by WaferMapView's TapHandler
property int sensorIndex: -1
property string sensorLabel: ""
diff --git a/src/pygui/ISC/Tabs/components/TransportBar.qml b/src/pygui/ISC/Tabs/components/TransportBar.qml
index 51b7516..1f03d9d 100644
--- a/src/pygui/ISC/Tabs/components/TransportBar.qml
+++ b/src/pygui/ISC/Tabs/components/TransportBar.qml
@@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Controls
+import QtQuick.Controls.impl
import QtQuick.Layouts
import ISC
@@ -177,8 +178,6 @@ Item {
implicitWidth: 56
implicitHeight: 56
hoverEnabled: true
- text: "⏮"
- font.pixelSize: 24
background: Rectangle {
radius: 8
color: skipStartBtn.pressed ? Theme.transportButtonHover
@@ -188,12 +187,13 @@ Item {
ColorAnimation { duration: Theme.durationFast }
}
}
- contentItem: Text {
- text: parent.text
+ contentItem: IconImage {
+ source: "../icons/prev.svg"
+ width: 22; height: 22
+ sourceSize.width: 22
+ sourceSize.height: 22
color: Theme.headingColor
- font: parent.font
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
+ anchors.centerIn: parent
}
onClicked: streamController.step(-1)
}
@@ -205,8 +205,6 @@ Item {
implicitWidth: 56
implicitHeight: 56
hoverEnabled: true
- text: "■"
- font.pixelSize: 22
background: Rectangle {
radius: 8
color: reviewStopBtn.pressed ? Theme.transportButtonHover
@@ -216,12 +214,13 @@ Item {
ColorAnimation { duration: Theme.durationFast }
}
}
- contentItem: Text {
- text: parent.text
+ contentItem: IconImage {
+ source: "../icons/stop.svg"
+ width: 20; height: 20
+ sourceSize.width: 20
+ sourceSize.height: 20
color: Theme.headingColor
- font: parent.font
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
+ anchors.centerIn: parent
}
onClicked: streamController.stop()
}
@@ -233,8 +232,6 @@ Item {
implicitWidth: 64
implicitHeight: 56
hoverEnabled: true
- text: streamController.playing ? "⏸" : "▶"
- font.pixelSize: 24
background: Rectangle {
radius: 8
color: playPauseBtn.pressed ? Theme.transportButtonHover
@@ -244,13 +241,13 @@ Item {
ColorAnimation { duration: Theme.durationFast }
}
}
- contentItem: Text {
- text: parent.text
+ contentItem: IconImage {
+ source: streamController.playing ? "../icons/pause.svg" : "../icons/play.svg"
+ width: 22; height: 22
+ sourceSize.width: 22
+ sourceSize.height: 22
color: Theme.headingColor
- font: parent.font
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- leftPadding: parent.text === "▶" ? 4 : 0
+ anchors.centerIn: parent
}
onClicked: {
if (streamController.playing) {
@@ -268,8 +265,6 @@ Item {
implicitWidth: 56
implicitHeight: 56
hoverEnabled: true
- text: "⏭"
- font.pixelSize: 24
background: Rectangle {
radius: 8
color: skipEndBtn.pressed ? Theme.transportButtonHover
@@ -279,12 +274,13 @@ Item {
ColorAnimation { duration: Theme.durationFast }
}
}
- contentItem: Text {
- text: parent.text
+ contentItem: IconImage {
+ source: "../icons/next.svg"
+ width: 22; height: 22
+ sourceSize.width: 22
+ sourceSize.height: 22
color: Theme.headingColor
- font: parent.font
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
+ anchors.centerIn: parent
}
onClicked: streamController.step(1)
}
diff --git a/src/pygui/ISC/Tabs/components/WaferMapView.qml b/src/pygui/ISC/Tabs/components/WaferMapView.qml
index ba4bce9..f65258d 100644
--- a/src/pygui/ISC/Tabs/components/WaferMapView.qml
+++ b/src/pygui/ISC/Tabs/components/WaferMapView.qml
@@ -29,6 +29,22 @@ Item {
highColor: Theme.sensorHigh
textColor: Theme.headingColor
+ // Grows the hovered marker smoothly; hoverScale setter triggers a repaint.
+ hoverScale: 1.0 + 0.35 * hoverPulse
+ property real hoverPulse: map.hoveredIndex >= 0 ? 1.0 : 0.0
+ Behavior on hoverPulse {
+ NumberAnimation { duration: 150; easing.type: Easing.OutQuad }
+ }
+
+ HoverHandler {
+ id: hoverHandler
+ onPointChanged: {
+ map.hoveredIndex = map.which_marker(hoverHandler.point.position.x, hoverHandler.point.position.y);
+ }
+ onHoveredChanged: if (!hoverHandler.hovered) map.hoveredIndex = -1
+ cursorShape: map.hoveredIndex >= 0 ? Qt.PointingHandCursor : Qt.ArrowCursor
+ }
+
TapHandler {
onTapped: ev => {
var idx = map.which_marker(ev.position.x, ev.position.y);
diff --git a/src/pygui/ISC/Tabs/icons/back.svg b/src/pygui/ISC/Tabs/icons/back.svg
new file mode 100644
index 0000000..b40bff8
--- /dev/null
+++ b/src/pygui/ISC/Tabs/icons/back.svg
@@ -0,0 +1,14 @@
+
diff --git a/src/pygui/ISC/Tabs/icons/next.svg b/src/pygui/ISC/Tabs/icons/next.svg
new file mode 100644
index 0000000..db62a0c
--- /dev/null
+++ b/src/pygui/ISC/Tabs/icons/next.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/pygui/ISC/Tabs/icons/pause.svg b/src/pygui/ISC/Tabs/icons/pause.svg
new file mode 100644
index 0000000..47b0c5c
--- /dev/null
+++ b/src/pygui/ISC/Tabs/icons/pause.svg
@@ -0,0 +1,14 @@
+
diff --git a/src/pygui/ISC/Tabs/icons/play.svg b/src/pygui/ISC/Tabs/icons/play.svg
new file mode 100644
index 0000000..5bd511c
--- /dev/null
+++ b/src/pygui/ISC/Tabs/icons/play.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/pygui/ISC/Tabs/icons/prev.svg b/src/pygui/ISC/Tabs/icons/prev.svg
new file mode 100644
index 0000000..056d5ab
--- /dev/null
+++ b/src/pygui/ISC/Tabs/icons/prev.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/pygui/ISC/Tabs/icons/stop.svg b/src/pygui/ISC/Tabs/icons/stop.svg
new file mode 100644
index 0000000..7862733
--- /dev/null
+++ b/src/pygui/ISC/Tabs/icons/stop.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/pygui/backend/visualization/wafer_map_item.py b/src/pygui/backend/visualization/wafer_map_item.py
index 96310cc..a74d086 100644
--- a/src/pygui/backend/visualization/wafer_map_item.py
+++ b/src/pygui/backend/visualization/wafer_map_item.py
@@ -52,6 +52,8 @@ class WaferMapItem(QQuickPaintedItem):
sizeChanged = Signal()
thicknessChanged = Signal()
showThicknessChanged = Signal()
+ hoveredIndexChanged = Signal()
+ hoverScaleChanged = Signal()
def __init__(self, parent=None):
@@ -68,6 +70,10 @@ class WaferMapItem(QQuickPaintedItem):
self._thickness_data: list[float] = []
self._show_thickness: bool = False
self._thickness_heatmap:QImage | None = None
+ # Hover highlight: index of the marker under the pointer (-1 = none)
+ # and a 1.0->~1.5 grow factor driven by a QML Behavior for a smooth animation.
+ self._hovered_index: int = -1
+ self._hover_scale: float = 1.0
# Dark-theme color defaults (match Theme.qml tokens)
self._ring_color = QColor("#2A3441") # waferRingColor (toneBorder)
@@ -81,6 +87,12 @@ class WaferMapItem(QQuickPaintedItem):
self._markers: dict[int, tuple[int, int]] = {} # sensor index → (px, py)
self._marker_r: int = 4
self._heatmap: QImage | None = None
+ # Size the markers/heatmap were last computed against. On first activation
+ # (e.g. a freshly-created Loader item) width()/height() can still be 0/stale
+ # when `sensors` is first set, computing degenerate marker positions that
+ # never get retried. paint() re-checks this every frame so geometry that
+ # settles after property assignment still gets picked up.
+ self._last_draw_size: int = -1
self.widthChanged.connect(self._on_resize)
self.heightChanged.connect(self._on_resize)
@@ -213,6 +225,29 @@ class WaferMapItem(QQuickPaintedItem):
self.showThicknessChanged.emit()
self.update()
+ @Property(int, notify=hoveredIndexChanged)
+ def hoveredIndex(self) -> int:
+ return self._hovered_index
+
+ @hoveredIndex.setter
+ def hoveredIndex(self, val: int) -> None:
+ val = int(val)
+ if val == self._hovered_index:
+ return
+ self._hovered_index = val
+ self.hoveredIndexChanged.emit()
+ self.update()
+
+ @Property(float, notify=hoverScaleChanged)
+ def hoverScale(self) -> float:
+ return self._hover_scale
+
+ @hoverScale.setter
+ def hoverScale(self, val: float) -> None:
+ self._hover_scale = float(val)
+ self.hoverScaleChanged.emit()
+ self.update()
+
# Colour properties — QML can bind these to Theme tokens
@Property(QColor, notify=colorsChanged)
def ringColor(self) -> QColor: return self._ring_color
@@ -440,7 +475,15 @@ class WaferMapItem(QQuickPaintedItem):
# ── paint ─────────────────────────────────────────────────────────────
def paint(self, painter: QPainter) -> None:
- ds = self._draw_size()
+ ds = self._draw_size()
+ if ds != self._last_draw_size:
+ # Geometry settled (or changed) since markers/heatmap were last built
+ # against a stale size — recompute now, at actual paint time.
+ self._last_draw_size = ds
+ self._compute_markers()
+ self._rebuild_heatmap()
+ self._rebuild_thickness()
+
r_px = int(ds / 2 - 4)
cx, cy = self._center()
@@ -544,10 +587,22 @@ class WaferMapItem(QQuickPaintedItem):
self._bands[i] if i < len(self._bands) else "in_range",
self._in_range_color,
)
+ is_hovered = i == self._hovered_index
+ marker_r = int(round(r * self._hover_scale)) if is_hovered else r
+
+ if is_hovered:
+ # Soft glow ring behind the marker, growing with hoverScale.
+ glow_r = int(round(marker_r * 1.9))
+ glow_color = QColor(color)
+ glow_color.setAlpha(70)
+ painter.setPen(Qt.PenStyle.NoPen)
+ painter.setBrush(QBrush(glow_color))
+ painter.drawEllipse(px - glow_r, py - glow_r, 2 * glow_r, 2 * glow_r)
+
# Filled circle with thin dark outline for contrast over heatmap
painter.setPen(QPen(QColor(0, 0, 0, 100), 1))
painter.setBrush(QBrush(color))
- painter.drawEllipse(px - r, py - r, 2 * r, 2 * r)
+ painter.drawEllipse(px - marker_r, py - marker_r, 2 * marker_r, 2 * marker_r)
if self._show_labels:
has_temp = i < len(self._values)