mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
Merge branch 'noctalia-dev:main' into feat/cava-fps-config
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"dark": {
|
||||
"mPrimary": "#ebbcba",
|
||||
"mOnPrimary": "#191724",
|
||||
"mOnPrimary": "#1f1d2e",
|
||||
"mSecondary": "#9ccfd8",
|
||||
"mOnSecondary": "#191724",
|
||||
"mOnSecondary": "#1f1d2e",
|
||||
"mTertiary": "#f6c177",
|
||||
"mOnTertiary": "#191724",
|
||||
"mOnTertiary": "#1f1d2e",
|
||||
"mError": "#eb6f92",
|
||||
"mOnError": "#1f1d2e",
|
||||
"mSurface": "#191724",
|
||||
"mSurface": "#1f1d2e",
|
||||
"mOnSurface": "#e0def4",
|
||||
"mSurfaceVariant": "#26233a",
|
||||
"mOnSurfaceVariant": "#908caa",
|
||||
"mOutline": "#403d52",
|
||||
"mShadow": "#191724"
|
||||
"mShadow": "#1f1d2e"
|
||||
},
|
||||
"light": {
|
||||
"mPrimary": "#d46e6b",
|
||||
|
||||
@@ -47,6 +47,8 @@ Singleton {
|
||||
// -----------
|
||||
function applyOpacity(color, opacity) {
|
||||
// Convert color to string and apply opacity
|
||||
if (!color)
|
||||
return "transparent"
|
||||
return color.toString().replace("#", "#" + opacity)
|
||||
}
|
||||
|
||||
|
||||
@@ -120,16 +120,18 @@ Singleton {
|
||||
|
||||
bar: JsonObject {
|
||||
property string position: "top" // Possible values: "top", "bottom"
|
||||
property bool showActiveWindow: 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 real backgroundOpacity: 1.0
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ Variants {
|
||||
layer.enabled: true
|
||||
}
|
||||
|
||||
// Left
|
||||
// Left Section - Dynamic Widgets
|
||||
Row {
|
||||
id: leftSection
|
||||
|
||||
@@ -57,14 +57,25 @@ Variants {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
SystemMonitor {}
|
||||
|
||||
ActiveWindow {}
|
||||
|
||||
MediaMini {}
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.left
|
||||
delegate: Loader {
|
||||
id: leftWidgetLoader
|
||||
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 {
|
||||
id: centerSection
|
||||
|
||||
@@ -73,10 +84,25 @@ Variants {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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 {
|
||||
id: rightSection
|
||||
|
||||
@@ -86,44 +112,38 @@ Variants {
|
||||
anchors.verticalCenter: bar.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
ScreenRecorderIndicator {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Repeater {
|
||||
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
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
visible: (Settings.data.bar.showActiveWindow && getTitle() !== "")
|
||||
visible: getTitle() !== ""
|
||||
|
||||
property bool showingFullTitle: false
|
||||
property int lastWindowIndex: -1
|
||||
@@ -22,8 +22,6 @@ NPill {
|
||||
|
||||
// Choose icon based on charge and charging state
|
||||
function batteryIcon() {
|
||||
if (!show)
|
||||
return ""
|
||||
|
||||
if (charging)
|
||||
return "battery_android_bolt"
|
||||
@@ -10,9 +10,7 @@ import qs.Widgets
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
readonly property bool bluetoothEnabled: Settings.data.network.bluetoothEnabled
|
||||
sizeMultiplier: 0.8
|
||||
visible: bluetoothEnabled
|
||||
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
@@ -33,8 +31,4 @@ NIconButton {
|
||||
onClicked: {
|
||||
bluetoothPanel.toggle(screen)
|
||||
}
|
||||
|
||||
BluetoothPanel {
|
||||
id: bluetoothPanel
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ Item {
|
||||
|
||||
width: pill.width
|
||||
height: pill.height
|
||||
visible: Settings.data.bar.showBrightness && firstBrightnessReceived && getMonitor() !== null
|
||||
visible: getMonitor() !== null
|
||||
|
||||
// Used to avoid opening the pill on Quickshell startup
|
||||
property bool firstBrightnessReceived: false
|
||||
@@ -11,7 +11,8 @@ Row {
|
||||
id: root
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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() {
|
||||
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
|
||||
@@ -109,14 +110,14 @@ Row {
|
||||
visible: Settings.data.audio.showMiniplayerAlbumArt
|
||||
|
||||
Rectangle {
|
||||
width: 16 * scaling
|
||||
height: 16 * scaling
|
||||
width: 18 * scaling
|
||||
height: 18 * scaling
|
||||
radius: width * 0.5
|
||||
color: Color.transparent
|
||||
antialiasing: true
|
||||
clip: true
|
||||
|
||||
NImageRounded {
|
||||
NImageCircled {
|
||||
id: trackArt
|
||||
visible: MediaService.trackArtUrl.toString() !== ""
|
||||
anchors.fill: parent
|
||||
@@ -126,8 +127,6 @@ Row {
|
||||
fallbackIcon: MediaService.isPlaying ? "pause" : "play_arrow"
|
||||
borderWidth: 0
|
||||
border.color: Color.transparent
|
||||
imageRadius: width
|
||||
antialiasing: true
|
||||
}
|
||||
|
||||
// Fallback icon when no album art available
|
||||
@@ -10,7 +10,6 @@ import qs.Widgets
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
visible: Settings.data.bar.showNotificationsHistory
|
||||
sizeMultiplier: 0.8
|
||||
icon: "notifications"
|
||||
tooltipText: "Notification History"
|
||||
@@ -8,7 +8,6 @@ Row {
|
||||
id: root
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
visible: (Settings.data.bar.showSystemInfo)
|
||||
|
||||
Rectangle {
|
||||
// Let the Rectangle size itself based on its content (the Row)
|
||||
@@ -12,9 +12,8 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
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
|
||||
|
||||
height: Math.round(Style.capsuleHeight * scaling)
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
color: Color.mSurfaceVariant
|
||||
@@ -95,14 +94,14 @@ Rectangle {
|
||||
return
|
||||
}
|
||||
|
||||
if (modelData.hasMenu && modelData.menu && trayMenu) {
|
||||
if (modelData.hasMenu && modelData.menu && trayMenu.item) {
|
||||
trayPanel.open()
|
||||
|
||||
// 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)
|
||||
trayMenu.menu = modelData.menu
|
||||
trayMenu.showAt(parent, menuX, menuY)
|
||||
trayMenu.item.menu = modelData.menu
|
||||
trayMenu.item.showAt(parent, menuX, menuY)
|
||||
} else {
|
||||
Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set")
|
||||
}
|
||||
@@ -142,7 +141,7 @@ Rectangle {
|
||||
|
||||
function close() {
|
||||
visible = false
|
||||
trayMenu.hideMenu()
|
||||
trayMenu.item.hideMenu()
|
||||
}
|
||||
|
||||
// Clicking outside of the rectangle to close
|
||||
@@ -151,8 +150,9 @@ Rectangle {
|
||||
onClicked: trayPanel.close()
|
||||
}
|
||||
|
||||
TrayMenu {
|
||||
Loader {
|
||||
id: trayMenu
|
||||
source: "TrayMenu.qml"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ PopupWindow {
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: mouseArea.containsMouse ? Color.mSecondary : Color.transparent
|
||||
color: mouseArea.containsMouse ? Color.mTertiary : Color.transparent
|
||||
radius: Style.radiusS * scaling
|
||||
visible: !(modelData?.isSeparator ?? false)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ NPanel {
|
||||
radius: Style.radiusM * scaling
|
||||
color: {
|
||||
if (availableDeviceArea.containsMouse && !isBusy)
|
||||
return Color.mSecondary
|
||||
return Color.mTertiary
|
||||
|
||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Color.mPrimary
|
||||
@@ -465,13 +465,12 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
NImageRounded {
|
||||
NImageCircled {
|
||||
anchors.centerIn: parent
|
||||
width: 100 * scaling
|
||||
height: 100 * scaling
|
||||
imagePath: Settings.data.general.avatarImage
|
||||
fallbackIcon: "person"
|
||||
imageRadius: width * 0.5
|
||||
}
|
||||
|
||||
// Hover animation
|
||||
|
||||
@@ -47,6 +47,7 @@ NPanel {
|
||||
id: barTab
|
||||
Tabs.BarTab {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioTab
|
||||
Tabs.AudioTab {}
|
||||
@@ -202,7 +203,7 @@ NPanel {
|
||||
width: parent.width
|
||||
height: 32 * 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
|
||||
property bool hovering: false
|
||||
property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnTertiary : Color.mOnSurface)
|
||||
@@ -265,7 +266,7 @@ NPanel {
|
||||
// Tab label on the main right side
|
||||
NText {
|
||||
text: root.tabsModel[currentTabIndex].label
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -211,14 +211,13 @@ ColumnLayout {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 2 * scaling
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 2 * scaling
|
||||
|
||||
NImageRounded {
|
||||
NImageCircled {
|
||||
imagePath: modelData.avatar_url || ""
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS * scaling
|
||||
fallbackIcon: "person"
|
||||
borderColor: Color.mPrimary
|
||||
borderWidth: Math.max(1, Style.borderL * scaling)
|
||||
imageRadius: width * 0.5
|
||||
borderWidth: Math.max(1, Style.borderM * scaling)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
@@ -71,70 +72,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
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 {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,33 +6,34 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
property real scaling: 1
|
||||
readonly property string tabIcon: "brightness_6"
|
||||
readonly property string tabLabel: "Brightness"
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
padding: Style.marginM * scaling
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
|
||||
contentWidth: parent.width
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: 0
|
||||
|
||||
width: parent.width
|
||||
ColumnLayout {
|
||||
|
||||
width: scrollView.availableWidth
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.margins: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Brightness Settings"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
// Bar Visibility Section
|
||||
NToggle {
|
||||
label: "Show Bar Icon"
|
||||
|
||||
@@ -163,28 +163,22 @@ ColumnLayout {
|
||||
Layout.bottomMargin: Style.marginL * scaling
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Predefined Color Schemes"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
// NText {
|
||||
// text: "Predefined Color Schemes"
|
||||
// font.pointSize: Style.fontSizeL * scaling
|
||||
// font.weight: Style.fontWeightBold
|
||||
// color: Color.mOnSurface
|
||||
// Layout.fillWidth: true
|
||||
// }
|
||||
NText {
|
||||
text: "Predefined Color Schemes"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
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."
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
@@ -33,6 +33,13 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "General Settings"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
// Profile section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
@@ -44,13 +51,13 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
// Avatar preview
|
||||
NImageRounded {
|
||||
NImageCircled {
|
||||
width: 64 * scaling
|
||||
height: 64 * scaling
|
||||
imagePath: Settings.data.general.avatarImage
|
||||
fallbackIcon: "person"
|
||||
borderColor: Color.mPrimary
|
||||
borderWidth: Math.max(1, Style.borderM)
|
||||
borderWidth: Math.max(1, Style.borderM * scaling)
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
|
||||
@@ -34,7 +34,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Launcer Options"
|
||||
text: "Launcher"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
@@ -56,7 +56,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Launcer Anchoring"
|
||||
text: "Launcher Position"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
|
||||
@@ -34,6 +34,13 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Interfaces"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "WiFi Enabled"
|
||||
description: "Enable WiFi connectivity."
|
||||
|
||||
@@ -33,6 +33,14 @@ ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
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
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
@@ -33,6 +33,14 @@ ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
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
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
@@ -31,7 +31,7 @@ Item {
|
||||
// Current wallpaper display
|
||||
NText {
|
||||
text: "Current Wallpaper"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
@@ -34,6 +34,14 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
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
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
@@ -164,7 +164,7 @@ NBox {
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
clip: true
|
||||
|
||||
NImageRounded {
|
||||
NImageCircled {
|
||||
id: trackArt
|
||||
visible: MediaService.trackArtUrl.toString() !== ""
|
||||
|
||||
@@ -174,7 +174,6 @@ NBox {
|
||||
fallbackIcon: "music_note"
|
||||
borderColor: Color.mOutline
|
||||
borderWidth: Math.max(1, Style.borderS * scaling)
|
||||
imageRadius: width * 0.5
|
||||
}
|
||||
|
||||
// Fallback icon when no album art available
|
||||
|
||||
@@ -28,7 +28,7 @@ NBox {
|
||||
anchors.margins: Style.marginM * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NImageRounded {
|
||||
NImageCircled {
|
||||
width: Style.baseWidgetSize * 1.25 * scaling
|
||||
height: Style.baseWidgetSize * 1.25 * scaling
|
||||
imagePath: Settings.data.general.avatarImage
|
||||
|
||||
@@ -147,7 +147,7 @@ NPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 1.5 * 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 {
|
||||
anchors.fill: parent
|
||||
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 placeholder: ""
|
||||
|
||||
signal selected(string key)
|
||||
|
||||
@@ -61,8 +62,10 @@ ColumnLayout {
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
text: (combo.currentIndex >= 0 && combo.currentIndex < root.model.count) ? root.model.get(
|
||||
combo.currentIndex).name : ""
|
||||
color: (combo.currentIndex >= 0
|
||||
&& 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 {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,12 @@ Rectangle {
|
||||
Rectangle {
|
||||
color: Color.transparent
|
||||
anchors.fill: parent
|
||||
anchors.margins: borderWidth
|
||||
|
||||
Image {
|
||||
id: img
|
||||
anchors.fill: parent
|
||||
source: imagePath
|
||||
visible: false
|
||||
visible: false // Hide since we're using it as shader source
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
@@ -37,24 +36,33 @@ Rectangle {
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
source: img
|
||||
maskEnabled: true
|
||||
maskSource: mask
|
||||
maskSpreadAtMax: 0.75
|
||||
visible: imagePath !== ""
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mask
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
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
|
||||
radius: scaledRadius
|
||||
antialiasing: true
|
||||
color: "transparent"
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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.Background
|
||||
import qs.Modules.Bar
|
||||
import qs.Modules.BluetoothPanel
|
||||
import qs.Modules.Calendar
|
||||
import qs.Modules.Dock
|
||||
import qs.Modules.IPC
|
||||
@@ -25,7 +26,7 @@ import qs.Modules.SettingsPanel
|
||||
import qs.Modules.PowerPanel
|
||||
import qs.Modules.SidePanel
|
||||
import qs.Modules.Toast
|
||||
|
||||
import qs.Modules.WiFiPanel
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
@@ -70,6 +71,14 @@ ShellRoot {
|
||||
id: powerPanel
|
||||
}
|
||||
|
||||
WiFiPanel {
|
||||
id: wifiPanel
|
||||
}
|
||||
|
||||
BluetoothPanel {
|
||||
id: bluetoothPanel
|
||||
}
|
||||
|
||||
ToastManager {}
|
||||
|
||||
IPCManager {}
|
||||
|
||||
Reference in New Issue
Block a user