mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
bar: widgets now use the entire bar space (e.g: full height with a vertical bar) this ease a lot for clicking widgets and avoid dead zones above and below the widget.
Keep an eye on this + plugins bar widget will need updating
This commit is contained in:
@@ -34,13 +34,18 @@ Item {
|
||||
signal middleClicked
|
||||
signal wheel(int delta)
|
||||
|
||||
// Dynamic sizing based on loaded component
|
||||
width: pillLoader.item ? pillLoader.item.width : 0
|
||||
height: pillLoader.item ? pillLoader.item.height : 0
|
||||
// Size based on content for the content dimension, fill parent for the extended dimension
|
||||
// Horizontal bars: width = content, height = fill parent (for extended click area)
|
||||
// Vertical bars: width = fill parent, height = content
|
||||
width: isVerticalBar ? parent.width : (pillLoader.item ? pillLoader.item.implicitWidth : 0)
|
||||
height: isVerticalBar ? (pillLoader.item ? pillLoader.item.implicitHeight : 0) : parent.height
|
||||
implicitWidth: pillLoader.item ? pillLoader.item.implicitWidth : 0
|
||||
implicitHeight: pillLoader.item ? pillLoader.item.implicitHeight : 0
|
||||
|
||||
// Loader to switch between vertical and horizontal pill implementations
|
||||
// Loader fills BarPill so child components can extend to full bar dimension
|
||||
Loader {
|
||||
id: pillLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: isVerticalBar ? verticalPillComponent : horizontalPillComponent
|
||||
|
||||
Component {
|
||||
|
||||
@@ -53,7 +53,8 @@ Item {
|
||||
|
||||
readonly property real iconSize: Style.toOdd(pillHeight * 0.48)
|
||||
|
||||
width: {
|
||||
// Content width calculation (for implicit sizing)
|
||||
readonly property real contentWidth: {
|
||||
if (collapseToIcon) {
|
||||
return hasIcon ? pillHeight : 0;
|
||||
}
|
||||
@@ -61,7 +62,12 @@ Item {
|
||||
var baseWidth = hasIcon ? pillHeight : 0;
|
||||
return baseWidth + Math.max(0, pill.width - overlap);
|
||||
}
|
||||
height: pillHeight
|
||||
|
||||
// Fill parent to extend click area to full bar height
|
||||
// Visual content is centered vertically within
|
||||
anchors.fill: parent
|
||||
implicitWidth: contentWidth
|
||||
implicitHeight: pillHeight
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
|
||||
@@ -60,9 +60,8 @@ Item {
|
||||
|
||||
readonly property real iconSize: Style.toOdd(pillHeight * 0.48)
|
||||
|
||||
// For vertical bars: width is just icon size, height includes pill space
|
||||
width: buttonSize
|
||||
height: {
|
||||
// Content height calculation (for implicit sizing and visual layout)
|
||||
readonly property real contentHeight: {
|
||||
if (collapseToIcon) {
|
||||
return hasIcon ? buttonSize : 0;
|
||||
}
|
||||
@@ -75,6 +74,15 @@ Item {
|
||||
return buttonSize;
|
||||
}
|
||||
|
||||
// Fill parent width to extend horizontal click area
|
||||
// Keep content-based height for visual layout
|
||||
anchors.left: parent ? parent.left : undefined
|
||||
anchors.right: parent ? parent.right : undefined
|
||||
anchors.verticalCenter: parent ? parent.verticalCenter : undefined
|
||||
height: contentHeight
|
||||
implicitWidth: buttonSize
|
||||
implicitHeight: contentHeight
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onTooltipTextChanged() {
|
||||
@@ -88,7 +96,7 @@ Item {
|
||||
Rectangle {
|
||||
id: pillBackground
|
||||
width: buttonSize
|
||||
height: root.height
|
||||
height: root.contentHeight
|
||||
radius: Style.radiusM
|
||||
color: root.bgColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
@@ -183,7 +191,7 @@ Item {
|
||||
|
||||
// Icon positioning based on direction
|
||||
x: 0
|
||||
y: openUpward ? (parent.height - height) : 0
|
||||
y: openUpward ? (root.contentHeight - height) : 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
NIcon {
|
||||
|
||||
@@ -15,9 +15,16 @@ Item {
|
||||
readonly property string section: widgetProps ? (widgetProps.section || "") : ""
|
||||
readonly property int sectionIndex: widgetProps ? (widgetProps.sectionWidgetIndex || 0) : 0
|
||||
|
||||
// Don't reserve space unless the loaded widget is really visible
|
||||
implicitWidth: getImplicitSize(loader.item, "implicitWidth")
|
||||
implicitHeight: getImplicitSize(loader.item, "implicitHeight")
|
||||
// Bar orientation and height for extended click areas
|
||||
readonly property string barPosition: Settings.getBarPositionForScreen(widgetScreen?.name)
|
||||
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
|
||||
readonly property real barHeight: Style.getBarHeightForScreen(widgetScreen?.name)
|
||||
|
||||
// Request full bar dimension from layout to extend click areas above/below widgets
|
||||
// For horizontal bars: full bar height, widget's content width
|
||||
// For vertical bars: full bar width, widget's content height
|
||||
implicitWidth: isVerticalBar ? barHeight : getImplicitSize(loader.item, "implicitWidth")
|
||||
implicitHeight: isVerticalBar ? getImplicitSize(loader.item, "implicitHeight") : barHeight
|
||||
|
||||
// Remove layout space left by hidden widgets
|
||||
visible: loader.item ? ((loader.item.opacity > 0.0) || (loader.item.hasOwnProperty("hideMode") && loader.item.hideMode === "transparent")) : false
|
||||
@@ -66,6 +73,19 @@ Item {
|
||||
|
||||
Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name);
|
||||
|
||||
// Extend widget to fill full bar dimension for extended click areas
|
||||
// For horizontal bars: widget fills bar height (content width preserved)
|
||||
// For vertical bars: widget fills bar width (content height preserved)
|
||||
if (root.isVerticalBar) {
|
||||
item.width = Qt.binding(function () {
|
||||
return root.barHeight;
|
||||
});
|
||||
} else {
|
||||
item.height = Qt.binding(function () {
|
||||
return root.barHeight;
|
||||
});
|
||||
}
|
||||
|
||||
// Apply properties to loaded widget
|
||||
for (var prop in widgetProps) {
|
||||
if (item.hasOwnProperty(prop)) {
|
||||
|
||||
@@ -76,8 +76,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: !shouldShow ? 0 : isVerticalBar ? capsuleHeight : visualizerWidth
|
||||
implicitHeight: !shouldShow ? 0 : isVerticalBar ? visualizerWidth : capsuleHeight
|
||||
// Content dimensions for implicit sizing
|
||||
readonly property real contentWidth: !shouldShow ? 0 : isVerticalBar ? capsuleHeight : visualizerWidth
|
||||
readonly property real contentHeight: !shouldShow ? 0 : isVerticalBar ? visualizerWidth : capsuleHeight
|
||||
|
||||
implicitWidth: contentWidth
|
||||
implicitHeight: contentHeight
|
||||
visible: shouldShow
|
||||
opacity: shouldShow ? 1.0 : 0.0
|
||||
|
||||
@@ -94,37 +98,40 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Store visualizer type to force re-evaluation
|
||||
readonly property string currentVisualizerType: Settings.data.audio.visualizerType
|
||||
|
||||
// Visual capsule centered in parent
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
width: root.contentWidth
|
||||
height: root.contentHeight
|
||||
anchors.centerIn: parent
|
||||
radius: Style.radiusS
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
}
|
||||
|
||||
// Store visualizer type to force re-evaluation
|
||||
readonly property string currentVisualizerType: Settings.data.audio.visualizerType
|
||||
// When visualizer type or playback changes, shouldShow updates automatically
|
||||
// The Loader dynamically loads the appropriate visualizer based on settings
|
||||
Loader {
|
||||
id: visualizerLoader
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
active: shouldShow
|
||||
asynchronous: true
|
||||
|
||||
// When visualizer type or playback changes, shouldShow updates automatically
|
||||
// The Loader dynamically loads the appropriate visualizer based on settings
|
||||
Loader {
|
||||
id: visualizerLoader
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
active: shouldShow
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: {
|
||||
switch (currentVisualizerType) {
|
||||
case "linear":
|
||||
return linearComponent;
|
||||
case "mirrored":
|
||||
return mirroredComponent;
|
||||
case "wave":
|
||||
return waveComponent;
|
||||
default:
|
||||
return null;
|
||||
sourceComponent: {
|
||||
switch (currentVisualizerType) {
|
||||
case "linear":
|
||||
return linearComponent;
|
||||
case "mirrored":
|
||||
return mirroredComponent;
|
||||
case "wave":
|
||||
return waveComponent;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import qs.Modules.Bar.Extras
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
@@ -45,76 +45,89 @@ Rectangle {
|
||||
readonly property string formatVertical: widgetSettings.formatVertical !== undefined ? widgetSettings.formatVertical : widgetMetadata.formatVertical
|
||||
readonly property string tooltipFormat: widgetSettings.tooltipFormat !== undefined ? widgetSettings.tooltipFormat : widgetMetadata.tooltipFormat
|
||||
|
||||
implicitWidth: isBarVertical ? capsuleHeight : Math.round((isBarVertical ? verticalLoader.implicitWidth : horizontalLoader.implicitWidth) + Style.marginXL)
|
||||
// Content dimensions for implicit sizing
|
||||
readonly property real contentWidth: isBarVertical ? capsuleHeight : Math.round((isBarVertical ? verticalLoader.implicitWidth : horizontalLoader.implicitWidth) + Style.marginXL)
|
||||
readonly property real contentHeight: isBarVertical ? Math.round(verticalLoader.implicitHeight + Style.marginS * 2) : capsuleHeight
|
||||
|
||||
implicitHeight: isBarVertical ? Math.round(verticalLoader.implicitHeight + Style.marginS * 2) : capsuleHeight
|
||||
// Size: use implicit width/height
|
||||
// BarWidgetLoader sets explicit width/height to extend click area
|
||||
implicitWidth: contentWidth
|
||||
implicitHeight: contentHeight
|
||||
|
||||
radius: Style.radiusS
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
|
||||
Item {
|
||||
id: clockContainer
|
||||
// Visual clock capsule - stays at content size, centered in parent
|
||||
Rectangle {
|
||||
id: visualClock
|
||||
width: root.contentWidth
|
||||
height: root.contentHeight
|
||||
anchors.centerIn: parent
|
||||
|
||||
// Horizontal
|
||||
Loader {
|
||||
id: horizontalLoader
|
||||
active: !isBarVertical
|
||||
radius: Style.radiusS
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
|
||||
Item {
|
||||
id: clockContainer
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: ColumnLayout {
|
||||
|
||||
// Horizontal
|
||||
Loader {
|
||||
id: horizontalLoader
|
||||
active: !isBarVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Settings.data.bar.showCapsule ? -5 : -3
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: I18n.locale.toString(now, formatHorizontal.trim()).split("\\n")
|
||||
NText {
|
||||
visible: text !== ""
|
||||
text: modelData
|
||||
family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault
|
||||
Binding on pointSize {
|
||||
value: {
|
||||
if (repeater.model.length == 1) {
|
||||
// Single line: Full size
|
||||
return barFontSize;
|
||||
} else if (repeater.model.length == 2) {
|
||||
// Two lines: First line is bigger than the second
|
||||
return (index == 0) ? Math.round(barFontSize * 0.9) : Math.round(barFontSize * 0.75);
|
||||
} else {
|
||||
// More than two lines: Make it small!
|
||||
return Math.round(barFontSize * 0.75);
|
||||
sourceComponent: ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Settings.data.bar.showCapsule ? -5 : -3
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: I18n.locale.toString(now, formatHorizontal.trim()).split("\\n")
|
||||
NText {
|
||||
visible: text !== ""
|
||||
text: modelData
|
||||
family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault
|
||||
Binding on pointSize {
|
||||
value: {
|
||||
if (repeater.model.length == 1) {
|
||||
// Single line: Full size
|
||||
return barFontSize;
|
||||
} else if (repeater.model.length == 2) {
|
||||
// Two lines: First line is bigger than the second
|
||||
return (index == 0) ? Math.round(barFontSize * 0.9) : Math.round(barFontSize * 0.75);
|
||||
} else {
|
||||
// More than two lines: Make it small!
|
||||
return Math.round(barFontSize * 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
applyUiScale: false
|
||||
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
applyUiScale: false
|
||||
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical
|
||||
Loader {
|
||||
id: verticalLoader
|
||||
active: isBarVertical
|
||||
anchors.centerIn: parent // Now this works without layout conflicts
|
||||
sourceComponent: ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: -2
|
||||
Repeater {
|
||||
model: I18n.locale.toString(now, formatVertical.trim()).split(" ")
|
||||
delegate: NText {
|
||||
visible: text !== ""
|
||||
text: modelData
|
||||
family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault
|
||||
pointSize: barFontSize
|
||||
applyUiScale: false
|
||||
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
// Vertical
|
||||
Loader {
|
||||
id: verticalLoader
|
||||
active: isBarVertical
|
||||
anchors.centerIn: parent // Now this works without layout conflicts
|
||||
sourceComponent: ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: -2
|
||||
Repeater {
|
||||
model: I18n.locale.toString(now, formatVertical.trim()).split(" ")
|
||||
delegate: NText {
|
||||
visible: text !== ""
|
||||
text: modelData
|
||||
family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault
|
||||
pointSize: barFontSize
|
||||
applyUiScale: false
|
||||
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import qs.Services.Keyboard
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
@@ -47,15 +47,12 @@ Rectangle {
|
||||
|
||||
readonly property bool hideWhenOff: (widgetSettings.hideWhenOff !== undefined) ? widgetSettings.hideWhenOff : (widgetMetadata.hideWhenOff !== undefined ? widgetMetadata.hideWhenOff : false)
|
||||
|
||||
implicitWidth: isVertical ? capsuleHeight : Math.round(layout.implicitWidth + Style.marginXL)
|
||||
implicitHeight: isVertical ? Math.round(layout.implicitHeight + Style.marginXL) : capsuleHeight
|
||||
// Content dimensions for implicit sizing
|
||||
readonly property real contentWidth: isVertical ? capsuleHeight : Math.round(layout.implicitWidth + Style.marginXL)
|
||||
readonly property real contentHeight: isVertical ? Math.round(layout.implicitHeight + Style.marginXL) : capsuleHeight
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
implicitWidth: contentWidth
|
||||
implicitHeight: contentHeight
|
||||
|
||||
NPopupContextMenu {
|
||||
id: contextMenu
|
||||
@@ -78,6 +75,72 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Visual capsule centered in parent
|
||||
Rectangle {
|
||||
id: visualCapsule
|
||||
width: root.contentWidth
|
||||
height: root.contentHeight
|
||||
anchors.centerIn: parent
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
|
||||
Item {
|
||||
id: layout
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
implicitWidth: rowLayout.visible ? rowLayout.implicitWidth : colLayout.implicitWidth
|
||||
implicitHeight: rowLayout.visible ? rowLayout.implicitHeight : colLayout.implicitHeight
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
visible: !root.isVertical
|
||||
spacing: 0
|
||||
|
||||
NIcon {
|
||||
visible: root.showCaps && (!root.hideWhenOff || LockKeysService.capsLockOn)
|
||||
icon: root.capsIcon
|
||||
color: LockKeysService.capsLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
NIcon {
|
||||
visible: root.showNum && (!root.hideWhenOff || LockKeysService.numLockOn)
|
||||
icon: root.numIcon
|
||||
color: LockKeysService.numLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
NIcon {
|
||||
visible: root.showScroll && (!root.hideWhenOff || LockKeysService.scrollLockOn)
|
||||
icon: root.scrollIcon
|
||||
color: LockKeysService.scrollLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: colLayout
|
||||
visible: root.isVertical
|
||||
spacing: 0
|
||||
|
||||
NIcon {
|
||||
visible: root.showCaps && (!root.hideWhenOff || LockKeysService.capsLockOn)
|
||||
icon: root.capsIcon
|
||||
color: LockKeysService.capsLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
NIcon {
|
||||
visible: root.showNum && (!root.hideWhenOff || LockKeysService.numLockOn)
|
||||
icon: root.numIcon
|
||||
color: LockKeysService.numLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
NIcon {
|
||||
visible: root.showScroll && (!root.hideWhenOff || LockKeysService.scrollLockOn)
|
||||
icon: root.scrollIcon
|
||||
color: LockKeysService.scrollLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
}
|
||||
} // closes layout
|
||||
} // closes visualCapsule
|
||||
|
||||
// MouseArea at root level for extended click area
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
@@ -87,57 +150,4 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: layout
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
implicitWidth: rowLayout.visible ? rowLayout.implicitWidth : colLayout.implicitWidth
|
||||
implicitHeight: rowLayout.visible ? rowLayout.implicitHeight : colLayout.implicitHeight
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
visible: !root.isVertical
|
||||
spacing: 0
|
||||
|
||||
NIcon {
|
||||
visible: root.showCaps && (!root.hideWhenOff || LockKeysService.capsLockOn)
|
||||
icon: root.capsIcon
|
||||
color: LockKeysService.capsLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
NIcon {
|
||||
visible: root.showNum && (!root.hideWhenOff || LockKeysService.numLockOn)
|
||||
icon: root.numIcon
|
||||
color: LockKeysService.numLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
NIcon {
|
||||
visible: root.showScroll && (!root.hideWhenOff || LockKeysService.scrollLockOn)
|
||||
icon: root.scrollIcon
|
||||
color: LockKeysService.scrollLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: colLayout
|
||||
visible: root.isVertical
|
||||
spacing: 0
|
||||
|
||||
NIcon {
|
||||
visible: root.showCaps && (!root.hideWhenOff || LockKeysService.capsLockOn)
|
||||
icon: root.capsIcon
|
||||
color: LockKeysService.capsLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
NIcon {
|
||||
visible: root.showNum && (!root.hideWhenOff || LockKeysService.numLockOn)
|
||||
icon: root.numIcon
|
||||
color: LockKeysService.numLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
NIcon {
|
||||
visible: root.showScroll && (!root.hideWhenOff || LockKeysService.scrollLockOn)
|
||||
icon: root.scrollIcon
|
||||
color: LockKeysService.scrollLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,11 +227,11 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Main container
|
||||
// Main container - stays at content size, pixel-perfect centered in parent
|
||||
Rectangle {
|
||||
id: container
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: Style.pixelAlignCenter(parent.width, width)
|
||||
y: Style.pixelAlignCenter(parent.height, height)
|
||||
width: isVertical ? (isHidden ? 0 : verticalSize) : (isHidden ? 0 : contentWidth)
|
||||
height: isVertical ? (isHidden ? 0 : verticalSize) : capsuleHeight
|
||||
radius: Style.radiusM
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+276
-264
@@ -10,7 +10,7 @@ import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
@@ -589,7 +589,8 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: {
|
||||
// Content dimensions for implicit sizing
|
||||
readonly property real contentWidth: {
|
||||
if (!visible)
|
||||
return 0;
|
||||
if (isVerticalBar)
|
||||
@@ -604,315 +605,326 @@ Rectangle {
|
||||
|
||||
return Math.round(calculatedWidth);
|
||||
}
|
||||
implicitHeight: visible ? (isVerticalBar ? Math.round(taskbarLayout.implicitHeight + Style.marginXL) : capsuleHeight) : 0
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
readonly property real contentHeight: visible ? (isVerticalBar ? Math.round(taskbarLayout.implicitHeight + Style.marginXL) : capsuleHeight) : 0
|
||||
|
||||
GridLayout {
|
||||
id: taskbarLayout
|
||||
implicitWidth: contentWidth
|
||||
implicitHeight: contentHeight
|
||||
|
||||
// Pixel-perfect centering
|
||||
x: isVerticalBar ? Style.pixelAlignCenter(parent.width, width) : ((root.showTitle) ? Style.pixelAlignCenter(parent.width, width) : Style.marginM)
|
||||
y: Style.pixelAlignCenter(parent.height, height)
|
||||
// Visual capsule centered in parent
|
||||
Rectangle {
|
||||
id: visualCapsule
|
||||
width: root.contentWidth
|
||||
height: root.contentHeight
|
||||
anchors.centerIn: parent
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
|
||||
// Configure GridLayout to behave like RowLayout or ColumnLayout
|
||||
rows: isVerticalBar ? -1 : 1 // -1 means unlimited
|
||||
columns: isVerticalBar ? 1 : -1 // -1 means unlimited
|
||||
GridLayout {
|
||||
id: taskbarLayout
|
||||
|
||||
rowSpacing: isVerticalBar ? Style.marginXXS : 0
|
||||
columnSpacing: isVerticalBar ? 0 : Style.marginXXS
|
||||
// Pixel-perfect centering
|
||||
x: isVerticalBar ? Style.pixelAlignCenter(parent.width, width) : ((root.showTitle) ? Style.pixelAlignCenter(parent.width, width) : Style.marginM)
|
||||
y: Style.pixelAlignCenter(parent.height, height)
|
||||
|
||||
Repeater {
|
||||
model: root.combinedModel
|
||||
delegate: Item {
|
||||
id: taskbarItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
property ShellScreen screen: root.screen
|
||||
// Configure GridLayout to behave like RowLayout or ColumnLayout
|
||||
rows: isVerticalBar ? -1 : 1 // -1 means unlimited
|
||||
columns: isVerticalBar ? 1 : -1 // -1 means unlimited
|
||||
|
||||
readonly property bool isRunning: modelData.window !== null
|
||||
readonly property bool isPinned: modelData.type === "pinned" || modelData.type === "pinned-running"
|
||||
readonly property bool isFocused: isRunning && modelData.window && modelData.window.isFocused
|
||||
readonly property bool isPinnedRunning: isPinned && isRunning && !isFocused
|
||||
readonly property bool isHovered: root.hoveredWindowId === modelData.id
|
||||
rowSpacing: isVerticalBar ? Style.marginXXS : 0
|
||||
columnSpacing: isVerticalBar ? 0 : Style.marginXXS
|
||||
|
||||
readonly property bool shouldShowTitle: root.showTitle && modelData.type !== "pinned"
|
||||
readonly property real itemSpacing: Style.marginS
|
||||
readonly property real contentWidth: shouldShowTitle ? root.itemSize + itemSpacing + root.titleWidth : root.itemSize
|
||||
Repeater {
|
||||
model: root.combinedModel
|
||||
delegate: Item {
|
||||
id: taskbarItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
property ShellScreen screen: root.screen
|
||||
|
||||
readonly property string title: modelData.title || modelData.appId || "Unknown application"
|
||||
readonly property color titleBgColor: (isHovered || isFocused) ? Color.mHover : Style.capsuleColor
|
||||
readonly property color titleFgColor: (isHovered || isFocused) ? Color.mOnHover : Color.mOnSurface
|
||||
readonly property bool isRunning: modelData.window !== null
|
||||
readonly property bool isPinned: modelData.type === "pinned" || modelData.type === "pinned-running"
|
||||
readonly property bool isFocused: isRunning && modelData.window && modelData.window.isFocused
|
||||
readonly property bool isPinnedRunning: isPinned && isRunning && !isFocused
|
||||
readonly property bool isHovered: root.hoveredWindowId === modelData.id
|
||||
|
||||
Layout.preferredWidth: root.showTitle ? Math.round(contentWidth + Style.marginXL) : Math.round(contentWidth) // Add margins for both pinned and running apps
|
||||
Layout.preferredHeight: root.itemSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
readonly property bool shouldShowTitle: root.showTitle && modelData.type !== "pinned"
|
||||
readonly property real itemSpacing: Style.marginS
|
||||
readonly property real contentWidth: shouldShowTitle ? root.itemSize + itemSpacing + root.titleWidth : root.itemSize
|
||||
|
||||
// Ensure dragged item is on top
|
||||
z: (root.dragSourceIndex === index) ? 1000 : 1
|
||||
readonly property string title: modelData.title || modelData.appId || "Unknown application"
|
||||
readonly property color titleBgColor: (isHovered || isFocused) ? Color.mHover : Style.capsuleColor
|
||||
readonly property color titleFgColor: (isHovered || isFocused) ? Color.mOnHover : Color.mOnSurface
|
||||
|
||||
property int modelIndex: index
|
||||
objectName: "taskbarAppItem"
|
||||
Layout.preferredWidth: root.showTitle ? Math.round(contentWidth + Style.marginXL) : Math.round(contentWidth) // Add margins for both pinned and running apps
|
||||
Layout.preferredHeight: root.itemSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
DropArea {
|
||||
anchors.fill: parent
|
||||
keys: ["taskbar-app"]
|
||||
onEntered: function (drag) {
|
||||
if (drag.source && drag.source.objectName === "taskbarAppItem") {
|
||||
root.dragTargetIndex = taskbarItem.modelIndex;
|
||||
// Ensure dragged item is on top
|
||||
z: (root.dragSourceIndex === index) ? 1000 : 1
|
||||
|
||||
property int modelIndex: index
|
||||
objectName: "taskbarAppItem"
|
||||
|
||||
DropArea {
|
||||
anchors.fill: parent
|
||||
keys: ["taskbar-app"]
|
||||
onEntered: function (drag) {
|
||||
if (drag.source && drag.source.objectName === "taskbarAppItem") {
|
||||
root.dragTargetIndex = taskbarItem.modelIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
onExited: function () {
|
||||
if (root.dragTargetIndex === taskbarItem.modelIndex) {
|
||||
onExited: function () {
|
||||
if (root.dragTargetIndex === taskbarItem.modelIndex) {
|
||||
root.dragTargetIndex = -1;
|
||||
}
|
||||
}
|
||||
onDropped: function (drop) {
|
||||
root.dragSourceIndex = -1;
|
||||
root.dragTargetIndex = -1;
|
||||
}
|
||||
}
|
||||
onDropped: function (drop) {
|
||||
root.dragSourceIndex = -1;
|
||||
root.dragTargetIndex = -1;
|
||||
Logger.d("Taskbar", "Dropped! Source: " + (drop.source ? drop.source.objectName : "null") + " Index: " + (drop.source ? drop.source.modelIndex : "?") + " -> Target Index: " + taskbarItem.modelIndex);
|
||||
if (drop.source && drop.source.objectName === "taskbarAppItem" && drop.source !== taskbarItem) {
|
||||
root.reorderApps(drop.source.modelIndex, taskbarItem.modelIndex);
|
||||
} else {
|
||||
Logger.d("Taskbar", "Drop ignored. Source objectName: " + (drop.source ? drop.source.objectName : "null"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: draggableContent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
anchors.centerIn: dragging ? undefined : parent
|
||||
|
||||
// Visual shifting logic
|
||||
readonly property bool isDragged: root.dragSourceIndex === index
|
||||
property real shiftOffset: 0
|
||||
|
||||
// Calculate shift based on drag state
|
||||
// If I am NOT the dragged item, but I am in the path of the drag
|
||||
Binding on shiftOffset {
|
||||
value: {
|
||||
if (root.dragSourceIndex !== -1 && root.dragTargetIndex !== -1 && !draggableContent.isDragged) {
|
||||
if (root.dragSourceIndex < root.dragTargetIndex) {
|
||||
// Dragging Right: Items between source and target shift Left
|
||||
if (index > root.dragSourceIndex && index <= root.dragTargetIndex) {
|
||||
return -1 * (root.isVerticalBar ? root.itemSize : draggableContent.width); // Simple approximation, could be refined
|
||||
}
|
||||
} else if (root.dragSourceIndex > root.dragTargetIndex) {
|
||||
// Dragging Left: Items between target and source shift Right
|
||||
if (index >= root.dragTargetIndex && index < root.dragSourceIndex) {
|
||||
return (root.isVerticalBar ? root.itemSize : draggableContent.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
x: !root.isVerticalBar ? draggableContent.shiftOffset : 0
|
||||
y: root.isVerticalBar ? draggableContent.shiftOffset : 0
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
Logger.d("Taskbar", "Dropped! Source: " + (drop.source ? drop.source.objectName : "null") + " Index: " + (drop.source ? drop.source.modelIndex : "?") + " -> Target Index: " + taskbarItem.modelIndex);
|
||||
if (drop.source && drop.source.objectName === "taskbarAppItem" && drop.source !== taskbarItem) {
|
||||
root.reorderApps(drop.source.modelIndex, taskbarItem.modelIndex);
|
||||
} else {
|
||||
Logger.d("Taskbar", "Drop ignored. Source objectName: " + (drop.source ? drop.source.objectName : "null"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property bool dragging: taskbarMouseArea.drag.active
|
||||
onDraggingChanged: {
|
||||
if (dragging) {
|
||||
root.dragSourceIndex = index;
|
||||
} else {
|
||||
// Don't reset immediately on release to allow drop to handle it,
|
||||
// or use a timer if needed, but drop handler usually fires.
|
||||
// However, if dropped outside, we need to reset.
|
||||
// Let's reset if not handled by drop area quickly?
|
||||
// Actually, drag.active becomes false on release.
|
||||
// We might want to clear it if no drop happened.
|
||||
if (root.dragSourceIndex === index) {
|
||||
// Slight delay/check? For now, let DropArea handle reset on success.
|
||||
// If cancelled (dropped nowhere), we should reset.
|
||||
Qt.callLater(() => {
|
||||
if (!taskbarMouseArea.drag.active && root.dragSourceIndex === index) {
|
||||
root.dragSourceIndex = -1;
|
||||
root.dragTargetIndex = -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Drag.active: dragging
|
||||
Drag.source: taskbarItem
|
||||
Drag.hotSpot.x: width / 2
|
||||
Drag.hotSpot.y: height / 2
|
||||
Drag.keys: ["taskbar-app"]
|
||||
|
||||
z: dragging ? 1000 : 0
|
||||
scale: dragging ? 1.05 : 1.0
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: titleBackground
|
||||
visible: shouldShowTitle
|
||||
anchors.centerIn: parent
|
||||
Item {
|
||||
id: draggableContent
|
||||
width: parent.width
|
||||
height: root.height
|
||||
color: titleBgColor
|
||||
radius: Style.radiusM
|
||||
height: parent.height
|
||||
anchors.centerIn: dragging ? undefined : parent
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
// Visual shifting logic
|
||||
readonly property bool isDragged: root.dragSourceIndex === index
|
||||
property real shiftOffset: 0
|
||||
|
||||
// Calculate shift based on drag state
|
||||
// If I am NOT the dragged item, but I am in the path of the drag
|
||||
Binding on shiftOffset {
|
||||
value: {
|
||||
if (root.dragSourceIndex !== -1 && root.dragTargetIndex !== -1 && !draggableContent.isDragged) {
|
||||
if (root.dragSourceIndex < root.dragTargetIndex) {
|
||||
// Dragging Right: Items between source and target shift Left
|
||||
if (index > root.dragSourceIndex && index <= root.dragTargetIndex) {
|
||||
return -1 * (root.isVerticalBar ? root.itemSize : draggableContent.width); // Simple approximation, could be refined
|
||||
}
|
||||
} else if (root.dragSourceIndex > root.dragTargetIndex) {
|
||||
// Dragging Left: Items between target and source shift Right
|
||||
if (index >= root.dragTargetIndex && index < root.dragSourceIndex) {
|
||||
return (root.isVerticalBar ? root.itemSize : draggableContent.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: taskbarItem.contentWidth
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
transform: Translate {
|
||||
x: !root.isVerticalBar ? draggableContent.shiftOffset : 0
|
||||
y: root.isVerticalBar ? draggableContent.shiftOffset : 0
|
||||
|
||||
RowLayout {
|
||||
id: itemLayout
|
||||
anchors.fill: parent
|
||||
spacing: taskbarItem.itemSpacing
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: root.itemSize
|
||||
Layout.preferredHeight: root.itemSize
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
property bool dragging: taskbarMouseArea.drag.active
|
||||
onDraggingChanged: {
|
||||
if (dragging) {
|
||||
root.dragSourceIndex = index;
|
||||
} else {
|
||||
// Don't reset immediately on release to allow drop to handle it,
|
||||
// or use a timer if needed, but drop handler usually fires.
|
||||
// However, if dropped outside, we need to reset.
|
||||
// Let's reset if not handled by drop area quickly?
|
||||
// Actually, drag.active becomes false on release.
|
||||
// We might want to clear it if no drop happened.
|
||||
if (root.dragSourceIndex === index) {
|
||||
// Slight delay/check? For now, let DropArea handle reset on success.
|
||||
// If cancelled (dropped nowhere), we should reset.
|
||||
Qt.callLater(() => {
|
||||
if (!taskbarMouseArea.drag.active && root.dragSourceIndex === index) {
|
||||
root.dragSourceIndex = -1;
|
||||
root.dragTargetIndex = -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: appIcon
|
||||
anchors.fill: parent
|
||||
Drag.active: dragging
|
||||
Drag.source: taskbarItem
|
||||
Drag.hotSpot.x: width / 2
|
||||
Drag.hotSpot.y: height / 2
|
||||
Drag.keys: ["taskbar-app"]
|
||||
|
||||
source: ThemeIcons.iconForAppId(taskbarItem.modelData.appId)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
z: dragging ? 1000 : 0
|
||||
scale: dragging ? 1.05 : 1.0
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
// Apply dock shader to all taskbar icons
|
||||
layer.enabled: widgetSettings.colorizeIcons !== false
|
||||
layer.effect: ShaderEffect {
|
||||
property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant
|
||||
property real colorizeMode: 0.0 // Dock mode (grayscale)
|
||||
Rectangle {
|
||||
id: titleBackground
|
||||
visible: shouldShowTitle
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: root.height
|
||||
color: titleBgColor
|
||||
radius: Style.radiusM
|
||||
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: taskbarItem.contentWidth
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
|
||||
RowLayout {
|
||||
id: itemLayout
|
||||
anchors.fill: parent
|
||||
spacing: taskbarItem.itemSpacing
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: root.itemSize
|
||||
Layout.preferredHeight: root.itemSize
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
|
||||
IconImage {
|
||||
id: appIcon
|
||||
anchors.fill: parent
|
||||
|
||||
source: ThemeIcons.iconForAppId(taskbarItem.modelData.appId)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
|
||||
// Apply dock shader to all taskbar icons
|
||||
layer.enabled: widgetSettings.colorizeIcons !== false
|
||||
layer.effect: ShaderEffect {
|
||||
property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant
|
||||
property real colorizeMode: 0.0 // Dock mode (grayscale)
|
||||
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
visible: !shouldShowTitle
|
||||
anchors.bottomMargin: -2
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Style.toOdd(root.itemSize * 0.25)
|
||||
height: 4
|
||||
color: taskbarItem.isFocused ? Color.mPrimary : "transparent"
|
||||
radius: Math.min(Style.radiusXXS, width / 2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
visible: !shouldShowTitle
|
||||
anchors.bottomMargin: -2
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Style.toOdd(root.itemSize * 0.25)
|
||||
height: 4
|
||||
color: taskbarItem.isFocused ? Color.mPrimary : "transparent"
|
||||
radius: Math.min(Style.radiusXXS, width / 2)
|
||||
NText {
|
||||
id: titleText
|
||||
visible: shouldShowTitle
|
||||
Layout.preferredWidth: root.titleWidth
|
||||
Layout.preferredHeight: root.itemSize
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.fillWidth: false
|
||||
|
||||
text: taskbarItem.title
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
|
||||
pointSize: barFontSize
|
||||
color: titleFgColor
|
||||
opacity: Style.opacityFull
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: titleText
|
||||
visible: shouldShowTitle
|
||||
Layout.preferredWidth: root.titleWidth
|
||||
Layout.preferredHeight: root.itemSize
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.fillWidth: false
|
||||
|
||||
text: taskbarItem.title
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
|
||||
pointSize: barFontSize
|
||||
color: titleFgColor
|
||||
opacity: Style.opacityFull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: taskbarMouseArea
|
||||
objectName: "taskbarMouseArea"
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
MouseArea {
|
||||
id: taskbarMouseArea
|
||||
objectName: "taskbarMouseArea"
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
drag.target: draggableContent
|
||||
drag.axis: root.isVerticalBar ? Drag.YAxis : Drag.XAxis
|
||||
preventStealing: true
|
||||
drag.target: draggableContent
|
||||
drag.axis: root.isVerticalBar ? Drag.YAxis : Drag.XAxis
|
||||
preventStealing: true
|
||||
|
||||
onPressed: {
|
||||
// Constrain drag to roughly the taskbar area but allow some freedom
|
||||
// Or just let it be free since we only care about drops
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (draggableContent.Drag.active) {
|
||||
draggableContent.Drag.drop();
|
||||
onPressed: {
|
||||
// Constrain drag to roughly the taskbar area but allow some freedom
|
||||
// Or just let it be free since we only care about drops
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function (mouse) {
|
||||
if (!modelData)
|
||||
return;
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (isRunning && modelData.window) {
|
||||
// Running app - focus it
|
||||
try {
|
||||
CompositorService.focusWindow(modelData.window);
|
||||
} catch (error) {
|
||||
Logger.e("Taskbar", "Failed to activate toplevel: " + error);
|
||||
}
|
||||
} else if (isPinned) {
|
||||
// Pinned app not running - launch it
|
||||
root.launchPinnedApp(modelData.appId);
|
||||
onReleased: {
|
||||
if (draggableContent.Drag.active) {
|
||||
draggableContent.Drag.drop();
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
}
|
||||
|
||||
onClicked: function (mouse) {
|
||||
if (!modelData)
|
||||
return;
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (isRunning && modelData.window) {
|
||||
// Running app - focus it
|
||||
try {
|
||||
CompositorService.focusWindow(modelData.window);
|
||||
} catch (error) {
|
||||
Logger.e("Taskbar", "Failed to activate toplevel: " + error);
|
||||
}
|
||||
} else if (isPinned) {
|
||||
// Pinned app not running - launch it
|
||||
root.launchPinnedApp(modelData.appId);
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
TooltipService.hide();
|
||||
// Only show context menu for running apps
|
||||
if (isRunning && modelData.window) {
|
||||
root.selectedWindowId = modelData.id;
|
||||
root.selectedAppId = modelData.appId;
|
||||
root.openTaskbarContextMenu(taskbarItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
root.hoveredWindowId = taskbarItem.modelData.id;
|
||||
TooltipService.show(taskbarItem, taskbarItem.title, BarService.getTooltipDirection(root.screen?.name));
|
||||
}
|
||||
onExited: {
|
||||
root.hoveredWindowId = "";
|
||||
TooltipService.hide();
|
||||
// Only show context menu for running apps
|
||||
if (isRunning && modelData.window) {
|
||||
root.selectedWindowId = modelData.id;
|
||||
root.selectedAppId = modelData.appId;
|
||||
root.openTaskbarContextMenu(taskbarItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
root.hoveredWindowId = taskbarItem.modelData.id;
|
||||
TooltipService.show(taskbarItem, taskbarItem.title, BarService.getTooltipDirection(root.screen?.name));
|
||||
}
|
||||
onExited: {
|
||||
root.hoveredWindowId = "";
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // closes GridLayout
|
||||
} // closes visualCapsule
|
||||
|
||||
function openTaskbarContextMenu(item) {
|
||||
// Build menu model directly
|
||||
|
||||
+185
-172
@@ -10,7 +10,7 @@ import qs.Modules.Bar.Extras
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
@@ -286,204 +286,217 @@ Rectangle {
|
||||
Component.onCompleted: {
|
||||
root.updateFilteredItems(); // Initial update
|
||||
}
|
||||
implicitWidth: isVertical ? capsuleHeight : Math.round(trayFlow.implicitWidth)
|
||||
implicitHeight: isVertical ? Math.round(trayFlow.implicitHeight) : capsuleHeight
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
|
||||
// Content dimensions for implicit sizing
|
||||
readonly property real contentWidth: isVertical ? capsuleHeight : Math.round(trayFlow.implicitWidth)
|
||||
readonly property real contentHeight: isVertical ? Math.round(trayFlow.implicitHeight) : capsuleHeight
|
||||
|
||||
implicitWidth: contentWidth
|
||||
implicitHeight: contentHeight
|
||||
visible: filteredItems.length > 0 || dropdownItems.length > 0
|
||||
opacity: (filteredItems.length > 0 || dropdownItems.length > 0) ? 1.0 : 0.0
|
||||
|
||||
Flow {
|
||||
id: trayFlow
|
||||
spacing: Style.marginXS
|
||||
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
||||
// Visual capsule centered in parent
|
||||
Rectangle {
|
||||
id: visualCapsule
|
||||
width: root.contentWidth
|
||||
height: root.contentHeight
|
||||
anchors.centerIn: parent
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
border.color: Style.capsuleBorderColor
|
||||
border.width: Style.capsuleBorderWidth
|
||||
|
||||
// Pixel-perfect centering
|
||||
x: isVertical ? Style.pixelAlignCenter(parent.width, width) : 0
|
||||
y: isVertical ? 0 : Style.pixelAlignCenter(parent.height, height)
|
||||
Flow {
|
||||
id: trayFlow
|
||||
spacing: Style.marginXS
|
||||
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
||||
|
||||
// Drawer opener (before items if opposite direction)
|
||||
NIconButton {
|
||||
id: chevronIconBefore
|
||||
visible: root.drawerEnabled && dropdownItems.length > 0 && BarService.getPillDirection(root)
|
||||
tooltipText: I18n.tr("tooltips.open-tray-dropdown")
|
||||
tooltipDirection: BarService.getTooltipDirection(root.screen?.name)
|
||||
baseSize: capsuleHeight
|
||||
applyUiScale: false
|
||||
customRadius: Style.radiusL
|
||||
colorBg: "transparent"
|
||||
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
|
||||
colorBorder: "transparent"
|
||||
colorBorderHover: "transparent"
|
||||
icon: {
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return "caret-up";
|
||||
case "left":
|
||||
return "caret-right";
|
||||
case "right":
|
||||
return "caret-left";
|
||||
case "top":
|
||||
default:
|
||||
return "caret-down";
|
||||
// Pixel-perfect centering
|
||||
x: isVertical ? Style.pixelAlignCenter(parent.width, width) : 0
|
||||
y: isVertical ? 0 : Style.pixelAlignCenter(parent.height, height)
|
||||
|
||||
// Drawer opener (before items if opposite direction)
|
||||
NIconButton {
|
||||
id: chevronIconBefore
|
||||
visible: root.drawerEnabled && dropdownItems.length > 0 && BarService.getPillDirection(root)
|
||||
tooltipText: I18n.tr("tooltips.open-tray-dropdown")
|
||||
tooltipDirection: BarService.getTooltipDirection(root.screen?.name)
|
||||
baseSize: capsuleHeight
|
||||
applyUiScale: false
|
||||
customRadius: Style.radiusL
|
||||
colorBg: "transparent"
|
||||
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
|
||||
colorBorder: "transparent"
|
||||
colorBorderHover: "transparent"
|
||||
icon: {
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return "caret-up";
|
||||
case "left":
|
||||
return "caret-right";
|
||||
case "right":
|
||||
return "caret-left";
|
||||
case "top":
|
||||
default:
|
||||
return "caret-down";
|
||||
}
|
||||
}
|
||||
onClicked: toggleDrawer(this)
|
||||
onRightClicked: toggleDrawer(this)
|
||||
}
|
||||
onClicked: toggleDrawer(this)
|
||||
onRightClicked: toggleDrawer(this)
|
||||
}
|
||||
|
||||
// Pinned items
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: root.filteredItems
|
||||
// Pinned items
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: root.filteredItems
|
||||
|
||||
delegate: Item {
|
||||
width: capsuleHeight
|
||||
height: capsuleHeight
|
||||
visible: modelData
|
||||
delegate: Item {
|
||||
width: capsuleHeight
|
||||
height: capsuleHeight
|
||||
visible: modelData
|
||||
|
||||
IconImage {
|
||||
id: trayIcon
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
x: Style.pixelAlignCenter(parent.width, width)
|
||||
y: Style.pixelAlignCenter(parent.height, height)
|
||||
asynchronous: true
|
||||
backer.fillMode: Image.PreserveAspectFit
|
||||
IconImage {
|
||||
id: trayIcon
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
x: Style.pixelAlignCenter(parent.width, width)
|
||||
y: Style.pixelAlignCenter(parent.height, height)
|
||||
asynchronous: true
|
||||
backer.fillMode: Image.PreserveAspectFit
|
||||
|
||||
property bool menuJustOpened: false
|
||||
property bool menuJustOpened: false
|
||||
|
||||
source: {
|
||||
let icon = modelData?.icon || "";
|
||||
if (!icon) {
|
||||
return "";
|
||||
source: {
|
||||
let icon = modelData?.icon || "";
|
||||
if (!icon) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Process icon path
|
||||
if (icon.includes("?path=")) {
|
||||
const chunks = icon.split("?path=");
|
||||
const name = chunks[0];
|
||||
const path = chunks[1];
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
|
||||
layer.enabled: widgetSettings.colorizeIcons !== false
|
||||
layer.effect: ShaderEffect {
|
||||
property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant
|
||||
property real colorizeMode: 1.0
|
||||
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
|
||||
}
|
||||
|
||||
// Process icon path
|
||||
if (icon.includes("?path=")) {
|
||||
const chunks = icon.split("?path=");
|
||||
const name = chunks[0];
|
||||
const path = chunks[1];
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
|
||||
layer.enabled: widgetSettings.colorizeIcons !== false
|
||||
layer.effect: ShaderEffect {
|
||||
property color targetColor: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mSurfaceVariant
|
||||
property real colorizeMode: 1.0
|
||||
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
if (!modelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Close any open menu first
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.close();
|
||||
}
|
||||
|
||||
if (!modelData.onlyMenu) {
|
||||
modelData.activate();
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
// Close the menu if it was visible
|
||||
if (popupMenuWindow && popupMenuWindow.visible) {
|
||||
popupMenuWindow.close();
|
||||
return;
|
||||
}
|
||||
modelData.secondaryActivate && modelData.secondaryActivate();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
TooltipService.hideImmediately();
|
||||
|
||||
// Close the menu if it was visible
|
||||
if (popupMenuWindow && popupMenuWindow.visible) {
|
||||
popupMenuWindow.close();
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
if (!modelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any opened panel
|
||||
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
|
||||
if (modelData.hasMenu && modelData.menu && trayMenu && trayMenu.item) {
|
||||
// Position menu based on bar position
|
||||
let menuX, menuY;
|
||||
if (barPosition === "left") {
|
||||
// For left bar: position menu to the right of the bar
|
||||
menuX = width + Style.marginM;
|
||||
menuY = 0;
|
||||
} else if (barPosition === "right") {
|
||||
// For right bar: position menu to the left of the bar
|
||||
menuX = -trayMenu.item.width - Style.marginM;
|
||||
menuY = 0;
|
||||
} else {
|
||||
// For horizontal bars: center horizontally and position below
|
||||
menuX = (width / 2) - (trayMenu.item.width / 2);
|
||||
menuY = (barPosition === "top") ? barHeight + Style.marginS - 2 : barHeight + Style.marginS - 2;
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Close any open menu first
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.close();
|
||||
}
|
||||
|
||||
PanelService.showTrayMenu(root.screen, modelData, trayMenu.item, parent, menuX, menuY, root.section, root.sectionWidgetIndex);
|
||||
} else {
|
||||
Logger.d("Tray", "No menu available for", modelData.id, "or trayMenu not set");
|
||||
if (!modelData.onlyMenu) {
|
||||
modelData.activate();
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
// Close the menu if it was visible
|
||||
if (popupMenuWindow && popupMenuWindow.visible) {
|
||||
popupMenuWindow.close();
|
||||
return;
|
||||
}
|
||||
modelData.secondaryActivate && modelData.secondaryActivate();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
TooltipService.hideImmediately();
|
||||
|
||||
// Close the menu if it was visible
|
||||
if (popupMenuWindow && popupMenuWindow.visible) {
|
||||
popupMenuWindow.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any opened panel
|
||||
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
|
||||
if (modelData.hasMenu && modelData.menu && trayMenu && trayMenu.item) {
|
||||
// Position menu based on bar position
|
||||
let menuX, menuY;
|
||||
if (barPosition === "left") {
|
||||
// For left bar: position menu to the right of the bar
|
||||
menuX = width + Style.marginM;
|
||||
menuY = 0;
|
||||
} else if (barPosition === "right") {
|
||||
// For right bar: position menu to the left of the bar
|
||||
menuX = -trayMenu.item.width - Style.marginM;
|
||||
menuY = 0;
|
||||
} else {
|
||||
// For horizontal bars: center horizontally and position below
|
||||
menuX = (width / 2) - (trayMenu.item.width / 2);
|
||||
menuY = (barPosition === "top") ? barHeight + Style.marginS - 2 : barHeight + Style.marginS - 2;
|
||||
}
|
||||
|
||||
PanelService.showTrayMenu(root.screen, modelData, trayMenu.item, parent, menuX, menuY, root.section, root.sectionWidgetIndex);
|
||||
} else {
|
||||
Logger.d("Tray", "No menu available for", modelData.id, "or trayMenu not set");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.close();
|
||||
onEntered: {
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.close();
|
||||
}
|
||||
TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection(root.screen?.name));
|
||||
}
|
||||
TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection(root.screen?.name));
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drawer opener (after items if normal direction)
|
||||
NIconButton {
|
||||
id: chevronIconAfter
|
||||
visible: root.drawerEnabled && dropdownItems.length > 0 && !BarService.getPillDirection(root)
|
||||
tooltipText: I18n.tr("tooltips.open-tray-dropdown")
|
||||
tooltipDirection: BarService.getTooltipDirection(root.screen?.name)
|
||||
baseSize: capsuleHeight
|
||||
applyUiScale: false
|
||||
customRadius: Style.radiusL
|
||||
colorBg: "transparent"
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: "transparent"
|
||||
colorBorderHover: "transparent"
|
||||
icon: {
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return "caret-up";
|
||||
case "left":
|
||||
return "caret-right";
|
||||
case "right":
|
||||
return "caret-left";
|
||||
case "top":
|
||||
default:
|
||||
return "caret-down";
|
||||
// Drawer opener (after items if normal direction)
|
||||
NIconButton {
|
||||
id: chevronIconAfter
|
||||
visible: root.drawerEnabled && dropdownItems.length > 0 && !BarService.getPillDirection(root)
|
||||
tooltipText: I18n.tr("tooltips.open-tray-dropdown")
|
||||
tooltipDirection: BarService.getTooltipDirection(root.screen?.name)
|
||||
baseSize: capsuleHeight
|
||||
applyUiScale: false
|
||||
customRadius: Style.radiusL
|
||||
colorBg: "transparent"
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: "transparent"
|
||||
colorBorderHover: "transparent"
|
||||
icon: {
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return "caret-up";
|
||||
case "left":
|
||||
return "caret-right";
|
||||
case "right":
|
||||
return "caret-left";
|
||||
case "top":
|
||||
default:
|
||||
return "caret-down";
|
||||
}
|
||||
}
|
||||
onClicked: toggleDrawer(this)
|
||||
onRightClicked: toggleDrawer(this)
|
||||
}
|
||||
onClicked: toggleDrawer(this)
|
||||
onRightClicked: toggleDrawer(this)
|
||||
}
|
||||
}
|
||||
} // closes Flow
|
||||
} // closes visualCapsule
|
||||
}
|
||||
|
||||
+43
-23
@@ -4,7 +4,7 @@ import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real baseSize: Style.baseWidgetSize
|
||||
@@ -24,6 +24,11 @@ Rectangle {
|
||||
property color colorBorderHover: Color.mOutline
|
||||
property real customRadius: -1 // -1 means use default (iRadiusL), otherwise use this value
|
||||
|
||||
// Expose border properties for backwards compatibility (aliases to visualButton)
|
||||
property alias border: visualButton.border
|
||||
property alias radius: visualButton.radius
|
||||
property alias color: visualButton.color
|
||||
|
||||
signal entered
|
||||
signal exited
|
||||
signal clicked
|
||||
@@ -31,31 +36,27 @@ Rectangle {
|
||||
signal middleClicked
|
||||
signal wheel(int angleDelta)
|
||||
|
||||
implicitWidth: applyUiScale ? Style.toOdd(baseSize * Style.uiScaleRatio) : Style.toOdd(baseSize)
|
||||
implicitHeight: applyUiScale ? Style.toOdd(baseSize * Style.uiScaleRatio) : Style.toOdd(baseSize)
|
||||
// Calculate button size based on settings
|
||||
readonly property real buttonSize: applyUiScale ? Style.toOdd(baseSize * Style.uiScaleRatio) : Style.toOdd(baseSize)
|
||||
|
||||
// Size: use implicit width/height which layout can override
|
||||
// BarWidgetLoader sets explicit width/height to extend click area
|
||||
implicitWidth: buttonSize
|
||||
implicitHeight: buttonSize
|
||||
|
||||
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
|
||||
color: root.enabled && root.hovering ? colorBgHover : colorBg
|
||||
radius: Math.min((customRadius >= 0 ? customRadius : Style.iRadiusL), width / 2)
|
||||
border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
enabled: !Color.isTransitioning
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
// Visual button - stays at buttonSize, centered in parent
|
||||
Rectangle {
|
||||
id: visualButton
|
||||
width: root.buttonSize
|
||||
height: root.buttonSize
|
||||
anchors.centerIn: parent
|
||||
|
||||
NIcon {
|
||||
icon: root.icon
|
||||
pointSize: Style.toOdd(root.width * 0.48)
|
||||
applyUiScale: root.applyUiScale
|
||||
color: root.enabled && root.hovering ? colorFgHover : colorFg
|
||||
// Pixel-perfect centering
|
||||
x: Style.pixelAlignCenter(root.width, width)
|
||||
y: Style.pixelAlignCenter(root.height, contentHeight)
|
||||
color: root.enabled && root.hovering ? colorBgHover : colorBg
|
||||
radius: Math.min((customRadius >= 0 ? customRadius : Style.iRadiusL), width / 2)
|
||||
border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
enabled: !Color.isTransitioning
|
||||
@@ -64,8 +65,27 @@ Rectangle {
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: root.icon
|
||||
pointSize: Style.toOdd(visualButton.width * 0.48)
|
||||
applyUiScale: root.applyUiScale
|
||||
color: root.enabled && root.hovering ? colorFgHover : colorFg
|
||||
// Pixel-perfect centering
|
||||
x: Style.pixelAlignCenter(visualButton.width, width)
|
||||
y: Style.pixelAlignCenter(visualButton.height, contentHeight)
|
||||
|
||||
Behavior on color {
|
||||
enabled: !Color.isTransitioning
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MouseArea fills root (extends beyond visual button for bar click area)
|
||||
MouseArea {
|
||||
// Always enabled to allow hover/tooltip even when the button is disabled
|
||||
enabled: true
|
||||
@@ -76,7 +96,7 @@ Rectangle {
|
||||
onEntered: {
|
||||
hovering = root.enabled ? true : false;
|
||||
if (tooltipText) {
|
||||
TooltipService.show(parent, tooltipText, tooltipDirection);
|
||||
TooltipService.show(root, tooltipText, tooltipDirection);
|
||||
}
|
||||
root.entered();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user