summaryrefslogtreecommitdiff
path: root/components/controls
diff options
context:
space:
mode:
authorATMDA <atdma2600@gmail.com>2025-11-19 21:04:32 -0500
committerATMDA <atdma2600@gmail.com>2025-11-19 21:04:32 -0500
commitef46a02b2f3561574c1ada5afefbbb806bcb6a3b (patch)
tree94a4c218e521fdd6b55f3154de4484593cd61829 /components/controls
parentrefactor: Updated Bluetooth, Ethernet, and Wireless lists to use DeviceList c... (diff)
downloadcaelestia-shell-ef46a02b2f3561574c1ada5afefbbb806bcb6a3b.tar.gz
caelestia-shell-ef46a02b2f3561574c1ada5afefbbb806bcb6a3b.tar.bz2
caelestia-shell-ef46a02b2f3561574c1ada5afefbbb806bcb6a3b.zip
refactor: ToggleButton Tooltips and DeviceList
Diffstat (limited to 'components/controls')
-rw-r--r--components/controls/ToggleButton.qml51
-rw-r--r--components/controls/Tooltip.qml170
2 files changed, 218 insertions, 3 deletions
diff --git a/components/controls/ToggleButton.qml b/components/controls/ToggleButton.qml
index 9d8e094..b2c2afe 100644
--- a/components/controls/ToggleButton.qml
+++ b/components/controls/ToggleButton.qml
@@ -1,5 +1,6 @@
import ".."
import qs.components
+import qs.components.controls
import qs.components.effects
import qs.services
import qs.config
@@ -13,12 +14,31 @@ StyledRect {
property string icon
property string label
property string accent: "Secondary"
+ property real iconSize: Appearance.font.size.large
+ property real horizontalPadding: Appearance.padding.large
+ property real verticalPadding: Appearance.padding.normal
+ property string tooltip: ""
+ property bool hovered: false
signal clicked
+ Component.onCompleted: {
+ hovered = toggleStateLayer.containsMouse;
+ }
+
+ Connections {
+ target: toggleStateLayer
+ function onContainsMouseChanged() {
+ const newHovered = toggleStateLayer.containsMouse;
+ if (hovered !== newHovered) {
+ hovered = newHovered;
+ }
+ }
+ }
+
Layout.preferredWidth: implicitWidth + (toggleStateLayer.pressed ? Appearance.padding.normal * 2 : toggled ? Appearance.padding.small * 2 : 0)
- implicitWidth: toggleBtnInner.implicitWidth + Appearance.padding.large * 2
- implicitHeight: toggleBtnIcon.implicitHeight + Appearance.padding.normal * 2
+ implicitWidth: toggleBtnInner.implicitWidth + horizontalPadding * 2
+ implicitHeight: toggleBtnIcon.implicitHeight + verticalPadding * 2
radius: toggled || toggleStateLayer.pressed ? Appearance.rounding.small : Math.min(width, height) / 2 * Math.min(1, Appearance.rounding.scale)
color: toggled ? Colours.palette[`m3${accent.toLowerCase()}`] : Colours.palette[`m3${accent.toLowerCase()}Container`]
@@ -46,7 +66,7 @@ StyledRect {
fill: root.toggled ? 1 : 0
text: root.icon
color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
- font.pointSize: Appearance.font.size.large
+ font.pointSize: root.iconSize
Behavior on fill {
Anim {}
@@ -78,5 +98,30 @@ StyledRect {
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
+
+ // Tooltip - positioned absolutely, doesn't affect layout
+ Loader {
+ id: tooltipLoader
+ active: root.tooltip !== ""
+ asynchronous: true
+ z: 10000
+ width: 0
+ height: 0
+ sourceComponent: Component {
+ Tooltip {
+ target: root
+ text: root.tooltip
+ }
+ }
+ // Completely remove from layout
+ Layout.fillWidth: false
+ Layout.fillHeight: false
+ Layout.preferredWidth: 0
+ Layout.preferredHeight: 0
+ Layout.maximumWidth: 0
+ Layout.maximumHeight: 0
+ Layout.minimumWidth: 0
+ Layout.minimumHeight: 0
+ }
}
diff --git a/components/controls/Tooltip.qml b/components/controls/Tooltip.qml
new file mode 100644
index 0000000..bab8086
--- /dev/null
+++ b/components/controls/Tooltip.qml
@@ -0,0 +1,170 @@
+import ".."
+import qs.components.effects
+import qs.services
+import qs.config
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Popup {
+ id: root
+
+ required property Item target
+ required property string text
+ property int delay: 500
+ property int timeout: 0
+
+ property bool tooltipVisible: false
+ property Timer showTimer: Timer {
+ interval: root.delay
+ onTriggered: root.tooltipVisible = true
+ }
+ property Timer hideTimer: Timer {
+ interval: root.timeout
+ onTriggered: root.tooltipVisible = false
+ }
+
+ // Popup properties - doesn't affect layout
+ parent: {
+ let p = target;
+ // Walk up to find the root Item (usually has anchors.fill: parent)
+ while (p && p.parent) {
+ const parentItem = p.parent;
+ // Check if this looks like a root pane Item
+ if (parentItem && parentItem.anchors && parentItem.anchors.fill !== undefined) {
+ return parentItem;
+ }
+ p = parentItem;
+ }
+ // Fallback
+ return target.parent?.parent?.parent ?? target.parent?.parent ?? target.parent ?? target;
+ }
+
+ visible: tooltipVisible
+ modal: false
+ closePolicy: Popup.NoAutoClose
+ padding: 0
+ margins: 0
+
+ // Update position when target moves or tooltip becomes visible
+ onTooltipVisibleChanged: {
+ if (tooltipVisible) {
+ Qt.callLater(updatePosition);
+ }
+ }
+ Connections {
+ target: root.target
+ function onXChanged() { if (root.tooltipVisible) root.updatePosition(); }
+ function onYChanged() { if (root.tooltipVisible) root.updatePosition(); }
+ function onWidthChanged() { if (root.tooltipVisible) root.updatePosition(); }
+ function onHeightChanged() { if (root.tooltipVisible) root.updatePosition(); }
+ }
+
+ function updatePosition() {
+ if (!target || !parent) return;
+
+ // Wait for tooltipRect to have its size calculated
+ Qt.callLater(() => {
+ if (!target || !parent || !tooltipRect) return;
+
+ // Get target position in parent's coordinate system
+ const targetPos = target.mapToItem(parent, 0, 0);
+ const targetCenterX = targetPos.x + target.width / 2;
+
+ // Get tooltip size (use width/height if available, otherwise implicit)
+ const tooltipWidth = tooltipRect.width > 0 ? tooltipRect.width : tooltipRect.implicitWidth;
+ const tooltipHeight = tooltipRect.height > 0 ? tooltipRect.height : tooltipRect.implicitHeight;
+
+ // Center tooltip horizontally on target
+ let newX = targetCenterX - tooltipWidth / 2;
+
+ // Position tooltip above target
+ let newY = targetPos.y - tooltipHeight - Appearance.spacing.small;
+
+ // Keep within bounds
+ const padding = Appearance.padding.normal;
+ if (newX < padding) {
+ newX = padding;
+ } else if (newX + tooltipWidth > (parent.width - padding)) {
+ newX = parent.width - tooltipWidth - padding;
+ }
+
+ // Update popup position
+ x = newX;
+ y = newY;
+ });
+ }
+
+ enter: Transition {
+ Anim {
+ property: "opacity"
+ from: 0
+ to: 1
+ duration: Appearance.anim.durations.expressiveFastSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
+ }
+ }
+
+ exit: Transition {
+ Anim {
+ property: "opacity"
+ from: 1
+ to: 0
+ duration: Appearance.anim.durations.expressiveFastSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
+ }
+ }
+
+ // Monitor hover state
+ Connections {
+ target: root.target
+ function onHoveredChanged() {
+ if (target.hovered) {
+ showTimer.start();
+ if (timeout > 0) {
+ hideTimer.stop();
+ hideTimer.start();
+ }
+ } else {
+ showTimer.stop();
+ hideTimer.stop();
+ tooltipVisible = false;
+ }
+ }
+ }
+
+ contentItem: StyledRect {
+ id: tooltipRect
+
+ implicitWidth: tooltipText.implicitWidth + Appearance.padding.normal * 2
+ implicitHeight: tooltipText.implicitHeight + Appearance.padding.smaller * 2
+
+ color: Colours.palette.m3surfaceContainerHighest
+ radius: Appearance.rounding.small
+
+ // Add elevation for depth
+ Elevation {
+ anchors.fill: parent
+ radius: parent.radius
+ z: -1
+ level: 3
+ }
+
+ StyledText {
+ id: tooltipText
+
+ anchors.centerIn: parent
+
+ text: root.text
+ color: Colours.palette.m3onSurface
+ font.pointSize: Appearance.font.size.small
+ }
+ }
+
+ Component.onCompleted: {
+ if (tooltipVisible) {
+ updatePosition();
+ }
+ }
+}
+