Merge branch 'noctalia-dev:main' into powermenu-ipc
This commit is contained in:
commit
5cee4c234a
45 changed files with 815 additions and 260 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
88
Commons/WidgetLoader.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -144,6 +144,7 @@ Loader {
|
||||||
maskEnabled: true
|
maskEnabled: true
|
||||||
maskSource: maskTexture
|
maskSource: maskTexture
|
||||||
maskInverted: false
|
maskInverted: false
|
||||||
|
maskSpreadAtMax: 0.75
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {}
|
mask: Region {}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
54
Modules/Bar/Widgets/WiFi.qml
Normal file
54
Modules/Bar/Widgets/WiFi.qml
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -225,7 +225,7 @@ NPanel {
|
||||||
Layout.bottomMargin: Style.marginM * scaling
|
Layout.bottomMargin: Style.marginM * scaling
|
||||||
radius: Style.radiusM * scaling
|
radius: Style.radiusM * scaling
|
||||||
color: Color.mSurface
|
color: Color.mSurface
|
||||||
border.color: searchInput.activeFocus ? Color.mTertiary : Color.mOutline
|
border.color: searchInput.activeFocus ? Color.mSecondary : Color.mOutline
|
||||||
border.width: Math.max(1, searchInput.activeFocus ? Style.borderM * scaling : Style.borderS * scaling)
|
border.width: Math.max(1, searchInput.activeFocus ? Style.borderM * scaling : Style.borderS * scaling)
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
@ -355,7 +355,7 @@ NPanel {
|
||||||
height: 65 * scaling
|
height: 65 * scaling
|
||||||
radius: Style.radiusM * scaling
|
radius: Style.radiusM * scaling
|
||||||
property bool isSelected: index === selectedIndex
|
property bool isSelected: index === selectedIndex
|
||||||
color: (appCardArea.containsMouse || isSelected) ? Color.mTertiary : Color.mSurface
|
color: (appCardArea.containsMouse || isSelected) ? Color.mSecondary : Color.mSurface
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
|
|
|
||||||
|
|
@ -294,13 +294,14 @@ Loader {
|
||||||
|
|
||||||
// Animated avatar with glow effect or audio visualizer
|
// Animated avatar with glow effect or audio visualizer
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 120 * scaling
|
width: 108 * scaling
|
||||||
height: 120 * scaling
|
height: 108 * scaling
|
||||||
radius: width * 0.5
|
radius: width * 0.5
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
border.color: Color.mPrimary
|
border.color: Color.mPrimary
|
||||||
border.width: Math.max(1, Style.borderL * scaling)
|
border.width: Math.max(1, Style.borderL * scaling)
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
z: 10
|
||||||
|
|
||||||
// Circular audio visualizer when music is playing
|
// Circular audio visualizer when music is playing
|
||||||
Loader {
|
Loader {
|
||||||
|
|
@ -464,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
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ NPanel {
|
||||||
width: notificationList ? notificationList.width : 380 * scaling
|
width: notificationList ? notificationList.width : 380 * scaling
|
||||||
height: Math.max(80, notificationContent.height + 30)
|
height: Math.max(80, notificationContent.height + 30)
|
||||||
radius: Style.radiusM * scaling
|
radius: Style.radiusM * scaling
|
||||||
color: notificationMouseArea.containsMouse ? Color.mTertiary : Color.mSurfaceVariant
|
color: notificationMouseArea.containsMouse ? Color.mSecondary : Color.mSurfaceVariant
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors {
|
anchors {
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ NPanel {
|
||||||
if (pending)
|
if (pending)
|
||||||
return Color.applyOpacity(Color.mPrimary, "20")
|
return Color.applyOpacity(Color.mPrimary, "20")
|
||||||
if (mouseArea.containsMouse)
|
if (mouseArea.containsMouse)
|
||||||
return Color.mTertiary
|
return Color.mSecondary
|
||||||
return Color.transparent
|
return Color.transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ NPanel {
|
||||||
id: barTab
|
id: barTab
|
||||||
Tabs.BarTab {}
|
Tabs.BarTab {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: audioTab
|
id: audioTab
|
||||||
Tabs.AudioTab {}
|
Tabs.AudioTab {}
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ ColumnLayout {
|
||||||
width: contributorsGrid.cellWidth - Style.marginL * scaling
|
width: contributorsGrid.cellWidth - Style.marginL * scaling
|
||||||
height: contributorsGrid.cellHeight - Style.marginXS * scaling
|
height: contributorsGrid.cellHeight - Style.marginXS * scaling
|
||||||
radius: Style.radiusL * scaling
|
radius: Style.radiusL * scaling
|
||||||
color: contributorArea.containsMouse ? Color.mTertiary : Color.transparent
|
color: contributorArea.containsMouse ? Color.mSecondary : Color.transparent
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
@ -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.borderM * scaling)
|
borderWidth: Math.max(1, Style.borderM * scaling)
|
||||||
imageRadius: width * 0.5
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,6 @@ ColumnLayout {
|
||||||
spacing: Style.marginL * scaling
|
spacing: Style.marginL * scaling
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Bar Components"
|
|
||||||
font.pointSize: Style.fontSizeXXL * scaling
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Color.mOnSurface
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: Style.marginXXS * scaling
|
spacing: Style.marginXXS * scaling
|
||||||
|
|
@ -78,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
|
||||||
|
|
||||||
|
|
@ -179,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)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,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 {
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ NBox {
|
||||||
// implicitWidth: 120 * scaling
|
// implicitWidth: 120 * scaling
|
||||||
// implicitHeight: 30 * scaling
|
// implicitHeight: 30 * scaling
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
border.color: playerSelector.activeFocus ? Color.mTertiary : Color.mOutline
|
border.color: playerSelector.activeFocus ? Color.mSecondary : Color.mOutline
|
||||||
border.width: Math.max(1, Style.borderS * scaling)
|
border.width: Math.max(1, Style.borderS * scaling)
|
||||||
radius: Style.radiusM * scaling
|
radius: Style.radiusM * scaling
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +138,7 @@ NBox {
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
width: popup.width - Style.marginS * scaling * 2
|
width: popup.width - Style.marginS * scaling * 2
|
||||||
color: highlighted ? Color.mTertiary : Color.transparent
|
color: highlighted ? Color.mSecondary : Color.transparent
|
||||||
radius: Style.radiusXS * scaling
|
radius: Style.radiusXS * scaling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeColorsToDisk(variant)
|
writeColorsToDisk(variant)
|
||||||
Logger.log("ColorScheme", "Applied color scheme:", path)
|
Logger.log("ColorScheme", "Applying color scheme:", path)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.error("ColorScheme", "Failed to parse scheme JSON:", e)
|
Logger.error("ColorScheme", "Failed to parse scheme JSON:", e)
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +90,8 @@ Singleton {
|
||||||
id: colorsWriter
|
id: colorsWriter
|
||||||
path: colorsJsonFilePath
|
path: colorsJsonFilePath
|
||||||
onSaved: {
|
onSaved: {
|
||||||
Logger.log("ColorScheme", "Colors saved")
|
|
||||||
|
// Logger.log("ColorScheme", "Colors saved")
|
||||||
}
|
}
|
||||||
JsonAdapter {
|
JsonAdapter {
|
||||||
id: out
|
id: out
|
||||||
|
|
@ -130,6 +131,9 @@ Singleton {
|
||||||
out.mOutline = pick(obj, "mOutline", "outline", out.mOutline)
|
out.mOutline = pick(obj, "mOutline", "outline", out.mOutline)
|
||||||
out.mShadow = pick(obj, "mShadow", "shadow", out.mShadow)
|
out.mShadow = pick(obj, "mShadow", "shadow", out.mShadow)
|
||||||
|
|
||||||
|
// Force a rewrite by updating the path
|
||||||
|
colorsWriter.path = ""
|
||||||
|
colorsWriter.path = colorsJsonFilePath
|
||||||
colorsWriter.writeAdapter()
|
colorsWriter.writeAdapter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
Shaders/frag/circled_image.frag
Normal file
30
Shaders/frag/circled_image.frag
Normal 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);
|
||||||
|
}
|
||||||
56
Shaders/frag/rounded_image.frag
Normal file
56
Shaders/frag/rounded_image.frag
Normal 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);
|
||||||
|
}
|
||||||
BIN
Shaders/qsb/circled_image.frag.qsb
Normal file
BIN
Shaders/qsb/circled_image.frag.qsb
Normal file
Binary file not shown.
BIN
Shaders/qsb/rounded_image.frag.qsb
Normal file
BIN
Shaders/qsb/rounded_image.frag.qsb
Normal file
Binary file not shown.
|
|
@ -16,6 +16,7 @@ ColumnLayout {
|
||||||
|
|
||||||
}
|
}
|
||||||
property string currentKey: ''
|
property string currentKey: ''
|
||||||
|
property string placeholder: ""
|
||||||
|
|
||||||
signal selected(string key)
|
signal selected(string key)
|
||||||
|
|
||||||
|
|
@ -50,7 +51,7 @@ ColumnLayout {
|
||||||
implicitWidth: Style.baseWidgetSize * 3.75 * scaling
|
implicitWidth: Style.baseWidgetSize * 3.75 * scaling
|
||||||
implicitHeight: preferredHeight
|
implicitHeight: preferredHeight
|
||||||
color: Color.mSurface
|
color: Color.mSurface
|
||||||
border.color: combo.activeFocus ? Color.mTertiary : Color.mOutline
|
border.color: combo.activeFocus ? Color.mSecondary : Color.mOutline
|
||||||
border.width: Math.max(1, Style.borderS * scaling)
|
border.width: Math.max(1, Style.borderS * scaling)
|
||||||
radius: Style.radiusM * scaling
|
radius: Style.radiusM * scaling
|
||||||
}
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -112,7 +115,7 @@ ColumnLayout {
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
width: combo.width - Style.marginM * scaling * 3
|
width: combo.width - Style.marginM * scaling * 3
|
||||||
color: highlighted ? Color.mTertiary : Color.transparent
|
color: highlighted ? Color.mSecondary : Color.transparent
|
||||||
radius: Style.radiusS * scaling
|
radius: Style.radiusS * scaling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
Widgets/NImageCircled.qml
Normal file
73
Widgets/NImageCircled.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,62 @@ Rectangle {
|
||||||
radius: scaledRadius
|
radius: scaledRadius
|
||||||
anchors.margins: Style.marginXXS * scaling
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "transparent"
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback icon
|
||||||
|
NIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: fallbackIcon
|
||||||
|
font.pointSize: Style.fontSizeXXL * scaling
|
||||||
|
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
||||||
|
z: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Border
|
// Border
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
@ -27,45 +83,7 @@ Rectangle {
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
border.color: parent.borderColor
|
border.color: parent.borderColor
|
||||||
border.width: parent.borderWidth
|
border.width: parent.borderWidth
|
||||||
|
antialiasing: true
|
||||||
z: 10
|
z: 10
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
|
||||||
id: img
|
|
||||||
anchors.fill: parent
|
|
||||||
source: imagePath
|
|
||||||
visible: false
|
|
||||||
mipmap: true
|
|
||||||
smooth: true
|
|
||||||
asynchronous: true
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiEffect {
|
|
||||||
anchors.fill: parent
|
|
||||||
source: img
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: mask
|
|
||||||
visible: imagePath !== ""
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: mask
|
|
||||||
anchors.fill: parent
|
|
||||||
layer.enabled: true
|
|
||||||
visible: false
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: scaledRadius
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback icon
|
|
||||||
NIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: fallbackIcon
|
|
||||||
font.pointSize: Style.fontSizeXXL * scaling
|
|
||||||
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
|
||||||
z: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: frame.radius
|
radius: frame.radius
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
border.color: input.activeFocus ? Color.mTertiary : Color.transparent
|
border.color: input.activeFocus ? Color.mSecondary : Color.transparent
|
||||||
border.width: input.activeFocus ? Math.max(1, Style.borderS * scaling) : 0
|
border.width: input.activeFocus ? Math.max(1, Style.borderS * scaling) : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
158
Widgets/NWidgetCard.qml
Normal file
158
Widgets/NWidgetCard.qml
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
shell.qml
11
shell.qml
|
|
@ -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 {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue