Merge branch 'noctalia-dev:main' into fix/gpu-screen-recorder-flatpak

This commit is contained in:
Kainoa Kanter 2025-08-22 12:37:38 -07:00 committed by GitHub
commit 893615fc9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 835 additions and 256 deletions

View file

@ -1,19 +1,19 @@
{ {
"dark": { "dark": {
"mPrimary": "#ebbcba", "mPrimary": "#ebbcba",
"mOnPrimary": "#191724", "mOnPrimary": "#1f1d2e",
"mSecondary": "#9ccfd8", "mSecondary": "#9ccfd8",
"mOnSecondary": "#191724", "mOnSecondary": "#1f1d2e",
"mTertiary": "#f6c177", "mTertiary": "#f6c177",
"mOnTertiary": "#191724", "mOnTertiary": "#1f1d2e",
"mError": "#eb6f92", "mError": "#eb6f92",
"mOnError": "#1f1d2e", "mOnError": "#1f1d2e",
"mSurface": "#191724", "mSurface": "#1f1d2e",
"mOnSurface": "#e0def4", "mOnSurface": "#e0def4",
"mSurfaceVariant": "#26233a", "mSurfaceVariant": "#26233a",
"mOnSurfaceVariant": "#908caa", "mOnSurfaceVariant": "#908caa",
"mOutline": "#403d52", "mOutline": "#403d52",
"mShadow": "#191724" "mShadow": "#1f1d2e"
}, },
"light": { "light": {
"mPrimary": "#d46e6b", "mPrimary": "#d46e6b",

View file

@ -47,6 +47,8 @@ Singleton {
// ----------- // -----------
function applyOpacity(color, opacity) { function applyOpacity(color, opacity) {
// Convert color to string and apply opacity // Convert color to string and apply opacity
if (!color)
return "transparent"
return color.toString().replace("#", "#" + opacity) return color.toString().replace("#", "#" + opacity)
} }

View file

@ -120,16 +120,18 @@ Singleton {
bar: JsonObject { bar: JsonObject {
property string position: "top" // Possible values: "top", "bottom" property string position: "top" // Possible values: "top", "bottom"
property bool showActiveWindow: true
property bool showActiveWindowIcon: true property bool showActiveWindowIcon: true
property bool showSystemInfo: false
property bool showMedia: false
property bool showBrightness: true
property bool showNotificationsHistory: true
property bool showTray: true
property bool alwaysShowBatteryPercentage: false property bool alwaysShowBatteryPercentage: false
property real backgroundOpacity: 1.0 property real backgroundOpacity: 1.0
property list<string> monitors: [] property list<string> monitors: []
// Widget configuration for modular bar system
property JsonObject widgets
widgets: JsonObject {
property list<string> left: ["SystemMonitor", "ActiveWindow", "MediaMini"]
property list<string> center: ["Workspace"]
property list<string> right: ["ScreenRecorderIndicator", "Tray", "NotificationHistory", "WiFi", "Bluetooth", "Battery", "Volume", "Brightness", "Clock", "SidePanelToggle"]
}
} }
// general // general

88
Commons/WidgetLoader.qml Normal file
View file

@ -0,0 +1,88 @@
import QtQuick
import qs.Commons
QtObject {
id: root
// Signal emitted when widget loading status changes
signal widgetLoaded(string widgetName)
signal widgetFailed(string widgetName, string error)
signal loadingComplete(int total, int loaded, int failed)
// Properties to track loading status
property int totalWidgets: 0
property int loadedWidgets: 0
property int failedWidgets: 0
// Auto-discover widget components
function getWidgetComponent(widgetName) {
if (!widgetName || widgetName.trim() === "") {
return null
}
const widgetPath = `../Modules/Bar/Widgets/${widgetName}.qml`
// Try to load the widget directly from file
const component = Qt.createComponent(widgetPath)
if (component.status === Component.Ready) {
return component
}
const errorMsg = `Failed to load ${widgetName}.qml widget, status: ${component.status}, error: ${component.errorString(
)}`
Logger.error("WidgetLoader", errorMsg)
return null
}
// Initialize loading tracking
function initializeLoading(widgetList) {
totalWidgets = widgetList.length
loadedWidgets = 0
failedWidgets = 0
}
// Track widget loading success
function onWidgetLoaded(widgetName) {
loadedWidgets++
widgetLoaded(widgetName)
if (loadedWidgets + failedWidgets === totalWidgets) {
Logger.log("WidgetLoader", `Loaded ${loadedWidgets} widgets`)
loadingComplete(totalWidgets, loadedWidgets, failedWidgets)
}
}
// Track widget loading failure
function onWidgetFailed(widgetName, error) {
failedWidgets++
widgetFailed(widgetName, error)
if (loadedWidgets + failedWidgets === totalWidgets) {
loadingComplete(totalWidgets, loadedWidgets, failedWidgets)
}
}
// This is where you should add your Modules/Bar/Widgets/
// so it gets registered in the BarTab
function discoverAvailableWidgets() {
const widgetFiles = ["ActiveWindow", "Battery", "Bluetooth", "Brightness", "Clock", "MediaMini", "NotificationHistory", "ScreenRecorderIndicator", "SidePanelToggle", "SystemMonitor", "Tray", "Volume", "WiFi", "Workspace"]
const availableWidgets = []
widgetFiles.forEach(widgetName => {
// Test if the widget can be loaded
const component = getWidgetComponent(widgetName)
if (component) {
availableWidgets.push({
"key": widgetName,
"name": widgetName
})
}
})
// Sort alphabetically
availableWidgets.sort((a, b) => a.name.localeCompare(b.name))
return availableWidgets
}
}

View file

@ -47,7 +47,7 @@ Variants {
layer.enabled: true layer.enabled: true
} }
// Left // Left Section - Dynamic Widgets
Row { Row {
id: leftSection id: leftSection
@ -57,14 +57,25 @@ Variants {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
SystemMonitor {} Repeater {
model: Settings.data.bar.widgets.left
ActiveWindow {} delegate: Loader {
id: leftWidgetLoader
MediaMini {} sourceComponent: widgetLoader.getWidgetComponent(modelData)
active: true
anchors.verticalCenter: parent.verticalCenter
onStatusChanged: {
if (status === Loader.Error) {
widgetLoader.onWidgetFailed(modelData, "Loader error")
} else if (status === Loader.Ready) {
widgetLoader.onWidgetLoaded(modelData)
}
}
}
}
} }
// Center // Center Section - Dynamic Widgets
Row { Row {
id: centerSection id: centerSection
@ -73,10 +84,25 @@ Variants {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Workspace {} Repeater {
model: Settings.data.bar.widgets.center
delegate: Loader {
id: centerWidgetLoader
sourceComponent: widgetLoader.getWidgetComponent(modelData)
active: true
anchors.verticalCenter: parent.verticalCenter
onStatusChanged: {
if (status === Loader.Error) {
widgetLoader.onWidgetFailed(modelData, "Loader error")
} else if (status === Loader.Ready) {
widgetLoader.onWidgetLoaded(modelData)
}
}
}
}
} }
// Right // Right Section - Dynamic Widgets
Row { Row {
id: rightSection id: rightSection
@ -86,44 +112,38 @@ Variants {
anchors.verticalCenter: bar.verticalCenter anchors.verticalCenter: bar.verticalCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
ScreenRecorderIndicator { Repeater {
anchors.verticalCenter: parent.verticalCenter model: Settings.data.bar.widgets.right
delegate: Loader {
id: rightWidgetLoader
sourceComponent: widgetLoader.getWidgetComponent(modelData)
active: true
anchors.verticalCenter: parent.verticalCenter
onStatusChanged: {
if (status === Loader.Error) {
widgetLoader.onWidgetFailed(modelData, "Loader error")
} else if (status === Loader.Ready) {
widgetLoader.onWidgetLoaded(modelData)
}
}
}
} }
Tray {
anchors.verticalCenter: parent.verticalCenter
}
NotificationHistory {
anchors.verticalCenter: parent.verticalCenter
}
WiFi {
anchors.verticalCenter: parent.verticalCenter
}
Bluetooth {
anchors.verticalCenter: parent.verticalCenter
}
Battery {
anchors.verticalCenter: parent.verticalCenter
}
Volume {
anchors.verticalCenter: parent.verticalCenter
}
Brightness {
anchors.verticalCenter: parent.verticalCenter
}
Clock {
anchors.verticalCenter: parent.verticalCenter
}
SidePanelToggle {}
} }
} }
// Widget loader instance
WidgetLoader {
id: widgetLoader
onWidgetFailed: function (widgetName, error) {
Logger.error("Bar", `Widget failed: ${widgetName} - ${error}`)
}
}
// Initialize widget loading tracking
Component.onCompleted: {
const allWidgets = [...Settings.data.bar.widgets.left, ...Settings.data.bar.widgets.center, ...Settings.data.bar.widgets.right]
widgetLoader.initializeLoading(allWidgets)
}
} }
} }

View file

@ -1,43 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
NIconButton {
id: root
readonly property bool wifiEnabled: Settings.data.network.wifiEnabled
sizeMultiplier: 0.8
visible: wifiEnabled
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
icon: {
let connected = false
let signalStrength = 0
for (const net in NetworkService.networks) {
if (NetworkService.networks[net].connected) {
connected = true
signalStrength = NetworkService.networks[net].signal
break
}
}
return connected ? NetworkService.signalIcon(signalStrength) : "wifi"
}
tooltipText: "WiFi Networks"
onClicked: {
wifiPanel.toggle(screen)
}
WiFiPanel {
id: wifiPanel
}
}

View file

@ -11,7 +11,7 @@ Row {
id: root id: root
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: (Settings.data.bar.showActiveWindow && getTitle() !== "") visible: getTitle() !== ""
property bool showingFullTitle: false property bool showingFullTitle: false
property int lastWindowIndex: -1 property int lastWindowIndex: -1

View file

@ -22,8 +22,6 @@ NPill {
// Choose icon based on charge and charging state // Choose icon based on charge and charging state
function batteryIcon() { function batteryIcon() {
if (!show)
return ""
if (charging) if (charging)
return "battery_android_bolt" return "battery_android_bolt"

View file

@ -10,9 +10,7 @@ import qs.Widgets
NIconButton { NIconButton {
id: root id: root
readonly property bool bluetoothEnabled: Settings.data.network.bluetoothEnabled
sizeMultiplier: 0.8 sizeMultiplier: 0.8
visible: bluetoothEnabled
colorBg: Color.mSurfaceVariant colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface colorFg: Color.mOnSurface
@ -33,8 +31,4 @@ NIconButton {
onClicked: { onClicked: {
bluetoothPanel.toggle(screen) bluetoothPanel.toggle(screen)
} }
BluetoothPanel {
id: bluetoothPanel
}
} }

View file

@ -10,7 +10,7 @@ Item {
width: pill.width width: pill.width
height: pill.height height: pill.height
visible: Settings.data.bar.showBrightness && firstBrightnessReceived && getMonitor() !== null visible: getMonitor() !== null
// Used to avoid opening the pill on Quickshell startup // Used to avoid opening the pill on Quickshell startup
property bool firstBrightnessReceived: false property bool firstBrightnessReceived: false

View file

@ -11,7 +11,8 @@ Row {
id: root id: root
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: Settings.data.bar.showMedia && (MediaService.canPlay || MediaService.canPause) visible: MediaService.currentPlayer !== null
width: MediaService.currentPlayer !== null ? implicitWidth : 0
function getTitle() { function getTitle() {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
@ -109,14 +110,14 @@ Row {
visible: Settings.data.audio.showMiniplayerAlbumArt visible: Settings.data.audio.showMiniplayerAlbumArt
Rectangle { Rectangle {
width: 16 * scaling width: 18 * scaling
height: 16 * scaling height: 18 * scaling
radius: width * 0.5 radius: width * 0.5
color: Color.transparent color: Color.transparent
antialiasing: true antialiasing: true
clip: true clip: true
NImageRounded { NImageCircled {
id: trackArt id: trackArt
visible: MediaService.trackArtUrl.toString() !== "" visible: MediaService.trackArtUrl.toString() !== ""
anchors.fill: parent anchors.fill: parent
@ -126,8 +127,6 @@ Row {
fallbackIcon: MediaService.isPlaying ? "pause" : "play_arrow" fallbackIcon: MediaService.isPlaying ? "pause" : "play_arrow"
borderWidth: 0 borderWidth: 0
border.color: Color.transparent border.color: Color.transparent
imageRadius: width
antialiasing: true
} }
// Fallback icon when no album art available // Fallback icon when no album art available

View file

@ -10,7 +10,6 @@ import qs.Widgets
NIconButton { NIconButton {
id: root id: root
visible: Settings.data.bar.showNotificationsHistory
sizeMultiplier: 0.8 sizeMultiplier: 0.8
icon: "notifications" icon: "notifications"
tooltipText: "Notification History" tooltipText: "Notification History"

View file

@ -8,7 +8,6 @@ Row {
id: root id: root
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: (Settings.data.bar.showSystemInfo)
Rectangle { Rectangle {
// Let the Rectangle size itself based on its content (the Row) // Let the Rectangle size itself based on its content (the Row)

View file

@ -12,9 +12,8 @@ import qs.Widgets
Rectangle { Rectangle {
readonly property real itemSize: 24 * scaling readonly property real itemSize: 24 * scaling
visible: Settings.data.bar.showTray && (SystemTray.items.values.length > 0) visible: SystemTray.items.values.length > 0
width: tray.width + Style.marginM * scaling * 2 width: tray.width + Style.marginM * scaling * 2
height: Math.round(Style.capsuleHeight * scaling) height: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling) radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant color: Color.mSurfaceVariant
@ -95,14 +94,14 @@ Rectangle {
return return
} }
if (modelData.hasMenu && modelData.menu && trayMenu) { if (modelData.hasMenu && modelData.menu && trayMenu.item) {
trayPanel.open() trayPanel.open()
// Anchor the menu to the tray icon item (parent) and position it below the icon // Anchor the menu to the tray icon item (parent) and position it below the icon
const menuX = (width / 2) - (trayMenu.width / 2) const menuX = (width / 2) - (trayMenu.item.width / 2)
const menuY = (Style.barHeight * scaling) const menuY = (Style.barHeight * scaling)
trayMenu.menu = modelData.menu trayMenu.item.menu = modelData.menu
trayMenu.showAt(parent, menuX, menuY) trayMenu.item.showAt(parent, menuX, menuY)
} else { } else {
Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set") Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set")
} }
@ -142,7 +141,7 @@ Rectangle {
function close() { function close() {
visible = false visible = false
trayMenu.hideMenu() trayMenu.item.hideMenu()
} }
// Clicking outside of the rectangle to close // Clicking outside of the rectangle to close
@ -151,8 +150,9 @@ Rectangle {
onClicked: trayPanel.close() onClicked: trayPanel.close()
} }
TrayMenu { Loader {
id: trayMenu id: trayMenu
source: "TrayMenu.qml"
} }
} }
} }

View file

@ -134,7 +134,7 @@ PopupWindow {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: mouseArea.containsMouse ? Color.mSecondary : Color.transparent color: mouseArea.containsMouse ? Color.mTertiary : Color.transparent
radius: Style.radiusS * scaling radius: Style.radiusS * scaling
visible: !(modelData?.isSeparator ?? false) visible: !(modelData?.isSeparator ?? false)

View file

@ -0,0 +1,54 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
NIconButton {
id: root
sizeMultiplier: 0.8
Component.onCompleted: {
Logger.log("WiFi", "Widget component completed")
Logger.log("WiFi", "NetworkService available:", !!NetworkService)
if (NetworkService) {
Logger.log("WiFi", "NetworkService.networks available:", !!NetworkService.networks)
}
}
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
icon: {
try {
let connected = false
let signalStrength = 0
for (const net in NetworkService.networks) {
if (NetworkService.networks[net].connected) {
connected = true
signalStrength = NetworkService.networks[net].signal
break
}
}
return connected ? NetworkService.signalIcon(signalStrength) : "wifi"
} catch (error) {
Logger.error("WiFi", "Error getting icon:", error)
return "wifi"
}
}
tooltipText: "WiFi Networks"
onClicked: {
try {
Logger.log("WiFi", "Button clicked, toggling panel")
wifiPanel.toggle(screen)
} catch (error) {
Logger.error("WiFi", "Error toggling panel:", error)
}
}
}

View file

@ -118,7 +118,7 @@ NPanel {
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: { color: {
if (availableDeviceArea.containsMouse && !isBusy) if (availableDeviceArea.containsMouse && !isBusy)
return Color.mSecondary return Color.mTertiary
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting) if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
return Color.mPrimary return Color.mPrimary

View file

@ -465,13 +465,12 @@ Loader {
} }
} }
NImageRounded { NImageCircled {
anchors.centerIn: parent anchors.centerIn: parent
width: 100 * scaling width: 100 * scaling
height: 100 * scaling height: 100 * scaling
imagePath: Settings.data.general.avatarImage imagePath: Settings.data.general.avatarImage
fallbackIcon: "person" fallbackIcon: "person"
imageRadius: width * 0.5
} }
// Hover animation // Hover animation

View file

@ -47,6 +47,7 @@ NPanel {
id: barTab id: barTab
Tabs.BarTab {} Tabs.BarTab {}
} }
Component { Component {
id: audioTab id: audioTab
Tabs.AudioTab {} Tabs.AudioTab {}
@ -202,7 +203,7 @@ NPanel {
width: parent.width width: parent.width
height: 32 * scaling height: 32 * scaling
radius: Style.radiusS * scaling radius: Style.radiusS * scaling
color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mSecondary : Color.transparent) color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mTertiary : Color.transparent)
readonly property bool selected: index === currentTabIndex readonly property bool selected: index === currentTabIndex
property bool hovering: false property bool hovering: false
property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnTertiary : Color.mOnSurface) property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnTertiary : Color.mOnSurface)
@ -265,7 +266,7 @@ NPanel {
// Tab label on the main right side // Tab label on the main right side
NText { NText {
text: root.tabsModel[currentTabIndex].label text: root.tabsModel[currentTabIndex].label
font.pointSize: Style.fontSizeXL * scaling font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mPrimary color: Color.mPrimary
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -211,14 +211,13 @@ ColumnLayout {
Layout.preferredWidth: Style.baseWidgetSize * 2 * scaling Layout.preferredWidth: Style.baseWidgetSize * 2 * scaling
Layout.preferredHeight: Style.baseWidgetSize * 2 * scaling Layout.preferredHeight: Style.baseWidgetSize * 2 * scaling
NImageRounded { NImageCircled {
imagePath: modelData.avatar_url || "" imagePath: modelData.avatar_url || ""
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.marginXS * scaling anchors.margins: Style.marginXS * scaling
fallbackIcon: "person" fallbackIcon: "person"
borderColor: Color.mPrimary borderColor: Color.mPrimary
borderWidth: Math.max(1, Style.borderL * scaling) borderWidth: Math.max(1, Style.borderM * scaling)
imageRadius: width * 0.5
} }
} }

View file

@ -33,6 +33,7 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
ColumnLayout { ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
@ -71,70 +72,7 @@ ColumnLayout {
} }
} }
NToggle { ColumnLayout {
label: "Show Active Window"
description: "Display the title of the currently focused window."
checked: Settings.data.bar.showActiveWindow
onToggled: checked => {
Settings.data.bar.showActiveWindow = checked
}
}
NToggle {
label: "Show Active Window's Icon"
description: "Display the app icon next to the title of the currently focused window."
checked: Settings.data.bar.showActiveWindowIcon
onToggled: checked => {
Settings.data.bar.showActiveWindowIcon = checked
}
}
NToggle {
label: "Show System Info"
description: "Display system statistics (CPU, RAM, Temperature)."
checked: Settings.data.bar.showSystemInfo
onToggled: checked => {
Settings.data.bar.showSystemInfo = checked
}
}
NToggle {
label: "Show Media"
description: "Display media controls and information."
checked: Settings.data.bar.showMedia
onToggled: checked => {
Settings.data.bar.showMedia = checked
}
}
NToggle {
label: "Show Notifications History"
description: "Display a shortcut to the notifications history."
checked: Settings.data.bar.showNotificationsHistory
onToggled: checked => {
Settings.data.bar.showNotificationsHistory = checked
}
}
NToggle {
label: "Show Applications Tray"
description: "Display the applications tray."
checked: Settings.data.bar.showTray
onToggled: checked => {
Settings.data.bar.showTray = checked
}
}
NToggle {
label: "Show Battery Percentage"
description: "Show battery percentage at all times."
checked: Settings.data.bar.alwaysShowBatteryPercentage
onToggled: checked => {
Settings.data.bar.alwaysShowBatteryPercentage = checked
}
}
ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
@ -172,7 +110,168 @@ ColumnLayout {
} }
} }
} }
NToggle {
label: "Show Active Window's Icon"
description: "Display the app icon next to the title of the currently focused window."
checked: Settings.data.bar.showActiveWindowIcon
onToggled: checked => {
Settings.data.bar.showActiveWindowIcon = checked
}
}
NToggle {
label: "Show Battery Percentage"
description: "Show battery percentage at all times."
checked: Settings.data.bar.alwaysShowBatteryPercentage
onToggled: checked => {
Settings.data.bar.alwaysShowBatteryPercentage = checked
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Widgets Management Section
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Widgets Positioning"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Add, remove, or reorder widgets in each section of the bar using the control buttons."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
// Bar Sections
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.marginM * scaling
spacing: Style.marginM * scaling
// Left Section
NWidgetCard {
sectionName: "Left"
widgetModel: Settings.data.bar.widgets.left
availableWidgets: availableWidgets
scrollView: scrollView
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
// Center Section
NWidgetCard {
sectionName: "Center"
widgetModel: Settings.data.bar.widgets.center
availableWidgets: availableWidgets
scrollView: scrollView
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
// Right Section
NWidgetCard {
sectionName: "Right"
widgetModel: Settings.data.bar.widgets.right
availableWidgets: availableWidgets
scrollView: scrollView
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
}
}
} }
} }
} }
// Helper functions
function addWidgetToSection(widgetName, section) {
console.log("Adding widget", widgetName, "to section", section)
var sectionArray = Settings.data.bar.widgets[section]
if (sectionArray) {
// Create a new array to avoid modifying the original
var newArray = sectionArray.slice()
newArray.push(widgetName)
console.log("Widget added. New array:", JSON.stringify(newArray))
// Assign the new array
Settings.data.bar.widgets[section] = newArray
}
}
function removeWidgetFromSection(section, index) {
console.log("Removing widget from section", section, "at index", index)
var sectionArray = Settings.data.bar.widgets[section]
if (sectionArray && index >= 0 && index < sectionArray.length) {
// Create a new array to avoid modifying the original
var newArray = sectionArray.slice()
newArray.splice(index, 1)
console.log("Widget removed. New array:", JSON.stringify(newArray))
// Assign the new array
Settings.data.bar.widgets[section] = newArray
}
}
function reorderWidgetInSection(section, fromIndex, toIndex) {
console.log("Reordering widget in section", section, "from", fromIndex, "to", toIndex)
var sectionArray = Settings.data.bar.widgets[section]
if (sectionArray && fromIndex >= 0 && fromIndex < sectionArray.length && toIndex >= 0
&& toIndex < sectionArray.length) {
// Create a new array to avoid modifying the original
var newArray = sectionArray.slice()
var item = newArray[fromIndex]
newArray.splice(fromIndex, 1)
newArray.splice(toIndex, 0, item)
console.log("Widget reordered. New array:", JSON.stringify(newArray))
// Assign the new array
Settings.data.bar.widgets[section] = newArray
}
}
// Widget loader for discovering available widgets
WidgetLoader {
id: widgetLoader
}
ListModel {
id: availableWidgets
}
Component.onCompleted: {
discoverWidgets()
}
// Automatically discover available widgets using WidgetLoader
function discoverWidgets() {
availableWidgets.clear()
// Use WidgetLoader to discover available widgets
const discoveredWidgets = widgetLoader.discoverAvailableWidgets()
// Add discovered widgets to the ListModel
discoveredWidgets.forEach(widget => {
availableWidgets.append(widget)
})
}
} }

View file

@ -6,33 +6,34 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { Item {
id: root property real scaling: 1
spacing: 0
readonly property string tabIcon: "brightness_6" readonly property string tabIcon: "brightness_6"
readonly property string tabLabel: "Brightness" readonly property string tabLabel: "Brightness"
Layout.fillWidth: true
Layout.fillHeight: true
ScrollView { ScrollView {
id: scrollView anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
padding: Style.marginM * scaling
clip: true clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
contentWidth: parent.width
ColumnLayout { ColumnLayout {
width: scrollView.availableWidth width: parent.width
spacing: 0
ColumnLayout { ColumnLayout {
width: scrollView.availableWidth
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.margins: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText {
text: "Brightness Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
// Bar Visibility Section // Bar Visibility Section
NToggle { NToggle {
label: "Show Bar Icon" label: "Show Bar Icon"

View file

@ -163,28 +163,22 @@ ColumnLayout {
Layout.bottomMargin: Style.marginL * scaling Layout.bottomMargin: Style.marginL * scaling
} }
NText {
text: "Predefined Color Schemes"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
ColumnLayout { ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
// NText { NText {
// text: "Predefined Color Schemes" text: "Predefined Color Schemes"
// font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
// font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
// color: Color.mOnSurface color: Color.mOnSurface
// Layout.fillWidth: true Layout.fillWidth: true
// } }
NText { NText {
text: "These color schemes only apply when 'Use Matugen' is disabled. When enabled, Matugen will generate colors based on your wallpaper instead. You can toggle between light and dark themes when using Matugen." text: "These color schemes only apply when 'Use Matugen' is disabled. When enabled, Matugen will generate colors based on your wallpaper instead. You can toggle between light and dark themes when using Matugen."
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurface
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }

View file

@ -33,6 +33,13 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText {
text: "General Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
// Profile section // Profile section
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
@ -44,13 +51,13 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
// Avatar preview // Avatar preview
NImageRounded { NImageCircled {
width: 64 * scaling width: 64 * scaling
height: 64 * scaling height: 64 * scaling
imagePath: Settings.data.general.avatarImage imagePath: Settings.data.general.avatarImage
fallbackIcon: "person" fallbackIcon: "person"
borderColor: Color.mPrimary borderColor: Color.mPrimary
borderWidth: Math.max(1, Style.borderM) borderWidth: Math.max(1, Style.borderM * scaling)
} }
NTextInput { NTextInput {

View file

@ -34,7 +34,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
NText { NText {
text: "Launcer Options" text: "Launcher"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSurface color: Color.mOnSurface
@ -56,7 +56,7 @@ ColumnLayout {
} }
NText { NText {
text: "Launcer Anchoring" text: "Launcher Position"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSurface color: Color.mOnSurface

View file

@ -34,6 +34,13 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText {
text: "Interfaces"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NToggle { NToggle {
label: "WiFi Enabled" label: "WiFi Enabled"
description: "Enable WiFi connectivity." description: "Enable WiFi connectivity."

View file

@ -33,6 +33,14 @@ ColumnLayout {
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText {
text: "Recordings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Output Directory // Output Directory
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling

View file

@ -33,6 +33,14 @@ ColumnLayout {
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText {
text: "Location"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Location section // Location section
ColumnLayout { ColumnLayout {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling

View file

@ -31,7 +31,7 @@ Item {
// Current wallpaper display // Current wallpaper display
NText { NText {
text: "Current Wallpaper" text: "Current Wallpaper"
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSurface color: Color.mOnSurface
} }

View file

@ -34,6 +34,14 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText {
text: "Directory"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Wallpaper Settings Category // Wallpaper Settings Category
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling

View file

@ -164,7 +164,7 @@ NBox {
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
clip: true clip: true
NImageRounded { NImageCircled {
id: trackArt id: trackArt
visible: MediaService.trackArtUrl.toString() !== "" visible: MediaService.trackArtUrl.toString() !== ""
@ -174,7 +174,6 @@ NBox {
fallbackIcon: "music_note" fallbackIcon: "music_note"
borderColor: Color.mOutline borderColor: Color.mOutline
borderWidth: Math.max(1, Style.borderS * scaling) borderWidth: Math.max(1, Style.borderS * scaling)
imageRadius: width * 0.5
} }
// Fallback icon when no album art available // Fallback icon when no album art available

View file

@ -28,7 +28,7 @@ NBox {
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginM * scaling
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NImageRounded { NImageCircled {
width: Style.baseWidgetSize * 1.25 * scaling width: Style.baseWidgetSize * 1.25 * scaling
height: Style.baseWidgetSize * 1.25 * scaling height: Style.baseWidgetSize * 1.25 * scaling
imagePath: Settings.data.general.avatarImage imagePath: Settings.data.general.avatarImage

View file

@ -147,7 +147,7 @@ NPanel {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 1.5 * scaling Layout.preferredHeight: Style.baseWidgetSize * 1.5 * scaling
radius: Style.radiusS * scaling radius: Style.radiusS * scaling
color: modelData.connected ? Color.mPrimary : (networkMouseArea.containsMouse ? Color.mSecondary : Color.transparent) color: modelData.connected ? Color.mPrimary : (networkMouseArea.containsMouse ? Color.mTertiary : Color.transparent)
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent

View file

@ -0,0 +1,30 @@
#version 450
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D source;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float imageOpacity;
} ubuf;
void main() {
// Center coordinates around (0, 0)
vec2 uv = qt_TexCoord0 - 0.5;
// Calculate distance from center
float distance = length(uv);
// Create circular mask - anything beyond radius 0.5 is transparent
float mask = 1.0 - smoothstep(0.48, 0.52, distance);
// Sample the texture
vec4 color = texture(source, qt_TexCoord0);
// Apply the circular mask and opacity
float finalAlpha = color.a * mask * ubuf.imageOpacity * ubuf.qt_Opacity;
fragColor = vec4(color.rgb * finalAlpha, finalAlpha);
}

View file

@ -0,0 +1,56 @@
#version 450
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D source;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
// Custom properties with non-conflicting names
float itemWidth;
float itemHeight;
float cornerRadius;
float imageOpacity;
} ubuf;
// Function to calculate the signed distance from a point to a rounded box
float roundedBoxSDF(vec2 centerPos, vec2 boxSize, float radius) {
vec2 d = abs(centerPos) - boxSize + radius;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
}
void main() {
// Get size from uniforms
vec2 itemSize = vec2(ubuf.itemWidth, ubuf.itemHeight);
float cornerRadius = ubuf.cornerRadius;
float itemOpacity = ubuf.imageOpacity;
// Normalize coordinates to [-0.5, 0.5] range
vec2 uv = qt_TexCoord0 - 0.5;
// Scale by aspect ratio to maintain uniform rounding
vec2 aspectRatio = itemSize / max(itemSize.x, itemSize.y);
uv *= aspectRatio;
// Calculate half size in normalized space
vec2 halfSize = 0.5 * aspectRatio;
// Normalize the corner radius
float normalizedRadius = cornerRadius / max(itemSize.x, itemSize.y);
// Calculate distance to rounded rectangle
float distance = roundedBoxSDF(uv, halfSize, normalizedRadius);
// Create smooth alpha mask
float smoothedAlpha = 1.0 - smoothstep(0.0, fwidth(distance), distance);
// Sample the texture
vec4 color = texture(source, qt_TexCoord0);
// Apply the rounded mask and opacity
// Make sure areas outside the rounded rect are completely transparent
float finalAlpha = color.a * smoothedAlpha * itemOpacity * ubuf.qt_Opacity;
fragColor = vec4(color.rgb * finalAlpha, finalAlpha);
}

Binary file not shown.

Binary file not shown.

View file

@ -16,6 +16,7 @@ ColumnLayout {
} }
property string currentKey: '' property string currentKey: ''
property string placeholder: ""
signal selected(string key) signal selected(string key)
@ -61,8 +62,10 @@ ColumnLayout {
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight elide: Text.ElideRight
text: (combo.currentIndex >= 0 && combo.currentIndex < root.model.count) ? root.model.get( color: (combo.currentIndex >= 0
combo.currentIndex).name : "" && combo.currentIndex < root.model.count) ? Color.mOnSurface : Color.mOnSurfaceVariant
text: (combo.currentIndex >= 0
&& combo.currentIndex < root.model.count) ? root.model.get(combo.currentIndex).name : root.placeholder
} }
indicator: NIcon { indicator: NIcon {

73
Widgets/NImageCircled.qml Normal file
View file

@ -0,0 +1,73 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Widgets
import qs.Commons
import qs.Services
Rectangle {
id: root
property string imagePath: ""
property string fallbackIcon: ""
property color borderColor: Color.transparent
property real borderWidth: 0
color: Color.transparent
radius: parent.width * 0.5
anchors.margins: Style.marginXXS * scaling
Rectangle {
color: Color.transparent
anchors.fill: parent
Image {
id: img
anchors.fill: parent
source: imagePath
visible: false // Hide since we're using it as shader source
mipmap: true
smooth: true
asynchronous: true
antialiasing: true
fillMode: Image.PreserveAspectCrop
}
ShaderEffect {
anchors.fill: parent
property var source: ShaderEffectSource {
sourceItem: img
hideSource: true
live: true
recursive: false
format: ShaderEffectSource.RGBA
}
property real imageOpacity: root.opacity
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/circled_image.frag.qsb")
supportsAtlasTextures: false
blending: true
}
// Fallback icon
NIcon {
anchors.centerIn: parent
text: fallbackIcon
font.pointSize: Style.fontSizeXXL * scaling
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
z: 0
}
}
//Border
Rectangle {
anchors.fill: parent
radius: parent.radius
color: Color.transparent
border.color: parent.borderColor
border.width: parent.borderWidth
antialiasing: true
z: 10
}
}

View file

@ -23,13 +23,12 @@ Rectangle {
Rectangle { Rectangle {
color: Color.transparent color: Color.transparent
anchors.fill: parent anchors.fill: parent
anchors.margins: borderWidth
Image { Image {
id: img id: img
anchors.fill: parent anchors.fill: parent
source: imagePath source: imagePath
visible: false visible: false // Hide since we're using it as shader source
mipmap: true mipmap: true
smooth: true smooth: true
asynchronous: true asynchronous: true
@ -37,24 +36,33 @@ Rectangle {
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
} }
MultiEffect { ShaderEffect {
anchors.fill: parent anchors.fill: parent
source: img
maskEnabled: true
maskSource: mask
maskSpreadAtMax: 0.75
visible: imagePath !== ""
}
Item { property var source: ShaderEffectSource {
id: mask sourceItem: img
anchors.fill: parent hideSource: true
layer.enabled: true live: true
visible: false recursive: false
format: ShaderEffectSource.RGBA
}
// Use custom property names to avoid conflicts with final properties
property real itemWidth: root.width
property real itemHeight: root.height
property real cornerRadius: root.radius
property real imageOpacity: root.opacity
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/rounded_image.frag.qsb")
// Qt6 specific properties - ensure proper blending
supportsAtlasTextures: false
blending: true
// Make sure the background is transparent
Rectangle { Rectangle {
id: background
anchors.fill: parent anchors.fill: parent
radius: scaledRadius color: "transparent"
antialiasing: true z: -1
} }
} }

158
Widgets/NWidgetCard.qml Normal file
View file

@ -0,0 +1,158 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
NCard {
id: root
property string sectionName: ""
property var widgetModel: []
property var availableWidgets: []
property var scrollView: null
signal addWidget(string widgetName, string section)
signal removeWidget(string section, int index)
signal reorderWidget(string section, int fromIndex, int toIndex)
Layout.fillWidth: true
Layout.minimumHeight: {
var widgetCount = widgetModel.length
if (widgetCount === 0)
return 140 * scaling
var availableWidth = scrollView ? scrollView.availableWidth - (Style.marginM * scaling * 2) : 400 * scaling
var avgWidgetWidth = 150 * scaling
var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth))
var rows = Math.ceil(widgetCount / widgetsPerRow)
return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginM * scaling
spacing: Style.marginM * scaling
RowLayout {
Layout.fillWidth: true
NText {
text: sectionName + " Section"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.alignment: Qt.AlignVCenter
}
Item {
Layout.fillWidth: true
}
NComboBox {
id: comboBox
width: 120 * scaling
model: availableWidgets
label: ""
description: ""
placeholder: "Add widget to " + sectionName.toLowerCase() + " section"
onSelected: key => {
comboBox.selectedKey = key
}
}
NIconButton {
icon: "add"
size: 24 * scaling
colorBg: Color.mPrimary
colorFg: Color.mOnPrimary
colorBgHover: Color.mPrimaryContainer
colorFgHover: Color.mOnPrimaryContainer
enabled: comboBox.selectedKey !== ""
Layout.alignment: Qt.AlignVCenter
onClicked: {
if (comboBox.selectedKey !== "") {
addWidget(comboBox.selectedKey, sectionName.toLowerCase())
comboBox.reset()
}
}
}
}
Flow {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 65 * scaling
spacing: Style.marginS * scaling
flow: Flow.LeftToRight
Repeater {
model: widgetModel
delegate: Rectangle {
width: widgetContent.implicitWidth + 16 * scaling
height: 48 * scaling
radius: Style.radiusS * scaling
color: Color.mPrimary
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
RowLayout {
id: widgetContent
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NIconButton {
icon: "chevron_left"
size: 20 * scaling
colorBg: Color.applyOpacity(Color.mOnPrimary, "20")
colorFg: Color.mOnPrimary
colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40")
colorFgHover: Color.mOnPrimary
enabled: index > 0
onClicked: {
if (index > 0) {
reorderWidget(sectionName.toLowerCase(), index, index - 1)
}
}
}
NText {
text: modelData
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnPrimary
horizontalAlignment: Text.AlignHCenter
}
NIconButton {
icon: "chevron_right"
size: 20 * scaling
colorBg: Color.applyOpacity(Color.mOnPrimary, "20")
colorFg: Color.mOnPrimary
colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40")
colorFgHover: Color.mOnPrimary
enabled: index < widgetModel.length - 1
onClicked: {
if (index < widgetModel.length - 1) {
reorderWidget(sectionName.toLowerCase(), index, index + 1)
}
}
}
NIconButton {
icon: "close"
size: 20 * scaling
colorBg: Color.applyOpacity(Color.mOnPrimary, "20")
colorFg: Color.mOnPrimary
colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40")
colorFgHover: Color.mOnPrimary
onClicked: {
removeWidget(sectionName.toLowerCase(), index)
}
}
}
}
}
}
}
}

View file

@ -16,6 +16,7 @@ import qs.Commons
import qs.Modules.Launcher import qs.Modules.Launcher
import qs.Modules.Background import qs.Modules.Background
import qs.Modules.Bar import qs.Modules.Bar
import qs.Modules.BluetoothPanel
import qs.Modules.Calendar import qs.Modules.Calendar
import qs.Modules.Dock import qs.Modules.Dock
import qs.Modules.IPC import qs.Modules.IPC
@ -25,7 +26,7 @@ import qs.Modules.SettingsPanel
import qs.Modules.PowerPanel import qs.Modules.PowerPanel
import qs.Modules.SidePanel import qs.Modules.SidePanel
import qs.Modules.Toast import qs.Modules.Toast
import qs.Modules.WiFiPanel
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@ -70,6 +71,14 @@ ShellRoot {
id: powerPanel id: powerPanel
} }
WiFiPanel {
id: wifiPanel
}
BluetoothPanel {
id: bluetoothPanel
}
ToastManager {} ToastManager {}
IPCManager {} IPCManager {}