Merge branch 'main' into feat/calendar-events

This commit is contained in:
Kainoa Kanter
2025-10-12 10:39:03 -07:00
122 changed files with 4714 additions and 1125 deletions
+102 -111
View File
@@ -11,6 +11,7 @@ NPanel {
id: root
property ShellScreen screen
readonly property var now: Time.date
preferredWidth: Settings.data.location.showWeekNumberInCalendar ? 400 : 380
preferredHeight: 520
@@ -45,14 +46,20 @@ NPanel {
ColumnLayout {
id: blueColumn
anchors.fill: parent
anchors.margins: Style.marginM * scaling
anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: Style.marginM * scaling
anchors.leftMargin: Style.marginM * scaling
anchors.bottomMargin: Style.marginM * scaling
anchors.rightMargin: clockItem.width + (Style.marginM * scaling * 2)
spacing: 0
// Combined layout for weather icon, date, and weather text
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 60 * scaling
height: 60 * scaling
clip: true
spacing: Style.marginS * scaling
// Weather icon and temperature
@@ -87,23 +94,38 @@ NPanel {
}
}
// Today day number
// Today day number - with simple, stable animation
NText {
visible: content.isCurrentMonth
opacity: content.isCurrentMonth ? 1.0 : 0.0
Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0
elide: Text.ElideNone
clip: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: Time.date.getDate()
pointSize: Style.fontSizeXXXL * 1.5 * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
Item {
visible: !content.isCurrentMonth
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
// Month, year, location
ColumnLayout {
Layout.fillWidth: false
Layout.preferredWidth: 170 * scaling
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.bottomMargin: Style.marginXXS * scaling
Layout.topMargin: -Style.marginXXS * scaling
spacing: -Style.marginXS * scaling
RowLayout {
@@ -111,17 +133,16 @@ NPanel {
NText {
text: Qt.locale().monthName(grid.month, Locale.LongFormat).toUpperCase()
pointSize: Style.fontSizeXL * 1.2 * scaling
pointSize: Style.fontSizeXL * 1.1 * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
Layout.alignment: Qt.AlignBaseline
Layout.maximumWidth: 150 * scaling
elide: Text.ElideRight
}
NText {
text: ` ${grid.year}`
pointSize: Style.fontSizeL * scaling
pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Qt.alpha(Color.mOnPrimary, 0.7)
Layout.alignment: Qt.AlignBaseline
@@ -154,95 +175,96 @@ NPanel {
}
}
// Spacer between date and clock
// Spacer to push content left
Item {
Layout.fillWidth: true
}
}
}
// Digital clock with circular progress
Item {
width: Style.fontSizeXXXL * 1.9 * scaling
height: Style.fontSizeXXXL * 1.9 * scaling
Layout.alignment: Qt.AlignVCenter
// Digital clock with circular progress
Item {
id: clockItem
Layout.alignment: Qt.AlignVCenter
anchors.right: parent.right
anchors.rightMargin: Style.marginM * scaling
anchors.verticalCenter: parent.verticalCenter
height: Math.round((Style.fontSizeXXXL * 1.9 * scaling) / 2) * 2
width: clockItem.height
// Seconds circular progress
Canvas {
id: secondsProgress
anchors.fill: parent
// Seconds circular progress
Canvas {
id: secondsProgress
anchors.fill: parent
property real progress: now.getSeconds() / 60
onProgressChanged: requestPaint()
Connections {
target: Time
function onDateChanged() {
const total = now.getSeconds() * 1000 + now.getMilliseconds()
secondsProgress.progress = total / 60000
}
}
onPaint: {
var ctx = getContext("2d")
var centerX = width / 2
var centerY = height / 2
var radius = Math.min(width, height) / 2 - 3 * scaling
ctx.reset()
property real progress: Time.date.getSeconds() / 60
onProgressChanged: requestPaint()
// Background circle
ctx.beginPath()
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15)
ctx.stroke()
Connections {
target: Time
function onDateChanged() {
secondsProgress.progress = Time.date.getSeconds() / 60
}
}
// Progress arc
ctx.beginPath()
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Color.mOnPrimary
ctx.lineCap = "round"
ctx.stroke()
}
}
onPaint: {
var ctx = getContext("2d")
var centerX = width / 2
var centerY = height / 2
var radius = Math.min(width, height) / 2 - 3 * scaling
// Digital clock
ColumnLayout {
anchors.centerIn: parent
spacing: -Style.marginXXS * scaling
ctx.reset()
// Background circle
ctx.beginPath()
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15)
ctx.stroke()
// Progress arc
ctx.beginPath()
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Color.mOnPrimary
ctx.lineCap = "round"
ctx.stroke()
}
NText {
text: {
var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(now, "hh AP") : Qt.locale().toString(now, "HH")
return t.split(" ")[0]
}
// Digital clock
ColumnLayout {
anchors.centerIn: parent
spacing: -Style.marginXXS * scaling
pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
}
NText {
text: {
var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH")
return t.split(" ")[0]
}
pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
}
NText {
text: Qt.formatTime(Time.date, "mm")
pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
}
}
NText {
text: Qt.formatTime(now, "mm")
pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
}
}
}
}
// 6-day forecast (outside blue banner)
// ... (rest of the file is unchanged) ...
RowLayout {
visible: weatherReady
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginL * scaling
Repeater {
model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0
delegate: ColumnLayout {
@@ -250,7 +272,6 @@ NPanel {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginS * scaling
NText {
text: {
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
@@ -261,14 +282,12 @@ NPanel {
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignHCenter
}
NIcon {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
pointSize: Style.fontSizeXXL * 1.5 * scaling
color: Color.mPrimary
}
NText {
Layout.alignment: Qt.AlignHCenter
text: {
@@ -289,27 +308,19 @@ NPanel {
}
}
}
// Loading indicator for weather
RowLayout {
visible: !weatherReady
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
NBusyIndicator {}
}
// Spacer
Item {}
// Navigation and divider
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS * scaling
NDivider {
Layout.fillWidth: true
}
NIconButton {
icon: "chevron-left"
onClicked: {
@@ -327,7 +338,6 @@ NPanel {
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30)
}
}
NIconButton {
icon: "calendar"
onClicked: {
@@ -337,7 +347,6 @@ NPanel {
CalendarService.loadEvents()
}
}
NIconButton {
icon: "chevron-right"
onClicked: {
@@ -356,31 +365,24 @@ NPanel {
}
}
}
// Names of days of the week
RowLayout {
Layout.fillWidth: true
spacing: 0
Item {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0
}
GridLayout {
Layout.fillWidth: true
columns: 7
rows: 1
columnSpacing: 0
rowSpacing: 0
Repeater {
model: 7
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.6 * scaling
NText {
anchors.centerIn: parent
text: {
@@ -397,8 +399,6 @@ NPanel {
}
}
}
// Grid with weeks and days
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
@@ -459,7 +459,7 @@ NPanel {
// Helper function to get color for a specific event
function getEventColor(event, isToday) {
if (isMultiDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mTertiary
return isToday ? Color.mOnSecondary : "#c4a7e7"
} else if (isAllDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mSecondary
} else {
@@ -473,14 +473,11 @@ NPanel {
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0
Layout.fillHeight: true
spacing: 0
Repeater {
model: 6
Item {
Layout.fillWidth: true
Layout.fillHeight: true
NText {
anchors.centerIn: parent
color: Color.mOutline
@@ -512,27 +509,21 @@ NPanel {
}
}
}
// Days Grid
MonthGrid {
id: grid
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Style.marginXXS * scaling
month: Time.date.getMonth()
year: Time.date.getFullYear()
locale: Qt.locale()
delegate: Item {
Rectangle {
width: Style.baseWidgetSize * 0.9 * scaling
height: Style.baseWidgetSize * 0.9 * scaling
anchors.centerIn: parent
radius: Style.radiusM * scaling
color: model.today ? Color.mSecondary : Color.transparent
NText {
anchors.centerIn: parent
text: model.day
+2 -2
View File
@@ -27,7 +27,7 @@ Item {
}
function getImplicitSize(item, prop) {
return (item && item.visible) ? item[prop] : 0
return (item && item.visible) ? Math.round(item[prop]) : 0
}
Loader {
@@ -77,7 +77,7 @@ Item {
// Error handling
onWidgetIdChanged: {
if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) {
Logger.warn("BarWidgetLoader", "Widget not found in bar registry:", widgetId)
Logger.warn("BarWidgetLoader", "Widget not found in registry:", widgetId)
}
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ PopupWindow {
readonly property int menuWidth: 180
implicitWidth: menuWidth * scaling
implicitWidth: Math.round(menuWidth * scaling)
// Use the content height of the Flickable for implicit height
implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9, flickable.contentHeight + (Style.marginS * 2 * scaling))
+19
View File
@@ -10,6 +10,7 @@ import qs.Widgets
Item {
id: root
property ShellScreen screen
property real scaling: 1.0
@@ -148,6 +149,15 @@ Item {
asynchronous: true
smooth: true
visible: source !== ""
// Apply dock shader to active window icon (always themed)
layer.enabled: widgetSettings.colorizeIcons !== false
layer.effect: ShaderEffect {
property color targetColor: Color.mOnSurface
property real colorizeMode: 0.0 // Dock mode (grayscale)
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
}
}
}
@@ -314,6 +324,15 @@ Item {
asynchronous: true
smooth: true
visible: source !== ""
// Apply dock shader to active window icon (always themed)
layer.enabled: widgetSettings.colorizeIcons !== false
layer.effect: ShaderEffect {
property color targetColor: Color.mOnSurface
property real colorizeMode: 0.0 // Dock mode (grayscale)
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
}
}
}
}
+1
View File
@@ -10,6 +10,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
baseSize: Style.capsuleHeight
+1
View File
@@ -9,6 +9,7 @@ import qs.Services
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
+1 -2
View File
@@ -11,8 +11,7 @@ import qs.Modules.Bar.Extras
Item {
id: root
// Widget properties passed from Bar.qml
property var screen
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
+1
View File
@@ -6,6 +6,7 @@ import qs.Services
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
icon: "dark-mode"
+1
View File
@@ -8,6 +8,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
baseSize: Style.capsuleHeight
+1
View File
@@ -11,6 +11,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
compact: (Settings.data.bar.density === "compact")
@@ -10,6 +10,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
+1
View File
@@ -9,6 +9,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
baseSize: Style.capsuleHeight
+1
View File
@@ -7,6 +7,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
icon: "camera-video"
+1
View File
@@ -8,6 +8,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
compact: (Settings.data.bar.density === "compact")
+1 -2
View File
@@ -8,8 +8,7 @@ import qs.Widgets
Item {
id: root
// Widget properties passed from Bar.qml
property var screen
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
+11 -2
View File
@@ -36,8 +36,8 @@ Rectangle {
}
// Always visible when there are toplevels
implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : taskbarLayout.implicitWidth + Style.marginM * scaling * 2
implicitHeight: isVerticalBar ? taskbarLayout.implicitHeight + Style.marginM * scaling * 2 : Math.round(Style.capsuleHeight * scaling)
implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : Math.round(taskbarLayout.implicitWidth + Style.marginM * scaling * 2)
implicitHeight: isVerticalBar ? Math.round(taskbarLayout.implicitHeight + Style.marginM * scaling * 2) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
@@ -81,6 +81,15 @@ Rectangle {
asynchronous: true
opacity: modelData.isFocused ? Style.opacityFull : 0.6
// Apply dock shader to all taskbar icons
layer.enabled: widgetSettings.colorizeIcons !== false
layer.effect: ShaderEffect {
property color targetColor: Color.mOnSurface
property real colorizeMode: 0.0 // Dock mode (grayscale)
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
}
Rectangle {
anchors.bottomMargin: -2 * scaling
anchors.bottom: parent.bottom
+182 -62
View File
@@ -16,10 +16,108 @@ Rectangle {
property ShellScreen screen
property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
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 itemSize: isVertical ? width * 0.75 : height * 0.85
property real itemSize: Math.round(Style.capsuleHeight * 0.65 * scaling)
property list<string> blacklist: widgetSettings.blacklist || widgetMetadata.blacklist || [] // Read from settings
property var filteredItems: []
function wildCardMatch(str, rule) {
if (!str || !rule) {
return false
}
Logger.log("Tray", "wildCardMatch - Input str:", str, "rule:", rule)
// Escape all special regex characters in the rule
let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
// Convert '*' to '.*' for wildcard matching
let pattern = escapedRule.replace(/\\\*/g, '.*')
// Add ^ and $ to match the entire string
pattern = '^' + pattern + '$'
Logger.log("Tray", "wildCardMatch - Generated pattern:", pattern)
try {
const regex = new RegExp(pattern, 'i')
// 'i' for case-insensitive
Logger.log("Tray", "wildCardMatch - Regex test result:", regex.test(str))
return regex.test(str)
} catch (e) {
Logger.warn("Tray", "Invalid regex pattern for wildcard match:", rule, e.message)
return false // If regex is invalid, it won't match
}
}
// Debounce timer for updateFilteredItems to prevent excessive calls
// when multiple events (e.g., SystemTray changes, settings saves)
// trigger it in rapid succession, reducing redundant processing.
Timer {
id: updateDebounceTimer
interval: 100 // milliseconds
running: false
repeat: false
onTriggered: _performFilteredItemsUpdate()
}
function _performFilteredItemsUpdate() {
if (!root.blacklist || root.blacklist.length === 0) {
if (SystemTray.items && SystemTray.items.values) {
filteredItems = SystemTray.items.values
} else {
filteredItems = []
}
return
}
let newItems = []
if (SystemTray.items && SystemTray.items.values) {
const trayItems = SystemTray.items.values
for (var i = 0; i < trayItems.length; i++) {
const item = trayItems[i]
if (!item) {
continue
}
const title = item.tooltipTitle || item.name || item.id || ""
let isBlacklisted = false
for (var j = 0; j < root.blacklist.length; j++) {
const rule = root.blacklist[j]
if (wildCardMatch(title, rule)) {
isBlacklisted = true
break
}
}
if (!isBlacklisted) {
newItems.push(item)
}
}
}
filteredItems = newItems
}
function updateFilteredItems() {
updateDebounceTimer.restart()
}
function onLoaded() {
// When the widget is fully initialized with its props set the screen for the trayMenu
@@ -28,9 +126,27 @@ Rectangle {
}
}
visible: SystemTray.items.values.length > 0
implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginS * scaling * 2)
implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginS * scaling * 2) : Math.round(Style.capsuleHeight * scaling)
Connections {
target: SystemTray.items
function onValuesChanged() {
root.updateFilteredItems()
}
}
Connections {
target: Settings
function onSettingsSaved() {
root.updateFilteredItems()
}
}
Component.onCompleted: {
root.updateFilteredItems() // Initial update
}
visible: filteredItems.length > 0
implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : Math.round(trayFlow.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: isVertical ? Math.round(trayFlow.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
@@ -39,12 +155,12 @@ Rectangle {
Flow {
id: trayFlow
anchors.centerIn: parent
spacing: Style.marginS * scaling
spacing: Style.marginM * scaling
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
Repeater {
id: repeater
model: SystemTray.items
model: filteredItems
delegate: Item {
width: itemSize
@@ -56,10 +172,7 @@ Rectangle {
property ShellScreen screen: root.screen
anchors.centerIn: parent
width: Style.marginL * scaling
height: Style.marginL * scaling
smooth: false
anchors.fill: parent
asynchronous: true
backer.fillMode: Image.PreserveAspectFit
source: {
@@ -70,7 +183,6 @@ Rectangle {
// Process icon path
if (icon.includes("?path=")) {
// Seems qmlfmt does not support the following ES6 syntax: const[name, path] = icon.split
const chunks = icon.split("?path=")
const name = chunks[0]
const path = chunks[1]
@@ -80,69 +192,77 @@ Rectangle {
return icon
}
opacity: status === Image.Ready ? 1 : 0
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
if (!modelData) {
return
}
layer.enabled: widgetSettings.colorizeIcons !== false
layer.effect: ShaderEffect {
property color targetColor: Color.mOnSurface
property real colorizeMode: 1.0 // Tray mode (intensity-based)
if (mouse.button === Qt.LeftButton) {
// Close any open menu first
trayPanel.close()
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
}
if (!modelData.onlyMenu) {
modelData.activate()
}
} else if (mouse.button === Qt.MiddleButton) {
// Close any open menu first
trayPanel.close()
modelData.secondaryActivate && modelData.secondaryActivate()
} else if (mouse.button === Qt.RightButton) {
TooltipService.hideImmediately()
// Close the menu if it was visible
if (trayPanel && trayPanel.visible) {
trayPanel.close()
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
if (!modelData) {
return
}
if (modelData.hasMenu && modelData.menu && trayMenu.item) {
trayPanel.open()
if (mouse.button === Qt.LeftButton) {
// Close any open menu first
trayPanel.close()
// 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 * scaling
menuY = 0
} else if (barPosition === "right") {
// For right bar: position menu to the left of the bar
menuX = -trayMenu.item.width - Style.marginM * scaling
menuY = 0
if (!modelData.onlyMenu) {
modelData.activate()
}
} else if (mouse.button === Qt.MiddleButton) {
// Close any open menu first
trayPanel.close()
modelData.secondaryActivate && modelData.secondaryActivate()
} else if (mouse.button === Qt.RightButton) {
TooltipService.hideImmediately()
// Close the menu if it was visible
if (trayPanel && trayPanel.visible) {
trayPanel.close()
return
}
if (modelData.hasMenu && modelData.menu && trayMenu.item) {
trayPanel.open()
// 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 * scaling
menuY = 0
} else if (barPosition === "right") {
// For right bar: position menu to the left of the bar
menuX = -trayMenu.item.width - Style.marginM * scaling
menuY = 0
} else {
// For horizontal bars: center horizontally and position below
menuX = (width / 2) - (trayMenu.item.width / 2)
menuY = Math.round(Style.barHeight * scaling)
}
trayMenu.item.menu = modelData.menu
trayMenu.item.showAt(parent, menuX, menuY)
} else {
// For horizontal bars: center horizontally and position below
menuX = (width / 2) - (trayMenu.item.width / 2)
menuY = Math.round(Style.barHeight * scaling)
Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set")
}
trayMenu.item.menu = modelData.menu
trayMenu.item.showAt(parent, menuX, menuY)
} else {
Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set")
}
}
}
onEntered: {
trayPanel.close()
TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
onEntered: {
trayPanel.close()
TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
}
onExited: TooltipService.hide()
}
onExited: TooltipService.hide()
}
}
}
@@ -8,6 +8,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
baseSize: Style.capsuleHeight
+1
View File
@@ -10,6 +10,7 @@ import qs.Widgets
NIconButton {
id: root
property ShellScreen screen
property real scaling: 1.0
compact: (Settings.data.bar.density === "compact")