ActiveWindow: similar behavior to MediaMini

This commit is contained in:
ItsLemmy
2025-09-24 20:48:03 -04:00
parent 8c1153192d
commit a8a1b0a422
3 changed files with 190 additions and 104 deletions
+170 -103
View File
@@ -33,51 +33,24 @@ Item {
readonly property string windowTitle: CompositorService.getFocusedWindowTitle()
readonly property bool hasActiveWindow: windowTitle !== ""
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
// 6% of total width
readonly property real minWidth: Math.max(1, screen.width * 0.06)
readonly property real maxWidth: minWidth * 2
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property real textSize: {
var base = isVertical ? width : height
return Math.max(1, compact ? base * 0.43 : base * 0.33)
}
// Widget settings - matching MediaMini pattern
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : (widgetMetadata.scrollingMode !== undefined ? widgetMetadata.scrollingMode : "hover")
readonly property real iconSize: textSize * 1.25
implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * 0.8 * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
visible: hasActiveWindow
// Fixed width
readonly property real widgetWidth: Math.max(1, screen.width * 0.06)
function calculatedVerticalHeight() {
// Use standard widget height like other widgets
return Math.round(Style.capsuleHeight * scaling)
return Math.round(Style.baseWidgetSize * 0.8 * scaling)
}
function calculatedHorizontalWidth() {
let total = Style.marginM * 2 * scaling // internal padding
implicitHeight: visible ? ((barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)) : 0
implicitWidth: visible ? ((barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)) : 0
if (showIcon) {
total += Style.capsuleHeight * 0.5 * scaling + 2 * scaling // icon + spacing
}
// Calculate actual text width more accurately
if (windowTitle !== "") {
// Estimate text width: average character width * number of characters
const avgCharWidth = Style.fontSizeS * scaling * 0.6 // rough estimate
const titleWidth = Math.min(windowTitle.length * avgCharWidth, 80 * scaling)
total += titleWidth
}
// Row layout handles spacing between widgets
return Math.max(total, Style.capsuleHeight * scaling) // Minimum width
}
visible: hasActiveWindow
function getAppIcon() {
try {
@@ -120,7 +93,7 @@ Item {
}
}
// A hidden text element to safely measure the full title width
// Hidden text element to measure full title width
NText {
id: fullTitleMetrics
visible: false
@@ -130,15 +103,13 @@ Item {
}
Rectangle {
id: windowTitleRect
id: windowActiveRect
visible: root.visible
anchors.left: (barPosition === "top" || barPosition === "bottom") ? parent.left : undefined
anchors.top: (barPosition === "left" || barPosition === "right") ? parent.top : undefined
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: width / 2
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: (barPosition === "left" || barPosition === "right") ? width / 2 : Math.round(Style.radiusM * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
@@ -146,21 +117,21 @@ Item {
anchors.fill: parent
anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
clip: true
// Horizontal layout for top/bottom bars
RowLayout {
id: horizontalLayout
anchors.centerIn: parent
spacing: 2 * scaling
id: rowLayout
anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling
visible: barPosition === "top" || barPosition === "bottom"
z: 1
// Window icon
Item {
Layout.preferredWidth: Style.capsuleHeight * 0.75 * scaling
Layout.preferredHeight: Style.capsuleHeight * 0.75 * scaling
Layout.preferredWidth: Math.round(18 * scaling)
Layout.preferredHeight: Math.round(18 * scaling)
Layout.alignment: Qt.AlignVCenter
visible: hasActiveWindow && showIcon
visible: showIcon && windowTitle !== ""
IconImage {
id: windowIcon
@@ -169,39 +140,139 @@ Item {
asynchronous: true
smooth: true
visible: source !== ""
// Handle loading errors gracefully
onStatusChanged: {
if (status === Image.Error) {
Logger.warn("ActiveWindow", "Failed to load icon:", source)
}
}
}
}
NText {
id: titleText
// Title container with scrolling
Item {
id: titleContainer
Layout.preferredWidth: {
try {
if (mouseArea.containsMouse) {
return Math.round(Math.min(fullTitleMetrics.contentWidth, root.maxWidth * scaling))
} else {
return Math.round(Math.min(fullTitleMetrics.contentWidth, 80 * scaling)) // Limited width for horizontal bars
// Calculate available width based on other elements
var iconWidth = (showIcon && windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0)
var totalMargins = Style.marginXXS * scaling * 2
var availableWidth = mainContainer.width - iconWidth - totalMargins
return Math.max(20 * scaling, availableWidth)
}
Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: titleText.height
clip: true
property bool isScrolling: false
property bool isResetting: false
property real textWidth: fullTitleMetrics.contentWidth
property real containerWidth: width
property bool needsScrolling: textWidth > containerWidth
// Timer for "always" mode with delay
Timer {
id: scrollStartTimer
interval: 1000
repeat: false
onTriggered: {
if (scrollingMode === "always" && titleContainer.needsScrolling) {
titleContainer.isScrolling = true
titleContainer.isResetting = false
}
} catch (e) {
Logger.warn("ActiveWindow", "Error calculating width:", e)
return 80 * scaling
}
}
Layout.alignment: Qt.AlignVCenter
horizontalAlignment: Text.AlignLeft
text: windowTitle
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
elide: mouseArea.containsMouse ? Text.ElideNone : Text.ElideRight
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
clip: true
// Update scrolling state based on mode
property var updateScrollingState: function() {
if (scrollingMode === "never") {
isScrolling = false
isResetting = false
} else if (scrollingMode === "always") {
if (needsScrolling) {
if (mouseArea.containsMouse) {
isScrolling = false
isResetting = true
} else {
scrollStartTimer.restart()
}
} else {
scrollStartTimer.stop()
isScrolling = false
isResetting = false
}
} else if (scrollingMode === "hover") {
if (mouseArea.containsMouse && needsScrolling) {
isScrolling = true
isResetting = false
} else {
isScrolling = false
if (needsScrolling) {
isResetting = true
}
}
}
}
onWidthChanged: updateScrollingState()
Component.onCompleted: updateScrollingState()
// React to hover changes
Connections {
target: mouseArea
function onContainsMouseChanged() {
titleContainer.updateScrollingState()
}
}
// Scrolling content with seamless loop
Item {
id: scrollContainer
height: parent.height
width: childrenRect.width
property real scrollX: 0
x: scrollX
Row {
spacing: 50 * scaling // Gap between text copies
NText {
id: titleText
text: windowTitle
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
}
// Second copy for seamless scrolling
NText {
text: windowTitle
font: titleText.font
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
visible: titleContainer.needsScrolling && titleContainer.isScrolling
}
}
// Reset animation
NumberAnimation on scrollX {
running: titleContainer.isResetting
to: 0
duration: 300
easing.type: Easing.OutQuad
onFinished: {
titleContainer.isResetting = false
}
}
// Seamless infinite scroll
NumberAnimation on scrollX {
id: infiniteScroll
running: titleContainer.isScrolling && !titleContainer.isResetting
from: 0
to: -(titleContainer.textWidth + 50 * scaling)
duration: Math.max(4000, windowTitle.length * 100)
loops: Animation.Infinite
easing.type: Easing.Linear
}
}
Behavior on Layout.preferredWidth {
NumberAnimation {
@@ -216,15 +287,17 @@ Item {
Item {
id: verticalLayout
anchors.centerIn: parent
width: parent.width - Style.marginXS * scaling * 2
height: parent.height - Style.marginXS * scaling * 2
visible: (barPosition === "left" || barPosition === "right") && hasActiveWindow
width: parent.width - Style.marginM * scaling * 2
height: parent.height - Style.marginM * scaling * 2
visible: barPosition === "left" || barPosition === "right"
z: 1
// Window icon
Item {
width: Style.capsuleHeight * 0.75 * scaling
height: Style.capsuleHeight * 0.75 * scaling
width: Style.baseWidgetSize * 0.5 * scaling
height: Style.baseWidgetSize * 0.5 * scaling
anchors.centerIn: parent
visible: windowTitle !== ""
IconImage {
id: windowIconVertical
@@ -233,13 +306,6 @@ Item {
asynchronous: true
smooth: true
visible: source !== ""
// Handle loading errors gracefully
onStatusChanged: {
if (status === Image.Error) {
Logger.warn("ActiveWindow", "Failed to load icon:", source)
}
}
}
}
}
@@ -250,30 +316,31 @@ Item {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onEntered: {
if (barPosition === "left" || barPosition === "right") {
tooltip.show()
} else if ((tooltip.text !== "") && (scrollingMode === "never")) {
tooltip.show()
}
}
onExited: {
if (barPosition === "left" || barPosition === "right") {
tooltip.hide()
}
tooltip.hide()
}
}
// Hover tooltip with full title (only for vertical bars)
NTooltip {
id: tooltip
target: verticalLayout
text: windowTitle
positionLeft: barPosition === "right"
positionRight: barPosition === "left"
delay: 500
}
}
}
NTooltip {
id: tooltip
text: windowTitle
target: (barPosition === "left" || barPosition === "right") ? verticalLayout : windowActiveRect
positionLeft: barPosition === "right"
positionRight: barPosition === "left"
positionAbove: Settings.data.bar.position === "bottom"
delay: Style.tooltipDelay
}
Connections {
target: CompositorService
function onActiveWindowChanged() {
@@ -293,4 +360,4 @@ Item {
}
}
}
}
}
@@ -15,10 +15,12 @@ ColumnLayout {
// Local state
property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon
property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.showIcon = valueShowIcon
settings.scrollingMode = valueScrollingMode
return settings
}
@@ -29,4 +31,20 @@ ColumnLayout {
checked: root.valueShowIcon
onToggled: checked => root.valueShowIcon = checked
}
NComboBox {
label: I18n.tr("bar.widget-settings.active-window.scrolling-mode")
model: [{
"key": "always",
"name": I18n.tr("options.scrolling-modes.always")
}, {
"key": "hover",
"name": I18n.tr("options.scrolling-modes.hover")
}, {
"key": "never",
"name": I18n.tr("options.scrolling-modes.never")
}]
currentKey: valueScrollingMode
onSelected: key => valueScrollingMode = key
minimumWidth: 200 * scaling
}
}
+2 -1
View File
@@ -40,7 +40,8 @@ Singleton {
property var widgetMetadata: ({
"ActiveWindow": {
"allowUserSettings": true,
"showIcon": true
"showIcon": true,
"scrollingMode": "hover"
},
"Battery": {
"allowUserSettings": true,