mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Merge branch 'main' into feat/calendar-events
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import qs.Widgets
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
baseSize: Style.capsuleHeight
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,6 +6,7 @@ import qs.Services
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
icon: "dark-mode"
|
||||
|
||||
@@ -8,6 +8,7 @@ import qs.Widgets
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
baseSize: Style.capsuleHeight
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@ import qs.Widgets
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
baseSize: Style.capsuleHeight
|
||||
|
||||
@@ -7,6 +7,7 @@ import qs.Widgets
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
icon: "camera-video"
|
||||
|
||||
@@ -8,6 +8,7 @@ import qs.Widgets
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -10,6 +10,7 @@ import qs.Widgets
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
|
||||
Reference in New Issue
Block a user