better quickshell
This commit is contained in:
@@ -15,8 +15,6 @@
|
|||||||
|
|
||||||
<summary>Niri & Quickshell</summary>
|
<summary>Niri & Quickshell</summary>
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/7e2db305-58bc-4b3d-9c65-7dc0461aead7
|
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img src="https://github.com/Uyanide/backgrounds/blob/master/screenshots/desktop-alt.jpg?raw=true"/>
|
<img src="https://github.com/Uyanide/backgrounds/blob/master/screenshots/desktop-alt.jpg?raw=true"/>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -48,11 +46,11 @@ https://github.com/user-attachments/assets/7e2db305-58bc-4b3d-9c65-7dc0461aead7
|
|||||||
- Shell: **Fish**
|
- Shell: **Fish**
|
||||||
- Prompt: **Oh My Posh**
|
- Prompt: **Oh My Posh**
|
||||||
- Terminal: **Kitty** & (**WezTerm** | Ghostty)
|
- Terminal: **Kitty** & (**WezTerm** | Ghostty)
|
||||||
- Power Menu: **Wlogout**
|
- Power Menu: **Wlogout** & Quickshell
|
||||||
- Colorscheme: **Catppuccin Mocha**
|
- Colorscheme: **Catppuccin Mocha**
|
||||||
- App Launcher: **Rofi** | Fuzzel
|
- App Launcher: **Rofi** | Fuzzel
|
||||||
- Desktop Widgets: Eww | **Quickshell**
|
- Desktop Widgets: Eww | **Quickshell**
|
||||||
- Wallpaper Daemon: **Awww** (previously Swww)
|
- Wallpaper Daemon: Awww | **Quickshell**
|
||||||
- Notification Daemon: Mako | **Quickshell**
|
- Notification Daemon: Mako | **Quickshell**
|
||||||
|
|
||||||
(**bold**: currently preferred)
|
(**bold**: currently preferred)
|
||||||
@@ -67,7 +65,7 @@ Ported from Hyprland, and shares some of the desktop components such as hyprlock
|
|||||||
|
|
||||||
## Quickshell
|
## Quickshell
|
||||||
|
|
||||||
Not based on, but heavily depends on many modules from [noctalia-shell](https://github.com/noctalia-dev/noctalia-shell). A thousand thanks to their great work.
|
Not based on, but heavily depends on many modules from (an old version of) [noctalia-shell](https://github.com/noctalia-dev/noctalia-shell). A thousand thanks to their great work.
|
||||||
|
|
||||||
This setup is currently only adapted for Niri.
|
This setup is currently only adapted for Niri.
|
||||||
|
|
||||||
@@ -80,8 +78,6 @@ This setup is currently only adapted for Niri.
|
|||||||
## Wallpaper & Colortheme
|
## Wallpaper & Colortheme
|
||||||
|
|
||||||
- [WallReel](https://github.com/Uyanide/WallReel): an Image Carousel implemented with QtQuick to browse and set wallpapers from.
|
- [WallReel](https://github.com/Uyanide/WallReel): an Image Carousel implemented with QtQuick to browse and set wallpapers from.
|
||||||
- [wallpaper-daemon](./config/scripts/.local/scripts/wallpaper-daemon): automatic blur (only works in niri).
|
|
||||||
- [change-wallpaper](./config/scripts/.local/scripts/change-wallpaper): script that changes wallpaper with a few extra features.
|
|
||||||
- [change-colortheme](./config/scripts/.local/scripts/change-colortheme): script that extract colors from the current wallpaper and generate a catppuccin color scheme accordingly.
|
- [change-colortheme](./config/scripts/.local/scripts/change-colortheme): script that extract colors from the current wallpaper and generate a catppuccin color scheme accordingly.
|
||||||
- [backgrounds](https://github.com/Uyanide/backgrounds) collection for personal use (mostly waifus).
|
- [backgrounds](https://github.com/Uyanide/backgrounds) collection for personal use (mostly waifus).
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ binds {
|
|||||||
Mod+Return { spawn "kitty"; }
|
Mod+Return { spawn "kitty"; }
|
||||||
Mod+Shift+W { spawn "wallreel"; }
|
Mod+Shift+W { spawn "wallreel"; }
|
||||||
Mod+O { spawn-sh "pkill -x -n pwvucontrol || pwvucontrol"; }
|
Mod+O { spawn-sh "pkill -x -n pwvucontrol || pwvucontrol"; }
|
||||||
|
Ctrl+Alt+Delete { spawn-sh "pkill -x wlogout || wlogout"; }
|
||||||
|
|
||||||
// Quickshell
|
// Quickshell
|
||||||
Mod+Space { spawn "qs" "ipc" "call" "panels" "toggleControlCenter"; }
|
Mod+Space { spawn "qs" "ipc" "call" "bars" "toggleLeft"; }
|
||||||
Mod+Shift+D { spawn "qs" "ipc" "call" "panels" "toggleCalendar"; }
|
Mod+N { spawn "qs" "ipc" "call" "bars" "toggleRight"; }
|
||||||
Mod+Shift+L { spawn "qs" "ipc" "call" "lyrics" "toggleBarLyrics"; }
|
Mod+Shift+L { spawn "qs" "ipc" "call" "bars" "toggleLyrics"; }
|
||||||
Mod+Shift+K { spawn-sh "quickshell-kill || quickshell"; }
|
Mod+Shift+K { spawn-sh "quickshell-kill || quickshell"; }
|
||||||
Mod+I { spawn "qs" "ipc" "call" "idleInhibitor" "toggleInhibitor"; }
|
Mod+I { spawn "qs" "ipc" "call" "idleInhibitor" "toggleInhibitor"; }
|
||||||
Mod+Alt+R { spawn "qs" "ipc" "call" "recording" "startOrStopRecording"; }
|
Mod+Alt+R { spawn "qs" "ipc" "call" "recording" "startOrStopRecording"; }
|
||||||
@@ -38,7 +39,6 @@ binds {
|
|||||||
// Actions
|
// Actions
|
||||||
Mod+V { spawn-sh "fzfclip-wrap"; }
|
Mod+V { spawn-sh "fzfclip-wrap"; }
|
||||||
Mod+Period { spawn-sh "pkill -x rofi || rofi-emoji"; }
|
Mod+Period { spawn-sh "pkill -x rofi || rofi-emoji"; }
|
||||||
Ctrl+Alt+Delete { spawn-sh "pkill -x wlogout || wlogout -p layer-shell"; }
|
|
||||||
Print { spawn "niri" "msg" "action" "screenshot-screen"; }
|
Print { spawn "niri" "msg" "action" "screenshot-screen"; }
|
||||||
Mod+Shift+S { spawn "niri" "msg" "action" "screenshot"; }
|
Mod+Shift+S { spawn "niri" "msg" "action" "screenshot"; }
|
||||||
Mod+Ctrl+Shift+S { spawn "niri" "msg" "action" "screenshot-window"; }
|
Mod+Ctrl+Shift+S { spawn "niri" "msg" "action" "screenshot-window"; }
|
||||||
@@ -48,18 +48,18 @@ binds {
|
|||||||
Mod+L { spawn "loginctl" "lock-session"; }
|
Mod+L { spawn "loginctl" "lock-session"; }
|
||||||
|
|
||||||
// Media
|
// Media
|
||||||
XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"; }
|
XF86AudioRaiseVolume allow-when-locked=true { spawn "qs" "ipc" "call" "media" "volumeUp"; }
|
||||||
XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%-"; }
|
XF86AudioLowerVolume allow-when-locked=true { spawn "qs" "ipc" "call" "media" "volumeDown"; }
|
||||||
XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; }
|
XF86AudioMute allow-when-locked=true { spawn "qs" "ipc" "call" "media" "toggleOutputMute"; }
|
||||||
XF86AudioMicMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; }
|
XF86AudioMicMute allow-when-locked=true { spawn "qs" "ipc" "call" "media" "toggleInputMute"; }
|
||||||
XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; }
|
XF86AudioPlay allow-when-locked=true { spawn "qs" "ipc" "call" "media" "playPause"; }
|
||||||
XF86AudioPause allow-when-locked=true { spawn-sh "playerctl play-pause"; }
|
XF86AudioPause allow-when-locked=true { spawn "qs" "ipc" "call" "media" "playPause"; }
|
||||||
XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; }
|
XF86AudioNext allow-when-locked=true { spawn "qs" "ipc" "call" "media" "next"; }
|
||||||
XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; }
|
XF86AudioPrev allow-when-locked=true { spawn "qs" "ipc" "call" "media" "previous"; }
|
||||||
|
|
||||||
// Brightness
|
// Brightness
|
||||||
XF86MonBrightnessUp allow-when-locked=true { spawn "set-brightness" "+10%"; }
|
XF86MonBrightnessUp allow-when-locked=true { spawn "qs" "ipc" "call" "brightness" "brightnessUp"; }
|
||||||
XF86MonBrightnessDown allow-when-locked=true { spawn "set-brightness" "10%-"; }
|
XF86MonBrightnessDown allow-when-locked=true { spawn "qs" "ipc" "call" "brightness" "brightnessDown"; }
|
||||||
|
|
||||||
// Window management
|
// Window management
|
||||||
Mod+Tab repeat=false { toggle-overview; }
|
Mod+Tab repeat=false { toggle-overview; }
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
// Switch configs
|
// Switch configs
|
||||||
spawn-at-startup "config-switch" "niri"
|
spawn-at-startup "config-switch" "niri"
|
||||||
|
|
||||||
// Wallpaper
|
|
||||||
spawn-at-startup "wallpaper-daemon"
|
|
||||||
|
|
||||||
// Not necessary maybe ...
|
// Not necessary maybe ...
|
||||||
spawn-at-startup "fcitx5"
|
spawn-at-startup "fcitx5"
|
||||||
|
|
||||||
@@ -23,7 +20,7 @@ spawn-at-startup "wl-paste" "--type" "image" "--watch" "cliphist" "store"
|
|||||||
spawn-at-startup "solaar" "-w" "hide"
|
spawn-at-startup "solaar" "-w" "hide"
|
||||||
|
|
||||||
// Some other heavy apps
|
// Some other heavy apps
|
||||||
spawn-at-startup "sunshine"
|
// spawn-at-startup "sunshine"
|
||||||
// spawn-at-startup "spotify"
|
// spawn-at-startup "spotify"
|
||||||
// spawn-at-startup "thunderbird"
|
// spawn-at-startup "thunderbird"
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ animations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layer-rule {
|
layer-rule {
|
||||||
match namespace="^swww-daemonbackdrop$"
|
match namespace="backdrop$"
|
||||||
place-within-backdrop true
|
place-within-backdrop true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
file:///home/kolkas/Desktop
|
file:///home/kolkas/Desktop
|
||||||
file:///home/kolkas/Nextcloud
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
ip.json
|
||||||
|
spotify-lyrics-offset.txt
|
||||||
|
notifications.json
|
||||||
|
images
|
||||||
|
location.json
|
||||||
|
network.json
|
||||||
|
shell-state.json
|
||||||
@@ -1,3 +1 @@
|
|||||||
# some sensitive files
|
settings.json
|
||||||
GeoInfoToken.txt
|
|
||||||
IpAliases.json
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
0
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"location": "Munich",
|
|
||||||
"notifications": {
|
|
||||||
"doNotDisturb": false
|
|
||||||
},
|
|
||||||
"primaryColor": "#89b4fa",
|
|
||||||
"showLyricsBar": false,
|
|
||||||
"sunsetDefaultEnabled": true,
|
|
||||||
"wifiEnabled": true
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"colors": {
|
||||||
|
"mPrimary": "#89b4fa"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1,216 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Templates as T
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property color handleColor: Qt.alpha(Colors.mHover, 0.8)
|
||||||
|
property color handleHoverColor: handleColor
|
||||||
|
property color handlePressedColor: handleColor
|
||||||
|
property color trackColor: "transparent"
|
||||||
|
property real handleWidth: Math.round(6 * Style.uiScaleRatio)
|
||||||
|
property real handleRadius: Style.radiusM
|
||||||
|
property int verticalPolicy: ScrollBar.AsNeeded
|
||||||
|
property int horizontalPolicy: ScrollBar.AsNeeded
|
||||||
|
property bool preventHorizontalScroll: horizontalPolicy === ScrollBar.AlwaysOff
|
||||||
|
property int boundsBehavior: Flickable.StopAtBounds
|
||||||
|
readonly property bool verticalScrollable: (contentItem.contentHeight > contentItem.height) || (verticalPolicy == ScrollBar.AlwaysOn)
|
||||||
|
readonly property bool horizontalScrollable: (contentItem.contentWidth > contentItem.width) || (horizontalPolicy == ScrollBar.AlwaysOn)
|
||||||
|
property bool showGradientMasks: true
|
||||||
|
property color gradientColor: Colors.mSurfaceVariant
|
||||||
|
property int gradientHeight: 16
|
||||||
|
property bool reserveScrollbarSpace: true
|
||||||
|
property real userRightPadding: 0
|
||||||
|
|
||||||
|
// Scroll speed multiplier for mouse wheel (1.0 = default, higher = faster)
|
||||||
|
property real wheelScrollMultiplier: 2.0
|
||||||
|
|
||||||
|
rightPadding: userRightPadding + (reserveScrollbarSpace && verticalScrollable ? handleWidth + Style.marginXS : 0)
|
||||||
|
|
||||||
|
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
|
||||||
|
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
|
||||||
|
|
||||||
|
// Configure the internal flickable when it becomes available
|
||||||
|
Component.onCompleted: {
|
||||||
|
configureFlickable();
|
||||||
|
createGradients();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamically create gradient overlays to avoid interfering with ScrollView content management
|
||||||
|
function createGradients() {
|
||||||
|
if (!showGradientMasks)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Qt.createQmlObject(`
|
||||||
|
import QtQuick
|
||||||
|
import qs.Constants
|
||||||
|
Rectangle {
|
||||||
|
x: root.leftPadding
|
||||||
|
y: root.topPadding
|
||||||
|
width: root.availableWidth
|
||||||
|
height: root.gradientHeight
|
||||||
|
z: 1
|
||||||
|
visible: root.showGradientMasks && root.verticalScrollable
|
||||||
|
opacity: root.contentItem.contentY <= 1 ? 0 : 1
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: root.gradientColor }
|
||||||
|
GradientStop { position: 1.0; color: "transparent" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, root, "topGradient");
|
||||||
|
|
||||||
|
Qt.createQmlObject(`
|
||||||
|
import QtQuick
|
||||||
|
import qs.Constants
|
||||||
|
Rectangle {
|
||||||
|
x: root.leftPadding
|
||||||
|
y: root.height - root.bottomPadding - height + 1
|
||||||
|
width: root.availableWidth
|
||||||
|
height: root.gradientHeight + 1
|
||||||
|
z: 1
|
||||||
|
visible: root.showGradientMasks && root.verticalScrollable
|
||||||
|
opacity: (root.contentItem.contentY + root.contentItem.height >= root.contentItem.contentHeight - 1) ? 0 : 1
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: "transparent" }
|
||||||
|
GradientStop { position: 1.0; color: root.gradientColor }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, root, "bottomGradient");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference to the internal Flickable for wheel handling
|
||||||
|
property Flickable _internalFlickable: null
|
||||||
|
|
||||||
|
// Function to configure the underlying Flickable
|
||||||
|
function configureFlickable() {
|
||||||
|
// Find the internal Flickable (it's usually the first child)
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
var child = children[i];
|
||||||
|
if (child.toString().indexOf("Flickable") !== -1) {
|
||||||
|
// Configure the flickable to prevent horizontal scrolling
|
||||||
|
child.boundsBehavior = root.boundsBehavior;
|
||||||
|
root._internalFlickable = child;
|
||||||
|
|
||||||
|
if (root.preventHorizontalScroll) {
|
||||||
|
child.flickableDirection = Flickable.VerticalFlick;
|
||||||
|
child.contentWidth = Qt.binding(() => child.width);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WheelHandler {
|
||||||
|
enabled: root.wheelScrollMultiplier !== 1.0 && root._internalFlickable !== null
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
onWheel: event => {
|
||||||
|
if (!root._internalFlickable)
|
||||||
|
return;
|
||||||
|
const flickable = root._internalFlickable;
|
||||||
|
const delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.angleDelta.y / 2;
|
||||||
|
const newY = flickable.contentY - (delta * root.wheelScrollMultiplier);
|
||||||
|
flickable.contentY = Math.max(0, Math.min(newY, flickable.contentHeight - flickable.height));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for changes in horizontalPolicy
|
||||||
|
onHorizontalPolicyChanged: {
|
||||||
|
preventHorizontalScroll = (horizontalPolicy === ScrollBar.AlwaysOff);
|
||||||
|
configureFlickable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
parent: root
|
||||||
|
x: root.mirrored ? 0 : root.width - width
|
||||||
|
y: root.topPadding
|
||||||
|
height: root.availableHeight
|
||||||
|
policy: root.verticalPolicy
|
||||||
|
interactive: root.verticalScrollable
|
||||||
|
|
||||||
|
contentItem: Rectangle {
|
||||||
|
implicitWidth: root.handleWidth
|
||||||
|
implicitHeight: 100
|
||||||
|
radius: root.handleRadius
|
||||||
|
color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor
|
||||||
|
opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.verticalScrollable ? (parent.active ? 1.0 : 0.0) : 0.0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
implicitWidth: root.handleWidth
|
||||||
|
implicitHeight: 100
|
||||||
|
color: root.trackColor
|
||||||
|
opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollable ? (parent.active ? 0.3 : 0.0) : 0.0
|
||||||
|
radius: root.handleRadius / 2
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.horizontal: ScrollBar {
|
||||||
|
parent: root
|
||||||
|
x: root.leftPadding
|
||||||
|
y: root.height - height
|
||||||
|
width: root.availableWidth
|
||||||
|
policy: root.horizontalPolicy
|
||||||
|
interactive: root.horizontalScrollable
|
||||||
|
|
||||||
|
contentItem: Rectangle {
|
||||||
|
implicitWidth: 100
|
||||||
|
implicitHeight: root.handleWidth
|
||||||
|
radius: root.handleRadius
|
||||||
|
color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor
|
||||||
|
opacity: parent.policy === ScrollBar.AlwaysOn ? 1.0 : root.horizontalScrollable ? (parent.active ? 1.0 : 0.0) : 0.0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
implicitWidth: 100
|
||||||
|
implicitHeight: root.handleWidth
|
||||||
|
color: root.trackColor
|
||||||
|
opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.horizontalScrollable ? (parent.active ? 0.3 : 0.0) : 0.0
|
||||||
|
radius: root.handleRadius / 2
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
// Rounded group container using the variant surface color.
|
||||||
|
// To be used in side panels and settings panes to group fields or buttons.
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool compact: false
|
||||||
|
|
||||||
|
color: compact ? Colors.transparent : Colors.mSurfaceVariant
|
||||||
|
radius: Style.radiusM
|
||||||
|
layer.enabled: !compact
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: true
|
||||||
|
blurMax: Style.shadowBlurMax
|
||||||
|
shadowBlur: Style.shadowBlur
|
||||||
|
shadowOpacity: Style.shadowOpacity
|
||||||
|
shadowColor: Colors.mShadow
|
||||||
|
shadowHorizontalOffset: Style.shadowHorizontalOffset
|
||||||
|
shadowVerticalOffset: Style.shadowVerticalOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-2
@@ -1,12 +1,11 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Noctalia
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool running: true
|
property bool running: true
|
||||||
property color color: Color.mPrimary
|
property color color: Colors.mPrimary
|
||||||
property int size: Style.baseWidgetSize
|
property int size: Style.baseWidgetSize
|
||||||
property int strokeWidth: Style.borderL
|
property int strokeWidth: Style.borderL
|
||||||
property int duration: Style.animationSlow * 2
|
property int duration: Style.animationSlow * 2
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Public properties
|
||||||
|
property string text: ""
|
||||||
|
property string icon: ""
|
||||||
|
property string tooltipText
|
||||||
|
property color backgroundColor: Colors.mPrimary
|
||||||
|
property color textColor: Colors.mOnPrimary
|
||||||
|
property color hoverColor: Colors.mHover
|
||||||
|
property color textHoverColor: Colors.mOnHover
|
||||||
|
property real fontSize: Style.fontSizeM
|
||||||
|
property int fontWeight: Style.fontWeightSemiBold
|
||||||
|
property real iconSize: Style.fontSizeL
|
||||||
|
property bool outlined: false
|
||||||
|
property int horizontalAlignment: Qt.AlignHCenter
|
||||||
|
property real buttonRadius: Style.radiusS
|
||||||
|
// Internal properties
|
||||||
|
property bool hovered: false
|
||||||
|
readonly property color contentColor: {
|
||||||
|
if (!root.enabled)
|
||||||
|
return Colors.mOnSurfaceVariant;
|
||||||
|
|
||||||
|
if (root.hovered)
|
||||||
|
return root.textHoverColor;
|
||||||
|
|
||||||
|
if (root.outlined)
|
||||||
|
return root.backgroundColor;
|
||||||
|
|
||||||
|
return root.textColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
signal clicked()
|
||||||
|
signal rightClicked()
|
||||||
|
signal middleClicked()
|
||||||
|
signal entered()
|
||||||
|
signal exited()
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
implicitWidth: contentRow.implicitWidth + (fontSize * 2)
|
||||||
|
implicitHeight: contentRow.implicitHeight + (fontSize)
|
||||||
|
// Appearance
|
||||||
|
radius: root.buttonRadius
|
||||||
|
color: {
|
||||||
|
if (!root.enabled)
|
||||||
|
return outlined ? "transparent" : Qt.lighter(Colors.mSurfaceVariant, 1.2);
|
||||||
|
|
||||||
|
if (root.hovered)
|
||||||
|
return hoverColor;
|
||||||
|
|
||||||
|
return root.outlined ? "transparent" : root.backgroundColor;
|
||||||
|
}
|
||||||
|
border.width: outlined ? Style.borderS : 0
|
||||||
|
border.color: {
|
||||||
|
if (!root.enabled)
|
||||||
|
return Colors.mOutline;
|
||||||
|
|
||||||
|
if (root.hovered)
|
||||||
|
return hoverColor;
|
||||||
|
|
||||||
|
return root.outlined ? root.backgroundColor : "transparent";
|
||||||
|
}
|
||||||
|
opacity: enabled ? 1 : 0.6
|
||||||
|
|
||||||
|
// Content
|
||||||
|
RowLayout {
|
||||||
|
id: contentRow
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: root.horizontalAlignment === Qt.AlignLeft ? parent.left : undefined
|
||||||
|
anchors.horizontalCenter: root.horizontalAlignment === Qt.AlignHCenter ? parent.horizontalCenter : undefined
|
||||||
|
anchors.leftMargin: root.horizontalAlignment === Qt.AlignLeft ? Style.marginL : 0
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
// Icon (optional)
|
||||||
|
UIcon {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
visible: root.icon !== ""
|
||||||
|
iconName: root.icon
|
||||||
|
iconSize: root.iconSize
|
||||||
|
color: contentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text
|
||||||
|
UText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
visible: root.text !== ""
|
||||||
|
text: root.text
|
||||||
|
pointSize: root.fontSize
|
||||||
|
font.weight: root.fontWeight
|
||||||
|
color: contentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse interaction
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.enabled
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
onEntered: {
|
||||||
|
root.hovered = true;
|
||||||
|
root.entered();
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
root.hovered = false;
|
||||||
|
root.exited();
|
||||||
|
}
|
||||||
|
onPressed: (mouse) => {
|
||||||
|
if (mouse.button === Qt.LeftButton)
|
||||||
|
root.clicked();
|
||||||
|
else if (mouse.button == Qt.RightButton)
|
||||||
|
root.rightClicked();
|
||||||
|
else if (mouse.button == Qt.MiddleButton)
|
||||||
|
root.middleClicked();
|
||||||
|
}
|
||||||
|
onCanceled: {
|
||||||
|
root.hovered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Utils
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var now: Time.now
|
||||||
|
// Style: "analog" or "digital"
|
||||||
|
property string clockStyle: "analog"
|
||||||
|
// Show seconds progress ring (digital only)
|
||||||
|
property bool showProgress: true
|
||||||
|
// Colors properties
|
||||||
|
property color backgroundColor: Colors.mPrimary
|
||||||
|
property color clockColor: Colors.mOnPrimary
|
||||||
|
property color secondHandColor: Colors.mError
|
||||||
|
property color progressColor: root.secondHandColor
|
||||||
|
// Font size properties for digital clock
|
||||||
|
property real hoursFontSize: Style.fontSizeXS
|
||||||
|
property real minutesFontSize: Style.fontSizeXXS
|
||||||
|
property int hoursFontWeight: Style.fontWeightBold
|
||||||
|
property int minutesFontWeight: Style.fontWeightBold
|
||||||
|
// Scale ratio for canvas line widths (used by desktop widget scaling)
|
||||||
|
property real scaleRatio: 1
|
||||||
|
|
||||||
|
height: Math.round((Style.fontSizeXXXL * 1.9) / 2) * 2
|
||||||
|
width: root.height
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: clockLoader
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceComponent: {
|
||||||
|
if (root.clockStyle === "analog")
|
||||||
|
return analogClockComponent;
|
||||||
|
|
||||||
|
if (root.clockStyle === "binary")
|
||||||
|
return binaryClockComponent;
|
||||||
|
|
||||||
|
return digitalClockComponent;
|
||||||
|
}
|
||||||
|
onLoaded: {
|
||||||
|
item.now = Qt.binding(function() {
|
||||||
|
return root.now;
|
||||||
|
});
|
||||||
|
item.backgroundColor = Qt.binding(function() {
|
||||||
|
return root.backgroundColor;
|
||||||
|
});
|
||||||
|
item.clockColor = Qt.binding(function() {
|
||||||
|
return root.clockColor;
|
||||||
|
});
|
||||||
|
if (item.hasOwnProperty("secondHandColor"))
|
||||||
|
item.secondHandColor = Qt.binding(function() {
|
||||||
|
return root.secondHandColor;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item.hasOwnProperty("progressColor"))
|
||||||
|
item.progressColor = Qt.binding(function() {
|
||||||
|
return root.progressColor;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item.hasOwnProperty("hoursFontSize"))
|
||||||
|
item.hoursFontSize = Qt.binding(function() {
|
||||||
|
return root.hoursFontSize;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item.hasOwnProperty("minutesFontSize"))
|
||||||
|
item.minutesFontSize = Qt.binding(function() {
|
||||||
|
return root.minutesFontSize;
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("hoursFontWeight" in item)
|
||||||
|
item.hoursFontWeight = Qt.binding(function() {
|
||||||
|
return root.hoursFontWeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("minutesFontWeight" in item)
|
||||||
|
item.minutesFontWeight = Qt.binding(function() {
|
||||||
|
return root.minutesFontWeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item.hasOwnProperty("scaleRatio"))
|
||||||
|
item.scaleRatio = Qt.binding(function() {
|
||||||
|
return root.scaleRatio;
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("showProgress" in item)
|
||||||
|
item.showProgress = Qt.binding(function() {
|
||||||
|
return root.showProgress;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: analogClockComponent
|
||||||
|
|
||||||
|
UClockAnalog {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: digitalClockComponent
|
||||||
|
|
||||||
|
UClockDigital {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: binaryClockComponent
|
||||||
|
|
||||||
|
UClockBinary {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analog Clock Component
|
||||||
|
component UClockAnalog: Item {
|
||||||
|
property var now
|
||||||
|
property color backgroundColor: Colors.mPrimary
|
||||||
|
property color clockColor: Colors.mOnPrimary
|
||||||
|
property color secondHandColor: Colors.mError
|
||||||
|
property real scaleRatio: 1
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: clockCanvas
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
onPaint: {
|
||||||
|
var currentTime = Time.now;
|
||||||
|
var hours = currentTime.getHours();
|
||||||
|
var minutes = currentTime.getMinutes();
|
||||||
|
var seconds = currentTime.getSeconds();
|
||||||
|
const markAlpha = 0.7;
|
||||||
|
var ctx = getContext("2d");
|
||||||
|
ctx.reset();
|
||||||
|
ctx.translate(width / 2, height / 2);
|
||||||
|
var radius = Math.min(width, height) / 2;
|
||||||
|
// Hour marks
|
||||||
|
ctx.strokeStyle = Qt.alpha(clockColor, markAlpha);
|
||||||
|
ctx.lineWidth = 2 * scaleRatio;
|
||||||
|
var scaleFactor = 0.7;
|
||||||
|
for (var i = 0; i < 12; i++) {
|
||||||
|
var scaleFactor = 0.8;
|
||||||
|
if (i % 3 === 0)
|
||||||
|
scaleFactor = 0.65;
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.rotate(i * Math.PI / 6);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, -radius * scaleFactor);
|
||||||
|
ctx.lineTo(0, -radius);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
// Hour hand
|
||||||
|
ctx.save();
|
||||||
|
var hourAngle = (hours % 12 + minutes / 60) * Math.PI / 6;
|
||||||
|
ctx.rotate(hourAngle);
|
||||||
|
ctx.strokeStyle = clockColor;
|
||||||
|
ctx.lineWidth = 3 * scaleRatio;
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.lineTo(0, -radius * 0.6);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
// Minute hand
|
||||||
|
ctx.save();
|
||||||
|
var minuteAngle = (minutes + seconds / 60) * Math.PI / 30;
|
||||||
|
ctx.rotate(minuteAngle);
|
||||||
|
ctx.strokeStyle = clockColor;
|
||||||
|
ctx.lineWidth = 2 * scaleRatio;
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.lineTo(0, -radius * 0.9);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
// Second hand
|
||||||
|
ctx.save();
|
||||||
|
var secondAngle = seconds * Math.PI / 30;
|
||||||
|
ctx.rotate(secondAngle);
|
||||||
|
ctx.strokeStyle = secondHandColor;
|
||||||
|
ctx.lineWidth = 1.6 * scaleRatio;
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.lineTo(0, -radius);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
// Center dot
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, 3 * scaleRatio, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = clockColor;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
Component.onCompleted: requestPaint()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onNowChanged() {
|
||||||
|
clockCanvas.requestPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: Time
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digital Clock Component
|
||||||
|
component UClockDigital: Item {
|
||||||
|
property var now
|
||||||
|
property color backgroundColor: Colors.mPrimary
|
||||||
|
property color clockColor: Colors.mOnPrimary
|
||||||
|
property color progressColor: Colors.mError
|
||||||
|
property real hoursFontSize: Style.fontSizeXS
|
||||||
|
property real minutesFontSize: Style.fontSizeXXS
|
||||||
|
property int hoursFontWeight: Style.fontWeightBold
|
||||||
|
property int minutesFontWeight: Style.fontWeightBold
|
||||||
|
property real scaleRatio: 1
|
||||||
|
property bool showProgress: true
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// Digital clock's seconds circular progress
|
||||||
|
Canvas {
|
||||||
|
id: secondsProgress
|
||||||
|
|
||||||
|
property real progress: now.getSeconds() / 60
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: showProgress
|
||||||
|
onProgressChanged: requestPaint()
|
||||||
|
onPaint: {
|
||||||
|
var ctx = getContext("2d");
|
||||||
|
var centerX = width / 2;
|
||||||
|
var centerY = height / 2;
|
||||||
|
var radius = Math.min(width, height) / 2 - 3 * scaleRatio;
|
||||||
|
ctx.reset();
|
||||||
|
// Background circle
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
||||||
|
ctx.lineWidth = 2.5 * scaleRatio;
|
||||||
|
ctx.strokeStyle = Qt.alpha(clockColor, 0.15);
|
||||||
|
ctx.stroke();
|
||||||
|
// Progress arc
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI);
|
||||||
|
ctx.lineWidth = 2.5 * scaleRatio;
|
||||||
|
ctx.strokeStyle = progressColor;
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onNowChanged() {
|
||||||
|
const total = now.getSeconds() * 1000 + now.getMilliseconds();
|
||||||
|
secondsProgress.progress = total / 60000;
|
||||||
|
}
|
||||||
|
|
||||||
|
target: Time
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digital clock
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: -Style.marginXXS
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: Qt.formatTime(now, "HH")
|
||||||
|
pointSize: hoursFontSize
|
||||||
|
font.weight: hoursFontWeight
|
||||||
|
color: clockColor
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: Qt.formatTime(now, "mm")
|
||||||
|
pointSize: minutesFontSize
|
||||||
|
font.weight: minutesFontWeight
|
||||||
|
color: clockColor
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary Clock Component
|
||||||
|
component UClockBinary: Item {
|
||||||
|
// BCD (Binary Coded Decimal) Format:
|
||||||
|
// H1 H2 : M1 M2 : S1 S2
|
||||||
|
// H1 (tens): 0-2 (2 bits)
|
||||||
|
// H2 (ones): 0-9 (4 bits)
|
||||||
|
// M1 (tens): 0-5 (3 bits)
|
||||||
|
// M2 (ones): 0-9 (4 bits)
|
||||||
|
// S1 (tens): 0-5 (3 bits)
|
||||||
|
// S2 (ones): 0-9 (4 bits)
|
||||||
|
|
||||||
|
property var now
|
||||||
|
property color backgroundColor
|
||||||
|
property color clockColor: Colors.mOnPrimary
|
||||||
|
readonly property int h: now.getHours()
|
||||||
|
readonly property int m: now.getMinutes()
|
||||||
|
readonly property int s: now.getSeconds()
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: parent.width * 0.05
|
||||||
|
|
||||||
|
// Hours
|
||||||
|
RowLayout {
|
||||||
|
spacing: parent.parent.width * 0.02
|
||||||
|
|
||||||
|
BinaryColumn {
|
||||||
|
value: Math.floor(h / 10)
|
||||||
|
bits: 2
|
||||||
|
dotSize: root.width * 0.08
|
||||||
|
activeColor: clockColor
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryColumn {
|
||||||
|
value: h % 10
|
||||||
|
bits: 4
|
||||||
|
dotSize: root.width * 0.08
|
||||||
|
activeColor: clockColor
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minutes
|
||||||
|
RowLayout {
|
||||||
|
spacing: parent.parent.width * 0.02
|
||||||
|
|
||||||
|
BinaryColumn {
|
||||||
|
value: Math.floor(m / 10)
|
||||||
|
bits: 3
|
||||||
|
dotSize: root.width * 0.08
|
||||||
|
activeColor: clockColor
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryColumn {
|
||||||
|
value: m % 10
|
||||||
|
bits: 4
|
||||||
|
dotSize: root.width * 0.08
|
||||||
|
activeColor: clockColor
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seconds
|
||||||
|
RowLayout {
|
||||||
|
spacing: parent.parent.width * 0.02
|
||||||
|
|
||||||
|
BinaryColumn {
|
||||||
|
value: Math.floor(s / 10)
|
||||||
|
bits: 3
|
||||||
|
dotSize: root.width * 0.08
|
||||||
|
activeColor: clockColor
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryColumn {
|
||||||
|
value: s % 10
|
||||||
|
bits: 4
|
||||||
|
dotSize: root.width * 0.08
|
||||||
|
activeColor: clockColor
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
component BinaryColumn: Column {
|
||||||
|
property int value: 0
|
||||||
|
property int bits: 4
|
||||||
|
property real dotSize: 10
|
||||||
|
property color activeColor: "white"
|
||||||
|
|
||||||
|
spacing: dotSize * 0.4
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: bits
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
property int bitIndex: (bits - 1) - index
|
||||||
|
property bool isActive: (value >> bitIndex) & 1
|
||||||
|
|
||||||
|
width: dotSize
|
||||||
|
height: dotSize
|
||||||
|
radius: dotSize / 2
|
||||||
|
color: isActive ? activeColor : Qt.alpha(activeColor, 0.2)
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+61
-29
@@ -2,16 +2,55 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UContextMenu - Popup-based context menu for use inside panels and dialogs
|
||||||
|
*
|
||||||
|
* Use this component when you need a context menu inside:
|
||||||
|
* - Settings panels
|
||||||
|
* - Dialogs
|
||||||
|
* - Repeater delegates
|
||||||
|
* - Any nested component context
|
||||||
|
*
|
||||||
|
* For bar widgets and top-level window contexts, use NPopupContextMenu instead,
|
||||||
|
* which provides better screen boundary handling and compositor integration.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* UContextMenu {
|
||||||
|
* id: contextMenu
|
||||||
|
* parent: Overlay.overlay
|
||||||
|
* model: [
|
||||||
|
* { "label": "Action 1", "action": "action1", "icon": "icon-name" },
|
||||||
|
* { "label": "Action 2", "action": "action2" }
|
||||||
|
* ]
|
||||||
|
* onTriggered: action => { Logger.i("MyModule", "Selected:", action) }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* MouseArea {
|
||||||
|
* onClicked: contextMenu.openAtItem(parent, mouse.x, mouse.y)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
Popup {
|
Popup {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias model: listView.model
|
property var model: []
|
||||||
property real itemHeight: 36
|
property real itemHeight: 36
|
||||||
property real itemPadding: Style.marginM
|
property real itemPadding: Style.marginM
|
||||||
|
property int verticalPolicy: ScrollBar.AsNeeded
|
||||||
|
property int horizontalPolicy: ScrollBar.AsNeeded
|
||||||
|
// Filter out hidden items to avoid spacing artifacts from zero-height items
|
||||||
|
readonly property var filteredModel: {
|
||||||
|
if (!model || model.length === 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var filtered = [];
|
||||||
|
for (var i = 0; i < model.length; i++) {
|
||||||
|
if (model[i].visible !== false)
|
||||||
|
filtered.push(model[i]);
|
||||||
|
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
signal triggered(string action)
|
signal triggered(string action)
|
||||||
|
|
||||||
@@ -30,22 +69,24 @@ Popup {
|
|||||||
|
|
||||||
width: 180
|
width: 180
|
||||||
padding: Style.marginS
|
padding: Style.marginS
|
||||||
onOpened: PanelService.willOpenPopup(root)
|
|
||||||
onClosed: PanelService.willClosePopup(root)
|
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Color.mSurfaceVariant
|
color: Colors.mSurfaceVariant
|
||||||
border.color: Color.mOutline
|
border.color: Colors.mOutline
|
||||||
border.width: Math.max(1, Style.borderS)
|
border.width: Style.borderS
|
||||||
radius: Style.radiusM
|
radius: Style.radiusM
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: NListView {
|
contentItem: UListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
implicitHeight: contentHeight
|
implicitHeight: Math.max(contentHeight, root.itemHeight)
|
||||||
spacing: Style.marginXXS
|
spacing: Style.marginXXS
|
||||||
interactive: contentHeight > root.height
|
interactive: contentHeight > root.height
|
||||||
|
verticalPolicy: root.verticalPolicy
|
||||||
|
horizontalPolicy: root.horizontalPolicy
|
||||||
|
reserveScrollbarSpace: false
|
||||||
|
model: root.filteredModel
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
delegate: ItemDelegate {
|
||||||
id: menuItem
|
id: menuItem
|
||||||
@@ -53,9 +94,8 @@ Popup {
|
|||||||
// Store reference to the popup
|
// Store reference to the popup
|
||||||
property var popup: root
|
property var popup: root
|
||||||
|
|
||||||
width: listView.width
|
width: listView.availableWidth
|
||||||
height: modelData.visible !== false ? root.itemHeight : 0
|
height: root.itemHeight
|
||||||
visible: modelData.visible !== false
|
|
||||||
opacity: modelData.enabled !== false ? 1 : 0.5
|
opacity: modelData.enabled !== false ? 1 : 0.5
|
||||||
enabled: modelData.enabled !== false
|
enabled: modelData.enabled !== false
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -66,27 +106,19 @@ Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: menuItem.hovered && menuItem.enabled ? Color.mTertiary : Color.transparent
|
color: menuItem.hovered && menuItem.enabled ? Colors.mHover : "transparent"
|
||||||
radius: Style.radiusS
|
radius: Style.radiusS
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
spacing: Style.marginS
|
spacing: Style.marginS
|
||||||
|
|
||||||
// Optional icon
|
// Optional icon
|
||||||
NIcon {
|
UIcon {
|
||||||
visible: modelData.icon !== undefined
|
visible: modelData.icon !== undefined
|
||||||
icon: modelData.icon || ""
|
iconName: modelData.icon || ""
|
||||||
pointSize: Style.fontSizeM
|
iconSize: Style.fontSizeM
|
||||||
color: menuItem.hovered && menuItem.enabled ? Color.mOnTertiary : Color.mOnSurface
|
color: menuItem.hovered && menuItem.enabled ? Colors.mOnHover : Colors.mOnSurface
|
||||||
Layout.leftMargin: root.itemPadding
|
Layout.leftMargin: root.itemPadding
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
@@ -98,10 +130,10 @@ Popup {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
UText {
|
||||||
text: modelData.label || modelData.text || ""
|
text: modelData.label || modelData.text || ""
|
||||||
pointSize: Style.fontSizeM
|
pointSize: Style.fontSizeM
|
||||||
color: menuItem.hovered && menuItem.enabled ? Color.mOnTertiary : Color.mOnSurface
|
color: menuItem.hovered && menuItem.enabled ? Colors.mOnHover : Colors.mOnSurface
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: modelData.icon === undefined ? root.itemPadding : 0
|
Layout.leftMargin: modelData.icon === undefined ? root.itemPadding : 0
|
||||||
+4
-4
@@ -12,22 +12,22 @@ Rectangle {
|
|||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 0
|
position: 0
|
||||||
color: Color.transparent
|
color: Colors.transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 0.1
|
position: 0.1
|
||||||
color: Color.mOutline
|
color: Colors.mOutline
|
||||||
}
|
}
|
||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 0.9
|
position: 0.9
|
||||||
color: Color.mOutline
|
color: Colors.mOutline
|
||||||
}
|
}
|
||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 1
|
position: 1
|
||||||
color: Color.transparent
|
color: Colors.transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
// Unified shadow system
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var source
|
||||||
|
property bool autoPaddingEnabled: false
|
||||||
|
property real shadowHorizontalOffset: Style.shadowHorizontalOffset
|
||||||
|
property real shadowVerticalOffset: Style.shadowVerticalOffset
|
||||||
|
property real shadowOpacity: Style.shadowOpacity
|
||||||
|
property color shadowColor: Colors.mShadow
|
||||||
|
property real shadowBlur: Style.shadowBlur
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
source: root.source
|
||||||
|
shadowEnabled: true
|
||||||
|
blurMax: Style.shadowBlurMax
|
||||||
|
shadowBlur: root.shadowBlur
|
||||||
|
shadowOpacity: root.shadowOpacity
|
||||||
|
shadowColor: root.shadowColor
|
||||||
|
shadowHorizontalOffset: root.shadowHorizontalOffset
|
||||||
|
shadowVerticalOffset: root.shadowVerticalOffset
|
||||||
|
autoPaddingEnabled: root.autoPaddingEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string iconName: ""
|
||||||
|
property string textOverride: ""
|
||||||
|
property string fontFamily: Fonts.icon
|
||||||
|
property int iconSize: Style.fontSizeL
|
||||||
|
|
||||||
|
color: Colors.mPrimary
|
||||||
|
text: textOverride ? textOverride : Icons.get(iconName) || Icons.get(Icons.defaultIcon)
|
||||||
|
font.family: fontFamily
|
||||||
|
font.pointSize: iconSize
|
||||||
|
font.bold: false
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias iconName: icon.iconName
|
||||||
|
property alias textOverride: icon.textOverride
|
||||||
|
property alias fontFamily: icon.fontFamily
|
||||||
|
property int baseSize: Style.fontSizeXXXL
|
||||||
|
property alias iconSize: icon.iconSize
|
||||||
|
property color colorFg: Colors.mPrimary
|
||||||
|
property color colorBg: Colors.transparent
|
||||||
|
property color colorFgHover: Colors.mOnPrimary
|
||||||
|
property color colorBgHover: colorFg
|
||||||
|
readonly property bool hovered: alwaysHover || mouseArea.containsMouse
|
||||||
|
property real radius: Style.radiusS
|
||||||
|
property bool disabledHover: false
|
||||||
|
property bool alwaysHover: false
|
||||||
|
|
||||||
|
signal entered()
|
||||||
|
signal exited()
|
||||||
|
signal clicked()
|
||||||
|
signal rightClicked()
|
||||||
|
signal middleClicked()
|
||||||
|
|
||||||
|
implicitWidth: baseSize
|
||||||
|
implicitHeight: baseSize
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
hoverEnabled: !disabledHover
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
onEntered: root.entered()
|
||||||
|
onExited: root.exited()
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (mouse.button === Qt.RightButton)
|
||||||
|
root.rightClicked();
|
||||||
|
else if (mouse.button === Qt.MiddleButton)
|
||||||
|
root.middleClicked();
|
||||||
|
else if (mouse.button === Qt.LeftButton)
|
||||||
|
root.clicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: root.hovered ? colorBgHover : colorBg
|
||||||
|
radius: root.radius
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationNormal
|
||||||
|
easing.type: Easing.InOutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
color: root.hovered ? colorFgHover : colorFg
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationNormal
|
||||||
|
easing.type: Easing.InOutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real radius: 0
|
||||||
|
property string imagePath: ""
|
||||||
|
property string fallbackIcon: ""
|
||||||
|
property real fallbackIconSize: Style.fontSizeXXL
|
||||||
|
property real borderWidth: 0
|
||||||
|
property color borderColor: "transparent"
|
||||||
|
property int imageFillMode: Image.PreserveAspectCrop
|
||||||
|
readonly property bool showFallback: (fallbackIcon !== undefined && fallbackIcon !== "") && (imagePath === undefined || imagePath === "" || imageSource.status === Image.Error)
|
||||||
|
readonly property int status: imageSource.status
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.radius
|
||||||
|
color: "transparent"
|
||||||
|
border.width: root.borderWidth
|
||||||
|
border.color: root.borderColor
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: imageSource
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: root.borderWidth
|
||||||
|
visible: false
|
||||||
|
source: root.imagePath
|
||||||
|
mipmap: true
|
||||||
|
smooth: true
|
||||||
|
asynchronous: true
|
||||||
|
antialiasing: true
|
||||||
|
fillMode: root.imageFillMode
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderEffect {
|
||||||
|
property variant source: imageSource
|
||||||
|
property real itemWidth: width
|
||||||
|
property real itemHeight: height
|
||||||
|
property real sourceWidth: imageSource.sourceSize.width
|
||||||
|
property real sourceHeight: imageSource.sourceSize.height
|
||||||
|
property real cornerRadius: Math.max(0, root.radius - root.borderWidth)
|
||||||
|
property real imageOpacity: 1
|
||||||
|
property int fillMode: root.imageFillMode
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: root.borderWidth
|
||||||
|
visible: !root.showFallback
|
||||||
|
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb")
|
||||||
|
supportsAtlasTextures: false
|
||||||
|
blending: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: root.borderWidth
|
||||||
|
visible: root.showFallback
|
||||||
|
iconName: root.fallbackIcon
|
||||||
|
iconSize: root.fallbackIconSize
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string label: ""
|
||||||
|
property string description: ""
|
||||||
|
property string icon: ""
|
||||||
|
property color labelColor: Colors.mOnSurface
|
||||||
|
property color descriptionColor: Colors.mOnSurfaceVariant
|
||||||
|
property color iconColor: Colors.mOnSurface
|
||||||
|
property bool showIndicator: false
|
||||||
|
property string indicatorTooltip: ""
|
||||||
|
|
||||||
|
opacity: enabled ? 1 : 0.6
|
||||||
|
spacing: Style.marginXXS
|
||||||
|
visible: root.label != "" || root.description != ""
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: Style.marginXS
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: root.label !== ""
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
visible: root.icon !== ""
|
||||||
|
iconName: root.icon
|
||||||
|
iconSize: Style.fontSizeXXL
|
||||||
|
color: root.iconColor
|
||||||
|
Layout.rightMargin: Style.marginS
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
Layout.fillWidth: !root.showIndicator
|
||||||
|
text: root.label
|
||||||
|
pointSize: Style.fontSizeL
|
||||||
|
font.weight: Style.fontWeightSemiBold
|
||||||
|
color: labelColor
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
visible: root.description !== ""
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.description
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
color: root.descriptionColor
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
textFormat: Text.StyledText
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+103
-63
@@ -2,19 +2,31 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Templates as T
|
import QtQuick.Templates as T
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Noctalia
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property color handleColor: Qt.alpha(Color.mTertiary, 0.8)
|
property color handleColor: Qt.alpha(Colors.mHover, 0.8)
|
||||||
property color handleHoverColor: handleColor
|
property color handleHoverColor: handleColor
|
||||||
property color handlePressedColor: handleColor
|
property color handlePressedColor: handleColor
|
||||||
property color trackColor: Color.transparent
|
property color trackColor: "transparent"
|
||||||
property real handleWidth: 6
|
property real handleWidth: 6
|
||||||
property real handleRadius: Style.radiusM
|
property real handleRadius: Style.radiusM
|
||||||
property int verticalPolicy: ScrollBar.AsNeeded
|
property int verticalPolicy: ScrollBar.AsNeeded
|
||||||
property int horizontalPolicy: ScrollBar.AsNeeded
|
property int horizontalPolicy: ScrollBar.AlwaysOff
|
||||||
|
readonly property bool verticalScrollBarActive: {
|
||||||
|
if (listView.ScrollBar.vertical.policy === ScrollBar.AlwaysOff)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return listView.contentHeight > listView.height;
|
||||||
|
}
|
||||||
|
readonly property bool contentOverflows: listView.contentHeight > listView.height
|
||||||
|
property bool showGradientMasks: true
|
||||||
|
property color gradientColor: Colors.mSurfaceVariant
|
||||||
|
property int gradientHeight: 16
|
||||||
|
property bool reserveScrollbarSpace: true
|
||||||
|
// Available width for content (excludes scrollbar space when reserveScrollbarSpace is true)
|
||||||
|
readonly property real availableWidth: width - (reserveScrollbarSpace ? handleWidth + Style.marginXS : 0)
|
||||||
// Forward ListView properties
|
// Forward ListView properties
|
||||||
property alias model: listView.model
|
property alias model: listView.model
|
||||||
property alias delegate: listView.delegate
|
property alias delegate: listView.delegate
|
||||||
@@ -53,6 +65,8 @@ Item {
|
|||||||
property alias dragging: listView.dragging
|
property alias dragging: listView.dragging
|
||||||
property alias horizontalVelocity: listView.horizontalVelocity
|
property alias horizontalVelocity: listView.horizontalVelocity
|
||||||
property alias verticalVelocity: listView.verticalVelocity
|
property alias verticalVelocity: listView.verticalVelocity
|
||||||
|
// Scroll speed multiplier for mouse wheel (1.0 = default, higher = faster)
|
||||||
|
property real wheelScrollMultiplier: 2
|
||||||
|
|
||||||
// Forward ListView methods
|
// Forward ListView methods
|
||||||
function positionViewAtIndex(index, mode) {
|
function positionViewAtIndex(index, mode) {
|
||||||
@@ -99,33 +113,110 @@ Item {
|
|||||||
return listView.itemAtIndex(index);
|
return listView.itemAtIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dynamically create gradient overlays
|
||||||
|
function createGradients() {
|
||||||
|
if (!showGradientMasks)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
Qt.createQmlObject(`
|
||||||
|
import QtQuick
|
||||||
|
import qs.Constants
|
||||||
|
Rectangle {
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: root.availableWidth
|
||||||
|
height: root.gradientHeight
|
||||||
|
z: 1
|
||||||
|
visible: root.showGradientMasks && root.contentOverflows
|
||||||
|
opacity: {
|
||||||
|
if (listView.contentY <= 1) return 0;
|
||||||
|
if (listView.currentItem && listView.currentItem.y - listView.contentY < root.gradientHeight) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: root.gradientColor }
|
||||||
|
GradientStop { position: 1.0; color: "transparent" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, root, "topGradient");
|
||||||
|
Qt.createQmlObject(`
|
||||||
|
import QtQuick
|
||||||
|
import qs.Constants
|
||||||
|
Rectangle {
|
||||||
|
x: 0
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: -1
|
||||||
|
width: root.availableWidth
|
||||||
|
height: root.gradientHeight + 1
|
||||||
|
z: 1
|
||||||
|
visible: root.showGradientMasks && root.contentOverflows
|
||||||
|
opacity: {
|
||||||
|
if (listView.contentY + listView.height >= listView.contentHeight - 1) return 0;
|
||||||
|
if (listView.currentItem && listView.currentItem.y + listView.currentItem.height > listView.contentY + listView.height - root.gradientHeight) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: "transparent" }
|
||||||
|
GradientStop { position: 1.0; color: root.gradientColor }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, root, "bottomGradient");
|
||||||
|
}
|
||||||
|
|
||||||
// Set reasonable implicit sizes for Layout usage
|
// Set reasonable implicit sizes for Layout usage
|
||||||
implicitWidth: 200
|
implicitWidth: 200
|
||||||
implicitHeight: 200
|
implicitHeight: 200
|
||||||
|
Component.onCompleted: {
|
||||||
|
createGradients();
|
||||||
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
// Enable clipping to keep content within bounds
|
anchors.rightMargin: root.reserveScrollbarSpace ? root.handleWidth + Style.marginXS : 0
|
||||||
clip: true
|
clip: true
|
||||||
// Enable flickable for smooth scrolling
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
WheelHandler {
|
||||||
|
enabled: !root.contentOverflows
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
onWheel: (event) => {
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WheelHandler {
|
||||||
|
enabled: root.wheelScrollMultiplier !== 1 && root.contentOverflows
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
onWheel: (event) => {
|
||||||
|
const delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.angleDelta.y / 2;
|
||||||
|
const newY = listView.contentY - (delta * root.wheelScrollMultiplier);
|
||||||
|
listView.contentY = Math.max(0, Math.min(newY, listView.contentHeight - listView.height));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: ScrollBar {
|
||||||
parent: listView
|
parent: root
|
||||||
x: listView.mirrored ? 0 : listView.width - width
|
x: root.mirrored ? 0 : root.width - width
|
||||||
y: 0
|
y: 0
|
||||||
height: listView.height
|
height: root.height
|
||||||
active: listView.ScrollBar.horizontal.active
|
|
||||||
policy: root.verticalPolicy
|
policy: root.verticalPolicy
|
||||||
|
visible: policy === ScrollBar.AlwaysOn || root.verticalScrollBarActive
|
||||||
|
|
||||||
contentItem: Rectangle {
|
contentItem: Rectangle {
|
||||||
implicitWidth: root.handleWidth
|
implicitWidth: root.handleWidth
|
||||||
implicitHeight: 100
|
implicitHeight: 100
|
||||||
radius: root.handleRadius
|
radius: root.handleRadius
|
||||||
color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor
|
color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor
|
||||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 1 : 0
|
opacity: parent.policy === ScrollBar.AlwaysOn ? 1 : root.verticalScrollBarActive ? (parent.active ? 1 : 0) : 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
@@ -147,58 +238,7 @@ Item {
|
|||||||
implicitWidth: root.handleWidth
|
implicitWidth: root.handleWidth
|
||||||
implicitHeight: 100
|
implicitHeight: 100
|
||||||
color: root.trackColor
|
color: root.trackColor
|
||||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 0.3 : 0
|
opacity: parent.policy === ScrollBar.AlwaysOn ? 0.3 : root.verticalScrollBarActive ? (parent.active ? 0.3 : 0) : 0
|
||||||
radius: root.handleRadius / 2
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.horizontal: ScrollBar {
|
|
||||||
id: horizontalScrollBar
|
|
||||||
|
|
||||||
parent: listView
|
|
||||||
x: 0
|
|
||||||
y: listView.height - height
|
|
||||||
width: listView.width
|
|
||||||
active: listView.ScrollBar.vertical.active
|
|
||||||
policy: root.horizontalPolicy
|
|
||||||
|
|
||||||
contentItem: Rectangle {
|
|
||||||
implicitWidth: 100
|
|
||||||
implicitHeight: root.handleWidth
|
|
||||||
radius: root.handleRadius
|
|
||||||
color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor
|
|
||||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 1 : 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
implicitWidth: 100
|
|
||||||
implicitHeight: root.handleWidth
|
|
||||||
color: root.trackColor
|
|
||||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 0.3 : 0
|
|
||||||
radius: root.handleRadius / 2
|
radius: root.handleRadius / 2
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
+16
-20
@@ -7,12 +7,12 @@ import qs.Constants
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property string symbol
|
required property string iconName
|
||||||
property int symbolSize: Fonts.icon
|
property int iconSize: Style.fontSizeM
|
||||||
property real maxValue: 100
|
property real maxValue: 100
|
||||||
property real value: 100
|
property real value: 100
|
||||||
property string textValue: "" // override value in textDisplay if set
|
property string textValue: "" // override value in textDisplay if set
|
||||||
property color fillColor: Colors.primary
|
property color fillColor: Colors.mPrimary
|
||||||
property string textSuffix: ""
|
property string textSuffix: ""
|
||||||
property bool pointerCursor: true
|
property bool pointerCursor: true
|
||||||
property bool expandOnValueChange: false
|
property bool expandOnValueChange: false
|
||||||
@@ -22,7 +22,7 @@ Item {
|
|||||||
property bool _isFirst: true
|
property bool _isFirst: true
|
||||||
property bool disableHover: false
|
property bool disableHover: false
|
||||||
property bool critical: false
|
property bool critical: false
|
||||||
property color criticalColor: Colors.red
|
property color criticalColor: Colors.mRed
|
||||||
readonly property real ratio: value / maxValue
|
readonly property real ratio: value / maxValue
|
||||||
property color realColor: critical ? criticalColor : fillColor
|
property color realColor: critical ? criticalColor : fillColor
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ Item {
|
|||||||
signal rightClicked()
|
signal rightClicked()
|
||||||
signal middleClicked()
|
signal middleClicked()
|
||||||
|
|
||||||
implicitHeight: parent.height - 5
|
implicitHeight: Math.max(iconSize, textLabel.implicitHeight) + 12
|
||||||
implicitWidth: parent.height + (_expand ? textDisplay.width : 0)
|
implicitWidth: height + textDisplay.implicitWidth
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: connectionLoader
|
id: connectionLoader
|
||||||
@@ -95,15 +95,14 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.top: parent.top
|
anchors.fill: parent
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: progressDisplay
|
id: progressDisplay
|
||||||
|
|
||||||
Layout.preferredHeight: parent.height
|
Layout.preferredHeight: root.height
|
||||||
Layout.preferredWidth: parent.height
|
Layout.preferredWidth: root.height
|
||||||
|
|
||||||
Canvas {
|
Canvas {
|
||||||
id: progressCircle
|
id: progressCircle
|
||||||
@@ -140,16 +139,15 @@ Item {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
UIcon {
|
||||||
id: symbolText
|
id: symbolIcon
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
text: symbol
|
|
||||||
font.family: Fonts.nerd
|
|
||||||
font.pointSize: symbolSize
|
|
||||||
color: root.realColor
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
iconName: root.iconName
|
||||||
|
iconSize: root.iconSize
|
||||||
|
color: root.realColor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -161,17 +159,15 @@ Item {
|
|||||||
implicitWidth: root._expand ? textLabel.implicitWidth + 10 : 0
|
implicitWidth: root._expand ? textLabel.implicitWidth + 10 : 0
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Text {
|
UText {
|
||||||
id: textLabel
|
id: textLabel
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 5
|
anchors.leftMargin: 5
|
||||||
text: (textValue || Math.round(root.value)) + root.textSuffix
|
text: (textValue || Math.round(root.value)) + root.textSuffix
|
||||||
font.pointSize: Fonts.small
|
font.pointSize: Style.fontSizeS
|
||||||
font.family: Fonts.primary
|
|
||||||
color: root.realColor
|
color: root.realColor
|
||||||
opacity: root._expand ? 1 : 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
Behavior on implicitWidth {
|
||||||
+2
-2
@@ -6,10 +6,10 @@ import qs.Constants
|
|||||||
T.ScrollView {
|
T.ScrollView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property color handleColor: Qt.alpha(Color.mTertiary, 0.8)
|
property color handleColor: Qt.alpha(Colors.mPrimary, 0.8)
|
||||||
property color handleHoverColor: handleColor
|
property color handleHoverColor: handleColor
|
||||||
property color handlePressedColor: handleColor
|
property color handlePressedColor: handleColor
|
||||||
property color trackColor: Color.transparent
|
property color trackColor: Colors.transparent
|
||||||
property real handleWidth: 6
|
property real handleWidth: 6
|
||||||
property real handleRadius: Style.radiusM
|
property real handleRadius: Style.radiusM
|
||||||
property int verticalPolicy: ScrollBar.AsNeeded
|
property int verticalPolicy: ScrollBar.AsNeeded
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property color fillColor: Colors.mPrimary
|
||||||
|
property var cutoutColor: Colors.mSurface
|
||||||
|
property bool snapAlways: true
|
||||||
|
property real heightRatio: 0.7
|
||||||
|
property string tooltipText
|
||||||
|
property string tooltipDirection: "auto"
|
||||||
|
property bool hovering: false
|
||||||
|
readonly property color effectiveFillColor: enabled ? fillColor : Colors.mOutline
|
||||||
|
readonly property real knobDiameter: Math.round((Style.baseWidgetSize * heightRatio) / 2) * 2
|
||||||
|
readonly property real trackHeight: Math.round((knobDiameter * 0.4) / 2) * 2
|
||||||
|
readonly property real trackRadius: Math.min(Style.radiusL, trackHeight / 2)
|
||||||
|
readonly property real cutoutExtra: Math.round((Style.baseWidgetSize * 0.1) / 2) * 2
|
||||||
|
|
||||||
|
padding: cutoutExtra / 2
|
||||||
|
snapMode: snapAlways ? Slider.SnapAlways : Slider.SnapOnRelease
|
||||||
|
implicitHeight: Math.max(trackHeight, knobDiameter)
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
id: bgContainer
|
||||||
|
|
||||||
|
readonly property real fillWidth: root.visualPosition * width
|
||||||
|
|
||||||
|
x: root.leftPadding
|
||||||
|
y: root.topPadding + Math.round((root.availableHeight - root.trackHeight) / 2)
|
||||||
|
implicitWidth: Style.sliderWidth
|
||||||
|
implicitHeight: root.trackHeight
|
||||||
|
width: root.availableWidth
|
||||||
|
height: root.trackHeight
|
||||||
|
|
||||||
|
// Background track
|
||||||
|
Shape {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: bgContainer.width > 0 && bgContainer.height > 0
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
id: bgPath
|
||||||
|
|
||||||
|
readonly property real w: bgContainer.width
|
||||||
|
readonly property real h: bgContainer.height
|
||||||
|
readonly property real r: root.trackRadius
|
||||||
|
|
||||||
|
strokeColor: Qt.alpha(Colors.mOutline, 0.5)
|
||||||
|
strokeWidth: Style.borderS
|
||||||
|
fillColor: Qt.alpha(Colors.mSurface, 0.5)
|
||||||
|
startX: r
|
||||||
|
startY: 0
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
x: bgPath.w - bgPath.r
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
x: bgPath.w
|
||||||
|
y: bgPath.r
|
||||||
|
radiusX: bgPath.r
|
||||||
|
radiusY: bgPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
x: bgPath.w
|
||||||
|
y: bgPath.h - bgPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
x: bgPath.w - bgPath.r
|
||||||
|
y: bgPath.h
|
||||||
|
radiusX: bgPath.r
|
||||||
|
radiusY: bgPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
x: bgPath.r
|
||||||
|
y: bgPath.h
|
||||||
|
}
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
x: 0
|
||||||
|
y: bgPath.h - bgPath.r
|
||||||
|
radiusX: bgPath.r
|
||||||
|
radiusY: bgPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
x: 0
|
||||||
|
y: bgPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
x: bgPath.r
|
||||||
|
y: 0
|
||||||
|
radiusX: bgPath.r
|
||||||
|
radiusY: bgPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearGradient {
|
||||||
|
id: fillGradient
|
||||||
|
|
||||||
|
x1: 0
|
||||||
|
y1: 0
|
||||||
|
x2: root.availableWidth
|
||||||
|
y2: 0
|
||||||
|
|
||||||
|
GradientStop {
|
||||||
|
position: 0
|
||||||
|
color: Qt.darker(effectiveFillColor, 1.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientStop {
|
||||||
|
position: 1
|
||||||
|
color: effectiveFillColor
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active/filled track
|
||||||
|
Shape {
|
||||||
|
width: bgContainer.fillWidth
|
||||||
|
height: bgContainer.height
|
||||||
|
visible: bgContainer.fillWidth > 0 && bgContainer.height > 0
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
id: fillPath
|
||||||
|
|
||||||
|
readonly property real fullWidth: root.availableWidth
|
||||||
|
readonly property real h: root.trackHeight
|
||||||
|
readonly property real r: root.trackRadius
|
||||||
|
|
||||||
|
strokeColor: "transparent"
|
||||||
|
fillGradient: fillGradient
|
||||||
|
startX: r
|
||||||
|
startY: 0
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
x: fillPath.fullWidth - fillPath.r
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
x: fillPath.fullWidth
|
||||||
|
y: fillPath.r
|
||||||
|
radiusX: fillPath.r
|
||||||
|
radiusY: fillPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
x: fillPath.fullWidth
|
||||||
|
y: fillPath.h - fillPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
x: fillPath.fullWidth - fillPath.r
|
||||||
|
y: fillPath.h
|
||||||
|
radiusX: fillPath.r
|
||||||
|
radiusY: fillPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
x: fillPath.r
|
||||||
|
y: fillPath.h
|
||||||
|
}
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
x: 0
|
||||||
|
y: fillPath.h - fillPath.r
|
||||||
|
radiusX: fillPath.r
|
||||||
|
radiusY: fillPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathLine {
|
||||||
|
x: 0
|
||||||
|
y: fillPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
x: fillPath.r
|
||||||
|
y: 0
|
||||||
|
radiusX: fillPath.r
|
||||||
|
radiusY: fillPath.r
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Circular cutout
|
||||||
|
Rectangle {
|
||||||
|
id: knobCutout
|
||||||
|
|
||||||
|
implicitWidth: root.knobDiameter + root.cutoutExtra
|
||||||
|
implicitHeight: root.knobDiameter + root.cutoutExtra
|
||||||
|
radius: Math.min(Style.radiusL, width / 2)
|
||||||
|
color: root.cutoutColor !== undefined ? root.cutoutColor : Colors.mSurface
|
||||||
|
x: root.visualPosition * (root.availableWidth - root.knobDiameter) - root.cutoutExtra / 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handle: Item {
|
||||||
|
implicitWidth: knobDiameter
|
||||||
|
implicitHeight: knobDiameter
|
||||||
|
x: root.leftPadding + root.visualPosition * (root.availableWidth - width)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: knob
|
||||||
|
|
||||||
|
implicitWidth: knobDiameter
|
||||||
|
implicitHeight: knobDiameter
|
||||||
|
radius: Math.min(Style.radiusL, width / 2)
|
||||||
|
color: root.pressed ? Colors.mHover : Colors.mSurface
|
||||||
|
border.color: effectiveFillColor
|
||||||
|
border.width: Style.borderL
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
enabled: true
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton // Don't accept any mouse buttons - only hover
|
||||||
|
propagateComposedEvents: true
|
||||||
|
onEntered: {
|
||||||
|
root.hovering = true;
|
||||||
|
if (root.tooltipText)
|
||||||
|
TooltipService.show(knob, root.tooltipText, root.tooltipDirection);
|
||||||
|
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
root.hovering = false;
|
||||||
|
if (root.tooltipText)
|
||||||
|
TooltipService.hide();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide tooltip when slider is pressed (anywhere on the slider)
|
||||||
|
Connections {
|
||||||
|
function onPressedChanged() {
|
||||||
|
if (root.pressed && root.tooltipText)
|
||||||
|
TooltipService.hide();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
target: root
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Constants
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
objectName: "NTabBar"
|
||||||
|
|
||||||
|
// Public properties
|
||||||
|
property int currentIndex: 0
|
||||||
|
property real spacing: Style.marginXS
|
||||||
|
property real margins: 0
|
||||||
|
property real tabHeight: Style.baseWidgetSize
|
||||||
|
property bool distributeEvenly: false
|
||||||
|
default property alias content: tabRow.children
|
||||||
|
|
||||||
|
onDistributeEvenlyChanged: _applyDistribution()
|
||||||
|
Component.onCompleted: _applyDistribution()
|
||||||
|
|
||||||
|
function _updateFirstLast() {
|
||||||
|
// Defensive check for QML initialization timing
|
||||||
|
if (!tabRow || !tabRow.children) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var kids = tabRow.children;
|
||||||
|
var len = kids.length;
|
||||||
|
var firstVisible = -1;
|
||||||
|
var lastVisible = -1;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
var child = kids[i];
|
||||||
|
// Only consider items that have isFirst/isLast (actual tab buttons, not Repeaters)
|
||||||
|
if (child.visible && "isFirst" in child) {
|
||||||
|
if (firstVisible === -1)
|
||||||
|
firstVisible = i;
|
||||||
|
lastVisible = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
var child = kids[i];
|
||||||
|
if ("isFirst" in child)
|
||||||
|
child.isFirst = (i === firstVisible);
|
||||||
|
if ("isLast" in child)
|
||||||
|
child.isLast = (i === lastVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _applyDistribution() {
|
||||||
|
if (!tabRow || !tabRow.children) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!distributeEvenly) {
|
||||||
|
for (var i = 0; i < tabRow.children.length; i++) {
|
||||||
|
var child = tabRow.children[i];
|
||||||
|
child.Layout.fillWidth = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < tabRow.children.length; i++) {
|
||||||
|
var child = tabRow.children[i];
|
||||||
|
child.Layout.fillWidth = true;
|
||||||
|
child.Layout.preferredWidth = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styling
|
||||||
|
implicitWidth: tabRow.implicitWidth + (margins * 2)
|
||||||
|
implicitHeight: tabHeight + (margins * 2)
|
||||||
|
color: Colors.mSurfaceVariant
|
||||||
|
radius: Style.radiusM
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: tabRow
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: margins
|
||||||
|
spacing: root.spacing
|
||||||
|
|
||||||
|
onChildrenChanged: {
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
var child = children[i];
|
||||||
|
child.visibleChanged.connect(root._updateFirstLast);
|
||||||
|
}
|
||||||
|
root._updateFirstLast();
|
||||||
|
root._applyDistribution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Components
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Public properties
|
||||||
|
property string text: ""
|
||||||
|
property string icon: ""
|
||||||
|
property bool checked: false
|
||||||
|
property int tabIndex: 0
|
||||||
|
property real pointSize: Style.fontSizeM
|
||||||
|
property bool isFirst: false
|
||||||
|
property bool isLast: false
|
||||||
|
|
||||||
|
// Internal state
|
||||||
|
property bool isHovered: false
|
||||||
|
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
// Sizing
|
||||||
|
Layout.fillHeight: true
|
||||||
|
implicitWidth: contentLayout.implicitWidth + Style.marginM * 2
|
||||||
|
|
||||||
|
topLeftRadius: isFirst ? Style.radiusM : Style.radiusXXXS
|
||||||
|
bottomLeftRadius: isFirst ? Style.radiusM : Style.radiusXXXS
|
||||||
|
topRightRadius: isLast ? Style.radiusM : Style.radiusXXXS
|
||||||
|
bottomRightRadius: isLast ? Style.radiusM : Style.radiusXXXS
|
||||||
|
|
||||||
|
color: root.isHovered ? Colors.mHover : (root.checked ? Colors.mPrimary : Colors.mSurface)
|
||||||
|
border.color: Colors.mOutline
|
||||||
|
border.width: Style.borderS
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
enabled: !Colors.isTransitioning
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content
|
||||||
|
RowLayout {
|
||||||
|
id: contentLayout
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Math.min(implicitWidth, parent.width - Style.marginS * 2)
|
||||||
|
spacing: (root.icon !== "" && root.text !== "") ? Style.marginXS : 0
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
visible: root.icon !== ""
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
iconName: root.icon
|
||||||
|
iconSize: root.pointSize * 1.2
|
||||||
|
color: root.isHovered ? Colors.mOnHover : (root.checked ? Colors.mOnPrimary : Colors.mOnSurface)
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
enabled: !Colors.isTransitioning
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
id: tabText
|
||||||
|
visible: root.text !== ""
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.text
|
||||||
|
pointSize: root.pointSize
|
||||||
|
font.weight: Style.fontWeightSemiBold
|
||||||
|
color: root.isHovered ? Colors.mOnHover : (root.checked ? Colors.mOnPrimary : Colors.mOnSurface)
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
enabled: !Colors.isTransitioning
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onEntered: {
|
||||||
|
root.isHovered = true;
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
root.isHovered = false;
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
root.clicked();
|
||||||
|
// Update parent NTabBar's currentIndex
|
||||||
|
if (root.parent && root.parent.parent && root.parent.parent.currentIndex !== undefined) {
|
||||||
|
root.parent.parent.currentIndex = root.tabIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-4
@@ -1,19 +1,17 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Noctalia
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string family: Fonts.primary
|
property string family: Fonts.primary
|
||||||
property real pointSize: Style.fontSizeM
|
property real pointSize: Style.fontSizeM
|
||||||
property real fontScale: 1
|
|
||||||
|
|
||||||
font.family: root.family
|
font.family: root.family
|
||||||
font.weight: Style.fontWeightMedium
|
font.weight: Style.fontWeightMedium
|
||||||
font.pointSize: root.pointSize * fontScale
|
font.pointSize: root.pointSize
|
||||||
color: Color.mOnSurface
|
color: Colors.mOnSurface
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
wrapMode: Text.NoWrap
|
wrapMode: Text.NoWrap
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
+36
-9
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -8,38 +9,54 @@ RowLayout {
|
|||||||
|
|
||||||
property string label: ""
|
property string label: ""
|
||||||
property string description: ""
|
property string description: ""
|
||||||
|
property string icon: ""
|
||||||
property bool checked: false
|
property bool checked: false
|
||||||
property bool hovering: false
|
property bool hovering: false
|
||||||
property int baseSize: Math.round(Style.baseWidgetSize * 0.8)
|
property int baseSize: Math.round(Style.baseWidgetSize * 0.8)
|
||||||
|
property var defaultValue: undefined
|
||||||
|
property string settingsPath: ""
|
||||||
|
readonly property bool isValueChanged: (defaultValue !== undefined) && (checked !== defaultValue)
|
||||||
|
readonly property string indicatorTooltip: defaultValue !== undefined ? I18n.tr("panels.indicator.default-value", {
|
||||||
|
"value": typeof defaultValue === "boolean" ? (defaultValue ? "true" : "false") : String(defaultValue)
|
||||||
|
}) : ""
|
||||||
|
|
||||||
signal toggled(bool checked)
|
signal toggled(bool checked)
|
||||||
signal entered()
|
signal entered()
|
||||||
signal exited()
|
signal exited()
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
opacity: enabled ? 1 : 0.6
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
NLabel {
|
ULabel {
|
||||||
|
Layout.fillWidth: true
|
||||||
label: root.label
|
label: root.label
|
||||||
description: root.description
|
description: root.description
|
||||||
|
icon: root.icon
|
||||||
|
iconColor: root.checked ? Colors.mPrimary : Colors.mOnSurface
|
||||||
|
visible: root.label !== "" || root.description !== ""
|
||||||
|
showIndicator: root.isValueChanged
|
||||||
|
indicatorTooltip: root.indicatorTooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: switcher
|
id: switcher
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
implicitWidth: Math.round(root.baseSize * 0.85) * 2
|
implicitWidth: Math.round(root.baseSize * 0.85) * 2
|
||||||
implicitHeight: Math.round(root.baseSize * 0.5) * 2
|
implicitHeight: Math.round(root.baseSize * 0.5) * 2
|
||||||
radius: height * 0.5
|
radius: Math.min(Style.radiusL, height / 2)
|
||||||
color: root.checked ? Color.mPrimary : Color.mSurface
|
color: root.checked ? Colors.mPrimary : Colors.mSurface
|
||||||
border.color: Color.mOutline
|
border.color: Colors.mOutline
|
||||||
border.width: Math.max(1, Style.borderS)
|
border.width: Style.borderS
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
implicitWidth: Math.round(root.baseSize * 0.4) * 2
|
implicitWidth: Math.round(root.baseSize * 0.4) * 2
|
||||||
implicitHeight: Math.round(root.baseSize * 0.4) * 2
|
implicitHeight: Math.round(root.baseSize * 0.4) * 2
|
||||||
radius: height * 0.5
|
radius: Math.min(Style.radiusL, height / 2)
|
||||||
color: root.checked ? Color.mOnPrimary : Color.mPrimary
|
color: root.checked ? Colors.mOnPrimary : Colors.mPrimary
|
||||||
border.color: root.checked ? Color.mSurface : Color.mSurface
|
border.color: root.checked ? Colors.mSurface : Colors.mSurface
|
||||||
border.width: Math.max(1, Style.borderM)
|
border.width: Style.borderM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.verticalCenterOffset: 0
|
anchors.verticalCenterOffset: 0
|
||||||
x: root.checked ? switcher.width - width - 3 : 3
|
x: root.checked ? switcher.width - width - 3 : 3
|
||||||
@@ -55,18 +72,28 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
enabled: root.enabled
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onEntered: {
|
onEntered: {
|
||||||
|
if (!enabled)
|
||||||
|
return ;
|
||||||
|
|
||||||
hovering = true;
|
hovering = true;
|
||||||
root.entered();
|
root.entered();
|
||||||
}
|
}
|
||||||
onExited: {
|
onExited: {
|
||||||
|
if (!enabled)
|
||||||
|
return ;
|
||||||
|
|
||||||
hovering = false;
|
hovering = false;
|
||||||
root.exited();
|
root.exited();
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
if (!enabled)
|
||||||
|
return ;
|
||||||
|
|
||||||
root.toggled(!root.checked);
|
root.toggled(!root.checked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
import qs.Constants
|
|
||||||
pragma Singleton
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// Compatibility colors for noctalia modules
|
|
||||||
readonly property color mPrimary: Colors.primary
|
|
||||||
readonly property color mOnPrimary: Colors.base
|
|
||||||
readonly property color mSecondary: Colors.primary
|
|
||||||
readonly property color mOnSecondary: Colors.base
|
|
||||||
readonly property color mTertiary: Colors.primary
|
|
||||||
readonly property color mOnTertiary: Colors.base
|
|
||||||
readonly property color mError: Colors.red
|
|
||||||
readonly property color mOnError: Colors.base
|
|
||||||
readonly property color mSurface: Colors.base
|
|
||||||
readonly property color mOnSurface: Colors.text
|
|
||||||
readonly property color mSurfaceVariant: Colors.surface
|
|
||||||
readonly property color mOnSurfaceVariant: Colors.overlay1
|
|
||||||
readonly property color mOutline: Colors.primary
|
|
||||||
readonly property color mShadow: Colors.crust
|
|
||||||
readonly property color transparent: "transparent"
|
|
||||||
}
|
|
||||||
@@ -1,40 +1,154 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Services
|
import Quickshell.Io
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Utils
|
||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property color primary: SettingsService.primaryColor
|
// Part of material3 color scheme
|
||||||
readonly property color transparent: "transparent"
|
property color mPrimary: defaultColors.mPrimary
|
||||||
readonly property color rosewater: "#f5e0dc"
|
property color mOnPrimary: defaultColors.mOnPrimary
|
||||||
readonly property color flamingo: "#f2cdcd"
|
property color mError: defaultColors.mError
|
||||||
readonly property color pink: "#f5c2e7"
|
property color mOnError: defaultColors.mOnError
|
||||||
readonly property color mauve: "#cba6f7"
|
property color mSurface: defaultColors.mSurface
|
||||||
readonly property color red: "#f38ba8"
|
property color mOnSurface: defaultColors.mOnSurface
|
||||||
readonly property color maroon: "#eba0ac"
|
property color mSurfaceVariant: defaultColors.mSurfaceVariant
|
||||||
readonly property color peach: "#fab387"
|
property color mOnSurfaceVariant: defaultColors.mOnSurfaceVariant
|
||||||
readonly property color yellow: "#f9e2af"
|
property color mOutline: defaultColors.mOutline
|
||||||
readonly property color green: "#a6e3a1"
|
property color mShadow: defaultColors.mShadow
|
||||||
readonly property color teal: "#94e2d5"
|
property color mHover: defaultColors.mHover
|
||||||
readonly property color sky: "#89dceb"
|
property color mOnHover: defaultColors.mOnHover
|
||||||
readonly property color sapphire: "#74c7ec"
|
// Supplementary colors
|
||||||
readonly property color blue: "#89b4fa"
|
property color mPink: defaultColors.mPink
|
||||||
readonly property color lavender: "#b4befe"
|
property color mPurple: defaultColors.mPurple
|
||||||
readonly property color text: "#cdd6f4"
|
property color mRed: defaultColors.mRed
|
||||||
readonly property color subtext1: "#bac2de"
|
property color mOrange: defaultColors.mOrange
|
||||||
readonly property color subtext0: "#a6adc8"
|
property color mYellow: defaultColors.mYellow
|
||||||
readonly property color overlay2: "#9399b2"
|
property color mGreen: defaultColors.mGreen
|
||||||
readonly property color overlay1: "#7f849c"
|
property color mCyan: defaultColors.mCyan
|
||||||
readonly property color overlay0: "#6c7086"
|
property color mSky: defaultColors.mSky
|
||||||
readonly property color surface2: "#585b70"
|
property color mBlue: defaultColors.mBlue
|
||||||
readonly property color surface1: "#45475a"
|
property color mLavender: defaultColors.mLavender
|
||||||
readonly property color surface0: "#313244"
|
// Special colors
|
||||||
readonly property color surface: "#292a3c"
|
property color distro: "#74c7ec"
|
||||||
readonly property color base: "#1e1e2e"
|
property color transparent: "#00000000"
|
||||||
readonly property color mantle: "#181825"
|
readonly property var cavaList: [mLavender, mBlue, mSky, mCyan, mGreen, mYellow, mOrange, mRed]
|
||||||
readonly property color crust: "#11111b"
|
|
||||||
readonly property color distroColor: "#74c7ec"
|
function reloadColors(newColors) {
|
||||||
readonly property var cavaList: ["#b4befe", "#89b4fa", "#74c7ec", "#89dceb", "#94e2d5", "#a6e3a1", "#f9e2af", "#fab387"]
|
if (typeof newColors === "string") {
|
||||||
|
try {
|
||||||
|
newColors = JSON.parse(newColors);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.e("Colors", "Failed to parse colors.json, using default colors. Error:", e);
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
} else if (typeof newColors !== "object") {
|
||||||
|
Logger.w("Colors", "Invalid colors data, using default colors. Data:", newColors);
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
mPrimary = newColors.mPrimary || defaultColors.mPrimary;
|
||||||
|
mOnPrimary = newColors.mOnPrimary || defaultColors.mOnPrimary;
|
||||||
|
mError = newColors.mError || defaultColors.mError;
|
||||||
|
mOnError = newColors.mOnError || defaultColors.mOnError;
|
||||||
|
mSurface = newColors.mSurface || defaultColors.mSurface;
|
||||||
|
mOnSurface = newColors.mOnSurface || defaultColors.mOnSurface;
|
||||||
|
mSurfaceVariant = newColors.mSurfaceVariant || defaultColors.mSurfaceVariant;
|
||||||
|
mOnSurfaceVariant = newColors.mOnSurfaceVariant || defaultColors.mOnSurfaceVariant;
|
||||||
|
mOutline = newColors.mOutline || defaultColors.mOutline;
|
||||||
|
mShadow = newColors.mShadow || defaultColors.mShadow;
|
||||||
|
mHover = newColors.mHover || defaultColors.mHover;
|
||||||
|
mOnHover = newColors.mOnHover || defaultColors.mOnHover;
|
||||||
|
mPink = newColors.mPink || defaultColors.mPink;
|
||||||
|
mPurple = newColors.mPurple || defaultColors.mPurple;
|
||||||
|
mRed = newColors.mRed || defaultColors.mRed;
|
||||||
|
mOrange = newColors.mOrange || defaultColors.mOrange;
|
||||||
|
mYellow = newColors.mYellow || defaultColors.mYellow;
|
||||||
|
mGreen = newColors.mGreen || defaultColors.mGreen;
|
||||||
|
mCyan = newColors.mCyan || defaultColors.mCyan;
|
||||||
|
mSky = newColors.mSky || defaultColors.mSky;
|
||||||
|
mBlue = newColors.mBlue || defaultColors.mBlue;
|
||||||
|
mLavender = newColors.mLavender || defaultColors.mLavender;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setColor(name, value) {
|
||||||
|
if (!adapter.colors)
|
||||||
|
adapter.colors = {
|
||||||
|
};
|
||||||
|
|
||||||
|
adapter.colors[name] = value;
|
||||||
|
colorFile.writeAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsetColor(name) {
|
||||||
|
if (!adapter.colors || !(name in adapter.colors))
|
||||||
|
return ;
|
||||||
|
|
||||||
|
delete adapter.colors[name];
|
||||||
|
colorFile.writeAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: defaultColors
|
||||||
|
|
||||||
|
readonly property color mPrimary: "#89b4fa"
|
||||||
|
readonly property color mOnPrimary: "#11111b"
|
||||||
|
readonly property color mError: "#f38ba8"
|
||||||
|
readonly property color mOnError: "#11111b"
|
||||||
|
readonly property color mSurface: "#1e1e2e"
|
||||||
|
readonly property color mOnSurface: "#cdd6f4"
|
||||||
|
readonly property color mSurfaceVariant: "#313244"
|
||||||
|
readonly property color mOnSurfaceVariant: "#a6adc8"
|
||||||
|
readonly property color mOutline: "#585b70"
|
||||||
|
readonly property color mShadow: "#11111b"
|
||||||
|
readonly property color mHover: "#45475a"
|
||||||
|
readonly property color mOnHover: "#cdd6f4"
|
||||||
|
readonly property color mPink: "#f5c2e7"
|
||||||
|
readonly property color mPurple: "#cba6f7"
|
||||||
|
readonly property color mRed: "#f38ba8"
|
||||||
|
readonly property color mOrange: "#fab387"
|
||||||
|
readonly property color mYellow: "#f9e2af"
|
||||||
|
readonly property color mGreen: "#a6e3a1"
|
||||||
|
readonly property color mCyan: "#94e2d5"
|
||||||
|
readonly property color mSky: "#74c7ec"
|
||||||
|
readonly property color mBlue: "#89b4fa"
|
||||||
|
readonly property color mLavender: "#b4befe"
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: colorFile
|
||||||
|
|
||||||
|
path: Paths.configDir + "colors.json"
|
||||||
|
printErrors: false
|
||||||
|
watchChanges: true
|
||||||
|
onFileChanged: reload()
|
||||||
|
|
||||||
|
JsonAdapter {
|
||||||
|
id: adapter
|
||||||
|
|
||||||
|
property var colors: ({
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onColorsChanged() {
|
||||||
|
colorReloadTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: colorReloadTimer
|
||||||
|
|
||||||
|
interval: 50
|
||||||
|
running: true
|
||||||
|
repeat: false
|
||||||
|
onTriggered: reloadColors(adapter.colors)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ Singleton {
|
|||||||
|
|
||||||
readonly property string primary: "LXGW WenKai"
|
readonly property string primary: "LXGW WenKai"
|
||||||
readonly property string nerd: "Meslo LGM Nerd Font Mono"
|
readonly property string nerd: "Meslo LGM Nerd Font Mono"
|
||||||
|
readonly property string icon: Icons.fontFamily
|
||||||
readonly property string sans: "LXGW WenKai"
|
readonly property string sans: "LXGW WenKai"
|
||||||
readonly property int small: Style.fontSizeS
|
|
||||||
readonly property int medium: Style.fontSizeM
|
|
||||||
readonly property int large: Style.fontSizeL
|
|
||||||
readonly property int icon: 14 // for nerd font
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +1,19 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Constants
|
||||||
import qs.Utils
|
import qs.Utils
|
||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Nerd fonts icons
|
|
||||||
readonly property string distro: ""
|
|
||||||
readonly property string tray: ""
|
|
||||||
readonly property string idleInhibitorActivated: ""
|
|
||||||
readonly property string idleInhibitorDeactivated: ""
|
|
||||||
readonly property string powerMenu: ""
|
|
||||||
readonly property string volumeHigh: ""
|
|
||||||
readonly property string volumeMedium: ""
|
|
||||||
readonly property string volumeLow: ""
|
|
||||||
readonly property string volumeMuted: ""
|
|
||||||
readonly property string brightness: ""
|
|
||||||
readonly property string charging: ""
|
|
||||||
readonly property string battery100: ""
|
|
||||||
readonly property string battery75: ""
|
|
||||||
readonly property string battery50: ""
|
|
||||||
readonly property string battery25: ""
|
|
||||||
readonly property string battery00: ""
|
|
||||||
readonly property string cpu: ""
|
|
||||||
readonly property string memory: ""
|
|
||||||
readonly property string tempHigh: ""
|
|
||||||
readonly property string tempMedium: ""
|
|
||||||
readonly property string tempLow: ""
|
|
||||||
readonly property string ip: ""
|
|
||||||
readonly property string upload: ""
|
|
||||||
readonly property string download: ""
|
|
||||||
readonly property string speedSlower: ""
|
|
||||||
readonly property string speedFaster: ""
|
|
||||||
readonly property string speedReset: ""
|
|
||||||
readonly property string reset: ""
|
|
||||||
readonly property string lines: ""
|
|
||||||
readonly property string record: ""
|
|
||||||
readonly property string wifiOn: ""
|
|
||||||
readonly property string wifiOff: ""
|
|
||||||
readonly property string bluetoothOn: ""
|
|
||||||
readonly property string bluetoothOff: ""
|
|
||||||
// Tabler icons
|
|
||||||
// Expose the font family name for easy access
|
// Expose the font family name for easy access
|
||||||
readonly property string fontFamily: currentFontLoader ? currentFontLoader.name : ""
|
readonly property string fontFamily: currentFontLoader ? currentFontLoader.name : ""
|
||||||
readonly property string defaultIcon: TablerIcons.defaultIcon
|
readonly property string defaultIcon: IconsTabler.defaultIcon
|
||||||
readonly property var icons: TablerIcons.icons
|
readonly property var icons: IconsTabler.icons
|
||||||
readonly property var aliases: TablerIcons.aliases
|
readonly property var aliases: IconsTabler.aliases
|
||||||
readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.ttf"
|
readonly property string fontPath: "/Assets/Fonts/tabler/noctalia-tabler-icons.ttf"
|
||||||
// Current active font loader
|
// Current active font loader
|
||||||
property FontLoader currentFontLoader: null
|
property FontLoader currentFontLoader: null
|
||||||
property int fontVersion: 0
|
property int fontVersion: 0
|
||||||
@@ -68,6 +34,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadFontWithCacheBusting() {
|
function loadFontWithCacheBusting() {
|
||||||
|
Logger.d("Icons", "Loading font with cache busting");
|
||||||
// Destroy old loader first
|
// Destroy old loader first
|
||||||
if (currentFontLoader) {
|
if (currentFontLoader) {
|
||||||
currentFontLoader.destroy();
|
currentFontLoader.destroy();
|
||||||
@@ -82,24 +49,29 @@ Singleton {
|
|||||||
`, root, "dynamicFontLoader_" + fontVersion);
|
`, root, "dynamicFontLoader_" + fontVersion);
|
||||||
// Connect to the new loader's status changes
|
// Connect to the new loader's status changes
|
||||||
currentFontLoader.statusChanged.connect(function() {
|
currentFontLoader.statusChanged.connect(function() {
|
||||||
if (currentFontLoader.status === FontLoader.Ready)
|
if (currentFontLoader.status === FontLoader.Ready) {
|
||||||
|
Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")");
|
||||||
fontReloaded();
|
fontReloaded();
|
||||||
else if (currentFontLoader.status === FontLoader.Error)
|
} else if (currentFontLoader.status === FontLoader.Error) {
|
||||||
Logger.error("Icons", "Font failed to load (version " + fontVersion + ")");
|
Logger.e("Icons", "Font failed to load (version " + fontVersion + ")");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadFont() {
|
function reloadFont() {
|
||||||
|
Logger.d("Icons", "Forcing font reload...");
|
||||||
fontVersion++;
|
fontVersion++;
|
||||||
loadFontWithCacheBusting();
|
loadFontWithCacheBusting();
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
Logger.i("Icons", "Service started");
|
||||||
loadFontWithCacheBusting();
|
loadFontWithCacheBusting();
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onReloadCompleted() {
|
function onReloadCompleted() {
|
||||||
|
Logger.d("Icons", "Quickshell reload completed - forcing font reload");
|
||||||
reloadFont();
|
reloadFont();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+105
-75
@@ -33,6 +33,7 @@ Singleton {
|
|||||||
"media-next": "player-skip-forward-filled",
|
"media-next": "player-skip-forward-filled",
|
||||||
"download-speed": "download",
|
"download-speed": "download",
|
||||||
"upload-speed": "upload",
|
"upload-speed": "upload",
|
||||||
|
"cpu-intensive": "alert-octagon",
|
||||||
"cpu-usage": "brand-speedtest",
|
"cpu-usage": "brand-speedtest",
|
||||||
"cpu-temperature": "flame",
|
"cpu-temperature": "flame",
|
||||||
"gpu-temperature": "device-desktop",
|
"gpu-temperature": "device-desktop",
|
||||||
@@ -42,6 +43,7 @@ Singleton {
|
|||||||
"powersaver": "leaf",
|
"powersaver": "leaf",
|
||||||
"storage": "database",
|
"storage": "database",
|
||||||
"ethernet": "sitemap",
|
"ethernet": "sitemap",
|
||||||
|
"ethernet-off": "sitemap-off",
|
||||||
"keyboard": "keyboard",
|
"keyboard": "keyboard",
|
||||||
"shutdown": "power",
|
"shutdown": "power",
|
||||||
"lock": "lock",
|
"lock": "lock",
|
||||||
@@ -49,6 +51,7 @@ Singleton {
|
|||||||
"logout": "logout",
|
"logout": "logout",
|
||||||
"reboot": "refresh",
|
"reboot": "refresh",
|
||||||
"suspend": "player-pause",
|
"suspend": "player-pause",
|
||||||
|
"hibernate": "zzz",
|
||||||
"nightlight-on": "moon",
|
"nightlight-on": "moon",
|
||||||
"nightlight-off": "moon-off",
|
"nightlight-off": "moon-off",
|
||||||
"nightlight-forced": "moon-stars",
|
"nightlight-forced": "moon-stars",
|
||||||
@@ -71,15 +74,19 @@ Singleton {
|
|||||||
"chevron-down": "chevron-down",
|
"chevron-down": "chevron-down",
|
||||||
"caret-up": "caret-up-filled",
|
"caret-up": "caret-up-filled",
|
||||||
"caret-down": "caret-down-filled",
|
"caret-down": "caret-down-filled",
|
||||||
|
"caret-left": "caret-left-filled",
|
||||||
|
"caret-right": "caret-right-filled",
|
||||||
"star": "star",
|
"star": "star",
|
||||||
"star-off": "star-off",
|
"star-off": "star-off",
|
||||||
"battery-exclamation": "battery-exclamation",
|
"battery-exclamation": "battery-exclamation",
|
||||||
"battery-charging": "battery-charging",
|
"battery-charging": "battery-charging",
|
||||||
|
"battery-charging-2": "battery-charging-2",
|
||||||
"battery-4": "battery-4",
|
"battery-4": "battery-4",
|
||||||
"battery-3": "battery-3",
|
"battery-3": "battery-3",
|
||||||
"battery-2": "battery-2",
|
"battery-2": "battery-2",
|
||||||
"battery-1": "battery-1",
|
"battery-1": "battery-1",
|
||||||
"battery": "battery",
|
"battery": "battery",
|
||||||
|
"battery-off": "battery-off",
|
||||||
"wifi-0": "wifi-0",
|
"wifi-0": "wifi-0",
|
||||||
"wifi-1": "wifi-1",
|
"wifi-1": "wifi-1",
|
||||||
"wifi-2": "wifi-2",
|
"wifi-2": "wifi-2",
|
||||||
@@ -88,10 +95,14 @@ Singleton {
|
|||||||
"microphone": "microphone",
|
"microphone": "microphone",
|
||||||
"microphone-mute": "microphone-off",
|
"microphone-mute": "microphone-off",
|
||||||
"volume-mute": "volume-off",
|
"volume-mute": "volume-off",
|
||||||
|
"volume-x": "volume-3",
|
||||||
"volume-zero": "volume-3",
|
"volume-zero": "volume-3",
|
||||||
"volume-low": "volume-2",
|
"volume-low": "volume-2",
|
||||||
"volume-high": "volume",
|
"volume-high": "volume",
|
||||||
"weather-sun": "sun",
|
"weather-sun": "sun",
|
||||||
|
"weather-moon": "moon",
|
||||||
|
"weather-moon-stars": "moon-stars",
|
||||||
|
"weather-cloud-off": "cloud-off",
|
||||||
"weather-cloud": "cloud",
|
"weather-cloud": "cloud",
|
||||||
"weather-cloud-haze": "cloud-fog",
|
"weather-cloud-haze": "cloud-fog",
|
||||||
"weather-cloud-lightning": "cloud-bolt",
|
"weather-cloud-lightning": "cloud-bolt",
|
||||||
@@ -101,24 +112,33 @@ Singleton {
|
|||||||
"brightness-low": "brightness-down-filled",
|
"brightness-low": "brightness-down-filled",
|
||||||
"brightness-high": "brightness-up-filled",
|
"brightness-high": "brightness-up-filled",
|
||||||
"settings-general": "adjustments-horizontal",
|
"settings-general": "adjustments-horizontal",
|
||||||
"settings-bar": "capsule-horizontal",
|
"settings-bar": "crop-16-9",
|
||||||
|
"settings-user-interface": "layout-board",
|
||||||
|
"settings-control-center": "adjustments-horizontal",
|
||||||
"settings-dock": "layout-bottombar",
|
"settings-dock": "layout-bottombar",
|
||||||
"settings-launcher": "rocket",
|
"settings-launcher": "rocket",
|
||||||
"settings-audio": "device-speaker",
|
"settings-audio": "device-speaker",
|
||||||
"settings-display": "device-desktop",
|
"settings-display": "device-desktop",
|
||||||
"settings-network": "sitemap",
|
"settings-network": "circles-relation",
|
||||||
"settings-brightness": "brightness-up",
|
"settings-brightness": "brightness-up",
|
||||||
"settings-location": "world-pin",
|
"settings-location": "world-pin",
|
||||||
"settings-color-scheme": "palette",
|
"settings-color-scheme": "palette",
|
||||||
"settings-wallpaper": "paint",
|
"settings-wallpaper": "paint",
|
||||||
"settings-wallpaper-selector": "library-photo",
|
"settings-wallpaper-selector": "library-photo",
|
||||||
"settings-screen-recorder": "video",
|
|
||||||
"settings-hooks": "link",
|
"settings-hooks": "link",
|
||||||
"settings-notifications": "bell",
|
"settings-notifications": "bell",
|
||||||
"settings-osd": "picture-in-picture",
|
"settings-osd": "picture-in-picture",
|
||||||
"settings-about": "info-square-rounded",
|
"settings-about": "info-square-rounded",
|
||||||
|
"settings-idle": "moon",
|
||||||
|
"settings-lock-screen": "lock",
|
||||||
|
"settings-session-menu": "power",
|
||||||
|
"settings-system-monitor": "activity",
|
||||||
"bluetooth": "bluetooth",
|
"bluetooth": "bluetooth",
|
||||||
"bt-device-generic": "bluetooth",
|
"bt-device-generic": "bluetooth",
|
||||||
|
"bt-device-gamepad": "device-gamepad-2",
|
||||||
|
"bt-device-microphone": "microphone",
|
||||||
|
"bt-device-headset": "headset",
|
||||||
|
"bt-device-earbuds": "device-airpods",
|
||||||
"bt-device-headphones": "headphones",
|
"bt-device-headphones": "headphones",
|
||||||
"bt-device-mouse": "mouse-2",
|
"bt-device-mouse": "mouse-2",
|
||||||
"bt-device-keyboard": "bluetooth",
|
"bt-device-keyboard": "bluetooth",
|
||||||
@@ -126,6 +146,12 @@ Singleton {
|
|||||||
"bt-device-watch": "device-watch",
|
"bt-device-watch": "device-watch",
|
||||||
"bt-device-speaker": "device-speaker",
|
"bt-device-speaker": "device-speaker",
|
||||||
"bt-device-tv": "device-tv",
|
"bt-device-tv": "device-tv",
|
||||||
|
"antenna-bars-1": "antenna-bars-1",
|
||||||
|
"antenna-bars-2": "antenna-bars-2",
|
||||||
|
"antenna-bars-3": "antenna-bars-3",
|
||||||
|
"antenna-bars-4": "antenna-bars-4",
|
||||||
|
"antenna-bars-5": "antenna-bars-5",
|
||||||
|
"antenna-bars-off": "antenna-bars-off",
|
||||||
"noctalia": "noctalia",
|
"noctalia": "noctalia",
|
||||||
"hyprland": "hyprland",
|
"hyprland": "hyprland",
|
||||||
"filepicker-folder": "folder",
|
"filepicker-folder": "folder",
|
||||||
@@ -152,7 +178,10 @@ Singleton {
|
|||||||
"filepicker-text": "file-text",
|
"filepicker-text": "file-text",
|
||||||
"filepicker-eye": "eye",
|
"filepicker-eye": "eye",
|
||||||
"filepicker-eye-off": "eye-off",
|
"filepicker-eye-off": "eye-off",
|
||||||
"filepicker-folder-current": "checks"
|
"filepicker-folder-current": "checks",
|
||||||
|
"plugin": "plug-connected",
|
||||||
|
"info": "file-description",
|
||||||
|
"official-plugin": "shield-filled"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonts Codepoints - do not change!
|
// Fonts Codepoints - do not change!
|
||||||
@@ -295,8 +324,8 @@ Singleton {
|
|||||||
"align-left": "\u{ea09}",
|
"align-left": "\u{ea09}",
|
||||||
"align-left-2": "\u{ff00}",
|
"align-left-2": "\u{ff00}",
|
||||||
"align-right": "\u{ea0a}",
|
"align-right": "\u{ea0a}",
|
||||||
"alpha"//"align-right-2": "\u{feff}",
|
//"align-right-2": "\u{feff}",
|
||||||
: "\u{f543}",
|
"alpha": "\u{f543}",
|
||||||
"alphabet-arabic": "\u{ff2f}",
|
"alphabet-arabic": "\u{ff2f}",
|
||||||
"alphabet-bangla": "\u{ff2e}",
|
"alphabet-bangla": "\u{ff2e}",
|
||||||
"alphabet-cyrillic": "\u{f1df}",
|
"alphabet-cyrillic": "\u{f1df}",
|
||||||
@@ -2084,7 +2113,7 @@ Singleton {
|
|||||||
"cloud-snow": "\u{ea73}",
|
"cloud-snow": "\u{ea73}",
|
||||||
"cloud-star": "\u{f85b}",
|
"cloud-star": "\u{f85b}",
|
||||||
"cloud-storm": "\u{ea74}",
|
"cloud-storm": "\u{ea74}",
|
||||||
"cloud-sun": "\u{ea7a}",
|
"cloud-sun": "\u{ec6d}",
|
||||||
"cloud-up": "\u{f85c}",
|
"cloud-up": "\u{f85c}",
|
||||||
"cloud-upload": "\u{ea75}",
|
"cloud-upload": "\u{ea75}",
|
||||||
"cloud-x": "\u{f85d}",
|
"cloud-x": "\u{f85d}",
|
||||||
@@ -3128,8 +3157,8 @@ Singleton {
|
|||||||
"friends": "\u{eab0}",
|
"friends": "\u{eab0}",
|
||||||
"friends-off": "\u{f136}",
|
"friends-off": "\u{f136}",
|
||||||
"frustum": "\u{fa9f}",
|
"frustum": "\u{fa9f}",
|
||||||
"frustum-plus"//"frustum-off": "\u{fa9d}",
|
//"frustum-off": "\u{fa9d}",
|
||||||
: "\u{fa9e}",
|
"frustum-plus": "\u{fa9e}",
|
||||||
"function": "\u{f225}",
|
"function": "\u{f225}",
|
||||||
"function-filled": "\u{fc2b}",
|
"function-filled": "\u{fc2b}",
|
||||||
"function-off": "\u{f3f0}",
|
"function-off": "\u{f3f0}",
|
||||||
@@ -3388,13 +3417,13 @@ Singleton {
|
|||||||
"hexagon-letter-x": "\u{f479}",
|
"hexagon-letter-x": "\u{f479}",
|
||||||
"hexagon-letter-x-filled": "\u{fe30}",
|
"hexagon-letter-x-filled": "\u{fe30}",
|
||||||
"hexagon-letter-y": "\u{f47a}",
|
"hexagon-letter-y": "\u{f47a}",
|
||||||
"hexagon-letter-z"//"hexagon-letter-y-filled": "\u{fe2f}",
|
//"hexagon-letter-y-filled": "\u{fe2f}",
|
||||||
: "\u{f47b}",
|
"hexagon-letter-z": "\u{f47b}",
|
||||||
"hexagon-minus"//"hexagon-letter-z-filled": "\u{fe2e}",
|
//"hexagon-letter-z-filled": "\u{fe2e}",
|
||||||
: "\u{fc8f}",
|
"hexagon-minus": "\u{fc8f}",
|
||||||
"hexagon-minus-2": "\u{fc8e}",
|
"hexagon-minus-2": "\u{fc8e}",
|
||||||
"hexagon-number-0"//"hexagon-minus-filled": "\u{fe2d}",
|
//"hexagon-minus-filled": "\u{fe2d}",
|
||||||
: "\u{f459}",
|
"hexagon-number-0": "\u{f459}",
|
||||||
"hexagon-number-0-filled": "\u{f74c}",
|
"hexagon-number-0-filled": "\u{f74c}",
|
||||||
"hexagon-number-1": "\u{f45a}",
|
"hexagon-number-1": "\u{f45a}",
|
||||||
"hexagon-number-1-filled": "\u{f74d}",
|
"hexagon-number-1-filled": "\u{f74d}",
|
||||||
@@ -3417,8 +3446,8 @@ Singleton {
|
|||||||
"hexagon-off": "\u{ee9c}",
|
"hexagon-off": "\u{ee9c}",
|
||||||
"hexagon-plus": "\u{fc45}",
|
"hexagon-plus": "\u{fc45}",
|
||||||
"hexagon-plus-2": "\u{fc90}",
|
"hexagon-plus-2": "\u{fc90}",
|
||||||
"hexagonal-prism"//"hexagon-plus-filled": "\u{fe2c}",
|
//"hexagon-plus-filled": "\u{fe2c}",
|
||||||
: "\u{faa5}",
|
"hexagonal-prism": "\u{faa5}",
|
||||||
"hexagonal-prism-off": "\u{faa3}",
|
"hexagonal-prism-off": "\u{faa3}",
|
||||||
"hexagonal-prism-plus": "\u{faa4}",
|
"hexagonal-prism-plus": "\u{faa4}",
|
||||||
"hexagonal-pyramid": "\u{faa8}",
|
"hexagonal-pyramid": "\u{faa8}",
|
||||||
@@ -3448,8 +3477,8 @@ Singleton {
|
|||||||
"home-eco": "\u{f351}",
|
"home-eco": "\u{f351}",
|
||||||
"home-edit": "\u{f352}",
|
"home-edit": "\u{f352}",
|
||||||
"home-exclamation": "\u{f33c}",
|
"home-exclamation": "\u{f33c}",
|
||||||
"home-hand"//"home-filled": "\u{fe2b}",
|
//"home-filled": "\u{fe2b}",
|
||||||
: "\u{f504}",
|
"home-hand": "\u{f504}",
|
||||||
"home-heart": "\u{f353}",
|
"home-heart": "\u{f353}",
|
||||||
"home-infinity": "\u{f505}",
|
"home-infinity": "\u{f505}",
|
||||||
"home-link": "\u{f354}",
|
"home-link": "\u{f354}",
|
||||||
@@ -3567,8 +3596,8 @@ Singleton {
|
|||||||
"ironing-2-filled": "\u{1006e}",
|
"ironing-2-filled": "\u{1006e}",
|
||||||
"ironing-3": "\u{f2f6}",
|
"ironing-3": "\u{f2f6}",
|
||||||
"ironing-3-filled": "\u{1006d}",
|
"ironing-3-filled": "\u{1006d}",
|
||||||
"ironing-off"//"ironing-filled": "\u{fe2a}",
|
//"ironing-filled": "\u{fe2a}",
|
||||||
: "\u{f2f7}",
|
"ironing-off": "\u{f2f7}",
|
||||||
"ironing-steam": "\u{f2f9}",
|
"ironing-steam": "\u{f2f9}",
|
||||||
"ironing-steam-filled": "\u{1006c}",
|
"ironing-steam-filled": "\u{1006c}",
|
||||||
"ironing-steam-off": "\u{f2f8}",
|
"ironing-steam-off": "\u{f2f8}",
|
||||||
@@ -3578,8 +3607,8 @@ Singleton {
|
|||||||
"italic": "\u{eb93}",
|
"italic": "\u{eb93}",
|
||||||
"jacket": "\u{f661}",
|
"jacket": "\u{f661}",
|
||||||
"jetpack": "\u{f581}",
|
"jetpack": "\u{f581}",
|
||||||
"jewish-star"//"jetpack-filled": "\u{fe29}",
|
//"jetpack-filled": "\u{fe29}",
|
||||||
: "\u{f3ff}",
|
"jewish-star": "\u{f3ff}",
|
||||||
"jewish-star-filled": "\u{f67e}",
|
"jewish-star-filled": "\u{f67e}",
|
||||||
"join-bevel": "\u{ff4c}",
|
"join-bevel": "\u{ff4c}",
|
||||||
"join-round": "\u{ff4b}",
|
"join-round": "\u{ff4b}",
|
||||||
@@ -3593,8 +3622,8 @@ Singleton {
|
|||||||
"kering": "\u{efb8}",
|
"kering": "\u{efb8}",
|
||||||
"kerning": "\u{efb8}",
|
"kerning": "\u{efb8}",
|
||||||
"key": "\u{eac7}",
|
"key": "\u{eac7}",
|
||||||
"key-off"//"key-filled": "\u{fe28}",
|
//"key-filled": "\u{fe28}",
|
||||||
: "\u{f14b}",
|
"key-off": "\u{f14b}",
|
||||||
"keyboard": "\u{ebd6}",
|
"keyboard": "\u{ebd6}",
|
||||||
"keyboard-filled": "\u{100a2}",
|
"keyboard-filled": "\u{100a2}",
|
||||||
"keyboard-hide": "\u{ec7e}",
|
"keyboard-hide": "\u{ec7e}",
|
||||||
@@ -3650,20 +3679,20 @@ Singleton {
|
|||||||
"layers-union": "\u{eacb}",
|
"layers-union": "\u{eacb}",
|
||||||
"layout": "\u{eadb}",
|
"layout": "\u{eadb}",
|
||||||
"layout-2": "\u{eacc}",
|
"layout-2": "\u{eacc}",
|
||||||
"layout-align-left"//"layout-2-filled": "\u{fe27}",
|
//"layout-2-filled": "\u{fe27}",
|
||||||
//"layout-align-bottom": "\u{eacd}",
|
//"layout-align-bottom": "\u{eacd}",
|
||||||
//"layout-align-bottom-filled": "\u{fe26}",
|
//"layout-align-bottom-filled": "\u{fe26}",
|
||||||
//"layout-align-center": "\u{eace}",
|
//"layout-align-center": "\u{eace}",
|
||||||
//"layout-align-center-filled": "\u{fe25}",
|
//"layout-align-center-filled": "\u{fe25}",
|
||||||
: "\u{eacf}",
|
"layout-align-left": "\u{eacf}",
|
||||||
"layout-align-middle"// "layout-align-left-filled": "\u{fe24}",
|
//"layout-align-left-filled": "\u{fe24}",
|
||||||
: "\u{ead0}",
|
"layout-align-middle": "\u{ead0}",
|
||||||
"layout-align-right"//"layout-align-middle-filled": "\u{fe23}",
|
//"layout-align-middle-filled": "\u{fe23}",
|
||||||
: "\u{ead1}",
|
"layout-align-right": "\u{ead1}",
|
||||||
"layout-align-top"//"layout-align-right-filled": "\u{fe22}",
|
//"layout-align-right-filled": "\u{fe22}",
|
||||||
: "\u{ead2}",
|
"layout-align-top": "\u{ead2}",
|
||||||
"layout-board"//"layout-align-top-filled": "\u{fe21}",
|
//"layout-align-top-filled": "\u{fe21}",
|
||||||
: "\u{ef95}",
|
"layout-board": "\u{ef95}",
|
||||||
"layout-board-filled": "\u{10182}",
|
"layout-board-filled": "\u{10182}",
|
||||||
"layout-board-split": "\u{ef94}",
|
"layout-board-split": "\u{ef94}",
|
||||||
"layout-board-split-filled": "\u{10183}",
|
"layout-board-split-filled": "\u{10183}",
|
||||||
@@ -3675,8 +3704,8 @@ Singleton {
|
|||||||
"layout-bottombar-filled": "\u{fc37}",
|
"layout-bottombar-filled": "\u{fc37}",
|
||||||
"layout-bottombar-inactive": "\u{fd45}",
|
"layout-bottombar-inactive": "\u{fd45}",
|
||||||
"layout-cards": "\u{ec13}",
|
"layout-cards": "\u{ec13}",
|
||||||
"layout-collage"// "layout-cards-filled": "\u{fe20}",
|
//"layout-cards-filled": "\u{fe20}",
|
||||||
: "\u{f389}",
|
"layout-collage": "\u{f389}",
|
||||||
"layout-columns": "\u{ead4}",
|
"layout-columns": "\u{ead4}",
|
||||||
"layout-dashboard": "\u{f02c}",
|
"layout-dashboard": "\u{f02c}",
|
||||||
"layout-dashboard-filled": "\u{fe1f}",
|
"layout-dashboard-filled": "\u{fe1f}",
|
||||||
@@ -4157,14 +4186,14 @@ Singleton {
|
|||||||
"microphone": "\u{eaf0}",
|
"microphone": "\u{eaf0}",
|
||||||
"microphone-2": "\u{ef2c}",
|
"microphone-2": "\u{ef2c}",
|
||||||
"microphone-2-off": "\u{f40d}",
|
"microphone-2-off": "\u{f40d}",
|
||||||
"microphone-off"//"microphone-filled": "\u{fe0f}",
|
//"microphone-filled": "\u{fe0f}",
|
||||||
: "\u{ed16}",
|
"microphone-off": "\u{ed16}",
|
||||||
"microscope": "\u{ef64}",
|
"microscope": "\u{ef64}",
|
||||||
"microscope-filled": "\u{10166}",
|
"microscope-filled": "\u{10166}",
|
||||||
"microscope-off": "\u{f40e}",
|
"microscope-off": "\u{f40e}",
|
||||||
"microwave": "\u{f248}",
|
"microwave": "\u{f248}",
|
||||||
"microwave-off"//"microwave-filled": "\u{fe0e}",
|
//"microwave-filled": "\u{fe0e}",
|
||||||
: "\u{f264}",
|
"microwave-off": "\u{f264}",
|
||||||
"military-award": "\u{f079}",
|
"military-award": "\u{f079}",
|
||||||
"military-rank": "\u{efcf}",
|
"military-rank": "\u{efcf}",
|
||||||
"military-rank-filled": "\u{ff5e}",
|
"military-rank-filled": "\u{ff5e}",
|
||||||
@@ -4398,7 +4427,7 @@ Singleton {
|
|||||||
"number-4-small": "\u{fcf9}",
|
"number-4-small": "\u{fcf9}",
|
||||||
"number-40-small": "\u{fffa}",
|
"number-40-small": "\u{fffa}",
|
||||||
"number-41-small": "\u{fff9}",
|
"number-41-small": "\u{fff9}",
|
||||||
"number-5"//"number-42-small": "\u{fff8}",
|
//"number-42-small": "\u{fff8}",
|
||||||
//"number-43-small": "\u{fff7}",
|
//"number-43-small": "\u{fff7}",
|
||||||
//"number-44-small": "\u{fff6}",
|
//"number-44-small": "\u{fff6}",
|
||||||
//"number-45-small": "\u{fff5}",
|
//"number-45-small": "\u{fff5}",
|
||||||
@@ -4406,10 +4435,10 @@ Singleton {
|
|||||||
//"number-47-small": "\u{fff3}",
|
//"number-47-small": "\u{fff3}",
|
||||||
//"number-48-small": "\u{fff2}",
|
//"number-48-small": "\u{fff2}",
|
||||||
//"number-49-small": "\u{fff1}",
|
//"number-49-small": "\u{fff1}",
|
||||||
: "\u{edf5}",
|
"number-5": "\u{edf5}",
|
||||||
"number-5-small": "\u{fcfa}",
|
"number-5-small": "\u{fcfa}",
|
||||||
"number-51-small"// "number-50-small": "\u{fff0}",
|
//"number-50-small": "\u{fff0}",
|
||||||
: "\u{ffef}",
|
"number-51-small": "\u{ffef}",
|
||||||
"number-52-small": "\u{ffee}",
|
"number-52-small": "\u{ffee}",
|
||||||
"number-53-small": "\u{ffed}",
|
"number-53-small": "\u{ffed}",
|
||||||
"number-54-small": "\u{ffec}",
|
"number-54-small": "\u{ffec}",
|
||||||
@@ -4761,6 +4790,7 @@ Singleton {
|
|||||||
"playstation-triangle": "\u{f2af}",
|
"playstation-triangle": "\u{f2af}",
|
||||||
"playstation-x": "\u{f2b0}",
|
"playstation-x": "\u{f2b0}",
|
||||||
"plug": "\u{ebd9}",
|
"plug": "\u{ebd9}",
|
||||||
|
"plug-filled": "\u{f6b3}",
|
||||||
"plug-connected": "\u{f00a}",
|
"plug-connected": "\u{f00a}",
|
||||||
"plug-connected-x": "\u{f0a0}",
|
"plug-connected-x": "\u{f0a0}",
|
||||||
"plug-off": "\u{f180}",
|
"plug-off": "\u{f180}",
|
||||||
@@ -4849,11 +4879,11 @@ Singleton {
|
|||||||
"quote": "\u{efbe}",
|
"quote": "\u{efbe}",
|
||||||
"quote-filled": "\u{1009c}",
|
"quote-filled": "\u{1009c}",
|
||||||
"quote-off": "\u{f188}",
|
"quote-off": "\u{f188}",
|
||||||
"radar"//"quotes": "\u{fb1e}",
|
//"quotes": "\u{fb1e}",
|
||||||
: "\u{f017}",
|
"radar": "\u{f017}",
|
||||||
"radar-2": "\u{f016}",
|
"radar-2": "\u{f016}",
|
||||||
"radar-off"//"radar-filled": "\u{fe0d}",
|
//"radar-filled": "\u{fe0d}",
|
||||||
: "\u{f41f}",
|
"radar-off": "\u{f41f}",
|
||||||
"radio": "\u{ef2d}",
|
"radio": "\u{ef2d}",
|
||||||
"radio-off": "\u{f420}",
|
"radio-off": "\u{f420}",
|
||||||
"radioactive": "\u{ecc0}",
|
"radioactive": "\u{ecc0}",
|
||||||
@@ -4913,12 +4943,12 @@ Singleton {
|
|||||||
"regex-off": "\u{f421}",
|
"regex-off": "\u{f421}",
|
||||||
"registered": "\u{eb14}",
|
"registered": "\u{eb14}",
|
||||||
"relation-many-to-many": "\u{ed7f}",
|
"relation-many-to-many": "\u{ed7f}",
|
||||||
"relation-one-to-many"//"relation-many-to-many-filled": "\u{fe0c}",
|
//"relation-many-to-many-filled": "\u{fe0c}",
|
||||||
: "\u{ed80}",
|
"relation-one-to-many": "\u{ed80}",
|
||||||
"relation-one-to-one"//"relation-one-to-many-filled": "\u{fe0b}",
|
//"relation-one-to-many-filled": "\u{fe0b}",
|
||||||
: "\u{ed81}",
|
"relation-one-to-one": "\u{ed81}",
|
||||||
"reload"//"relation-one-to-one-filled": "\u{fe0a}",
|
//"relation-one-to-one-filled": "\u{fe0a}",
|
||||||
: "\u{f3ae}",
|
"reload": "\u{f3ae}",
|
||||||
"reorder": "\u{fc15}",
|
"reorder": "\u{fc15}",
|
||||||
"repeat": "\u{eb72}",
|
"repeat": "\u{eb72}",
|
||||||
"repeat-off": "\u{f18e}",
|
"repeat-off": "\u{f18e}",
|
||||||
@@ -5070,8 +5100,8 @@ Singleton {
|
|||||||
"search": "\u{eb1c}",
|
"search": "\u{eb1c}",
|
||||||
"search-off": "\u{f19c}",
|
"search-off": "\u{f19c}",
|
||||||
"section": "\u{eed5}",
|
"section": "\u{eed5}",
|
||||||
"section-sign"//"section-filled": "\u{fe09}",
|
//"section-filled": "\u{fe09}",
|
||||||
: "\u{f019}",
|
"section-sign": "\u{f019}",
|
||||||
"seeding": "\u{ed51}",
|
"seeding": "\u{ed51}",
|
||||||
"seeding-filled": "\u{10006}",
|
"seeding-filled": "\u{10006}",
|
||||||
"seeding-off": "\u{f19d}",
|
"seeding-off": "\u{f19d}",
|
||||||
@@ -5279,8 +5309,8 @@ Singleton {
|
|||||||
"sort-z-a": "\u{f550}",
|
"sort-z-a": "\u{f550}",
|
||||||
"sos": "\u{f24a}",
|
"sos": "\u{f24a}",
|
||||||
"soup": "\u{ef2e}",
|
"soup": "\u{ef2e}",
|
||||||
"soup-off"//"soup-filled": "\u{fe08}",
|
//"soup-filled": "\u{fe08}",
|
||||||
: "\u{f42d}",
|
"soup-off": "\u{f42d}",
|
||||||
"source-code": "\u{f4a2}",
|
"source-code": "\u{f4a2}",
|
||||||
"space": "\u{ec0c}",
|
"space": "\u{ec0c}",
|
||||||
"space-off": "\u{f1aa}",
|
"space-off": "\u{f1aa}",
|
||||||
@@ -5373,22 +5403,22 @@ Singleton {
|
|||||||
"square-half": "\u{effb}",
|
"square-half": "\u{effb}",
|
||||||
"square-key": "\u{f638}",
|
"square-key": "\u{f638}",
|
||||||
"square-letter-a": "\u{f47c}",
|
"square-letter-a": "\u{f47c}",
|
||||||
"square-letter-b"//"square-letter-a-filled": "\u{fe07}",
|
//"square-letter-a-filled": "\u{fe07}",
|
||||||
: "\u{f47d}",
|
"square-letter-b": "\u{f47d}",
|
||||||
"square-letter-c"//"square-letter-b-filled": "\u{fe06}",
|
//"square-letter-b-filled": "\u{fe06}",
|
||||||
: "\u{f47e}",
|
"square-letter-c": "\u{f47e}",
|
||||||
"square-letter-d"//"square-letter-c-filled": "\u{fe05}",
|
//"square-letter-c-filled": "\u{fe05}",
|
||||||
: "\u{f47f}",
|
"square-letter-d": "\u{f47f}",
|
||||||
"square-letter-e"//"square-letter-d-filled": "\u{fe04}",
|
//"square-letter-d-filled": "\u{fe04}",
|
||||||
: "\u{f480}",
|
"square-letter-e": "\u{f480}",
|
||||||
"square-letter-f"//"square-letter-e-filled": "\u{fe03}",
|
//"square-letter-e-filled": "\u{fe03}",
|
||||||
: "\u{f481}",
|
"square-letter-f": "\u{f481}",
|
||||||
"square-letter-g"//"square-letter-f-filled": "\u{fe02}",
|
//"square-letter-f-filled": "\u{fe02}",
|
||||||
: "\u{f482}",
|
"square-letter-g": "\u{f482}",
|
||||||
"square-letter-h"//"square-letter-g-filled": "\u{fe01}",
|
//"square-letter-g-filled": "\u{fe01}",
|
||||||
: "\u{f483}",
|
"square-letter-h": "\u{f483}",
|
||||||
"square-letter-i"//"square-letter-h-filled": "\u{fe00}",
|
//"square-letter-h-filled": "\u{fe00}",
|
||||||
: "\u{f484}",
|
"square-letter-i": "\u{f484}",
|
||||||
"square-letter-i-filled": "\u{fdff}",
|
"square-letter-i-filled": "\u{fdff}",
|
||||||
"square-letter-j": "\u{f485}",
|
"square-letter-j": "\u{f485}",
|
||||||
"square-letter-j-filled": "\u{fdfe}",
|
"square-letter-j-filled": "\u{fdfe}",
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property string cacheDir: Quickshell.shellDir + "/Assets/Cache/"
|
||||||
|
readonly property string configDir: Quickshell.shellDir + "/Assets/Config/"
|
||||||
|
readonly property string recordingDir: Quickshell.env("HOME") + "/Videos/recordings/"
|
||||||
|
}
|
||||||
@@ -4,10 +4,6 @@ import Quickshell.Io
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
/*
|
|
||||||
Preset sizes for font, radii, ?
|
|
||||||
*/
|
|
||||||
|
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Font size
|
// Font size
|
||||||
@@ -19,6 +15,7 @@ Singleton {
|
|||||||
readonly property real fontSizeXL: 16
|
readonly property real fontSizeXL: 16
|
||||||
readonly property real fontSizeXXL: 18
|
readonly property real fontSizeXXL: 18
|
||||||
readonly property real fontSizeXXXL: 24
|
readonly property real fontSizeXXXL: 24
|
||||||
|
readonly property real fontNerd: 16
|
||||||
// Font weight
|
// Font weight
|
||||||
readonly property int fontWeightRegular: 400
|
readonly property int fontWeightRegular: 400
|
||||||
readonly property int fontWeightMedium: 500
|
readonly property int fontWeightMedium: 500
|
||||||
@@ -50,19 +47,22 @@ Singleton {
|
|||||||
readonly property real opacityHeavy: 0.75
|
readonly property real opacityHeavy: 0.75
|
||||||
readonly property real opacityAlmost: 0.95
|
readonly property real opacityAlmost: 0.95
|
||||||
readonly property real opacityFull: 1
|
readonly property real opacityFull: 1
|
||||||
|
// Shadows
|
||||||
|
readonly property real shadowOpacity: 0.85
|
||||||
|
readonly property real shadowBlur: 1
|
||||||
|
readonly property int shadowBlurMax: 22
|
||||||
|
readonly property real shadowHorizontalOffset: 2
|
||||||
|
readonly property real shadowVerticalOffset: 3
|
||||||
// Animation duration (ms)
|
// Animation duration (ms)
|
||||||
readonly property int animationFast: 150
|
readonly property int animationFast: 150
|
||||||
readonly property int animationNormal: 300
|
readonly property int animationNormal: 300
|
||||||
readonly property int animationSlow: 450
|
readonly property int animationSlow: 450
|
||||||
readonly property int animationSlowest: 1000
|
readonly property int animationSlowest: 1000
|
||||||
// Delays
|
|
||||||
readonly property int tooltipDelay: 300
|
|
||||||
readonly property int tooltipDelayLong: 1200
|
|
||||||
readonly property int pillDelay: 500
|
|
||||||
// Settings widgets base size
|
// Settings widgets base size
|
||||||
readonly property real baseWidgetSize: 33
|
readonly property int baseWidgetSize: 33
|
||||||
readonly property real sliderWidth: 200
|
readonly property int sliderWidth: 200
|
||||||
// Bar Dimensions
|
// Bar Dimensions
|
||||||
readonly property real barHeight: 45
|
readonly property int barHeight: 45
|
||||||
readonly property real capsuleHeight: 35
|
readonly property int sidebarWidth: 360
|
||||||
|
readonly property int capsuleHeight: 35
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property var modelData
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
screen: modelData
|
||||||
|
WlrLayershell.namespace: "quickshell-background"
|
||||||
|
WlrLayershell.layer: WlrLayer.Background
|
||||||
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
bottom: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Colors.mSurface
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: bgManager
|
||||||
|
|
||||||
|
property string activeSource: BackgroundService.previewPath || (BarService.focusMode ? BackgroundService.cachedBlurredPath : BackgroundService.cachedPath)
|
||||||
|
property bool showFirst: true
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
onActiveSourceChanged: {
|
||||||
|
showFirst = !showFirst;
|
||||||
|
if (showFirst)
|
||||||
|
img1.source = activeSource;
|
||||||
|
else
|
||||||
|
img2.source = activeSource;
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (showFirst)
|
||||||
|
img1.source = activeSource;
|
||||||
|
else
|
||||||
|
img2.source = activeSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: img1
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
opacity: (bgManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationSlow
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: img2
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
opacity: (!bgManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationSlow
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
screen: modelData
|
||||||
|
WlrLayershell.namespace: "quickshell-backdrop"
|
||||||
|
WlrLayershell.layer: WlrLayer.Background
|
||||||
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
bottom: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Colors.mSurface
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: backdropManager
|
||||||
|
|
||||||
|
property string activeSource: BackgroundService.cachedBlurredPath
|
||||||
|
property bool showFirst: true
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
onActiveSourceChanged: {
|
||||||
|
showFirst = !showFirst;
|
||||||
|
if (showFirst)
|
||||||
|
backImg1.source = activeSource;
|
||||||
|
else
|
||||||
|
backImg2.source = activeSource;
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (showFirst)
|
||||||
|
backImg1.source = activeSource;
|
||||||
|
else
|
||||||
|
backImg2.source = activeSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: backImg1
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
opacity: (backdropManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationSlow
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: backImg2
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
opacity: (!backdropManager.showFirst && status === Image.Ready) ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationSlow
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,12 +3,11 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.UPower
|
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Modules.Bar.Components
|
import qs.Modules.Bar.Components
|
||||||
import qs.Modules.Bar.Misc
|
import qs.Modules.Bar.Modules
|
||||||
import qs.Modules.Misc
|
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
@@ -22,6 +21,7 @@ Variants {
|
|||||||
|
|
||||||
screen: modelData
|
screen: modelData
|
||||||
WlrLayershell.namespace: "quickshell-bar"
|
WlrLayershell.namespace: "quickshell-bar"
|
||||||
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
color: Colors.transparent
|
color: Colors.transparent
|
||||||
implicitHeight: Style.barHeight
|
implicitHeight: Style.barHeight
|
||||||
|
|
||||||
@@ -35,12 +35,11 @@ Variants {
|
|||||||
id: barBackground
|
id: barBackground
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Niri.noFocus ? null : Colors.base
|
|
||||||
|
|
||||||
gradient: Gradient {
|
gradient: Gradient {
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 0
|
position: 0
|
||||||
color: Qt.rgba(Colors.base.r, Colors.base.g, Colors.base.b, Niri.noFocus ? 0.8 : 1)
|
color: Qt.rgba(Colors.mSurface.r, Colors.mSurface.g, Colors.mSurface.b, BarService.focusMode ? 1 : 0.8)
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
@@ -54,7 +53,7 @@ Variants {
|
|||||||
|
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 1
|
position: 1
|
||||||
color: Qt.rgba(Colors.base.r, Colors.base.g, Colors.base.b, Niri.noFocus ? 0 : 1)
|
color: Qt.rgba(Colors.mSurface.r, Colors.mSurface.g, Colors.mSurface.b, BarService.focusMode ? 1 : 0)
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
@@ -81,42 +80,22 @@ Variants {
|
|||||||
leftMargin: 5
|
leftMargin: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
SymbolButton {
|
UIconButton {
|
||||||
symbol: Icons.distro
|
textOverride: ""
|
||||||
buttonColor: Colors.distroColor
|
fontFamily: Fonts.nerd
|
||||||
onClicked: {
|
baseSize: parent.height - Style.marginXXS * 2
|
||||||
PanelService.getPanel("controlCenterPanel")?.toggle(this)
|
iconSize: Style.fontNerd
|
||||||
|
colorFg: Colors.distro
|
||||||
|
onClicked: () => {
|
||||||
|
BarService.toggleLeft();
|
||||||
}
|
}
|
||||||
onRightClicked: {
|
onRightClicked: () => {
|
||||||
Quickshell.execDetached(["rofi", "-show", "drun"]);
|
BarService.toggleRight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SymbolButton {
|
|
||||||
symbol: SettingsService.wifiEnabled ? Icons.wifiOn : Icons.wifiOff
|
|
||||||
buttonColor: Colors.rosewater
|
|
||||||
onClicked: {
|
|
||||||
PanelService.getPanel("wifiPanel")?.toggle(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SymbolButton {
|
|
||||||
symbol: BluetoothService.enabled ? Icons.bluetoothOn : Icons.bluetoothOff
|
|
||||||
buttonColor: Colors.blue
|
|
||||||
onClicked: {
|
|
||||||
PanelService.getPanel("bluetoothPanel")?.toggle(this)
|
|
||||||
}
|
|
||||||
onRightClicked: {
|
|
||||||
Quickshell.execDetached(["blueman-manager"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 5
|
|
||||||
}
|
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
|
implicitWidth: Style.marginXL
|
||||||
}
|
}
|
||||||
|
|
||||||
Workspace {
|
Workspace {
|
||||||
@@ -124,28 +103,17 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
}
|
implicitWidth: Style.marginXL
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CavaBar {
|
CavaBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
}
|
implicitWidth: Style.marginXL
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusedWindow {
|
FocusedWindow {
|
||||||
maxWidth: 400
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -176,21 +144,27 @@ Variants {
|
|||||||
rightMargin: 5
|
rightMargin: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
sourceComponent: LyricsService.showLyricsBar ? lyricsComponent : monitorsComponent
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: monitorsComponent
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: monitorsLayout
|
id: monitorsLayout
|
||||||
visible: !SettingsService.showLyricsBar
|
|
||||||
|
|
||||||
height: parent.height
|
height: rightLayout.height
|
||||||
|
spacing: Style.marginM
|
||||||
|
Component.onCompleted: {
|
||||||
|
SystemStatService.registerComponent("BarMonitors");
|
||||||
|
}
|
||||||
|
|
||||||
NetworkSpeed {
|
NetworkSpeed {
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
RecordIndicator {
|
RecordIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,43 +189,49 @@ Variants {
|
|||||||
|
|
||||||
Volume {
|
Volume {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: lyricsComponent
|
||||||
|
|
||||||
LyricsBar {
|
LyricsBar {
|
||||||
id: lyricsBar
|
|
||||||
visible: SettingsService.showLyricsBar
|
|
||||||
width: 600
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
}
|
||||||
width: 5
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
RowLayout {
|
||||||
width: 5
|
height: rightLayout.height
|
||||||
}
|
spacing: Style.marginS
|
||||||
|
|
||||||
TrayExpander {
|
TrayExpander {
|
||||||
screen: modelData
|
screen: modelData
|
||||||
|
baseSize: rightLayout.height - Style.marginXXS * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
SymbolButton {
|
UIconButton {
|
||||||
symbol: Caffeine.isInhibited ? Icons.idleInhibitorActivated : Icons.idleInhibitorDeactivated
|
iconName: Caffeine.isInhibited ? "mug-off" : "mug"
|
||||||
buttonColor: Caffeine.isInhibited ? Colors.peach : Colors.yellow
|
colorFg: Caffeine.isInhibited ? Colors.mOrange : Colors.mYellow
|
||||||
onClicked: {
|
baseSize: rightLayout.height - Style.marginXXS * 2
|
||||||
|
alwaysHover: Caffeine.isInhibited
|
||||||
|
onClicked: () => {
|
||||||
Caffeine.manualToggle();
|
Caffeine.manualToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SymbolButton {
|
UIconButton {
|
||||||
symbol: Icons.powerMenu
|
iconName: "power"
|
||||||
buttonColor: Colors.red
|
colorFg: Colors.mRed
|
||||||
onClicked: {
|
baseSize: rightLayout.height - Style.marginXXS * 2
|
||||||
Quickshell.execDetached(["wlogout"]);
|
onClicked: () => {
|
||||||
|
BarService.toggleRight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,3 +242,5 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import Quickshell.Io
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Modules.Bar.Misc
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
MonitorItem {
|
|
||||||
symbol: SystemStatService.cpuTemp > 80 ? Icons.tempHigh : SystemStatService.cpuTemp > 50 ? Icons.tempMedium : Icons.tempLow
|
|
||||||
fillColor: Colors.yellow
|
|
||||||
critical: SystemStatService.cpuTemp > 80
|
|
||||||
value: Math.round(SystemStatService.cpuTemp)
|
|
||||||
maxValue: 100
|
|
||||||
textSuffix: "°C"
|
|
||||||
onClicked: {
|
|
||||||
if (action.running) {
|
|
||||||
action.signal(15);
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
action.exec(["wezterm", "start", "--", "btop"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: action
|
|
||||||
|
|
||||||
running: false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import Quickshell.Io
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Modules.Bar.Misc
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
MonitorItem {
|
|
||||||
symbol: Icons.cpu
|
|
||||||
fillColor: Colors.teal
|
|
||||||
critical: SystemStatService.cpuUsage > 90
|
|
||||||
value: Math.round(SystemStatService.cpuUsage)
|
|
||||||
maxValue: 100
|
|
||||||
textSuffix: "%"
|
|
||||||
onClicked: {
|
|
||||||
if (action.running) {
|
|
||||||
action.signal(15);
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
action.exec(["wezterm", "start", "--", "btop"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: action
|
|
||||||
|
|
||||||
running: false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -6,13 +6,14 @@ import qs.Constants
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
implicitHeight: parent.height
|
implicitHeight: Style.barHeight - Style.marginL * 2
|
||||||
|
implicitWidth: Style.marginM
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 1.5
|
width: 1.5
|
||||||
height: parent.height * 0.32
|
height: parent.height
|
||||||
color: Colors.text
|
color: Colors.mOnSurface
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -5,7 +5,7 @@ import QtQuick.Controls
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.SystemTray
|
import Quickshell.Services.SystemTray
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Modules.Bar.Misc
|
import qs.Modules.Bar.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Utils
|
import qs.Utils
|
||||||
@@ -107,7 +107,7 @@ Rectangle {
|
|||||||
trayMenu.item.menu = modelData.menu
|
trayMenu.item.menu = modelData.menu
|
||||||
trayMenu.item.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.d("Tray", "No menu available for", modelData.id, "or trayMenu not set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ Rectangle {
|
|||||||
Loader {
|
Loader {
|
||||||
id: trayMenu
|
id: trayMenu
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
setSource("../Misc/TrayMenu.qml", {
|
setSource("./TrayMenu.qml", {
|
||||||
"screen": root.screen
|
"screen": root.screen
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: TimeService.time + " | " + TimeService.dateString
|
|
||||||
font.pointSize: Fonts.medium
|
|
||||||
font.family: Fonts.primary
|
|
||||||
color: Colors.primary
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (mouse.button === Qt.LeftButton)
|
|
||||||
PanelService.getPanel("calendarPanel")?.toggle(this)
|
|
||||||
else if (mouse.button === Qt.RightButton)
|
|
||||||
PanelService.getPanel("notificationHistoryPanel")?.toggle(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-11
@@ -5,7 +5,7 @@ import Quickshell
|
|||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Utils
|
import qs.Utils
|
||||||
import qs.Noctalia
|
import qs.Components
|
||||||
|
|
||||||
PopupWindow {
|
PopupWindow {
|
||||||
id: root
|
id: root
|
||||||
@@ -86,8 +86,8 @@ PopupWindow {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Colors.base
|
color: Colors.mSurface
|
||||||
border.color: Colors.primary
|
border.color: Colors.mPrimary
|
||||||
border.width: 2
|
border.width: 2
|
||||||
radius: Style.radiusM
|
radius: Style.radiusM
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ PopupWindow {
|
|||||||
color: Colors.transparent
|
color: Colors.transparent
|
||||||
property var subMenu: null
|
property var subMenu: null
|
||||||
|
|
||||||
NDivider {
|
UDivider {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - (Style.marginM * 2)
|
width: parent.width - (Style.marginM * 2)
|
||||||
visible: modelData?.isSeparator ?? false
|
visible: modelData?.isSeparator ?? false
|
||||||
@@ -134,7 +134,7 @@ PopupWindow {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: mouseArea.containsMouse ? Colors.primary : Colors.transparent
|
color: mouseArea.containsMouse ? Colors.mPrimary : Colors.transparent
|
||||||
radius: Style.radiusS
|
radius: Style.radiusS
|
||||||
visible: !(modelData?.isSeparator ?? false)
|
visible: !(modelData?.isSeparator ?? false)
|
||||||
|
|
||||||
@@ -144,10 +144,10 @@ PopupWindow {
|
|||||||
anchors.rightMargin: Style.marginM
|
anchors.rightMargin: Style.marginM
|
||||||
spacing: Style.marginS
|
spacing: Style.marginS
|
||||||
|
|
||||||
NText {
|
UText {
|
||||||
id: text
|
id: text
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
|
color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Colors.mOnPrimary : Colors.mOnSurface) : Colors.mOnSurfaceVariant
|
||||||
text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..."
|
text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..."
|
||||||
pointSize: Style.fontSizeS
|
pointSize: Style.fontSizeS
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
@@ -162,12 +162,12 @@ PopupWindow {
|
|||||||
visible: (modelData?.icon ?? "") !== ""
|
visible: (modelData?.icon ?? "") !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
NIcon {
|
UIcon {
|
||||||
icon: modelData?.hasChildren ? "menu" : ""
|
iconName: modelData?.hasChildren ? "menu" : ""
|
||||||
pointSize: Style.fontSizeS
|
iconSize: Style.fontSizeS
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
visible: modelData?.hasChildren ?? false
|
visible: modelData?.hasChildren ?? false
|
||||||
color: (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface)
|
color: (mouseArea.containsMouse ? Colors.mOnPrimary : Colors.mOnSurface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Constants
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property string symbol
|
|
||||||
property color buttonColor: Colors.distroColor
|
|
||||||
readonly property alias hovered: mouseArea.containsMouse
|
|
||||||
property real iconSize: Fonts.icon
|
|
||||||
property real radius: Style.radiusS
|
|
||||||
property bool disabledHover: false
|
|
||||||
|
|
||||||
signal clicked()
|
|
||||||
signal rightClicked()
|
|
||||||
|
|
||||||
implicitHeight: parent.height
|
|
||||||
implicitWidth: parent.height
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
hoverEnabled: !disabledHover
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (mouse.button === Qt.RightButton)
|
|
||||||
root.rightClicked();
|
|
||||||
else if (mouse.button === Qt.LeftButton)
|
|
||||||
root.clicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.fill: parent
|
|
||||||
text: symbol
|
|
||||||
font.family: Fonts.nerd
|
|
||||||
font.pointSize: iconSize
|
|
||||||
font.bold: false
|
|
||||||
color: buttonColor
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: parent.hovered ? buttonColor : Colors.transparent
|
|
||||||
opacity: 0.3
|
|
||||||
radius: root.radius
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationNormal
|
|
||||||
easing.type: Easing.InOutCubic
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+5
-6
@@ -1,20 +1,19 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Services.UPower
|
import Quickshell.Services.UPower
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Modules.Bar.Misc
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
MonitorItem {
|
UProgressExpand {
|
||||||
readonly property var battery: UPower.displayDevice
|
readonly property var battery: UPower.displayDevice
|
||||||
readonly property bool isReady: (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
|
readonly property bool isReady: (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
|
||||||
readonly property real percent: (isReady ? (battery.percentage * 100) : 0)
|
readonly property real percent: (isReady ? (battery.percentage * 100) : 0)
|
||||||
readonly property bool charging: (isReady ? battery.state === UPowerDeviceState.Charging : false)
|
readonly property bool charging: (isReady ? battery.state === UPowerDeviceState.Charging : false)
|
||||||
property int lowBatteryThreshold: 20
|
property int lowBatteryThreshold: 20
|
||||||
|
|
||||||
symbol: {
|
iconName: {
|
||||||
return charging ? Icons.charging : percent >= 80 ? Icons.battery100 : percent >= 60 ? Icons.battery75 : percent >= 40 ? Icons.battery50 : percent >= 20 ? Icons.battery25 : Icons.battery00;
|
return charging ? "battery-charging" : percent >= 80 ? "battery-4" : percent >= 60 ? "battery-3" : percent >= 40 ? "battery-2" : percent >= 20 ? "battery-1" : "battery-0";
|
||||||
}
|
}
|
||||||
fillColor: Colors.sapphire
|
fillColor: Colors.mSky
|
||||||
value: percent
|
value: percent
|
||||||
critical: isReady && !charging && percent <= lowBatteryThreshold
|
critical: isReady && !charging && percent <= lowBatteryThreshold
|
||||||
maxValue: 100
|
maxValue: 100
|
||||||
+4
-4
@@ -1,18 +1,18 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Modules.Bar.Misc
|
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
MonitorItem {
|
UProgressExpand {
|
||||||
property ShellScreen screen: null
|
property ShellScreen screen: null
|
||||||
|
|
||||||
function getMonitor() {
|
function getMonitor() {
|
||||||
return BrightnessService.getMonitorForScreen(screen) || null;
|
return BrightnessService.getMonitorForScreen(screen) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
symbol: Icons.brightness
|
iconName: "sun-filled"
|
||||||
fillColor: Colors.blue
|
fillColor: Colors.mBlue
|
||||||
value: {
|
value: {
|
||||||
const monitor = getMonitor();
|
const monitor = getMonitor();
|
||||||
return monitor ? Math.round(monitor.brightness * 100) : "N/A";
|
return monitor ? Math.round(monitor.brightness * 100) : "N/A";
|
||||||
+7
-7
@@ -2,7 +2,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Modules.Bar.Misc
|
import qs.Modules.Bar.Services
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Utils
|
import qs.Utils
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Item {
|
|||||||
property int mode: 0
|
property int mode: 0
|
||||||
|
|
||||||
implicitWidth: root.barWidth * CavaBarService.count + root.barSpacing * (CavaBarService.count - 1)
|
implicitWidth: root.barWidth * CavaBarService.count + root.barSpacing * (CavaBarService.count - 1)
|
||||||
implicitHeight: parent.height - 10
|
implicitHeight: Style.barHeight - Style.marginS * 2
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -53,9 +53,7 @@ Item {
|
|||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
if (mouse.button === Qt.LeftButton) {
|
if (mouse.button === Qt.LeftButton) {
|
||||||
MusicManager.playPause();
|
MediaService.playPause();
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
|
||||||
SettingsService.showLyricsBar = !SettingsService.showLyricsBar;
|
|
||||||
} else if (mouse.button === Qt.MiddleButton) {
|
} else if (mouse.button === Qt.MiddleButton) {
|
||||||
mode = (mode + 1) % 3;
|
mode = (mode + 1) % 3;
|
||||||
if (mode === 0) {
|
if (mode === 0) {
|
||||||
@@ -71,13 +69,15 @@ Item {
|
|||||||
CavaBarService.forceEnable = false;
|
CavaBarService.forceEnable = false;
|
||||||
CavaBarService.forceDisable = true;
|
CavaBarService.forceDisable = true;
|
||||||
}
|
}
|
||||||
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
|
LyricsService.toggleLyricsBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onWheel: function(wheel) {
|
onWheel: function(wheel) {
|
||||||
if (wheel.angleDelta.y > 0)
|
if (wheel.angleDelta.y > 0)
|
||||||
MusicManager.previous();
|
MediaService.previous();
|
||||||
else if (wheel.angleDelta.y < 0)
|
else if (wheel.angleDelta.y < 0)
|
||||||
MusicManager.next();
|
MediaService.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Modules.Bar.Services
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
UProgressExpand {
|
||||||
|
iconName: "temperature"
|
||||||
|
fillColor: Colors.mYellow
|
||||||
|
critical: SystemStatService.cpuTemp > 80
|
||||||
|
value: Math.round(SystemStatService.cpuTemp)
|
||||||
|
maxValue: 100
|
||||||
|
textSuffix: "°C"
|
||||||
|
onClicked: {
|
||||||
|
MonitorProcess.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Modules.Bar.Services
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
UProgressExpand {
|
||||||
|
// Quickshell.execDetached(["wezterm", "start", "--", "btop"]);
|
||||||
|
|
||||||
|
iconName: "cpu"
|
||||||
|
fillColor: Colors.mCyan
|
||||||
|
critical: SystemStatService.cpuUsage > 90
|
||||||
|
value: Math.round(SystemStatService.cpuUsage)
|
||||||
|
maxValue: 100
|
||||||
|
textSuffix: "%"
|
||||||
|
onClicked: {
|
||||||
|
MonitorProcess.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-13
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Utils
|
import qs.Utils
|
||||||
@@ -10,7 +11,7 @@ import qs.Utils
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property real maxWidth: 250
|
property real maxWidth: 320
|
||||||
property string fallbackIcon: "application-x-executable"
|
property string fallbackIcon: "application-x-executable"
|
||||||
|
|
||||||
function getAppIcon(appId) {
|
function getAppIcon(appId) {
|
||||||
@@ -23,24 +24,25 @@ Item {
|
|||||||
return iconResult;
|
return iconResult;
|
||||||
|
|
||||||
} catch (iconError) {
|
} catch (iconError) {
|
||||||
Logger.warn("FocusedWindow", "Error getting icon from CompositorService: " + iconError);
|
Logger.w("FocusedWindow", "Error getting icon from CompositorService: " + iconError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ThemeIcons.iconFromName(root.fallbackIcon);
|
return ThemeIcons.iconFromName(root.fallbackIcon);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.warn("FocusedWindow", "Error in getAppIcon:", e);
|
Logger.w("FocusedWindow", "Error in getAppIcon:", e);
|
||||||
return ThemeIcons.iconFromName(root.fallbackIcon);
|
return ThemeIcons.iconFromName(root.fallbackIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: parent.height
|
implicitWidth: layout.implicitWidth
|
||||||
|
implicitHeight: Math.max(windowIcon.implicitHeight, windowTitle.implicitHeight)
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: layout
|
id: layout
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 10
|
spacing: 10
|
||||||
visible: Niri.focusedWindowId !== -1
|
visible: Niri.hasFocusedWindow
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
// Layout.alignment: Qt.AlignVCenter
|
// Layout.alignment: Qt.AlignVCenter
|
||||||
@@ -79,24 +81,20 @@ Item {
|
|||||||
height: parent.height
|
height: parent.height
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
Text {
|
UText {
|
||||||
id: windowTitle
|
id: windowTitle
|
||||||
|
|
||||||
text: Niri.focusedWindowTitle
|
text: Niri.focusedWindowTitle
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
font.pointSize: Fonts.medium
|
color: Colors.mPrimary
|
||||||
font.family: Fonts.primary
|
|
||||||
color: Colors.primary
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
UText {
|
||||||
text: Niri.focusedWindowTitle
|
text: Niri.focusedWindowTitle
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: windowTitle.right
|
anchors.left: windowTitle.right
|
||||||
anchors.leftMargin: titleContainer.scrollSpacing
|
anchors.leftMargin: titleContainer.scrollSpacing
|
||||||
font.pointSize: Fonts.medium
|
color: Colors.mPrimary
|
||||||
font.family: Fonts.primary
|
|
||||||
color: Colors.primary
|
|
||||||
visible: titleContainer.shouldScroll
|
visible: titleContainer.shouldScroll
|
||||||
}
|
}
|
||||||
|
|
||||||
+4
-5
@@ -3,15 +3,14 @@ import QtQuick.Layouts
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Modules.Bar.Misc
|
import qs.Components
|
||||||
|
|
||||||
MonitorItem {
|
UProgressExpand {
|
||||||
symbol: Icons.ip
|
iconName: "world"
|
||||||
fillColor: Colors.peach
|
fillColor: Colors.mOrange
|
||||||
value: 100
|
value: 100
|
||||||
maxValue: 100
|
maxValue: 100
|
||||||
textValue: displayText
|
textValue: displayText
|
||||||
symbolSize: 18
|
|
||||||
|
|
||||||
property int displayIndex: 0
|
property int displayIndex: 0
|
||||||
readonly property list<string> displayTexts: [IpService.countryCode, IpService.ip, IpService.alias]
|
readonly property list<string> displayTexts: [IpService.countryCode, IpService.ip, IpService.alias]
|
||||||
+26
-34
@@ -1,84 +1,76 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
implicitHeight: parent.height
|
|
||||||
radius: Style.radiusS
|
radius: Style.radiusS
|
||||||
color: Colors.base
|
color: Colors.mSurface
|
||||||
border.color: Colors.primary
|
border.color: Colors.mPrimary
|
||||||
border.width: Style.borderS
|
border.width: Style.borderS
|
||||||
|
Component.onCompleted: {
|
||||||
Connections {
|
|
||||||
function onShowLyricsBarChanged() {
|
|
||||||
visible = SettingsService.showLyricsBar;
|
|
||||||
if (visible)
|
|
||||||
LyricsService.startSyncing();
|
LyricsService.startSyncing();
|
||||||
else
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
LyricsService.stopSyncing();
|
LyricsService.stopSyncing();
|
||||||
}
|
}
|
||||||
|
implicitHeight: Style.barHeight - Style.marginXS * 2
|
||||||
target: SettingsService
|
implicitWidth: 600
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: Style.marginM
|
anchors.leftMargin: Style.marginM
|
||||||
anchors.rightMargin: Style.marginM
|
anchors.rightMargin: Style.marginM
|
||||||
spacing: Style.marginS
|
spacing: Style.marginXS
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
implicitWidth: parent.width - slowerButton.implicitWidth * 3 - parent.spacing * 3 - parent.anchors.leftMargin - parent.anchors.rightMargin
|
implicitWidth: parent.width - slowerButton.implicitWidth * 3 - parent.spacing * 3 - parent.anchors.leftMargin - parent.anchors.rightMargin
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
NText {
|
UText {
|
||||||
text: LyricsService.lyrics[LyricsService.currentIndex] || ""
|
text: LyricsService.lyrics[LyricsService.currentIndex] || ""
|
||||||
family: Fonts.sans
|
family: Fonts.sans
|
||||||
pointSize: Style.fontSizeS
|
pointSize: Style.fontSizeM
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NIconButton {
|
UIconButton {
|
||||||
id: slowerButton
|
id: slowerButton
|
||||||
|
|
||||||
baseSize: 24
|
colorFg: Colors.mBlue
|
||||||
colorBg: Color.transparent
|
iconName: "rotate-2"
|
||||||
colorBgHover: Colors.blue
|
baseSize: parent.height - Style.marginXS * 2
|
||||||
colorFg: Colors.blue
|
iconSize: Style.fontSizeM
|
||||||
icon: "rotate-2"
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
LyricsService.increaseOffset();
|
LyricsService.increaseOffset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NIconButton {
|
UIconButton {
|
||||||
id: playPauseButton
|
id: playPauseButton
|
||||||
|
|
||||||
baseSize: 24
|
colorFg: Colors.mYellow
|
||||||
colorBg: Color.transparent
|
iconName: "rotate-clockwise-2"
|
||||||
colorBgHover: Colors.yellow
|
baseSize: parent.height - Style.marginXS * 2
|
||||||
colorFg: Colors.yellow
|
iconSize: Style.fontSizeM
|
||||||
icon: "rotate-clockwise-2"
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
LyricsService.decreaseOffset();
|
LyricsService.decreaseOffset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NIconButton {
|
UIconButton {
|
||||||
id: nextButton
|
id: nextButton
|
||||||
|
|
||||||
baseSize: 24
|
colorFg: Colors.mGreen
|
||||||
colorBg: Color.transparent
|
iconName: "rotate-clockwise"
|
||||||
colorBgHover: Colors.green
|
baseSize: parent.height - Style.marginXS * 2
|
||||||
colorFg: Colors.green
|
iconSize: Style.fontSizeM
|
||||||
icon: "rotate-clockwise"
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
LyricsService.resetOffset();
|
LyricsService.resetOffset();
|
||||||
}
|
}
|
||||||
+6
-17
@@ -1,34 +1,23 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Io
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Modules.Bar.Misc
|
import qs.Modules.Bar.Services
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
MonitorItem {
|
UProgressExpand {
|
||||||
property bool _showPercent: false
|
property bool _showPercent: false
|
||||||
|
|
||||||
symbol: Icons.memory
|
iconName: "database"
|
||||||
fillColor: Colors.green
|
fillColor: Colors.mGreen
|
||||||
critical: SystemStatService.memPercent > 90
|
critical: SystemStatService.memPercent > 90
|
||||||
value: Math.round(SystemStatService.memPercent)
|
value: Math.round(SystemStatService.memPercent)
|
||||||
maxValue: 100
|
maxValue: 100
|
||||||
textValue: _showPercent ? SystemStatService.memPercent : SystemStatService.memGb
|
textValue: _showPercent ? SystemStatService.memPercent : SystemStatService.memGb
|
||||||
textSuffix: _showPercent ? "%" : "GB"
|
textSuffix: _showPercent ? "%" : "GB"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (action.running) {
|
MonitorProcess.toggle();
|
||||||
action.signal(15);
|
|
||||||
return ;
|
|
||||||
}
|
|
||||||
action.exec(["wezterm", "start", "--", "btop"]);
|
|
||||||
}
|
}
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
_showPercent = !_showPercent;
|
_showPercent = !_showPercent;
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
|
||||||
id: action
|
|
||||||
|
|
||||||
running: false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+9
-13
@@ -2,6 +2,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
@@ -16,35 +17,30 @@ Item {
|
|||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
Text {
|
UIcon {
|
||||||
text: Icons.download
|
iconName: "arrow-big-down-line-filled"
|
||||||
font.pointSize: Fonts.icon - 3
|
|
||||||
color: Colors.primary
|
|
||||||
Layout.leftMargin: 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
|
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
|
||||||
font.pointSize: Fonts.medium
|
font.pointSize: Style.fontSizeM
|
||||||
font.family: Fonts.primary
|
font.family: Fonts.primary
|
||||||
color: Colors.primary
|
color: Colors.mPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: 5
|
width: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
UIcon {
|
||||||
text: Icons.upload
|
iconName: "arrow-big-up-line-filled"
|
||||||
font.pointSize: Fonts.icon - 3
|
|
||||||
color: Colors.primary
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
|
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
|
||||||
font.pointSize: Fonts.medium
|
font.pointSize: Style.fontSizeM
|
||||||
font.family: Fonts.primary
|
font.family: Fonts.primary
|
||||||
color: Colors.primary
|
color: Colors.mPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+20
-16
@@ -1,18 +1,20 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property color fillColor: Colors.red
|
property color fillColor: Colors.mRed
|
||||||
property color _actualColor: Colors.red
|
property color _actualColor: Colors.mRed
|
||||||
|
property bool _expand: mouseArea.containsMouse
|
||||||
|
|
||||||
visible: RecordService.isRecording
|
visible: RecordService.isRecording
|
||||||
implicitHeight: parent.height
|
implicitHeight: Math.max(symbolIcon.implicitHeight, textLabel.implicitHeight)
|
||||||
implicitWidth: layout.width + 10
|
implicitWidth: height + expander.implicitWidth
|
||||||
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
id: blinkAnimation
|
id: blinkAnimation
|
||||||
@@ -45,34 +47,36 @@ Item {
|
|||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Text {
|
UIcon {
|
||||||
text: Icons.record
|
id: symbolIcon
|
||||||
font.pointSize: 18
|
|
||||||
color: _actualColor
|
iconName: "capture-filled"
|
||||||
|
iconSize: Style.fontSizeM + 12
|
||||||
|
color: root._actualColor
|
||||||
|
Layout.preferredWidth: parent.height
|
||||||
|
Layout.preferredHeight: parent.height
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: expander
|
id: expander
|
||||||
|
|
||||||
implicitWidth: mouseArea.containsMouse ? ipText.implicitWidth + 10 : 0
|
|
||||||
implicitHeight: parent.height
|
implicitHeight: parent.height
|
||||||
|
implicitWidth: root._expand ? textLabel.implicitWidth + 10 : 0
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Text {
|
UText {
|
||||||
id: ipText
|
id: textLabel
|
||||||
|
|
||||||
text: RecordService.recordingDisplay
|
|
||||||
font.pointSize: Fonts.medium
|
|
||||||
font.family: Fonts.primary
|
|
||||||
color: fillColor
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 5
|
anchors.leftMargin: 5
|
||||||
|
text: RecordService.recordingDisplay || "Recording"
|
||||||
|
color: root.fillColor
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
Behavior on implicitWidth {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Style.animationFast
|
duration: Style.animationNormal
|
||||||
easing.type: Easing.InOutCubic
|
easing.type: Easing.InOutCubic
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: TimeService.time + " | " + TimeService.dateString
|
||||||
|
font.pointSize: Style.fontSizeM
|
||||||
|
font.family: Fonts.primary
|
||||||
|
color: Colors.mPrimary
|
||||||
|
}
|
||||||
+10
-7
@@ -2,16 +2,18 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Modules.Bar.Misc
|
import qs.Modules.Bar.Components
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property ShellScreen screen
|
property ShellScreen screen
|
||||||
|
property int baseSize: Style.baseWidgetSize
|
||||||
|
|
||||||
implicitHeight: parent.height
|
implicitWidth: baseSize + trayContainer.implicitWidth
|
||||||
implicitWidth: layout.implicitWidth
|
implicitHeight: layout.implicitHeight
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: layout
|
id: layout
|
||||||
@@ -20,9 +22,10 @@ Item {
|
|||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
SymbolButton {
|
UIconButton {
|
||||||
symbol: Icons.tray
|
iconName: "layout-sidebar-right-expand-filled"
|
||||||
buttonColor: Colors.green
|
colorFg: Colors.mGreen
|
||||||
|
baseSize: root.baseSize
|
||||||
disabledHover: true
|
disabledHover: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +44,7 @@ Item {
|
|||||||
|
|
||||||
Behavior on implicitWidth {
|
Behavior on implicitWidth {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 200
|
duration: Style.animationNormal
|
||||||
easing.type: Easing.InOutCubic
|
easing.type: Easing.InOutCubic
|
||||||
}
|
}
|
||||||
|
|
||||||
+5
-5
@@ -1,12 +1,12 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Modules.Bar.Misc
|
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
MonitorItem {
|
UProgressExpand {
|
||||||
symbol: AudioService.muted ? Icons.volumeMuted : (AudioService.volume >= 0.5 ? Icons.volumeHigh : (AudioService.volume >= 0.2 ? Icons.volumeMedium : Icons.volumeLow))
|
iconName: AudioService.muted ? "volume-3" : (AudioService.volume >= 0.5 ? "volume" : (AudioService.volume >= 0.2 ? "volume-2" : "volume-2"))
|
||||||
fillColor: Colors.lavender
|
fillColor: Colors.mLavender
|
||||||
value: Math.round(AudioService.volume * 100)
|
value: Math.round(AudioService.volume * 100)
|
||||||
maxValue: 100
|
maxValue: 100
|
||||||
textSuffix: "%"
|
textSuffix: "%"
|
||||||
@@ -18,7 +18,7 @@ MonitorItem {
|
|||||||
AudioService.decreaseVolume();
|
AudioService.decreaseVolume();
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
AudioService.toggleMute();
|
AudioService.setOutputMuted(!AudioService.muted);
|
||||||
}
|
}
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
Quickshell.execDetached(["sh", "-c", "pkill -x -n pwvucontrol || pwvucontrol"]);
|
Quickshell.execDetached(["sh", "-c", "pkill -x -n pwvucontrol || pwvucontrol"]);
|
||||||
+41
-115
@@ -1,4 +1,3 @@
|
|||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
@@ -12,78 +11,59 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property ShellScreen screen
|
required property ShellScreen screen
|
||||||
property bool hovered: false
|
|
||||||
property ListModel localWorkspaces
|
property ListModel localWorkspaces
|
||||||
property real masterProgress: 0
|
property real masterProgress: 0
|
||||||
property bool effectsActive: false
|
property bool effectsActive: false
|
||||||
property color effectColor: Colors.primary
|
property color effectColor: Colors.mPrimary
|
||||||
property int horizontalPadding: 16
|
property int horizontalPadding: 16
|
||||||
property int spacingBetweenPills: 8
|
property int spacingBetweenPills: 8
|
||||||
property bool isDestroying: false
|
|
||||||
|
|
||||||
signal workspaceChanged(int workspaceId, color primaryColor)
|
|
||||||
|
|
||||||
function triggerUnifiedWave() {
|
function triggerUnifiedWave() {
|
||||||
effectColor = Colors.primary;
|
effectColor = Colors.mPrimary;
|
||||||
masterAnimation.restart();
|
masterAnimation.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWorkspaceFocus() {
|
function syncWorkspaces() {
|
||||||
for (let i = 0; i < localWorkspaces.count; i++) {
|
let j = 0;
|
||||||
const ws = localWorkspaces.get(i);
|
let focusChanged = false;
|
||||||
if (ws.isFocused === true) {
|
for (let i = 0; i < Niri.workspaces.count; i++) {
|
||||||
root.triggerUnifiedWave();
|
const ws = Niri.workspaces.get(i);
|
||||||
root.workspaceChanged(ws.id, Colors.primary);
|
if (ws.output.toLowerCase() === screen.name.toLowerCase()) {
|
||||||
break;
|
if (j < localWorkspaces.count) {
|
||||||
}
|
const existing = localWorkspaces.get(j);
|
||||||
}
|
if (ws.isFocused && !existing.isFocused)
|
||||||
}
|
focusChanged = true;
|
||||||
|
|
||||||
implicitWidth: {
|
localWorkspaces.setProperty(j, "id", ws.id);
|
||||||
let total = 0;
|
localWorkspaces.setProperty(j, "idx", ws.idx);
|
||||||
for (let i = 0; i < localWorkspaces.count; i++) {
|
localWorkspaces.setProperty(j, "isFocused", ws.isFocused);
|
||||||
const ws = localWorkspaces.get(i);
|
localWorkspaces.setProperty(j, "isActive", ws.isActive);
|
||||||
if (ws.isFocused)
|
localWorkspaces.setProperty(j, "isUrgent", ws.isUrgent);
|
||||||
total += 44;
|
localWorkspaces.setProperty(j, "isOccupied", ws.isOccupied);
|
||||||
else if (ws.isActive)
|
} else {
|
||||||
total += 28;
|
|
||||||
else
|
|
||||||
total += 16;
|
|
||||||
}
|
|
||||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills;
|
|
||||||
total += horizontalPadding * 2;
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
height: parent.height
|
|
||||||
Component.onCompleted: {
|
|
||||||
localWorkspaces.clear();
|
|
||||||
for (let i = 0; i < WorkspaceManager.workspaces.count; i++) {
|
|
||||||
const ws = WorkspaceManager.workspaces.get(i);
|
|
||||||
if (ws.output.toLowerCase() === screen.name.toLowerCase())
|
|
||||||
localWorkspaces.append(ws);
|
localWorkspaces.append(ws);
|
||||||
|
if (ws.isFocused)
|
||||||
|
focusChanged = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
workspaceRepeater.model = localWorkspaces;
|
j++;
|
||||||
updateWorkspaceFocus();
|
|
||||||
}
|
}
|
||||||
Component.onDestruction: {
|
|
||||||
root.isDestroying = true;
|
|
||||||
}
|
}
|
||||||
|
while (localWorkspaces.count > j)localWorkspaces.remove(localWorkspaces.count - 1)
|
||||||
|
if (focusChanged)
|
||||||
|
triggerUnifiedWave();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: pillRow.implicitWidth + horizontalPadding * 2
|
||||||
|
Component.onCompleted: syncWorkspaces()
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onWorkspacesChanged() {
|
function onWorkspaceChanged() {
|
||||||
localWorkspaces.clear();
|
syncWorkspaces();
|
||||||
for (let i = 0; i < WorkspaceManager.workspaces.count; i++) {
|
|
||||||
const ws = WorkspaceManager.workspaces.get(i);
|
|
||||||
if (ws.output.toLowerCase() === screen.name.toLowerCase())
|
|
||||||
localWorkspaces.append(ws);
|
|
||||||
|
|
||||||
}
|
|
||||||
workspaceRepeater.model = localWorkspaces;
|
|
||||||
updateWorkspaceFocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: WorkspaceManager
|
target: Niri
|
||||||
}
|
}
|
||||||
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
@@ -118,35 +98,11 @@ Item {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: workspaceBackground
|
|
||||||
|
|
||||||
width: parent.width - 15
|
|
||||||
height: 26
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
radius: 12
|
|
||||||
color: Colors.transparent
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
layer.effect: DropShadow {
|
|
||||||
color: "black"
|
|
||||||
radius: 12
|
|
||||||
samples: 24
|
|
||||||
verticalOffset: 0
|
|
||||||
horizontalOffset: 0
|
|
||||||
opacity: 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: pillRow
|
id: pillRow
|
||||||
|
|
||||||
spacing: spacingBetweenPills
|
spacing: spacingBetweenPills
|
||||||
anchors.verticalCenter: workspaceBackground.verticalCenter
|
anchors.centerIn: parent
|
||||||
width: root.width - horizontalPadding * 2
|
|
||||||
x: horizontalPadding
|
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: workspaceRepeater
|
id: workspaceRepeater
|
||||||
@@ -172,23 +128,18 @@ Item {
|
|||||||
id: workspacePill
|
id: workspacePill
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: {
|
radius: height / 2
|
||||||
if (model.isFocused)
|
|
||||||
return 12;
|
|
||||||
else
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
color: {
|
color: {
|
||||||
if (model.isFocused)
|
if (model.isFocused)
|
||||||
return Colors.primary;
|
return Colors.mPrimary;
|
||||||
|
|
||||||
if (model.isActive)
|
if (model.isActive)
|
||||||
return Colors.overlay2;
|
return Colors.mOnSurfaceVariant;
|
||||||
|
|
||||||
if (model.isUrgent)
|
if (model.isUrgent)
|
||||||
return Theme.error;
|
return Theme.error;
|
||||||
|
|
||||||
return Colors.surface2;
|
return Colors.mSurfaceVariant;
|
||||||
}
|
}
|
||||||
scale: model.isFocused ? 1 : 0.9
|
scale: model.isFocused ? 1 : 0.9
|
||||||
z: 0
|
z: 0
|
||||||
@@ -199,28 +150,11 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
WorkspaceManager.switchToWorkspace(model.idx);
|
Niri.switchToWorkspace(model);
|
||||||
}
|
}
|
||||||
z: 20
|
z: 20
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
}
|
}
|
||||||
// Material 3-inspired smooth animation for width, height, scale, color, opacity, and radius
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 350
|
|
||||||
easing.type: Easing.OutBack
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 350
|
|
||||||
easing.type: Easing.OutBack
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
@@ -246,14 +180,6 @@ Item {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on radius {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 350
|
|
||||||
easing.type: Easing.OutBack
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -262,8 +188,8 @@ Item {
|
|||||||
anchors.centerIn: workspacePillContainer
|
anchors.centerIn: workspacePillContainer
|
||||||
width: workspacePillContainer.width + 18 * root.masterProgress
|
width: workspacePillContainer.width + 18 * root.masterProgress
|
||||||
height: workspacePillContainer.height + 18 * root.masterProgress
|
height: workspacePillContainer.height + 18 * root.masterProgress
|
||||||
radius: width / 2
|
radius: height / 2
|
||||||
color: "transparent"
|
color: root.effectColor
|
||||||
border.color: root.effectColor
|
border.color: root.effectColor
|
||||||
border.width: 2 + 6 * (1 - root.masterProgress)
|
border.width: 2 + 6 * (1 - root.masterProgress)
|
||||||
opacity: root.effectsActive && model.isFocused ? (1 - root.masterProgress) * 0.7 : 0
|
opacity: root.effectsActive && model.isFocused ? (1 - root.masterProgress) * 0.7 : 0
|
||||||
+1
-1
@@ -6,7 +6,7 @@ pragma Singleton
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int count: 6
|
property int count: 7
|
||||||
property bool forceEnable: false
|
property bool forceEnable: false
|
||||||
property bool forceDisable: false
|
property bool forceDisable: false
|
||||||
property alias values: cavaProcess.values
|
property alias values: cavaProcess.values
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
if (process.running) {
|
||||||
|
process.signal(15);
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
process.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: process
|
||||||
|
|
||||||
|
running: false
|
||||||
|
command: ["wezterm", "start", "--", "btop"]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ Shape {
|
|||||||
property int concaveHeight: 60 * size
|
property int concaveHeight: 60 * size
|
||||||
property int offsetX: -20
|
property int offsetX: -20
|
||||||
property int offsetY: -20
|
property int offsetY: -20
|
||||||
property color fillColor: Colors.base
|
property color fillColor: Colors.mSurface
|
||||||
property int arcRadius: 20 * size
|
property int arcRadius: 20 * size
|
||||||
property var modelData: null
|
property var modelData: null
|
||||||
// Position flags derived from position string
|
// Position flags derived from position string
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ import qs.Services
|
|||||||
Scope {
|
Scope {
|
||||||
id: rootScope
|
id: rootScope
|
||||||
|
|
||||||
property var shell
|
|
||||||
property string namespace: "quickshell-corners"
|
property string namespace: "quickshell-corners"
|
||||||
property int topMargin: 45
|
property int topMargin: 45
|
||||||
property int cornerHeight: 20
|
property int cornerHeight: 20
|
||||||
property real cornerSize: 1
|
property real cornerSize: 1
|
||||||
property real opacity: Niri.noFocus ? 0 : 1
|
property real opacity: BarService.focusMode ? 1 : 0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: cornersRootItem
|
id: cornersRootItem
|
||||||
@@ -26,7 +25,15 @@ Scope {
|
|||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
id: screenItem
|
||||||
|
|
||||||
property var modelData
|
property var modelData
|
||||||
|
// property int leftOffset: BarService.leftOffset(modelData)
|
||||||
|
// property int rightOffset: BarService.rightOffset(modelData)
|
||||||
|
readonly property var leftBar: BarService.getLeftSidebar(modelData.name)
|
||||||
|
readonly property var rightBar: BarService.getRightSidebar(modelData.name)
|
||||||
|
property int leftOffset: leftBar?.isOpen ? leftBar.barWidth : 0
|
||||||
|
property int rightOffset: rightBar?.isOpen ? rightBar.barWidth : 0
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: fakeBar
|
id: fakeBar
|
||||||
@@ -45,7 +52,7 @@ Scope {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Colors.base
|
color: Colors.mSurface
|
||||||
opacity: rootScope.opacity
|
opacity: rootScope.opacity
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,9 +66,10 @@ Scope {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
screen: modelData
|
screen: modelData
|
||||||
margins.top: topMargin
|
margins.top: topMargin
|
||||||
|
margins.left: screenItem.leftOffset
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
visible: true
|
visible: true
|
||||||
WlrLayershell.layer: WlrLayer.Background
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
aboveWindows: false
|
aboveWindows: false
|
||||||
WlrLayershell.namespace: namespace
|
WlrLayershell.namespace: namespace
|
||||||
implicitHeight: cornerHeight
|
implicitHeight: cornerHeight
|
||||||
@@ -87,9 +95,10 @@ Scope {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
screen: modelData
|
screen: modelData
|
||||||
margins.top: topMargin
|
margins.top: topMargin
|
||||||
|
margins.right: screenItem.rightOffset
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
visible: true
|
visible: true
|
||||||
WlrLayershell.layer: WlrLayer.Background
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
aboveWindows: false
|
aboveWindows: false
|
||||||
WlrLayershell.namespace: namespace
|
WlrLayershell.namespace: namespace
|
||||||
implicitHeight: cornerHeight
|
implicitHeight: cornerHeight
|
||||||
@@ -114,9 +123,10 @@ Scope {
|
|||||||
anchors.left: true
|
anchors.left: true
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
screen: modelData
|
screen: modelData
|
||||||
|
margins.left: screenItem.leftOffset
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
visible: true
|
visible: true
|
||||||
WlrLayershell.layer: WlrLayer.Background
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
aboveWindows: false
|
aboveWindows: false
|
||||||
WlrLayershell.namespace: namespace
|
WlrLayershell.namespace: namespace
|
||||||
implicitHeight: cornerHeight
|
implicitHeight: cornerHeight
|
||||||
@@ -141,9 +151,10 @@ Scope {
|
|||||||
anchors.right: true
|
anchors.right: true
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
screen: modelData
|
screen: modelData
|
||||||
|
margins.right: screenItem.rightOffset
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
visible: true
|
visible: true
|
||||||
WlrLayershell.layer: WlrLayer.Background
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
aboveWindows: false
|
aboveWindows: false
|
||||||
WlrLayershell.namespace: namespace
|
WlrLayershell.namespace: namespace
|
||||||
implicitHeight: cornerHeight
|
implicitHeight: cornerHeight
|
||||||
@@ -161,6 +172,22 @@ Scope {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on leftOffset {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationSlow
|
||||||
|
easing.type: Easing.InOutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on rightOffset {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationSlow
|
||||||
|
easing.type: Easing.InOutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -169,7 +196,7 @@ Scope {
|
|||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 1000
|
duration: Style.animationSlowest
|
||||||
easing.type: Easing.InOutCubic
|
easing.type: Easing.InOutCubic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,254 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Bluetooth
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Modules.Panel.Misc
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
NPanel {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
preferredWidth: 380
|
|
||||||
preferredHeight: 500
|
|
||||||
|
|
||||||
panelContent: Rectangle {
|
|
||||||
color: Color.transparent
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginL
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
// HEADER
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "bluetooth"
|
|
||||||
pointSize: Style.fontSizeXXL
|
|
||||||
color: Color.mPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Bluetooth"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Color.mOnSurface
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NToggle {
|
|
||||||
id: bluetoothSwitch
|
|
||||||
|
|
||||||
checked: BluetoothService.enabled
|
|
||||||
onToggled: (checked) => {
|
|
||||||
return BluetoothService.setBluetoothEnabled(checked);
|
|
||||||
}
|
|
||||||
baseSize: Style.baseWidgetSize * 0.65
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
enabled: BluetoothService.enabled
|
|
||||||
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "refresh"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: {
|
|
||||||
if (BluetoothService.adapter)
|
|
||||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
|
|
||||||
|
|
||||||
}
|
|
||||||
colorFg: Colors.green
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorFgHover: Colors.base
|
|
||||||
colorBgHover: Colors.green
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "close"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: {
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
colorFg: Colors.red
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorFgHover: Colors.base
|
|
||||||
colorBgHover: Colors.red
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NDivider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled)
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
color: Color.transparent
|
|
||||||
|
|
||||||
// Center the content within this rectangle
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "bluetooth-off"
|
|
||||||
pointSize: 64
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Bluetooth is turned off"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Enable Bluetooth"
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NScrollView {
|
|
||||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
horizontalPolicy: ScrollBar.AlwaysOff
|
|
||||||
verticalPolicy: ScrollBar.AsNeeded
|
|
||||||
clip: true
|
|
||||||
contentWidth: availableWidth
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
// Connected devices
|
|
||||||
BluetoothDevicesList {
|
|
||||||
property var items: {
|
|
||||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var filtered = Bluetooth.devices.values.filter((dev) => {
|
|
||||||
return dev && !dev.blocked && dev.connected;
|
|
||||||
});
|
|
||||||
return BluetoothService.sortDevices(filtered);
|
|
||||||
}
|
|
||||||
|
|
||||||
label: "Connected Devices"
|
|
||||||
model: items
|
|
||||||
visible: items.length > 0
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Known devices
|
|
||||||
BluetoothDevicesList {
|
|
||||||
property var items: {
|
|
||||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var filtered = Bluetooth.devices.values.filter((dev) => {
|
|
||||||
return dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted);
|
|
||||||
});
|
|
||||||
return BluetoothService.sortDevices(filtered);
|
|
||||||
}
|
|
||||||
|
|
||||||
label: "Known Devices"
|
|
||||||
tooltipText: "Connect/Disconnect Devices"
|
|
||||||
model: items
|
|
||||||
visible: items.length > 0
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Available devices
|
|
||||||
BluetoothDevicesList {
|
|
||||||
property var items: {
|
|
||||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var filtered = Bluetooth.devices.values.filter((dev) => {
|
|
||||||
return dev && !dev.blocked && !dev.paired && !dev.trusted;
|
|
||||||
});
|
|
||||||
return BluetoothService.sortDevices(filtered);
|
|
||||||
}
|
|
||||||
|
|
||||||
label: "Available Devices"
|
|
||||||
model: items
|
|
||||||
visible: items.length > 0
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback - No devices, scanning
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: Style.marginM
|
|
||||||
visible: {
|
|
||||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
|
||||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
|
||||||
}).length;
|
|
||||||
return (availableCount === 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: Style.marginXS
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "refresh"
|
|
||||||
pointSize: Style.fontSizeXXL * 1.5
|
|
||||||
color: Color.mPrimary
|
|
||||||
|
|
||||||
RotationAnimation on rotation {
|
|
||||||
running: true
|
|
||||||
loops: Animation.Infinite
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: Style.animationSlow * 4
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Scanning..."
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
color: Color.mOnSurface
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Pairing Mode"
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,526 +0,0 @@
|
|||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
NPanel {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
preferredWidth: 400
|
|
||||||
preferredHeight: 520
|
|
||||||
|
|
||||||
panelContent: ColumnLayout {
|
|
||||||
id: content
|
|
||||||
|
|
||||||
readonly property int firstDayOfWeek: Qt.locale().firstDayOfWeek
|
|
||||||
property bool isCurrentMonth: checkIsCurrentMonth()
|
|
||||||
readonly property bool weatherReady: (LocationService.data.weather !== null)
|
|
||||||
|
|
||||||
function checkIsCurrentMonth() {
|
|
||||||
return (Time.date.getMonth() === grid.month) && (Time.date.getFullYear() === grid.year);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getISOWeekNumber(date) {
|
|
||||||
const target = new Date(date.getTime());
|
|
||||||
target.setHours(0, 0, 0, 0);
|
|
||||||
const dayOfWeek = target.getDay() || 7;
|
|
||||||
target.setDate(target.getDate() + 4 - dayOfWeek);
|
|
||||||
const yearStart = new Date(target.getFullYear(), 0, 1);
|
|
||||||
const weekNumber = Math.ceil(((target - yearStart) / 8.64e+07 + 1) / 7);
|
|
||||||
return weekNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginL
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onDateChanged() {
|
|
||||||
isCurrentMonth = checkIsCurrentMonth();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combined blue banner with date/time and weather summary
|
|
||||||
NBox {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: blueColumn.implicitHeight + Style.marginM * 2
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: blueColumn
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginM
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
// Combined layout for weather icon, date, and weather text
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 60
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
// Weather icon and temperature
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
spacing: Style.marginXXS
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "cloud"
|
|
||||||
pointSize: Style.fontSizeXXL
|
|
||||||
color: Colors.text
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: {
|
|
||||||
if (!weatherReady)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
var temp = LocationService.data.weather.current_weather.temperature;
|
|
||||||
var suffix = "C";
|
|
||||||
temp = Math.round(temp);
|
|
||||||
return `${temp}°${suffix}`;
|
|
||||||
}
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Colors.text
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Today day number
|
|
||||||
NText {
|
|
||||||
visible: content.isCurrentMonth
|
|
||||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
|
||||||
text: Time.date.getDate()
|
|
||||||
pointSize: Style.fontSizeXXXL * 1.5
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Colors.text
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
visible: !content.isCurrentMonth
|
|
||||||
}
|
|
||||||
|
|
||||||
// Month, year, location
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: false
|
|
||||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
|
||||||
spacing: -Style.marginXS
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: Qt.locale().monthName(grid.month, Locale.LongFormat).toUpperCase()
|
|
||||||
pointSize: Style.fontSizeXL * 1.2
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Colors.text
|
|
||||||
Layout.alignment: Qt.AlignBaseline
|
|
||||||
Layout.maximumWidth: 150
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: ` ${grid.year}`
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Qt.alpha(Colors.text, 0.7)
|
|
||||||
Layout.alignment: Qt.AlignBaseline
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: {
|
|
||||||
if (!weatherReady)
|
|
||||||
return "Weather unavailable";
|
|
||||||
|
|
||||||
const chunks = LocationService.data.name.split(",");
|
|
||||||
return chunks[0];
|
|
||||||
}
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: Colors.text
|
|
||||||
Layout.maximumWidth: 150
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: weatherReady ? ` (${LocationService.data.weather.timezone_abbreviation})` : ""
|
|
||||||
pointSize: Style.fontSizeXS
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
color: Qt.alpha(Colors.text, 0.7)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spacer between date and clock
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Digital clock with circular progress
|
|
||||||
Item {
|
|
||||||
width: Style.fontSizeXXXL * 1.9
|
|
||||||
height: Style.fontSizeXXXL * 1.9
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
|
|
||||||
// Seconds circular progress
|
|
||||||
Canvas {
|
|
||||||
id: secondsProgress
|
|
||||||
|
|
||||||
property real progress: Time.date.getSeconds() / 60
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
onProgressChanged: requestPaint()
|
|
||||||
onPaint: {
|
|
||||||
var ctx = getContext("2d");
|
|
||||||
var centerX = width / 2;
|
|
||||||
var centerY = height / 2;
|
|
||||||
var radius = Math.min(width, height) / 2 - 3;
|
|
||||||
ctx.reset();
|
|
||||||
// Background circle
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
|
||||||
ctx.lineWidth = 2.5;
|
|
||||||
ctx.strokeStyle = Qt.alpha(Colors.text, 0.15);
|
|
||||||
ctx.stroke();
|
|
||||||
// Progress arc
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI);
|
|
||||||
ctx.lineWidth = 2.5;
|
|
||||||
ctx.strokeStyle = Colors.text;
|
|
||||||
ctx.lineCap = "round";
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onDateChanged() {
|
|
||||||
secondsProgress.progress = Time.date.getSeconds() / 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
target: Time
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Digital clock
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: -Style.marginXXS
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: {
|
|
||||||
var t = Qt.locale().toString(new Date(), "HH");
|
|
||||||
return t.split(" ")[0];
|
|
||||||
}
|
|
||||||
pointSize: Style.fontSizeXS
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Colors.text
|
|
||||||
family: Fonts.sans
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: Qt.formatTime(Time.date, "mm")
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Colors.text
|
|
||||||
family: Fonts.sans
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6-day forecast (outside blue banner)
|
|
||||||
RowLayout {
|
|
||||||
visible: weatherReady
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: Style.marginL
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0
|
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
|
||||||
Layout.preferredWidth: 0
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: {
|
|
||||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
|
|
||||||
return Qt.locale().toString(weatherDate, "ddd");
|
|
||||||
}
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
|
||||||
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
|
|
||||||
pointSize: Style.fontSizeXXL * 1.5
|
|
||||||
color: LocationService.weatherColorFromCode(LocationService.data.weather.daily.weathercode[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: {
|
|
||||||
var max = LocationService.data.weather.daily.temperature_2m_max[index];
|
|
||||||
var min = LocationService.data.weather.daily.temperature_2m_min[index];
|
|
||||||
max = Math.round(max);
|
|
||||||
min = Math.round(min);
|
|
||||||
return `${max}°/${min}°`;
|
|
||||||
}
|
|
||||||
pointSize: Style.fontSizeXS
|
|
||||||
color: Colors.text
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading indicator for weather
|
|
||||||
RowLayout {
|
|
||||||
visible: !weatherReady
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
NBusyIndicator {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spacer
|
|
||||||
Item {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigation and divider
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
NDivider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "chevron-left"
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorBorder: Color.transparent
|
|
||||||
colorBorderHover: Color.transparent
|
|
||||||
onClicked: {
|
|
||||||
let newDate = new Date(grid.year, grid.month - 1, 1);
|
|
||||||
grid.year = newDate.getFullYear();
|
|
||||||
grid.month = newDate.getMonth();
|
|
||||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "calendar"
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorBorder: Color.transparent
|
|
||||||
colorBorderHover: Color.transparent
|
|
||||||
onClicked: {
|
|
||||||
grid.month = Time.date.getMonth();
|
|
||||||
grid.year = Time.date.getFullYear();
|
|
||||||
content.isCurrentMonth = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "chevron-right"
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorBorder: Color.transparent
|
|
||||||
colorBorderHover: Color.transparent
|
|
||||||
onClicked: {
|
|
||||||
let newDate = new Date(grid.year, grid.month + 1, 1);
|
|
||||||
grid.year = newDate.getFullYear();
|
|
||||||
grid.month = newDate.getMonth();
|
|
||||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names of days of the week
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
GridLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
columns: 7
|
|
||||||
rows: 1
|
|
||||||
columnSpacing: 0
|
|
||||||
rowSpacing: 0
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 7
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: Style.baseWidgetSize * 0.6
|
|
||||||
|
|
||||||
NText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: {
|
|
||||||
let dayIndex = (content.firstDayOfWeek + index) % 7;
|
|
||||||
const dayNames = ["S", "M", "T", "W", "T", "F", "S"];
|
|
||||||
return dayNames[dayIndex];
|
|
||||||
}
|
|
||||||
color: Color.mPrimary
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grid with weeks and days
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
// Column of week numbers
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
|
||||||
Layout.fillHeight: true
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 6
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
NText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: Color.mOutline
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
font.weight: Style.fontWeightMedium
|
|
||||||
text: {
|
|
||||||
let firstOfMonth = new Date(grid.year, grid.month, 1);
|
|
||||||
let firstDayOfWeek = content.firstDayOfWeek;
|
|
||||||
let firstOfMonthDayOfWeek = firstOfMonth.getDay();
|
|
||||||
let daysBeforeFirst = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
|
|
||||||
if (daysBeforeFirst === 0)
|
|
||||||
daysBeforeFirst = 7;
|
|
||||||
|
|
||||||
let gridStartDate = new Date(grid.year, grid.month, 1 - daysBeforeFirst);
|
|
||||||
let rowStartDate = new Date(gridStartDate);
|
|
||||||
rowStartDate.setDate(gridStartDate.getDate() + (index * 7));
|
|
||||||
let thursday = new Date(rowStartDate);
|
|
||||||
if (firstDayOfWeek === 0) {
|
|
||||||
thursday.setDate(rowStartDate.getDate() + 4);
|
|
||||||
} else if (firstDayOfWeek === 1) {
|
|
||||||
thursday.setDate(rowStartDate.getDate() + 3);
|
|
||||||
} else {
|
|
||||||
let daysToThursday = (4 - firstDayOfWeek + 7) % 7;
|
|
||||||
thursday.setDate(rowStartDate.getDate() + daysToThursday);
|
|
||||||
}
|
|
||||||
return `${getISOWeekNumber(thursday)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Days Grid
|
|
||||||
MonthGrid {
|
|
||||||
id: grid
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
spacing: Style.marginXXS
|
|
||||||
month: Time.date.getMonth()
|
|
||||||
year: Time.date.getFullYear()
|
|
||||||
locale: Qt.locale()
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
Rectangle {
|
|
||||||
width: Style.baseWidgetSize * 0.9
|
|
||||||
height: Style.baseWidgetSize * 0.9
|
|
||||||
anchors.centerIn: parent
|
|
||||||
radius: Style.radiusM
|
|
||||||
color: model.today ? Color.mSecondary : Color.transparent
|
|
||||||
|
|
||||||
NText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: model.day
|
|
||||||
color: {
|
|
||||||
if (model.today)
|
|
||||||
return Color.mOnSecondary;
|
|
||||||
|
|
||||||
if (model.month === grid.month)
|
|
||||||
return Color.mOnSurface;
|
|
||||||
|
|
||||||
return Color.mOnSurfaceVariant;
|
|
||||||
}
|
|
||||||
opacity: model.month === grid.month ? 1 : 0.4
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Modules.Bar.Misc
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
GridLayout {
|
|
||||||
id: buttonsGrid
|
|
||||||
|
|
||||||
columns: 2
|
|
||||||
columnSpacing: 10
|
|
||||||
rowSpacing: 10
|
|
||||||
Layout.margins: 10
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
id: slowerButton
|
|
||||||
|
|
||||||
baseSize: 32
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorBgHover: Colors.blue
|
|
||||||
colorFg: Colors.blue
|
|
||||||
icon: "arrow-bar-up"
|
|
||||||
onClicked: {
|
|
||||||
LyricsService.increaseOffset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
id: playPauseButton
|
|
||||||
|
|
||||||
baseSize: 32
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorBgHover: Colors.yellow
|
|
||||||
colorFg: Colors.yellow
|
|
||||||
icon: "arrow-bar-down"
|
|
||||||
onClicked: {
|
|
||||||
LyricsService.decreaseOffset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
id: nextButton
|
|
||||||
|
|
||||||
baseSize: 32
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorBgHover: Colors.green
|
|
||||||
colorFg: Colors.green
|
|
||||||
icon: "rotate-clockwise"
|
|
||||||
onClicked: {
|
|
||||||
LyricsService.resetOffset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
id: fasterButton
|
|
||||||
|
|
||||||
baseSize: 32
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorBgHover: Colors.red
|
|
||||||
colorFg: Colors.red
|
|
||||||
icon: "trash"
|
|
||||||
onClicked: {
|
|
||||||
LyricsService.clearCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
id: barLyricsButton
|
|
||||||
|
|
||||||
baseSize: 32
|
|
||||||
colorBg: SettingsService.showLyricsBar ? Colors.peach : Color.transparent
|
|
||||||
colorBgHover: Colors.peach
|
|
||||||
colorFg: SettingsService.showLyricsBar ? Colors.base : Colors.peach
|
|
||||||
icon: "app-window"
|
|
||||||
onClicked: {
|
|
||||||
SettingsService.showLyricsBar = !SettingsService.showLyricsBar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
id: textButton
|
|
||||||
|
|
||||||
baseSize: 32
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorBgHover: Colors.subtext1
|
|
||||||
colorFg: Colors.subtext1
|
|
||||||
icon: "align-box-left-bottom"
|
|
||||||
onClicked: {
|
|
||||||
LyricsService.showLyricsText();
|
|
||||||
controlCenterPanel.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,458 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Effects
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
NBox {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// Background artwork that covers everything
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
NImageRounded {
|
|
||||||
id: bgArtImage
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
imagePath: MusicManager.trackArtUrl
|
|
||||||
imageRadius: Style.radiusM
|
|
||||||
visible: MusicManager.trackArtUrl !== ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dark overlay for readability
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Color.mSurfaceVariant
|
|
||||||
opacity: 0.85
|
|
||||||
radius: Style.radiusM
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Color.transparent
|
|
||||||
radius: Style.radiusM
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background visualizer on top of the artwork
|
|
||||||
Item {
|
|
||||||
id: visualizerContainer
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Cava {
|
|
||||||
id: cava
|
|
||||||
|
|
||||||
count: 32
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: cava.values
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
width: (parent.width - (cava.count - 1) * Style.marginXS) / cava.count
|
|
||||||
height: modelData * parent.height
|
|
||||||
x: index * (width + Style.marginXS)
|
|
||||||
color: Color.mPrimary
|
|
||||||
radius: width / 2
|
|
||||||
opacity: 0.25
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
maskSpreadAtMin: 0
|
|
||||||
|
|
||||||
maskSource: ShaderEffectSource {
|
|
||||||
|
|
||||||
sourceItem: Rectangle {
|
|
||||||
width: root.width
|
|
||||||
height: root.height
|
|
||||||
radius: Style.radiusM
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player selector - positioned at the very top
|
|
||||||
Rectangle {
|
|
||||||
id: playerSelectorButton
|
|
||||||
|
|
||||||
property var currentPlayer: MusicManager.getAvailablePlayers()[MusicManager.selectedPlayerIndex]
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.topMargin: Style.marginXS
|
|
||||||
anchors.leftMargin: Style.marginM
|
|
||||||
anchors.rightMargin: Style.marginM
|
|
||||||
height: Style.barHeight
|
|
||||||
visible: MusicManager.getAvailablePlayers().length > 1
|
|
||||||
radius: Style.radiusM
|
|
||||||
color: Color.transparent
|
|
||||||
Component.onCompleted: {
|
|
||||||
MusicManager.selectedPlayerIndex = -1;
|
|
||||||
}
|
|
||||||
Component.onDestruction: {
|
|
||||||
MusicManager.selectedPlayerIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "caret-down"
|
|
||||||
pointSize: Style.fontSizeXXL
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: playerSelectorButton.currentPlayer ? playerSelectorButton.currentPlayer.identity : ""
|
|
||||||
pointSize: Style.fontSizeXS
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: playerSelectorMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
var menuItems = [];
|
|
||||||
var players = MusicManager.getAvailablePlayers();
|
|
||||||
for (var i = 0; i < players.length; i++) {
|
|
||||||
menuItems.push({
|
|
||||||
"label": players[i].identity,
|
|
||||||
"action": i.toString(),
|
|
||||||
"icon": "disc",
|
|
||||||
"enabled": true,
|
|
||||||
"visible": true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
playerContextMenu.model = menuItems;
|
|
||||||
playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NContextMenu {
|
|
||||||
id: playerContextMenu
|
|
||||||
|
|
||||||
parent: root
|
|
||||||
width: 200
|
|
||||||
onTriggered: function(action) {
|
|
||||||
var index = parseInt(action);
|
|
||||||
if (!isNaN(index)) {
|
|
||||||
MusicManager.selectedPlayerIndex = index;
|
|
||||||
MusicManager.updateCurrentPlayer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginM
|
|
||||||
|
|
||||||
// No media player detected
|
|
||||||
ColumnLayout {
|
|
||||||
id: fallback
|
|
||||||
|
|
||||||
visible: !main.visible
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Style.marginL
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.preferredWidth: Style.fontSizeXXXL * 4
|
|
||||||
Layout.preferredHeight: Style.fontSizeXXXL * 4
|
|
||||||
|
|
||||||
// Pulsating audio circles (background)
|
|
||||||
Repeater {
|
|
||||||
model: 3
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width * (1 + index * 0.2)
|
|
||||||
height: width
|
|
||||||
radius: width / 2
|
|
||||||
color: "transparent"
|
|
||||||
border.color: Color.mOnSurfaceVariant
|
|
||||||
border.width: 2
|
|
||||||
opacity: 0
|
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
|
||||||
running: true
|
|
||||||
loops: Animation.Infinite
|
|
||||||
|
|
||||||
PauseAnimation {
|
|
||||||
duration: index * 600
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
from: 1
|
|
||||||
to: 0
|
|
||||||
duration: 2000
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SequentialAnimation on scale {
|
|
||||||
running: true
|
|
||||||
loops: Animation.Infinite
|
|
||||||
|
|
||||||
PauseAnimation {
|
|
||||||
duration: index * 600
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
from: 0.5
|
|
||||||
to: 1.2
|
|
||||||
duration: 2000
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spinning disc
|
|
||||||
NIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
icon: "disc"
|
|
||||||
pointSize: Style.fontSizeXXXL * 3
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
|
|
||||||
RotationAnimator on rotation {
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: 8000
|
|
||||||
loops: Animation.Infinite
|
|
||||||
running: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Descriptive text
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: Style.marginXS
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// MediaPlayer Main Content
|
|
||||||
ColumnLayout {
|
|
||||||
id: main
|
|
||||||
|
|
||||||
visible: MusicManager.currentPlayer && MusicManager.canPlay
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
// Spacer to push content down
|
|
||||||
Item {
|
|
||||||
Layout.preferredHeight: Style.marginM
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata at the bottom left
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
spacing: Style.marginXS
|
|
||||||
|
|
||||||
NText {
|
|
||||||
visible: MusicManager.trackTitle !== ""
|
|
||||||
text: MusicManager.trackTitle
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
visible: MusicManager.trackArtist !== ""
|
|
||||||
text: MusicManager.trackArtist
|
|
||||||
color: Color.mPrimary
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
visible: MusicManager.trackAlbum !== ""
|
|
||||||
text: MusicManager.trackAlbum
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress slider
|
|
||||||
Item {
|
|
||||||
id: progressWrapper
|
|
||||||
|
|
||||||
property real localSeekRatio: -1
|
|
||||||
property real lastSentSeekRatio: -1
|
|
||||||
property real seekEpsilon: 0.01
|
|
||||||
property real progressRatio: {
|
|
||||||
if (!MusicManager.currentPlayer || MusicManager.trackLength <= 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
const r = MusicManager.currentPosition / MusicManager.trackLength;
|
|
||||||
if (isNaN(r) || !isFinite(r))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return Math.max(0, Math.min(1, r));
|
|
||||||
}
|
|
||||||
property real effectiveRatio: (MusicManager.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
|
|
||||||
|
|
||||||
visible: (MusicManager.currentPlayer && MusicManager.trackLength > 0)
|
|
||||||
Layout.fillWidth: true
|
|
||||||
height: Style.baseWidgetSize * 0.5
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: seekDebounce
|
|
||||||
|
|
||||||
interval: 75
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (MusicManager.isSeeking && progressWrapper.localSeekRatio >= 0) {
|
|
||||||
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio));
|
|
||||||
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
|
|
||||||
MusicManager.seekByRatio(next);
|
|
||||||
progressWrapper.lastSentSeekRatio = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NSlider {
|
|
||||||
id: progressSlider
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
from: 0
|
|
||||||
to: 1
|
|
||||||
stepSize: 0
|
|
||||||
snapAlways: false
|
|
||||||
enabled: MusicManager.trackLength > 0 && MusicManager.canSeek
|
|
||||||
heightRatio: 0.65
|
|
||||||
onMoved: {
|
|
||||||
progressWrapper.localSeekRatio = value;
|
|
||||||
seekDebounce.restart();
|
|
||||||
}
|
|
||||||
onPressedChanged: {
|
|
||||||
if (pressed) {
|
|
||||||
MusicManager.isSeeking = true;
|
|
||||||
progressWrapper.localSeekRatio = value;
|
|
||||||
MusicManager.seekByRatio(value);
|
|
||||||
progressWrapper.lastSentSeekRatio = value;
|
|
||||||
} else {
|
|
||||||
seekDebounce.stop();
|
|
||||||
MusicManager.seekByRatio(value);
|
|
||||||
MusicManager.isSeeking = false;
|
|
||||||
progressWrapper.localSeekRatio = -1;
|
|
||||||
progressWrapper.lastSentSeekRatio = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Binding {
|
|
||||||
target: progressSlider
|
|
||||||
property: "value"
|
|
||||||
value: progressWrapper.progressRatio
|
|
||||||
when: !MusicManager.isSeeking
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Media controls
|
|
||||||
RowLayout {
|
|
||||||
spacing: Style.marginS
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "media-prev"
|
|
||||||
visible: MusicManager.canGoPrevious
|
|
||||||
onClicked: MusicManager.canGoPrevious ? MusicManager.previous() : {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: MusicManager.isPlaying ? "media-pause" : "media-play"
|
|
||||||
visible: (MusicManager.canPlay || MusicManager.canPause)
|
|
||||||
onClicked: (MusicManager.canPlay || MusicManager.canPause) ? MusicManager.playPause() : {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "media-next"
|
|
||||||
visible: MusicManager.canGoNext
|
|
||||||
onClicked: MusicManager.canGoNext ? MusicManager.next() : {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Modules.Panel.Misc
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: sunsetControlRow
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
id: barLyricsButton
|
|
||||||
|
|
||||||
implicitHeight: 32
|
|
||||||
implicitWidth: 32
|
|
||||||
colorBg: SunsetService.isRunning ? Colors.flamingo : Color.transparent
|
|
||||||
colorBgHover: Colors.flamingo
|
|
||||||
colorFg: SunsetService.isRunning ? Colors.base : Colors.flamingo
|
|
||||||
icon: "sunset-2"
|
|
||||||
onClicked: SunsetService.toggleSunset()
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: SunsetService.isRunning ? "Temp: " + SunsetService.temperature + " K" : "Sunset Off"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NBox {
|
|
||||||
id: monitors
|
|
||||||
|
|
||||||
compact: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: content
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginS
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
MonitorSlider {
|
|
||||||
icon: "cpu-usage"
|
|
||||||
value: SystemStatService.cpuUsage
|
|
||||||
from: 0
|
|
||||||
to: 100
|
|
||||||
colorFill: Colors.teal
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MonitorSlider {
|
|
||||||
icon: "memory"
|
|
||||||
value: SystemStatService.memPercent
|
|
||||||
from: 0
|
|
||||||
to: 100
|
|
||||||
colorFill: Colors.green
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MonitorSlider {
|
|
||||||
icon: "cpu-temperature"
|
|
||||||
value: SystemStatService.cpuTemp
|
|
||||||
from: 0
|
|
||||||
to: 100
|
|
||||||
colorFill: Colors.yellow
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MonitorSlider {
|
|
||||||
icon: "storage"
|
|
||||||
value: SystemStatService.diskPercent
|
|
||||||
from: 0
|
|
||||||
to: 100
|
|
||||||
colorFill: Colors.peach
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Effects
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Services.UPower
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
readonly property bool hasPP: PowerProfileService.available
|
|
||||||
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
NBox {
|
|
||||||
id: whoamiBox
|
|
||||||
|
|
||||||
property string uptimeText: "--"
|
|
||||||
property string hostname: "--"
|
|
||||||
|
|
||||||
function updateSystemInfo() {
|
|
||||||
uptimeProcess.running = true;
|
|
||||||
hostnameProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: content
|
|
||||||
|
|
||||||
spacing: root.spacing
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: root.spacing
|
|
||||||
|
|
||||||
NImageCircled {
|
|
||||||
width: Style.baseWidgetSize * 1.5
|
|
||||||
height: Style.baseWidgetSize * 1.5
|
|
||||||
imagePath: Quickshell.shellDir + "/Assets/Images/Avatar.jpg"
|
|
||||||
fallbackIcon: "person"
|
|
||||||
borderColor: Color.mPrimary
|
|
||||||
borderWidth: Math.max(1, Style.borderM)
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
Layout.topMargin: Style.marginXS
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Style.marginXXS
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: `${Quickshell.env("USER") || "user"} @ ${whoamiBox.hostname}`
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
font.pointSize: Style.fontSizeL
|
|
||||||
font.capitalization: Font.Capitalize
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Uptime: " + whoamiBox.uptimeText
|
|
||||||
font.pointSize: Style.fontSizeM
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------
|
|
||||||
// Uptime
|
|
||||||
Timer {
|
|
||||||
interval: 60000
|
|
||||||
repeat: true
|
|
||||||
running: true
|
|
||||||
onTriggered: uptimeProcess.running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: uptimeProcess
|
|
||||||
|
|
||||||
command: ["cat", "/proc/uptime"]
|
|
||||||
running: true
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0]);
|
|
||||||
whoamiBox.uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds);
|
|
||||||
uptimeProcess.running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: hostnameProcess
|
|
||||||
|
|
||||||
command: ["cat", "/etc/hostname"]
|
|
||||||
running: true
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
whoamiBox.hostname = this.text.trim();
|
|
||||||
hostnameProcess.running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: utilitiesRow
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
// Performance
|
|
||||||
NIconButton {
|
|
||||||
implicitHeight: 32
|
|
||||||
implicitWidth: 32
|
|
||||||
icon: PowerProfileService.getIcon(PowerProfile.Performance)
|
|
||||||
enabled: hasPP
|
|
||||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
|
||||||
colorBgHover: Colors.red
|
|
||||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Colors.red : Color.transparent
|
|
||||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Colors.red
|
|
||||||
onClicked: PowerProfileService.setProfile(PowerProfile.Performance)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Balanced
|
|
||||||
NIconButton {
|
|
||||||
implicitHeight: 32
|
|
||||||
implicitWidth: 32
|
|
||||||
icon: PowerProfileService.getIcon(PowerProfile.Balanced)
|
|
||||||
enabled: hasPP
|
|
||||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
|
||||||
colorBgHover: Colors.blue
|
|
||||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Colors.blue : Color.transparent
|
|
||||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Colors.blue
|
|
||||||
onClicked: PowerProfileService.setProfile(PowerProfile.Balanced)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eco
|
|
||||||
NIconButton {
|
|
||||||
implicitHeight: 32
|
|
||||||
implicitWidth: 32
|
|
||||||
icon: PowerProfileService.getIcon(PowerProfile.PowerSaver)
|
|
||||||
enabled: hasPP
|
|
||||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
|
||||||
colorBgHover: Colors.green
|
|
||||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Colors.green : Color.transparent
|
|
||||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Colors.green
|
|
||||||
onClicked: PowerProfileService.setProfile(PowerProfile.PowerSaver)
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lyrics Offset
|
|
||||||
NText {
|
|
||||||
text: `Lyrics Offset: ${LyricsService.offset >= 0 ? '+' : ''}${LyricsService.offset} ms`
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Services.Pipewire
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Modules.Panel.Cards
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
NPanel {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// Positioning
|
|
||||||
readonly property string controlCenterPosition: "top_left"
|
|
||||||
property real topCardHeight: 120
|
|
||||||
property real middleCardHeight: 100
|
|
||||||
property real bottomCardHeight: 200
|
|
||||||
|
|
||||||
preferredWidth: 480
|
|
||||||
preferredHeight: topCardHeight + middleCardHeight + bottomCardHeight + Style.marginL * 4
|
|
||||||
panelKeyboardFocus: false
|
|
||||||
panelAnchorHorizontalCenter: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_center")
|
|
||||||
panelAnchorVerticalCenter: false
|
|
||||||
panelAnchorLeft: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_left")
|
|
||||||
panelAnchorRight: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_right")
|
|
||||||
panelAnchorBottom: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("bottom_")
|
|
||||||
panelAnchorTop: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("top_")
|
|
||||||
|
|
||||||
panelContent: Item {
|
|
||||||
id: content
|
|
||||||
|
|
||||||
property real cardSpacing: Style.marginL
|
|
||||||
|
|
||||||
// Layout content
|
|
||||||
ColumnLayout {
|
|
||||||
id: layout
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: content.cardSpacing
|
|
||||||
spacing: content.cardSpacing
|
|
||||||
|
|
||||||
// Top Card: profile + utilities
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: topCardHeight
|
|
||||||
|
|
||||||
TopLeftCard {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumHeight: topCardHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
LyricsControl {
|
|
||||||
Layout.preferredHeight: topCardHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
LyricsCard {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: middleCardHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// Media + stats column
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: bottomCardHeight
|
|
||||||
spacing: content.cardSpacing
|
|
||||||
|
|
||||||
SystemMonitorCard {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: bottomCardHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaCard {
|
|
||||||
Layout.preferredWidth: 270
|
|
||||||
Layout.preferredHeight: bottomCardHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string icon: "volume-high"
|
|
||||||
property real value: 50
|
|
||||||
property real from: 0
|
|
||||||
property real to: 100
|
|
||||||
property color colorFill: Colors.primary
|
|
||||||
property color colorRest: Colors.surface0
|
|
||||||
|
|
||||||
implicitHeight: layout.implicitHeight
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: layout
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
id: iconItem
|
|
||||||
|
|
||||||
icon: root.icon
|
|
||||||
color: root.colorFill
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: whole
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: root.colorRest
|
|
||||||
radius: height / 2
|
|
||||||
height: Style.baseWidgetSize * 0.3
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: fill
|
|
||||||
|
|
||||||
width: Math.max(0, Math.min(whole.width, (root.value - root.from) / (root.to - root.from) * whole.width))
|
|
||||||
height: parent.height
|
|
||||||
color: root.colorFill
|
|
||||||
radius: height / 2
|
|
||||||
anchors.left: parent.left
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,341 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Services.Notifications
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
// Notification History panel
|
|
||||||
NPanel {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
preferredWidth: 380
|
|
||||||
preferredHeight: 480
|
|
||||||
|
|
||||||
panelContent: Rectangle {
|
|
||||||
id: notificationRect
|
|
||||||
|
|
||||||
color: Color.transparent
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginL
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
// Header section
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "bell"
|
|
||||||
pointSize: Style.fontSizeXXL
|
|
||||||
color: Color.mPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Notifications"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Color.mOnSurface
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: SettingsService.notifications.doNotDisturb ? "bell-off" : "bell"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: SettingsService.notifications.doNotDisturb = !SettingsService.notifications.doNotDisturb
|
|
||||||
colorFg: SettingsService.notifications.doNotDisturb ? Colors.base : Colors.green
|
|
||||||
colorBg: SettingsService.notifications.doNotDisturb ? Colors.green : Color.transparent
|
|
||||||
colorFgHover: Colors.base
|
|
||||||
colorBgHover: Colors.green
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "trash"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: {
|
|
||||||
NotificationService.clearHistory();
|
|
||||||
// Close panel as there is nothing more to see.
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
colorFg: Colors.red
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorFgHover: Colors.base
|
|
||||||
colorBgHover: Colors.red
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "close"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: root.close()
|
|
||||||
colorFg: Colors.blue
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorFgHover: Colors.base
|
|
||||||
colorBgHover: Colors.blue
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NDivider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty state when no notifications
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
visible: NotificationService.historyList.count === 0
|
|
||||||
spacing: Style.marginL
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "bell-off"
|
|
||||||
pointSize: 64
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "No Notifications"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification list
|
|
||||||
NListView {
|
|
||||||
id: notificationList
|
|
||||||
|
|
||||||
// Track which notification is expanded
|
|
||||||
property string expandedId: ""
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
horizontalPolicy: ScrollBar.AlwaysOff
|
|
||||||
verticalPolicy: ScrollBar.AsNeeded
|
|
||||||
model: NotificationService.historyList
|
|
||||||
spacing: Style.marginM
|
|
||||||
clip: true
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
visible: NotificationService.historyList.count > 0
|
|
||||||
|
|
||||||
delegate: NBox {
|
|
||||||
property string notificationId: model.id
|
|
||||||
property bool isExpanded: notificationList.expandedId === notificationId
|
|
||||||
|
|
||||||
width: notificationList.width
|
|
||||||
height: notificationLayout.implicitHeight + (Style.marginM * 2)
|
|
||||||
|
|
||||||
// Click to expand/collapse
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
// Don't capture clicks on the delete button
|
|
||||||
anchors.rightMargin: 48
|
|
||||||
enabled: (summaryText.truncated || bodyText.truncated)
|
|
||||||
onClicked: {
|
|
||||||
if (notificationList.expandedId === notificationId)
|
|
||||||
notificationList.expandedId = "";
|
|
||||||
else
|
|
||||||
notificationList.expandedId = notificationId;
|
|
||||||
}
|
|
||||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: notificationLayout
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginM
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
NImageCircled {
|
|
||||||
Layout.preferredWidth: 40
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
Layout.topMargin: 20
|
|
||||||
imagePath: model.cachedImage || model.originalImage || ""
|
|
||||||
borderColor: Color.transparent
|
|
||||||
borderWidth: 0
|
|
||||||
fallbackIcon: "bell"
|
|
||||||
fallbackIconSize: 24
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification content column
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
spacing: Style.marginXS
|
|
||||||
Layout.rightMargin: -(Style.marginM + Style.baseWidgetSize * 0.6)
|
|
||||||
|
|
||||||
// Header row with app name and timestamp
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
// Urgency indicator
|
|
||||||
Rectangle {
|
|
||||||
Layout.preferredWidth: 6
|
|
||||||
Layout.preferredHeight: 6
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
radius: 3
|
|
||||||
visible: model.urgency !== 1
|
|
||||||
color: {
|
|
||||||
if (model.urgency === 2)
|
|
||||||
return Color.mError;
|
|
||||||
else if (model.urgency === 0)
|
|
||||||
return Color.mOnSurfaceVariant;
|
|
||||||
else
|
|
||||||
return Color.transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: model.appName || "Unknown App"
|
|
||||||
pointSize: Style.fontSizeXS
|
|
||||||
color: Color.mSecondary
|
|
||||||
family: Fonts.sans
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: Time.formatRelativeTime(model.timestamp)
|
|
||||||
pointSize: Style.fontSizeXS
|
|
||||||
color: Color.mSecondary
|
|
||||||
family: Fonts.sans
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summary
|
|
||||||
NText {
|
|
||||||
id: summaryText
|
|
||||||
|
|
||||||
text: model.summary || "No Summary"
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Color.mOnSurface
|
|
||||||
textFormat: Text.PlainText
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
maximumLineCount: isExpanded ? 999 : 2
|
|
||||||
family: Fonts.sans
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
// Body
|
|
||||||
NText {
|
|
||||||
id: bodyText
|
|
||||||
|
|
||||||
text: model.body || ""
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
textFormat: Text.PlainText
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
maximumLineCount: isExpanded ? 999 : 3
|
|
||||||
elide: Text.ElideRight
|
|
||||||
family: Fonts.sans
|
|
||||||
visible: text.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spacer for expand indicator
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: (!isExpanded && (summaryText.truncated || bodyText.truncated)) ? (Style.marginS) : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand indicator
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: !isExpanded && (summaryText.truncated || bodyText.truncated)
|
|
||||||
spacing: Style.marginXS
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Click to expand"
|
|
||||||
pointSize: Style.fontSizeXS
|
|
||||||
color: Color.mPrimary
|
|
||||||
family: Fonts.sans
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "chevron-down"
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
color: Color.mPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete button
|
|
||||||
NIconButton {
|
|
||||||
icon: "trash"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.7
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
onClicked: {
|
|
||||||
// Remove from history using the service API
|
|
||||||
NotificationService.removeFromHistory(notificationId);
|
|
||||||
}
|
|
||||||
colorFg: Colors.red
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorFgHover: Colors.base
|
|
||||||
colorBgHover: Colors.red
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationNormal
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Smooth color transition on hover
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,633 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
NPanel {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string passwordSsid: ""
|
|
||||||
property string passwordInput: ""
|
|
||||||
property string expandedSsid: ""
|
|
||||||
|
|
||||||
preferredWidth: 400
|
|
||||||
preferredHeight: 500
|
|
||||||
onOpened: NetworkService.scan()
|
|
||||||
|
|
||||||
panelContent: Rectangle {
|
|
||||||
color: Color.transparent
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginL
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
// Header
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: SettingsService.wifiEnabled ? "wifi" : "wifi-off"
|
|
||||||
pointSize: Style.fontSizeXXL
|
|
||||||
color: SettingsService.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "WiFi"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Color.mOnSurface
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NToggle {
|
|
||||||
id: wifiSwitch
|
|
||||||
|
|
||||||
checked: SettingsService.wifiEnabled
|
|
||||||
onToggled: (checked) => {
|
|
||||||
return NetworkService.setWifiEnabled(checked);
|
|
||||||
}
|
|
||||||
baseSize: Style.baseWidgetSize * 0.65
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "refresh"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
enabled: SettingsService.wifiEnabled && !NetworkService.scanning
|
|
||||||
onClicked: NetworkService.scan()
|
|
||||||
colorFg: Colors.green
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorFgHover: Colors.base
|
|
||||||
colorBgHover: Colors.green
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "close"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: root.close()
|
|
||||||
colorFg: Colors.red
|
|
||||||
colorBg: Color.transparent
|
|
||||||
colorFgHover: Colors.base
|
|
||||||
colorBgHover: Colors.red
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NDivider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error message
|
|
||||||
Rectangle {
|
|
||||||
visible: NetworkService.lastError.length > 0
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: errorRow.implicitHeight + (Style.marginM * 2)
|
|
||||||
color: Qt.rgba(Color.mError.r, Color.mError.g, Color.mError.b, 0.1)
|
|
||||||
radius: Style.radiusS
|
|
||||||
border.width: Math.max(1, Style.borderS)
|
|
||||||
border.color: Color.mError
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: errorRow
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginM
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "warning"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
color: Color.mError
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: NetworkService.lastError
|
|
||||||
color: Color.mError
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "close"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.6
|
|
||||||
onClicked: NetworkService.lastError = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main content area
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
color: Color.transparent
|
|
||||||
|
|
||||||
// WiFi disabled state
|
|
||||||
ColumnLayout {
|
|
||||||
visible: !SettingsService.wifiEnabled
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "wifi-off"
|
|
||||||
pointSize: 64
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Wi-Fi Disabled"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Please enable Wi-Fi to connect to a network."
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scanning state
|
|
||||||
ColumnLayout {
|
|
||||||
visible: SettingsService.wifiEnabled && NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Style.marginL
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NBusyIndicator {
|
|
||||||
running: true
|
|
||||||
color: Color.mPrimary
|
|
||||||
size: Style.baseWidgetSize
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Searching for networks..."
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Networks list container
|
|
||||||
NScrollView {
|
|
||||||
visible: SettingsService.wifiEnabled && (!NetworkService.scanning || Object.keys(NetworkService.networks).length > 0)
|
|
||||||
anchors.fill: parent
|
|
||||||
horizontalPolicy: ScrollBar.AlwaysOff
|
|
||||||
verticalPolicy: ScrollBar.AsNeeded
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
// Network list
|
|
||||||
Repeater {
|
|
||||||
model: {
|
|
||||||
if (!SettingsService.wifiEnabled)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
const nets = Object.values(NetworkService.networks);
|
|
||||||
return nets.sort((a, b) => {
|
|
||||||
if (a.connected !== b.connected)
|
|
||||||
return b.connected - a.connected;
|
|
||||||
|
|
||||||
return b.signal - a.signal;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
NBox {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: netColumn.implicitHeight + (Style.marginM * 2)
|
|
||||||
// Add opacity for operations in progress
|
|
||||||
opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: netColumn
|
|
||||||
|
|
||||||
width: parent.width - (Style.marginM * 2)
|
|
||||||
x: Style.marginM
|
|
||||||
y: Style.marginM
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
// Main row
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: NetworkService.signalIcon(modelData.signal)
|
|
||||||
pointSize: Style.fontSizeXXL
|
|
||||||
color: modelData.connected ? Color.mPrimary : Color.mOnSurface
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: modelData.ssid
|
|
||||||
pointSize: Style.fontSizeM
|
|
||||||
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
|
|
||||||
color: Color.mOnSurface
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
spacing: Style.marginXS
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: `${modelData.signal}%`
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "•"
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open"
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: Style.marginXXS
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the status badges area (around line 237)
|
|
||||||
Rectangle {
|
|
||||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
|
||||||
color: Color.mPrimary
|
|
||||||
radius: height * 0.5
|
|
||||||
width: connectedText.implicitWidth + (Style.marginS * 2)
|
|
||||||
height: connectedText.implicitHeight + (Style.marginXXS * 2)
|
|
||||||
|
|
||||||
NText {
|
|
||||||
id: connectedText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Connected"
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
color: Color.mOnPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: NetworkService.disconnectingFrom === modelData.ssid
|
|
||||||
color: Color.mError
|
|
||||||
radius: height * 0.5
|
|
||||||
width: disconnectingText.implicitWidth + (Style.marginS * 2)
|
|
||||||
height: disconnectingText.implicitHeight + (Style.marginXXS * 2)
|
|
||||||
|
|
||||||
NText {
|
|
||||||
id: disconnectingText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "disconnecting"
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
color: Color.mOnPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: NetworkService.forgettingNetwork === modelData.ssid
|
|
||||||
color: Color.mError
|
|
||||||
radius: height * 0.5
|
|
||||||
width: forgettingText.implicitWidth + (Style.marginS * 2)
|
|
||||||
height: forgettingText.implicitHeight + (Style.marginXXS * 2)
|
|
||||||
|
|
||||||
NText {
|
|
||||||
id: forgettingText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "forgetting"
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
color: Color.mOnPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: modelData.cached && !modelData.connected && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
|
||||||
color: Color.transparent
|
|
||||||
border.color: Color.mOutline
|
|
||||||
border.width: Math.max(1, Style.borderS)
|
|
||||||
radius: height * 0.5
|
|
||||||
width: savedText.implicitWidth + (Style.marginS * 2)
|
|
||||||
height: savedText.implicitHeight + (Style.marginXXS * 2)
|
|
||||||
|
|
||||||
NText {
|
|
||||||
id: savedText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "saved"
|
|
||||||
pointSize: Style.fontSizeXXS
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action area
|
|
||||||
RowLayout {
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
NBusyIndicator {
|
|
||||||
visible: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid
|
|
||||||
running: visible
|
|
||||||
color: Color.mPrimary
|
|
||||||
size: Style.baseWidgetSize * 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
|
||||||
icon: "trash"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid
|
|
||||||
}
|
|
||||||
|
|
||||||
NButton {
|
|
||||||
visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && passwordSsid !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
|
||||||
text: {
|
|
||||||
if (modelData.existing || modelData.cached)
|
|
||||||
return "Connect";
|
|
||||||
|
|
||||||
if (!NetworkService.isSecured(modelData.security))
|
|
||||||
return "Connect";
|
|
||||||
|
|
||||||
return "Enter Password";
|
|
||||||
}
|
|
||||||
outlined: !hovered
|
|
||||||
fontSize: Style.fontSizeXS
|
|
||||||
enabled: !NetworkService.connecting
|
|
||||||
onClicked: {
|
|
||||||
if (modelData.existing || modelData.cached || !NetworkService.isSecured(modelData.security)) {
|
|
||||||
NetworkService.connect(modelData.ssid);
|
|
||||||
} else {
|
|
||||||
passwordSsid = modelData.ssid;
|
|
||||||
passwordInput = "";
|
|
||||||
expandedSsid = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NButton {
|
|
||||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
|
||||||
text: "Disconnect"
|
|
||||||
outlined: !hovered
|
|
||||||
fontSize: Style.fontSizeXS
|
|
||||||
backgroundColor: Color.mError
|
|
||||||
onClicked: NetworkService.disconnect(modelData.ssid)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password input
|
|
||||||
Rectangle {
|
|
||||||
visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
|
||||||
Layout.fillWidth: true
|
|
||||||
height: passwordRow.implicitHeight + Style.marginS * 2
|
|
||||||
color: Color.mSurfaceVariant
|
|
||||||
border.color: Color.mOutline
|
|
||||||
border.width: Math.max(1, Style.borderS)
|
|
||||||
radius: Style.radiusS
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: passwordRow
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginS
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
radius: Style.radiusXS
|
|
||||||
color: Color.mSurface
|
|
||||||
border.color: pwdInput.activeFocus ? Color.mSecondary : Color.mOutline
|
|
||||||
border.width: Math.max(1, Style.borderS)
|
|
||||||
|
|
||||||
TextInput {
|
|
||||||
id: pwdInput
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.margins: Style.marginS
|
|
||||||
text: passwordInput
|
|
||||||
font.family: Fonts.sans
|
|
||||||
font.pointSize: Style.fontSizeS
|
|
||||||
color: Color.mOnSurface
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
selectByMouse: true
|
|
||||||
focus: visible
|
|
||||||
passwordCharacter: "●"
|
|
||||||
onTextChanged: passwordInput = text
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible)
|
|
||||||
forceActiveFocus();
|
|
||||||
|
|
||||||
}
|
|
||||||
onAccepted: {
|
|
||||||
if (text && !NetworkService.connecting) {
|
|
||||||
NetworkService.connect(passwordSsid, text);
|
|
||||||
passwordSsid = "";
|
|
||||||
passwordInput = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
visible: parent.text.length === 0
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Enter Password"
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NButton {
|
|
||||||
text: "Connect"
|
|
||||||
fontSize: Style.fontSizeXXS
|
|
||||||
enabled: passwordInput.length > 0 && !NetworkService.connecting
|
|
||||||
outlined: true
|
|
||||||
onClicked: {
|
|
||||||
NetworkService.connect(passwordSsid, passwordInput);
|
|
||||||
passwordSsid = "";
|
|
||||||
passwordInput = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "close"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: {
|
|
||||||
passwordSsid = "";
|
|
||||||
passwordInput = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forget network
|
|
||||||
Rectangle {
|
|
||||||
visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
|
||||||
Layout.fillWidth: true
|
|
||||||
height: forgetRow.implicitHeight + Style.marginS * 2
|
|
||||||
color: Color.mSurfaceVariant
|
|
||||||
radius: Style.radiusS
|
|
||||||
border.width: Math.max(1, Style.borderS)
|
|
||||||
border.color: Color.mOutline
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: forgetRow
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginS
|
|
||||||
spacing: Style.marginM
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
NIcon {
|
|
||||||
icon: "trash"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
color: Color.mError
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "Forget this network?"
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
color: Color.mError
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
NButton {
|
|
||||||
id: forgetButton
|
|
||||||
|
|
||||||
text: "Forget"
|
|
||||||
fontSize: Style.fontSizeXXS
|
|
||||||
backgroundColor: Color.mError
|
|
||||||
outlined: forgetButton.hovered ? false : true
|
|
||||||
onClicked: {
|
|
||||||
NetworkService.forget(modelData.ssid);
|
|
||||||
expandedSsid = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
icon: "close"
|
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
|
||||||
onClicked: expandedSsid = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Smooth opacity animation
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationNormal
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty state when no networks
|
|
||||||
ColumnLayout {
|
|
||||||
visible: SettingsService.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Style.marginL
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: "search"
|
|
||||||
pointSize: 64
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: "No networks found"
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
color: Color.mOnSurfaceVariant
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
NButton {
|
|
||||||
text: "Scan Again"
|
|
||||||
icon: "refresh"
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
onClicked: NetworkService.scan()
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Modules.Misc
|
||||||
|
import qs.Modules.Sidebar
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int barWidth: Style.sidebarWidth
|
||||||
|
property int realWidth: 0
|
||||||
|
property bool isOpen: false
|
||||||
|
property Component contentComponent: null
|
||||||
|
property bool isLeft: true
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
realWidth = barWidth;
|
||||||
|
isOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
realWidth = 0;
|
||||||
|
isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (root.isLeft)
|
||||||
|
BarService.registerLeft(modelData.name, root);
|
||||||
|
else
|
||||||
|
BarService.registerRight(modelData.name, root);
|
||||||
|
}
|
||||||
|
screen: modelData
|
||||||
|
WlrLayershell.namespace: root.isLeft ? "quickshell-sidebar-left" : "quickshell-sidebar-right"
|
||||||
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
implicitWidth: realWidth > 0 ? barWidth : 0 // jump change for better performance (maybe)
|
||||||
|
visible: realWidth > 0
|
||||||
|
margins.top: Style.barHeight
|
||||||
|
color: Colors.transparent
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: isLeft
|
||||||
|
top: true
|
||||||
|
bottom: true
|
||||||
|
right: !isLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: sidebarContent
|
||||||
|
|
||||||
|
width: root.barWidth
|
||||||
|
height: parent.height
|
||||||
|
x: isLeft ? root.realWidth - root.barWidth : root.barWidth - root.realWidth
|
||||||
|
color: Colors.mSurface
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.realWidth > 0
|
||||||
|
asynchronous: true
|
||||||
|
sourceComponent: root.contentComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: sidebarContent
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on realWidth {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationSlow
|
||||||
|
easing.type: Easing.InOutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+34
-33
@@ -5,7 +5,7 @@ import Quickshell
|
|||||||
import Quickshell.Bluetooth
|
import Quickshell.Bluetooth
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Noctalia
|
import qs.Components
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -17,12 +17,12 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Style.marginM
|
spacing: Style.marginS
|
||||||
|
|
||||||
NText {
|
UText {
|
||||||
text: root.label
|
text: root.label
|
||||||
pointSize: Style.fontSizeL
|
pointSize: Style.fontSizeL
|
||||||
color: Color.mSecondary
|
color: Colors.mPrimary
|
||||||
font.weight: Style.fontWeightMedium
|
font.weight: Style.fontWeightMedium
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: root.model.length > 0
|
visible: root.model.length > 0
|
||||||
@@ -35,39 +35,41 @@ ColumnLayout {
|
|||||||
model: root.model
|
model: root.model
|
||||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||||
|
|
||||||
NBox {
|
UBox {
|
||||||
id: device
|
id: device
|
||||||
|
|
||||||
readonly property bool canConnect: BluetoothService.canConnect(modelData)
|
readonly property bool canConnect: BluetoothService.canConnect(modelData)
|
||||||
readonly property bool canDisconnect: BluetoothService.canDisconnect(modelData)
|
readonly property bool canDisconnect: BluetoothService.canDisconnect(modelData)
|
||||||
readonly property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
readonly property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
||||||
|
|
||||||
function getContentColor(defaultColor = Color.mOnSurface) {
|
compact: true
|
||||||
|
|
||||||
|
function getContentColor(defaultColor = Colors.mOnSurface) {
|
||||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
||||||
return Color.mPrimary;
|
return Colors.mPrimary;
|
||||||
|
|
||||||
if (modelData.blocked)
|
if (modelData.blocked)
|
||||||
return Color.mError;
|
return Colors.mError;
|
||||||
|
|
||||||
return defaultColor;
|
return defaultColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: deviceLayout.implicitHeight + (Style.marginM * 2)
|
Layout.preferredHeight: deviceLayout.implicitHeight + (Style.marginS * 2)
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: deviceLayout
|
id: deviceLayout
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Style.marginM
|
anchors.margins: Style.marginS
|
||||||
spacing: Style.marginM
|
spacing: Style.marginS
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
// One device BT icon
|
// One device BT icon
|
||||||
NIcon {
|
UIcon {
|
||||||
icon: BluetoothService.getDeviceIcon(modelData)
|
iconName: BluetoothService.getDeviceIcon(modelData)
|
||||||
pointSize: Style.fontSizeXXL
|
iconSize: Style.fontSizeXXL
|
||||||
color: getContentColor(Color.mOnSurface)
|
color: getContentColor(Colors.mOnSurface)
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,21 +78,21 @@ ColumnLayout {
|
|||||||
spacing: Style.marginXXS
|
spacing: Style.marginXXS
|
||||||
|
|
||||||
// Device name
|
// Device name
|
||||||
NText {
|
UText {
|
||||||
text: modelData.name || modelData.deviceName
|
text: modelData.name || modelData.deviceName
|
||||||
pointSize: Style.fontSizeM
|
pointSize: Style.fontSizeM
|
||||||
font.weight: Style.fontWeightMedium
|
font.weight: Style.fontWeightMedium
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
color: getContentColor(Color.mOnSurface)
|
color: getContentColor(Colors.mOnSurface)
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
NText {
|
UText {
|
||||||
text: BluetoothService.getStatusString(modelData)
|
text: BluetoothService.getStatusString(modelData)
|
||||||
visible: text !== ""
|
visible: text !== ""
|
||||||
pointSize: Style.fontSizeXS
|
pointSize: Style.fontSizeXS
|
||||||
color: getContentColor(Color.mOnSurfaceVariant)
|
color: getContentColor(Colors.mOnSurfaceVariant)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal Strength
|
// Signal Strength
|
||||||
@@ -100,34 +102,34 @@ ColumnLayout {
|
|||||||
spacing: Style.marginXS
|
spacing: Style.marginXS
|
||||||
|
|
||||||
// Device signal strength - "Unknown" when not connected
|
// Device signal strength - "Unknown" when not connected
|
||||||
NText {
|
UText {
|
||||||
text: BluetoothService.getSignalStrength(modelData)
|
text: BluetoothService.getSignalStrength(modelData)
|
||||||
pointSize: Style.fontSizeXS
|
pointSize: Style.fontSizeXS
|
||||||
color: getContentColor(Color.mOnSurfaceVariant)
|
color: getContentColor(Colors.mOnSurfaceVariant)
|
||||||
}
|
}
|
||||||
|
|
||||||
NIcon {
|
UIcon {
|
||||||
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
||||||
icon: BluetoothService.getSignalIcon(modelData)
|
iconName: BluetoothService.getSignalIcon(modelData)
|
||||||
pointSize: Style.fontSizeXS
|
iconSize: Style.fontSizeXS
|
||||||
color: getContentColor(Color.mOnSurface)
|
color: getContentColor(Colors.mOnSurface)
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
UText {
|
||||||
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
||||||
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
|
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
|
||||||
pointSize: Style.fontSizeXS
|
pointSize: Style.fontSizeXS
|
||||||
color: getContentColor(Color.mOnSurface)
|
color: getContentColor(Colors.mOnSurface)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery
|
// Battery
|
||||||
NText {
|
UText {
|
||||||
visible: modelData.batteryAvailable
|
visible: modelData.batteryAvailable
|
||||||
text: BluetoothService.getBattery(modelData)
|
text: BluetoothService.getBattery(modelData)
|
||||||
pointSize: Style.fontSizeXS
|
pointSize: Style.fontSizeXS
|
||||||
color: getContentColor(Color.mOnSurfaceVariant)
|
color: getContentColor(Colors.mOnSurfaceVariant)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -138,19 +140,18 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call to action
|
// Call to action
|
||||||
NButton {
|
UButton {
|
||||||
id: button
|
id: button
|
||||||
|
|
||||||
visible: (modelData.state !== BluetoothDeviceState.Connecting)
|
visible: (modelData.state !== BluetoothDeviceState.Connecting)
|
||||||
enabled: (canConnect || canDisconnect) && !isBusy
|
enabled: (canConnect || canDisconnect) && !isBusy
|
||||||
outlined: !button.hovered
|
|
||||||
fontSize: Style.fontSizeXS
|
fontSize: Style.fontSizeXS
|
||||||
fontWeight: Style.fontWeightMedium
|
fontWeight: Style.fontWeightMedium
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
if (device.canDisconnect && !isBusy)
|
if (device.canDisconnect && !isBusy)
|
||||||
return Color.mError;
|
return Colors.mError;
|
||||||
|
|
||||||
return Color.mPrimary;
|
return Colors.mPrimary;
|
||||||
}
|
}
|
||||||
text: {
|
text: {
|
||||||
if (modelData.pairing)
|
if (modelData.pairing)
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Bluetooth
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
import qs.Components
|
||||||
|
import qs.Modules.Sidebar.Misc
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
color: Colors.transparent
|
||||||
|
|
||||||
|
// Center the content within this rectangle
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
iconName: "bluetooth-off"
|
||||||
|
iconSize: 64
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Bluetooth is turned off"
|
||||||
|
pointSize: Style.fontSizeL
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Enable Bluetooth"
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UScrollView {
|
||||||
|
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
horizontalPolicy: ScrollBar.AlwaysOff
|
||||||
|
verticalPolicy: ScrollBar.AsNeeded
|
||||||
|
clip: true
|
||||||
|
contentWidth: availableWidth
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
// Connected devices
|
||||||
|
BluetoothDevicesList {
|
||||||
|
property var items: {
|
||||||
|
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var filtered = Bluetooth.devices.values.filter((dev) => {
|
||||||
|
return dev && !dev.blocked && dev.connected;
|
||||||
|
});
|
||||||
|
return BluetoothService.sortDevices(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
label: "Connected Devices"
|
||||||
|
model: items
|
||||||
|
visible: items.length > 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Known devices
|
||||||
|
BluetoothDevicesList {
|
||||||
|
property var items: {
|
||||||
|
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var filtered = Bluetooth.devices.values.filter((dev) => {
|
||||||
|
return dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted);
|
||||||
|
});
|
||||||
|
return BluetoothService.sortDevices(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
label: "Known Devices"
|
||||||
|
model: items
|
||||||
|
visible: items.length > 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available devices
|
||||||
|
BluetoothDevicesList {
|
||||||
|
property var items: {
|
||||||
|
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var filtered = Bluetooth.devices.values.filter((dev) => {
|
||||||
|
return dev && !dev.blocked && !dev.paired && !dev.trusted;
|
||||||
|
});
|
||||||
|
return BluetoothService.sortDevices(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
label: "Available Devices"
|
||||||
|
model: items
|
||||||
|
visible: items.length > 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback - No devices, scanning
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: Style.marginL
|
||||||
|
|
||||||
|
visible: {
|
||||||
|
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var availableCount = Bluetooth.devices.values.filter((dev) => {
|
||||||
|
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||||
|
}).length;
|
||||||
|
return (availableCount === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
UBusyIndicator {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
running: true
|
||||||
|
width: Style.fontSizeL
|
||||||
|
height: Style.fontSizeL
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Pairing Mode"
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
import qs.Utils
|
||||||
|
|
||||||
|
UBox {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Internal state
|
||||||
|
readonly property var now: Time.now
|
||||||
|
readonly property bool weatherReady: LocationService.data.weather !== null
|
||||||
|
// Expose current month/year for potential synchronization with CalendarMonthCard
|
||||||
|
readonly property int currentMonth: now.getMonth()
|
||||||
|
readonly property int currentYear: now.getFullYear()
|
||||||
|
|
||||||
|
implicitHeight: (60) + Style.marginM * 2
|
||||||
|
color: Colors.mPrimary
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: capsuleColumn
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.topMargin: Style.marginM
|
||||||
|
anchors.bottomMargin: Style.marginM
|
||||||
|
anchors.rightMargin: clockLoader.width + Style.marginXL * 2
|
||||||
|
anchors.leftMargin: Style.marginXL
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
// Combined layout for date, month year, location and time-zone
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 60
|
||||||
|
clip: true
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
// Today day number
|
||||||
|
UText {
|
||||||
|
Layout.preferredWidth: implicitWidth
|
||||||
|
elide: Text.ElideNone
|
||||||
|
clip: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||||
|
text: root.now.getDate()
|
||||||
|
pointSize: Style.fontSizeXXXL * 1.5
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Colors.mOnPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
// Month, year, location
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||||
|
Layout.bottomMargin: Style.marginXXS
|
||||||
|
Layout.topMargin: -Style.marginXXS
|
||||||
|
spacing: -Style.marginXS
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][root.currentMonth]
|
||||||
|
pointSize: Style.fontSizeXL * 1.1
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Colors.mOnPrimary
|
||||||
|
Layout.alignment: Qt.AlignBaseline
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: `${root.currentYear}`
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Qt.alpha(Colors.mOnPrimary, 0.7)
|
||||||
|
Layout.alignment: Qt.AlignBaseline
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: {
|
||||||
|
if (!root.weatherReady)
|
||||||
|
return "Loading weather...";
|
||||||
|
|
||||||
|
const chunks = SettingsService.location.split(",");
|
||||||
|
return chunks[0];
|
||||||
|
}
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
color: Colors.mOnPrimary
|
||||||
|
Layout.maximumWidth: 150
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: root.weatherReady && ` (${LocationService.data.weather.timezone_abbreviation})`
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
color: Qt.alpha(Colors.mOnPrimary, 0.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacer
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analog/Digital clock
|
||||||
|
UClock {
|
||||||
|
id: clockLoader
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Style.marginXL
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
clockStyle: "analog"
|
||||||
|
progressColor: Colors.mOnPrimary
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
now: root.now
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Utils
|
||||||
|
|
||||||
|
// Calendar month grid with navigation
|
||||||
|
UBox {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Internal state - independent from header
|
||||||
|
readonly property var now: Time.now
|
||||||
|
property int calendarMonth: now.getMonth()
|
||||||
|
property int calendarYear: now.getFullYear()
|
||||||
|
readonly property var locale: Qt.locale("en")
|
||||||
|
readonly property int firstDayOfWeek: locale.firstDayOfWeek
|
||||||
|
|
||||||
|
// Helper function to calculate ISO week number
|
||||||
|
function getISOWeekNumber(date) {
|
||||||
|
const target = new Date(date.valueOf());
|
||||||
|
const dayNr = (date.getDay() + 6) % 7;
|
||||||
|
target.setDate(target.getDate() - dayNr + 3);
|
||||||
|
const firstThursday = new Date(target.getFullYear(), 0, 4);
|
||||||
|
const diff = target - firstThursday;
|
||||||
|
const oneWeek = 1000 * 60 * 60 * 24 * 7;
|
||||||
|
const weekNumber = 1 + Math.round(diff / oneWeek);
|
||||||
|
return weekNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if an event is all-day
|
||||||
|
function isAllDayEvent(event) {
|
||||||
|
const duration = event.end - event.start;
|
||||||
|
const startDate = new Date(event.start * 1000);
|
||||||
|
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
|
||||||
|
return duration === 86400 && isAtMidnight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation functions
|
||||||
|
function navigateToPreviousMonth() {
|
||||||
|
let newDate = new Date(root.calendarYear, root.calendarMonth - 1, 1);
|
||||||
|
root.calendarYear = newDate.getFullYear();
|
||||||
|
root.calendarMonth = newDate.getMonth();
|
||||||
|
const now = new Date();
|
||||||
|
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
|
||||||
|
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
|
||||||
|
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||||
|
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToNextMonth() {
|
||||||
|
let newDate = new Date(root.calendarYear, root.calendarMonth + 1, 1);
|
||||||
|
root.calendarYear = newDate.getFullYear();
|
||||||
|
root.calendarMonth = newDate.getMonth();
|
||||||
|
const now = new Date();
|
||||||
|
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
|
||||||
|
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
|
||||||
|
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||||
|
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: calendarContent.implicitHeight + Style.marginM * 2
|
||||||
|
compact: true
|
||||||
|
|
||||||
|
// Wheel handler for month navigation
|
||||||
|
WheelHandler {
|
||||||
|
id: wheelHandler
|
||||||
|
|
||||||
|
target: root
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
onWheel: function(event) {
|
||||||
|
if (event.angleDelta.y > 0) {
|
||||||
|
// Scroll up - go to previous month
|
||||||
|
root.navigateToPreviousMonth();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.angleDelta.y < 0) {
|
||||||
|
// Scroll down - go to next month
|
||||||
|
root.navigateToNextMonth();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: calendarContent
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginM
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
// Navigation row
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: Style.marginS
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: locale.monthName(root.calendarMonth, Locale.LongFormat).toUpperCase() + " " + root.calendarYear
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
}
|
||||||
|
|
||||||
|
UDivider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "chevron-left"
|
||||||
|
onClicked: root.navigateToPreviousMonth()
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "calendar"
|
||||||
|
onClicked: {
|
||||||
|
root.calendarMonth = root.now.getMonth();
|
||||||
|
root.calendarYear = root.now.getFullYear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "chevron-right"
|
||||||
|
onClicked: root.navigateToNextMonth()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Day names header
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columns: 7
|
||||||
|
rows: 1
|
||||||
|
columnSpacing: 0
|
||||||
|
rowSpacing: 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 7
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Style.fontSizeS * 2
|
||||||
|
|
||||||
|
UText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: {
|
||||||
|
let dayIndex = (root.firstDayOfWeek + index) % 7;
|
||||||
|
const dayName = locale.dayName(dayIndex, Locale.ShortFormat);
|
||||||
|
return dayName.substring(0, 2).toUpperCase();
|
||||||
|
}
|
||||||
|
color: Colors.mPrimary
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calendar grid with week numbers
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
// Week numbers column
|
||||||
|
ColumnLayout {
|
||||||
|
property var weekNumbers: {
|
||||||
|
if (!grid.daysModel || grid.daysModel.length === 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
const weeks = [];
|
||||||
|
const numWeeks = Math.ceil(grid.daysModel.length / 7);
|
||||||
|
for (var i = 0; i < numWeeks; i++) {
|
||||||
|
const dayIndex = i * 7;
|
||||||
|
if (dayIndex < grid.daysModel.length) {
|
||||||
|
const weekDay = grid.daysModel[dayIndex];
|
||||||
|
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
|
||||||
|
let thursday = new Date(date);
|
||||||
|
if (root.firstDayOfWeek === 0) {
|
||||||
|
thursday.setDate(date.getDate() + 4);
|
||||||
|
} else if (root.firstDayOfWeek === 1) {
|
||||||
|
thursday.setDate(date.getDate() + 3);
|
||||||
|
} else {
|
||||||
|
let daysToThursday = (4 - root.firstDayOfWeek + 7) % 7;
|
||||||
|
thursday.setDate(date.getDate() + daysToThursday);
|
||||||
|
}
|
||||||
|
weeks.push(root.getISOWeekNumber(thursday));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return weeks;
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.preferredWidth: Style.baseWidgetSize * 0.7
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
spacing: Style.marginXXS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: parent.weekNumbers
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: Style.baseWidgetSize * 0.7
|
||||||
|
Layout.preferredHeight: Style.baseWidgetSize * 0.9
|
||||||
|
|
||||||
|
UText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: Qt.alpha(Colors.mPrimary, 0.7)
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
text: modelData
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calendar grid
|
||||||
|
GridLayout {
|
||||||
|
id: grid
|
||||||
|
|
||||||
|
property int month: root.calendarMonth
|
||||||
|
property int year: root.calendarYear
|
||||||
|
property var daysModel: {
|
||||||
|
const firstOfMonth = new Date(year, month, 1);
|
||||||
|
const lastOfMonth = new Date(year, month + 1, 0);
|
||||||
|
const daysInMonth = lastOfMonth.getDate();
|
||||||
|
const firstDayOfWeek = root.firstDayOfWeek;
|
||||||
|
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
|
||||||
|
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
|
||||||
|
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
|
||||||
|
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
|
||||||
|
const days = [];
|
||||||
|
const today = new Date();
|
||||||
|
// Previous month days
|
||||||
|
const prevMonth = new Date(year, month, 0);
|
||||||
|
const prevMonthDays = prevMonth.getDate();
|
||||||
|
for (var i = daysBefore - 1; i >= 0; i--) {
|
||||||
|
const day = prevMonthDays - i;
|
||||||
|
days.push({
|
||||||
|
"day": day,
|
||||||
|
"month": month - 1,
|
||||||
|
"year": month === 0 ? year - 1 : year,
|
||||||
|
"today": false,
|
||||||
|
"currentMonth": false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Current month days
|
||||||
|
for (var day = 1; day <= daysInMonth; day++) {
|
||||||
|
const date = new Date(year, month, day);
|
||||||
|
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
|
||||||
|
days.push({
|
||||||
|
"day": day,
|
||||||
|
"month": month,
|
||||||
|
"year": year,
|
||||||
|
"today": isToday,
|
||||||
|
"currentMonth": true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Next month days
|
||||||
|
for (var i = 1; i <= daysAfter; i++) {
|
||||||
|
days.push({
|
||||||
|
"day": i,
|
||||||
|
"month": month + 1,
|
||||||
|
"year": month === 11 ? year + 1 : year,
|
||||||
|
"today": false,
|
||||||
|
"currentMonth": false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columns: 7
|
||||||
|
columnSpacing: Style.marginXXS
|
||||||
|
rowSpacing: Style.marginXXS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: grid.daysModel
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Style.baseWidgetSize * 0.9
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Style.baseWidgetSize * 0.9
|
||||||
|
height: Style.baseWidgetSize * 0.9
|
||||||
|
anchors.centerIn: parent
|
||||||
|
radius: Style.radiusM
|
||||||
|
color: modelData.today ? Colors.mPrimary : "transparent"
|
||||||
|
|
||||||
|
UText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData.day
|
||||||
|
color: {
|
||||||
|
if (modelData.today)
|
||||||
|
return Colors.mOnPrimary;
|
||||||
|
|
||||||
|
if (modelData.currentMonth)
|
||||||
|
return Colors.mOnSurface;
|
||||||
|
|
||||||
|
return Colors.mOnSurfaceVariant;
|
||||||
|
}
|
||||||
|
opacity: modelData.currentMonth ? 1 : 0.4
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
font.weight: modelData.today ? Style.fontWeightBold : Style.fontWeightMedium
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
UBox {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string currentPanel: "bluetooth" // "bluetooth", "wifi"
|
||||||
|
|
||||||
|
implicitHeight: contentLoader.implicitHeight + toggleGroup.implicitHeight + Style.marginXS * 2 + Style.marginS * 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Style.marginXS
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginS
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: toggleGroup
|
||||||
|
|
||||||
|
Layout.preferredWidth: Style.baseWidgetSize * 2.8
|
||||||
|
Layout.preferredHeight: Style.baseWidgetSize
|
||||||
|
radius: Math.min(Style.radiusS, height / 2)
|
||||||
|
color: Colors.mSurface
|
||||||
|
// border.color: Colors.mOutline
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginS / 2
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: btnBluetooth
|
||||||
|
|
||||||
|
width: root.currentPanel === "bluetooth" ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
|
||||||
|
height: parent.height
|
||||||
|
radius: Math.min(Style.radiusS, height / 2)
|
||||||
|
color: root.currentPanel === "bluetooth" ? Colors.mPrimary : "transparent"
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
iconName: "bluetooth"
|
||||||
|
iconSize: Style.fontSizeL
|
||||||
|
color: root.currentPanel === "bluetooth" ? Colors.mOnPrimary : Colors.mOnSurface
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.currentPanel = "bluetooth"
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: btnWifi
|
||||||
|
|
||||||
|
width: root.currentPanel === "wifi" ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
|
||||||
|
height: parent.height
|
||||||
|
radius: Math.min(Style.radiusS, height / 2)
|
||||||
|
color: root.currentPanel === "wifi" ? Colors.mPrimary : "transparent"
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 250
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
iconName: "wifi"
|
||||||
|
iconSize: Style.fontSizeL
|
||||||
|
color: root.currentPanel === "wifi" ? Colors.mOnPrimary : Colors.mOnSurface
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.currentPanel = "wifi"
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
sourceComponent: currentPanel === "bluetooth" ? bluetoothHeaderComponent : wifiHeaderComponent
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: bluetoothHeaderComponent
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
UToggle {
|
||||||
|
id: bluetoothSwitch
|
||||||
|
|
||||||
|
checked: BluetoothService.enabled
|
||||||
|
onToggled: (checked) => {
|
||||||
|
return BluetoothService.setBluetoothEnabled(checked);
|
||||||
|
}
|
||||||
|
baseSize: Style.baseWidgetSize * 0.65
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
enabled: BluetoothService.enabled
|
||||||
|
iconName: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "refresh"
|
||||||
|
baseSize: Style.baseWidgetSize * 0.8
|
||||||
|
onClicked: {
|
||||||
|
if (BluetoothService.adapter)
|
||||||
|
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
|
||||||
|
|
||||||
|
}
|
||||||
|
colorFg: Colors.mGreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: wifiHeaderComponent
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
UToggle {
|
||||||
|
id: wifiSwitch
|
||||||
|
|
||||||
|
checked: SettingsService.wifiEnabled
|
||||||
|
onToggled: (checked) => {
|
||||||
|
return NetworkService.setWifiEnabled(checked);
|
||||||
|
}
|
||||||
|
baseSize: Style.baseWidgetSize * 0.65
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "refresh"
|
||||||
|
baseSize: Style.baseWidgetSize * 0.8
|
||||||
|
enabled: SettingsService.wifiEnabled && !NetworkService.scanning
|
||||||
|
onClicked: NetworkService.scan()
|
||||||
|
colorFg: Colors.mGreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UDivider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: contentLoader
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
sourceComponent: currentPanel === "bluetooth" ? bluetoothComponent : wifiComponent
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: bluetoothComponent
|
||||||
|
|
||||||
|
BluetoothCard {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: wifiComponent
|
||||||
|
|
||||||
|
WifiCard {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -1,12 +1,12 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
import qs.Constants
|
import qs.Constants
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Utils
|
import qs.Utils
|
||||||
|
|
||||||
NBox {
|
UBox {
|
||||||
id: lyricsBox
|
id: lyricsBox
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -25,13 +25,13 @@ NBox {
|
|||||||
Repeater {
|
Repeater {
|
||||||
model: LyricsService.lyrics
|
model: LyricsService.lyrics
|
||||||
|
|
||||||
NText {
|
UText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: modelData
|
text: modelData
|
||||||
font.pointSize: index === LyricsService.currentIndex ? Style.fontSizeM : Style.fontSizeS
|
font.pointSize: index === LyricsService.currentIndex ? Style.fontSizeS : Style.fontSizeXS
|
||||||
font.weight: index === LyricsService.currentIndex ? Style.fontWeightBold : Style.fontWeightRegular
|
font.weight: index === LyricsService.currentIndex ? Style.fontWeightBold : Style.fontWeightRegular
|
||||||
font.family: Fonts.sans
|
font.family: Fonts.sans
|
||||||
color: index === LyricsService.currentIndex ? Color.mOnSurface : Color.mOnSurfaceVariant
|
color: index === LyricsService.currentIndex ? Colors.mOnSurface : Colors.mOnSurfaceVariant
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
UText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.maximumWidth: buttonsGrid.width
|
||||||
|
Layout.bottomMargin: Style.marginS
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: (LyricsService.offset > 0 ? "+" + LyricsService.offset : LyricsService.offset) + " ms"
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: buttonsGrid
|
||||||
|
|
||||||
|
columns: 2
|
||||||
|
columnSpacing: Style.marginS
|
||||||
|
rowSpacing: Style.marginS
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
id: slowerButton
|
||||||
|
|
||||||
|
baseSize: 32
|
||||||
|
colorFg: Colors.mCyan
|
||||||
|
iconName: "arrow-bar-up"
|
||||||
|
onClicked: {
|
||||||
|
LyricsService.increaseOffset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
id: playPauseButton
|
||||||
|
|
||||||
|
baseSize: 32
|
||||||
|
colorFg: Colors.mPurple
|
||||||
|
iconName: "arrow-bar-down"
|
||||||
|
onClicked: {
|
||||||
|
LyricsService.decreaseOffset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
id: nextButton
|
||||||
|
|
||||||
|
baseSize: 32
|
||||||
|
colorFg: Colors.mGreen
|
||||||
|
iconName: "rotate-clockwise"
|
||||||
|
onClicked: {
|
||||||
|
LyricsService.resetOffset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
id: fasterButton
|
||||||
|
|
||||||
|
baseSize: 32
|
||||||
|
colorFg: Colors.mRed
|
||||||
|
iconName: "trash"
|
||||||
|
onClicked: {
|
||||||
|
LyricsService.clearCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
id: barLyricsButton
|
||||||
|
|
||||||
|
baseSize: 32
|
||||||
|
colorFg: Colors.mSky
|
||||||
|
alwaysHover: LyricsService.showLyricsBar
|
||||||
|
iconName: "app-window"
|
||||||
|
onClicked: {
|
||||||
|
LyricsService.toggleLyricsBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
id: textButton
|
||||||
|
|
||||||
|
baseSize: 32
|
||||||
|
colorFg: Colors.mYellow
|
||||||
|
iconName: "align-box-left-bottom"
|
||||||
|
onClicked: {
|
||||||
|
LyricsService.showLyricsText();
|
||||||
|
controlCenterPanel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
baseSize: 32
|
||||||
|
colorFg: Colors.mOrange
|
||||||
|
alwaysHover: SunsetService.isEnabled
|
||||||
|
iconName: "sunset-2"
|
||||||
|
onClicked: SunsetService.toggleSunset()
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
baseSize: 32
|
||||||
|
colorFg: Colors.mBlue
|
||||||
|
alwaysHover: MediaService.autoSwitchingPaused
|
||||||
|
iconName: "lock-square"
|
||||||
|
onClicked: MediaService.toggleAutoSwitchingPaused()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,483 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Effects
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
import qs.Utils
|
||||||
|
|
||||||
|
UBox {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitHeight: 200
|
||||||
|
|
||||||
|
// Track whether we have an active media player
|
||||||
|
readonly property bool hasActivePlayer: MediaService.currentPlayer && MediaService.canPlay
|
||||||
|
|
||||||
|
// Wrapper - rounded rect clipper
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
layer.enabled: true
|
||||||
|
layer.smooth: true
|
||||||
|
|
||||||
|
// Solid color background (always present as base layer)
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Colors.mSurface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background image that covers everything
|
||||||
|
Image {
|
||||||
|
id: bgImage
|
||||||
|
|
||||||
|
readonly property int dim: 256
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: source.toString() !== ""
|
||||||
|
source: MediaService.trackArtUrl
|
||||||
|
sourceSize: Qt.size(dim, dim)
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
layer.enabled: true
|
||||||
|
layer.smooth: true
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
blurEnabled: true
|
||||||
|
blurMax: 8
|
||||||
|
blur: 0.33
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark overlay for readability
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Colors.mSurface
|
||||||
|
opacity: 0.65
|
||||||
|
radius: Style.radiusM
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background visualizer on top of the artwork
|
||||||
|
Item {
|
||||||
|
id: visualizerContainer
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
layer.enabled: true
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Cava {
|
||||||
|
id: cava
|
||||||
|
|
||||||
|
count: 32
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: cava.values
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: (parent.width - (cava.count - 1) * Style.marginXS) / cava.count
|
||||||
|
height: modelData * parent.height
|
||||||
|
x: index * (width + Style.marginXS)
|
||||||
|
color: Colors.mPrimary
|
||||||
|
radius: width / 2
|
||||||
|
opacity: 0.25
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
maskEnabled: true
|
||||||
|
maskThresholdMin: 0.5
|
||||||
|
maskSpreadAtMin: 0
|
||||||
|
|
||||||
|
maskSource: ShaderEffectSource {
|
||||||
|
|
||||||
|
sourceItem: Rectangle {
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
|
radius: Style.radiusM
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
maskEnabled: true
|
||||||
|
maskThresholdMin: 0.95
|
||||||
|
maskSpreadAtMin: 0.15
|
||||||
|
|
||||||
|
maskSource: ShaderEffectSource {
|
||||||
|
|
||||||
|
sourceItem: Rectangle {
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
|
radius: Style.radiusM
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player selector
|
||||||
|
Rectangle {
|
||||||
|
id: playerSelectorButton
|
||||||
|
|
||||||
|
property var currentPlayer: MediaService.getAvailablePlayers()[MediaService.selectedPlayerIndex]
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: Style.marginXS
|
||||||
|
anchors.leftMargin: Style.marginM
|
||||||
|
anchors.rightMargin: Style.marginM
|
||||||
|
height: Style.baseWidgetSize
|
||||||
|
visible: MediaService.getAvailablePlayers().length > 1
|
||||||
|
radius: Style.radiusM
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
iconName: "caret-down"
|
||||||
|
iconSize: Style.fontSizeXXL
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: playerSelectorButton.currentPlayer ? playerSelectorButton.currentPlayer.identity : ""
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: playerSelectorMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
var menuItems = [];
|
||||||
|
var players = MediaService.getAvailablePlayers();
|
||||||
|
for (var i = 0; i < players.length; i++) {
|
||||||
|
menuItems.push({
|
||||||
|
"label": players[i].identity,
|
||||||
|
"action": i.toString(),
|
||||||
|
"icon": "disc",
|
||||||
|
"enabled": true,
|
||||||
|
"visible": true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
playerContextMenu.model = menuItems;
|
||||||
|
playerContextMenu.openAtItem(playerSelectorButton, 0, playerSelectorButton.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UContextMenu {
|
||||||
|
id: playerContextMenu
|
||||||
|
|
||||||
|
parent: root
|
||||||
|
width: 200
|
||||||
|
verticalPolicy: ScrollBar.AlwaysOff
|
||||||
|
onTriggered: function(action) {
|
||||||
|
var index = parseInt(action);
|
||||||
|
if (!isNaN(index))
|
||||||
|
MediaService.switchToPlayer(index);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content container that adjusts for player selector
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: playerSelectorButton.visible ? (playerSelectorButton.height + Style.marginXS + Style.marginM) : Style.marginM
|
||||||
|
anchors.leftMargin: Style.marginM
|
||||||
|
anchors.rightMargin: Style.marginM
|
||||||
|
anchors.bottomMargin: Style.marginM
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: fallback
|
||||||
|
visible: !root.hasActivePlayer
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
implicitWidth: Style.fontSizeXXXL * 4
|
||||||
|
implicitHeight: Style.fontSizeXXXL * 4
|
||||||
|
|
||||||
|
// Pulsating audio circles (background)
|
||||||
|
Repeater {
|
||||||
|
model: 3
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width * (1 + index * 0.2)
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Colors.mOnSurfaceVariant
|
||||||
|
border.width: 2
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: true
|
||||||
|
loops: Animation.Infinite
|
||||||
|
|
||||||
|
PauseAnimation {
|
||||||
|
duration: index * 600
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
from: 1
|
||||||
|
to: 0
|
||||||
|
duration: 2000
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation on scale {
|
||||||
|
running: true
|
||||||
|
loops: Animation.Infinite
|
||||||
|
|
||||||
|
PauseAnimation {
|
||||||
|
duration: index * 600
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
from: 0.5
|
||||||
|
to: 1.2
|
||||||
|
duration: 2000
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spinning disc
|
||||||
|
UIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
iconName: "disc"
|
||||||
|
iconSize: Style.fontSizeXXXL * 3
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
|
||||||
|
RotationAnimator on rotation {
|
||||||
|
from: 0
|
||||||
|
to: 360
|
||||||
|
duration: 8000
|
||||||
|
loops: Animation.Infinite
|
||||||
|
running: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MediaPlayer Main Content - use Loader for performance
|
||||||
|
Loader {
|
||||||
|
id: mainLoader
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.hasActivePlayer
|
||||||
|
|
||||||
|
sourceComponent: Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
// Exceptionaly we put shadow on text and controls to ease readability
|
||||||
|
UDropShadow {
|
||||||
|
anchors.fill: main
|
||||||
|
source: main
|
||||||
|
autoPaddingEnabled: true
|
||||||
|
shadowBlur: 1
|
||||||
|
shadowOpacity: 0.9
|
||||||
|
shadowHorizontalOffset: 0
|
||||||
|
shadowVerticalOffset: 0
|
||||||
|
shadowColor: "black"
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: main
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
ColumnLayout {
|
||||||
|
id: metadata
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
UText {
|
||||||
|
visible: MediaService.trackTitle !== ""
|
||||||
|
text: MediaService.trackTitle
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
maximumLineCount: 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
visible: MediaService.trackArtist !== ""
|
||||||
|
text: MediaService.trackArtist
|
||||||
|
color: Colors.mPrimary
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
visible: MediaService.trackAlbum !== ""
|
||||||
|
text: MediaService.trackAlbum
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress slider
|
||||||
|
Item {
|
||||||
|
id: progressWrapper
|
||||||
|
|
||||||
|
property real localSeekRatio: -1
|
||||||
|
property real lastSentSeekRatio: -1
|
||||||
|
property real seekEpsilon: 0.01
|
||||||
|
property real progressRatio: {
|
||||||
|
if (!MediaService.currentPlayer || MediaService.trackLength <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const r = MediaService.currentPosition / MediaService.trackLength;
|
||||||
|
if (isNaN(r) || !isFinite(r))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return Math.max(0, Math.min(1, r));
|
||||||
|
}
|
||||||
|
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
|
||||||
|
|
||||||
|
visible: (MediaService.currentPlayer && MediaService.trackLength > 0)
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: Style.baseWidgetSize * 0.5
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: seekDebounce
|
||||||
|
|
||||||
|
interval: 75
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
|
||||||
|
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio));
|
||||||
|
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
|
||||||
|
MediaService.seekByRatio(next);
|
||||||
|
progressWrapper.lastSentSeekRatio = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
USlider {
|
||||||
|
id: progressSlider
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
stepSize: 0
|
||||||
|
snapAlways: false
|
||||||
|
enabled: MediaService.trackLength > 0 && MediaService.canSeek
|
||||||
|
heightRatio: 0.6
|
||||||
|
onMoved: {
|
||||||
|
progressWrapper.localSeekRatio = value;
|
||||||
|
seekDebounce.restart();
|
||||||
|
}
|
||||||
|
onPressedChanged: {
|
||||||
|
if (pressed) {
|
||||||
|
MediaService.isSeeking = true;
|
||||||
|
progressWrapper.localSeekRatio = value;
|
||||||
|
MediaService.seekByRatio(value);
|
||||||
|
progressWrapper.lastSentSeekRatio = value;
|
||||||
|
} else {
|
||||||
|
seekDebounce.stop();
|
||||||
|
MediaService.seekByRatio(value);
|
||||||
|
MediaService.isSeeking = false;
|
||||||
|
progressWrapper.localSeekRatio = -1;
|
||||||
|
progressWrapper.lastSentSeekRatio = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: progressSlider
|
||||||
|
property: "value"
|
||||||
|
value: progressWrapper.progressRatio
|
||||||
|
when: !MediaService.isSeeking
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media controls
|
||||||
|
RowLayout {
|
||||||
|
spacing: Style.marginS
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "media-prev"
|
||||||
|
visible: MediaService.canGoPrevious
|
||||||
|
onClicked: MediaService.canGoPrevious ? MediaService.previous() : {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||||
|
visible: (MediaService.canPlay || MediaService.canPause)
|
||||||
|
onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "media-next"
|
||||||
|
visible: MediaService.canGoNext
|
||||||
|
onClicked: MediaService.canGoNext ? MediaService.next() : {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+939
@@ -0,0 +1,939 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Notifications
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
import qs.Utils
|
||||||
|
|
||||||
|
// Notification History panel
|
||||||
|
Rectangle {
|
||||||
|
// Check if below visible area
|
||||||
|
// Stop at edges
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Calculate content height based on header + tabs (if visible) + content
|
||||||
|
property real calculatedHeight: {
|
||||||
|
if (NotificationService.historyList.count === 0)
|
||||||
|
return headerBox.implicitHeight + scrollView.implicitHeight + Style.marginL * 2 + Style.marginM;
|
||||||
|
|
||||||
|
return headerBox.implicitHeight + scrollView.implicitHeight + Style.marginL * 2 + Style.marginM;
|
||||||
|
}
|
||||||
|
property real contentPreferredHeight: Math.min(root.height, Math.ceil(calculatedHeight))
|
||||||
|
property real layoutWidth: Math.max(1, root.width - Style.marginL * 2)
|
||||||
|
// State (lazy-loaded with root)
|
||||||
|
property var rangeCounts: [0, 0, 0, 0]
|
||||||
|
property var lastKnownDate: null // Track the current date to detect day changes
|
||||||
|
// UI state (lazy-loaded with root)
|
||||||
|
// 0 = All, 1 = Today, 2 = Yesterday, 3 = Earlier
|
||||||
|
property int currentRange: 1
|
||||||
|
// start on Today by default
|
||||||
|
property bool groupByDate: true
|
||||||
|
// Keyboard navigation state
|
||||||
|
property int focusIndex: -1
|
||||||
|
property int actionIndex: -1 // For actions within a notification
|
||||||
|
|
||||||
|
function parseActions(actions) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(actions || "[]");
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveSelection(dir) {
|
||||||
|
var m = NotificationService.historyList;
|
||||||
|
if (!m || m.count === 0)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var newIndex = focusIndex;
|
||||||
|
var found = false;
|
||||||
|
var count = m.count;
|
||||||
|
// If no selection yet, start from beginning (or end if up)
|
||||||
|
if (focusIndex === -1) {
|
||||||
|
if (dir > 0)
|
||||||
|
newIndex = -1;
|
||||||
|
else
|
||||||
|
newIndex = count;
|
||||||
|
}
|
||||||
|
// Loop to find next visible item
|
||||||
|
var loopCount = 0;
|
||||||
|
while (loopCount < count) {
|
||||||
|
newIndex += dir;
|
||||||
|
// Bounds check
|
||||||
|
if (newIndex < 0 || newIndex >= count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var item = m.get(newIndex);
|
||||||
|
if (item && isInCurrentRange(item.timestamp)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loopCount++;
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
focusIndex = newIndex;
|
||||||
|
actionIndex = -1; // Reset action selection
|
||||||
|
scrollToItem(focusIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveAction(dir) {
|
||||||
|
if (focusIndex === -1)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var item = NotificationService.historyList.get(focusIndex);
|
||||||
|
if (!item)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var actions = parseActions(item.actionsJson);
|
||||||
|
if (actions.length === 0)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var newActionIndex = actionIndex + dir;
|
||||||
|
// Clamp between -1 (body) and actions.length - 1
|
||||||
|
if (newActionIndex < -1)
|
||||||
|
newActionIndex = -1;
|
||||||
|
|
||||||
|
if (newActionIndex >= actions.length)
|
||||||
|
newActionIndex = actions.length - 1;
|
||||||
|
|
||||||
|
actionIndex = newActionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateSelection() {
|
||||||
|
if (focusIndex === -1)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var item = NotificationService.historyList.get(focusIndex);
|
||||||
|
if (!item)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
if (actionIndex >= 0) {
|
||||||
|
var actions = parseActions(item.actionsJson);
|
||||||
|
if (actionIndex < actions.length)
|
||||||
|
NotificationService.invokeAction(item.id, actions[actionIndex].identifier);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var delegate = notificationColumn.children[focusIndex];
|
||||||
|
if (!delegate)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
if (!(delegate.canExpand || delegate.isExpanded))
|
||||||
|
return ;
|
||||||
|
|
||||||
|
if (scrollView.expandedId === item.id)
|
||||||
|
scrollView.expandedId = "";
|
||||||
|
else
|
||||||
|
scrollView.expandedId = item.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSelection() {
|
||||||
|
if (focusIndex === -1)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var item = NotificationService.historyList.get(focusIndex);
|
||||||
|
if (!item)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
NotificationService.removeFromHistory(item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToItem(index) {
|
||||||
|
// Find the delegate item
|
||||||
|
if (index < 0 || index >= notificationColumn.children.length)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var item = notificationColumn.children[index];
|
||||||
|
if (item && item.visible) {
|
||||||
|
// Use the internal flickable from NScrollView for accurate scrolling
|
||||||
|
var flickable = scrollView._internalFlickable;
|
||||||
|
if (!flickable || !flickable.contentItem)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var pos = flickable.contentItem.mapFromItem(item, 0, 0);
|
||||||
|
var itemY = pos.y;
|
||||||
|
var itemHeight = item.height;
|
||||||
|
var currentContentY = flickable.contentY;
|
||||||
|
var viewHeight = flickable.height;
|
||||||
|
// Check if above visible area
|
||||||
|
if (itemY < currentContentY)
|
||||||
|
flickable.contentY = Math.max(0, itemY - Style.marginM);
|
||||||
|
else if (itemY + itemHeight > currentContentY + viewHeight)
|
||||||
|
flickable.contentY = (itemY + itemHeight) - viewHeight + Style.marginM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFocus() {
|
||||||
|
focusIndex = -1;
|
||||||
|
actionIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions (lazy-loaded with root)
|
||||||
|
function dateOnly(d) {
|
||||||
|
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDateKey(d) {
|
||||||
|
// Returns a string key for the date (YYYY-MM-DD) for comparison
|
||||||
|
var date = dateOnly(d);
|
||||||
|
return date.getFullYear() + "-" + date.getMonth() + "-" + date.getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function rangeForTimestamp(ts) {
|
||||||
|
var dt = new Date(ts);
|
||||||
|
var today = dateOnly(new Date());
|
||||||
|
var thatDay = dateOnly(dt);
|
||||||
|
var diffMs = today - thatDay;
|
||||||
|
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||||
|
if (diffDays === 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (diffDays === 1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalcRangeCounts() {
|
||||||
|
var m = NotificationService.historyList;
|
||||||
|
if (!m || typeof m.count === "undefined" || m.count <= 0) {
|
||||||
|
root.rangeCounts = [0, 0, 0, 0];
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
var counts = [0, 0, 0, 0];
|
||||||
|
counts[0] = m.count;
|
||||||
|
for (var i = 0; i < m.count; ++i) {
|
||||||
|
var item = m.get(i);
|
||||||
|
if (!item || typeof item.timestamp === "undefined")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var r = rangeForTimestamp(item.timestamp);
|
||||||
|
counts[r + 1] = counts[r + 1] + 1;
|
||||||
|
}
|
||||||
|
root.rangeCounts = counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInCurrentRange(ts) {
|
||||||
|
if (currentRange === 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return rangeForTimestamp(ts) === (currentRange - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function countForRange(range) {
|
||||||
|
return rangeCounts[range] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasNotificationsInCurrentRange() {
|
||||||
|
var m = NotificationService.historyList;
|
||||||
|
if (!m || m.count === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (var i = 0; i < m.count; ++i) {
|
||||||
|
var item = m.get(i);
|
||||||
|
if (item && isInCurrentRange(item.timestamp))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
onCurrentRangeChanged: resetFocus()
|
||||||
|
Component.onCompleted: {
|
||||||
|
NotificationService.updateLastSeenTs();
|
||||||
|
recalcRangeCounts();
|
||||||
|
// Initialize lastKnownDate
|
||||||
|
lastKnownDate = getDateKey(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onCountChanged() {
|
||||||
|
root.recalcRangeCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: NotificationService.historyList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer to check for day changes at midnight
|
||||||
|
Timer {
|
||||||
|
// Day has changed, recalculate counts
|
||||||
|
|
||||||
|
id: dayChangeTimer
|
||||||
|
|
||||||
|
interval: 60000 // Check every minute
|
||||||
|
repeat: true
|
||||||
|
running: true // Always runs when root exists (panel is open)
|
||||||
|
onTriggered: {
|
||||||
|
var currentDateKey = root.getDateKey(new Date());
|
||||||
|
if (root.lastKnownDate !== null && root.lastKnownDate !== currentDateKey)
|
||||||
|
root.recalcRangeCounts();
|
||||||
|
|
||||||
|
root.lastKnownDate = currentDateKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: mainColumn
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
// Header section
|
||||||
|
UBox {
|
||||||
|
id: headerBox
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: header.implicitHeight + Style.marginM * 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: header
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginM
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: headerRow
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
iconName: "bell"
|
||||||
|
iconSize: Style.fontSizeXXL
|
||||||
|
color: Colors.mPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Notifications" + " (" + root.countForRange(tabsBox.currentIndex) + ")"
|
||||||
|
pointSize: Style.fontSizeL
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: NotificationService.doNotDisturb ? "bell-off" : "bell"
|
||||||
|
baseSize: Style.baseWidgetSize * 0.8
|
||||||
|
colorFg: Colors.mGreen
|
||||||
|
alwaysHover: NotificationService.doNotDisturb
|
||||||
|
onClicked: NotificationService.toggleDoNotDisturb()
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
// Close panel as there is nothing more to see.
|
||||||
|
|
||||||
|
iconName: "trash"
|
||||||
|
baseSize: Style.baseWidgetSize * 0.8
|
||||||
|
colorFg: Colors.mError
|
||||||
|
onClicked: {
|
||||||
|
NotificationService.clearHistory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time range tabs ([All] / [Today] / [Yesterday] / [Earlier])
|
||||||
|
UTabBar {
|
||||||
|
id: tabsBox
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: NotificationService.historyList.count > 0 && root.groupByDate
|
||||||
|
currentIndex: root.currentRange
|
||||||
|
tabHeight: Math.round(Style.baseWidgetSize * 0.8)
|
||||||
|
spacing: Style.marginXS
|
||||||
|
distributeEvenly: true
|
||||||
|
|
||||||
|
UTabButton {
|
||||||
|
tabIndex: 0
|
||||||
|
text: "All"
|
||||||
|
checked: tabsBox.currentIndex === 0
|
||||||
|
onClicked: root.currentRange = 0
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
}
|
||||||
|
|
||||||
|
UTabButton {
|
||||||
|
tabIndex: 1
|
||||||
|
text: "Today"
|
||||||
|
checked: tabsBox.currentIndex === 1
|
||||||
|
onClicked: root.currentRange = 1
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
}
|
||||||
|
|
||||||
|
UTabButton {
|
||||||
|
tabIndex: 2
|
||||||
|
text: "Yesterday"
|
||||||
|
checked: tabsBox.currentIndex === 2
|
||||||
|
onClicked: root.currentRange = 2
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
}
|
||||||
|
|
||||||
|
UTabButton {
|
||||||
|
tabIndex: 3
|
||||||
|
text: "Earlier"
|
||||||
|
checked: tabsBox.currentIndex === 3
|
||||||
|
onClicked: root.currentRange = 3
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification list container with gradient overlay
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
NScrollView {
|
||||||
|
id: scrollView
|
||||||
|
|
||||||
|
// Track which notification is expanded
|
||||||
|
property string expandedId: ""
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
horizontalPolicy: ScrollBar.AlwaysOff
|
||||||
|
verticalPolicy: ScrollBar.AsNeeded
|
||||||
|
reserveScrollbarSpace: false
|
||||||
|
gradientColor: Colors.mSurface
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
// Empty state when no notifications
|
||||||
|
UBox {
|
||||||
|
visible: !root.hasNotificationsInCurrentRange()
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: emptyState.implicitHeight + Style.marginM * 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: emptyState
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginM
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
iconName: "bell-off"
|
||||||
|
iconSize: (NotificationService.historyList.count === 0) ? 48 : Style.baseWidgetSize
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "No notifications"
|
||||||
|
pointSize: (NotificationService.historyList.count === 0) ? Style.fontSizeL : Style.fontSizeM
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
visible: NotificationService.historyList.count === 0
|
||||||
|
text: "No notifications in this range"
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification list container
|
||||||
|
Item {
|
||||||
|
visible: root.hasNotificationsInCurrentRange()
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: notificationColumn.implicitHeight
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: notificationColumn
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: NotificationService.historyList
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: notificationDelegate
|
||||||
|
|
||||||
|
property int listIndex: index
|
||||||
|
property string notificationId: model.id
|
||||||
|
property string appName: model.appName || ""
|
||||||
|
property bool isExpanded: scrollView.expandedId === notificationId
|
||||||
|
property bool canExpand: summaryText.truncated || bodyText.truncated
|
||||||
|
property real swipeOffset: 0
|
||||||
|
property real pressGlobalX: 0
|
||||||
|
property real pressGlobalY: 0
|
||||||
|
property bool isSwiping: false
|
||||||
|
property bool isRemoving: false
|
||||||
|
property string pendingLink: ""
|
||||||
|
readonly property real swipeStartThreshold: 16
|
||||||
|
readonly property real swipeDismissThreshold: Math.max(110, width * 0.3)
|
||||||
|
readonly property int removeAnimationDuration: Style.animationNormal
|
||||||
|
readonly property int notificationTextFormat: notificationDelegate.isExpanded ? Text.MarkdownText : Text.StyledText
|
||||||
|
readonly property real actionButtonSize: Style.baseWidgetSize * 0.7
|
||||||
|
readonly property real buttonClusterWidth: notificationDelegate.actionButtonSize * 2 + Style.marginXS
|
||||||
|
readonly property real iconSize: 40
|
||||||
|
// Parse actions safely
|
||||||
|
property var actionsList: parseActions(model.actionsJson)
|
||||||
|
property bool isFocused: index === root.focusIndex
|
||||||
|
|
||||||
|
function isSafeLink(link) {
|
||||||
|
if (!link)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const lower = link.toLowerCase();
|
||||||
|
const schemes = ["http://", "https://", "mailto:"];
|
||||||
|
return schemes.some((scheme) => {
|
||||||
|
return lower.startsWith(scheme);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkAtPoint(x, y) {
|
||||||
|
if (!notificationDelegate.isExpanded)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (summaryText) {
|
||||||
|
const summaryPoint = summaryText.mapFromItem(historyInteractionArea, x, y);
|
||||||
|
if (summaryPoint.x >= 0 && summaryPoint.y >= 0 && summaryPoint.x <= summaryText.width && summaryPoint.y <= summaryText.height) {
|
||||||
|
const summaryLink = summaryText.linkAt ? summaryText.linkAt(summaryPoint.x, summaryPoint.y) : "";
|
||||||
|
if (isSafeLink(summaryLink))
|
||||||
|
return summaryLink;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bodyText) {
|
||||||
|
const bodyPoint = bodyText.mapFromItem(historyInteractionArea, x, y);
|
||||||
|
if (bodyPoint.x >= 0 && bodyPoint.y >= 0 && bodyPoint.x <= bodyText.width && bodyPoint.y <= bodyText.height) {
|
||||||
|
const bodyLink = bodyText.linkAt ? bodyText.linkAt(bodyPoint.x, bodyPoint.y) : "";
|
||||||
|
if (isSafeLink(bodyLink))
|
||||||
|
return bodyLink;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCursorAt(x, y) {
|
||||||
|
if (notificationDelegate.isExpanded && notificationDelegate.linkAtPoint(x, y))
|
||||||
|
historyInteractionArea.cursorShape = Qt.PointingHandCursor;
|
||||||
|
else
|
||||||
|
historyInteractionArea.cursorShape = Qt.ArrowCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissBySwipe() {
|
||||||
|
if (isRemoving)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
isRemoving = true;
|
||||||
|
isSwiping = false;
|
||||||
|
swipeOffset = swipeOffset >= 0 ? width + Style.marginL : -width - Style.marginL;
|
||||||
|
opacity = 0;
|
||||||
|
removeTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
visible: root.isInCurrentRange(model.timestamp)
|
||||||
|
height: visible && !isRemoving ? contentColumn.height + Style.marginM * 2 : 0
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible) {
|
||||||
|
notificationDelegate.isSwiping = false;
|
||||||
|
notificationDelegate.swipeOffset = 0;
|
||||||
|
notificationDelegate.opacity = 1;
|
||||||
|
notificationDelegate.isRemoving = false;
|
||||||
|
removeTimer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component.onDestruction: removeTimer.stop()
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: removeTimer
|
||||||
|
|
||||||
|
interval: notificationDelegate.removeAnimationDuration
|
||||||
|
repeat: false
|
||||||
|
onTriggered: NotificationService.removeFromHistory(notificationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
// return "transparent";
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Style.radiusM
|
||||||
|
color: Colors.mSurfaceVariant
|
||||||
|
border.color: {
|
||||||
|
if (notificationDelegate.isFocused)
|
||||||
|
return Colors.mPrimary;
|
||||||
|
|
||||||
|
return Qt.alpha(Colors.mOutline, Style.opacityHeavy);
|
||||||
|
}
|
||||||
|
border.width: notificationDelegate.isFocused ? Style.borderM : Style.borderS
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click to expand/collapse
|
||||||
|
MouseArea {
|
||||||
|
id: historyInteractionArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: notificationDelegate.buttonClusterWidth + Style.marginM
|
||||||
|
enabled: !notificationDelegate.isRemoving
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.ArrowCursor
|
||||||
|
onPressed: (mouse) => {
|
||||||
|
root.focusIndex = index;
|
||||||
|
root.actionIndex = -1;
|
||||||
|
if (notificationDelegate.isExpanded) {
|
||||||
|
const link = notificationDelegate.linkAtPoint(mouse.x, mouse.y);
|
||||||
|
if (link)
|
||||||
|
notificationDelegate.pendingLink = link;
|
||||||
|
else
|
||||||
|
notificationDelegate.pendingLink = "";
|
||||||
|
}
|
||||||
|
if (mouse.button !== Qt.LeftButton)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
const globalPoint = historyInteractionArea.mapToGlobal(mouse.x, mouse.y);
|
||||||
|
notificationDelegate.pressGlobalX = globalPoint.x;
|
||||||
|
notificationDelegate.pressGlobalY = globalPoint.y;
|
||||||
|
notificationDelegate.isSwiping = false;
|
||||||
|
}
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
if (!(mouse.buttons & Qt.LeftButton) || notificationDelegate.isRemoving)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
const globalPoint = historyInteractionArea.mapToGlobal(mouse.x, mouse.y);
|
||||||
|
const deltaX = globalPoint.x - notificationDelegate.pressGlobalX;
|
||||||
|
const deltaY = globalPoint.y - notificationDelegate.pressGlobalY;
|
||||||
|
if (!notificationDelegate.isSwiping) {
|
||||||
|
if (Math.abs(deltaX) < notificationDelegate.swipeStartThreshold)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
// Only start a swipe-dismiss when horizontal movement is dominant.
|
||||||
|
if (Math.abs(deltaX) <= Math.abs(deltaY) * 1.15)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
notificationDelegate.isSwiping = true;
|
||||||
|
}
|
||||||
|
if (notificationDelegate.pendingLink && Math.abs(deltaX) >= notificationDelegate.swipeStartThreshold)
|
||||||
|
notificationDelegate.pendingLink = "";
|
||||||
|
|
||||||
|
notificationDelegate.swipeOffset = deltaX;
|
||||||
|
}
|
||||||
|
onReleased: (mouse) => {
|
||||||
|
if (mouse.button !== Qt.LeftButton)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
if (notificationDelegate.isSwiping) {
|
||||||
|
if (Math.abs(notificationDelegate.swipeOffset) >= notificationDelegate.swipeDismissThreshold)
|
||||||
|
notificationDelegate.dismissBySwipe();
|
||||||
|
else
|
||||||
|
notificationDelegate.swipeOffset = 0;
|
||||||
|
notificationDelegate.isSwiping = false;
|
||||||
|
notificationDelegate.pendingLink = "";
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (notificationDelegate.pendingLink) {
|
||||||
|
Qt.openUrlExternally(notificationDelegate.pendingLink);
|
||||||
|
notificationDelegate.pendingLink = "";
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
// Focus sender window (and invoke default action if available)
|
||||||
|
var actions = notificationDelegate.actionsList;
|
||||||
|
var hasDefault = actions.some(function(a) {
|
||||||
|
return a.identifier === "default";
|
||||||
|
});
|
||||||
|
if (hasDefault) {
|
||||||
|
NotificationService.focusSenderWindow(notificationDelegate.appName);
|
||||||
|
NotificationService.invokeAction(notificationDelegate.notificationId, "default");
|
||||||
|
} else {
|
||||||
|
NotificationService.focusSenderWindow(notificationDelegate.appName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onCanceled: {
|
||||||
|
notificationDelegate.isSwiping = false;
|
||||||
|
notificationDelegate.swipeOffset = 0;
|
||||||
|
notificationDelegate.pendingLink = "";
|
||||||
|
historyInteractionArea.cursorShape = Qt.ArrowCursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
target: historyInteractionArea
|
||||||
|
onPointChanged: notificationDelegate.updateCursorAt(point.position.x, point.position.y)
|
||||||
|
onActiveChanged: {
|
||||||
|
if (!active)
|
||||||
|
historyInteractionArea.cursorShape = Qt.ArrowCursor;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: contentColumn
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Style.marginM
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
UImageRounded {
|
||||||
|
anchors.verticalCenter: notificationDelegate.isExpanded ? undefined : parent.verticalCenter
|
||||||
|
width: notificationDelegate.iconSize
|
||||||
|
height: notificationDelegate.iconSize
|
||||||
|
radius: Math.min(Style.radiusL, width / 2)
|
||||||
|
imagePath: model.cachedImage || model.originalImage || ""
|
||||||
|
borderColor: "transparent"
|
||||||
|
borderWidth: 0
|
||||||
|
fallbackIcon: "bell"
|
||||||
|
fallbackIconSize: 24
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content
|
||||||
|
Column {
|
||||||
|
width: parent.width - notificationDelegate.iconSize - notificationDelegate.buttonClusterWidth - Style.marginM * 2
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
// Header row with app name and timestamp
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
// Urgency indicator
|
||||||
|
Rectangle {
|
||||||
|
width: 6
|
||||||
|
height: 6
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: 3
|
||||||
|
visible: model.urgency !== 1
|
||||||
|
color: {
|
||||||
|
if (model.urgency === 2)
|
||||||
|
return Colors.mError;
|
||||||
|
else if (model.urgency === 0)
|
||||||
|
return Colors.mOnSurfaceVariant;
|
||||||
|
else
|
||||||
|
return "transparent";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: model.appName || "Unknown App"
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Colors.mPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
text: " " + Time.formatRelativeTime(model.timestamp)
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
UText {
|
||||||
|
id: summaryText
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
text: notificationDelegate.isExpanded ? (model.summaryMarkdown || I18n.tr("common.no-summary")) : (model.summary || I18n.tr("common.no-summary"))
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
textFormat: notificationDelegate.notificationTextFormat
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
maximumLineCount: notificationDelegate.isExpanded ? 999 : 2
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body
|
||||||
|
UText {
|
||||||
|
id: bodyText
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
text: notificationDelegate.isExpanded ? (model.bodyMarkdown || "") : (model.body || "")
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
textFormat: notificationDelegate.notificationTextFormat
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
maximumLineCount: notificationDelegate.isExpanded ? 999 : 3
|
||||||
|
elide: Text.ElideRight
|
||||||
|
visible: text.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions Flow
|
||||||
|
Flow {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.marginS
|
||||||
|
visible: notificationDelegate.actionsList.length > 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: notificationDelegate.actionsList
|
||||||
|
|
||||||
|
delegate: UButton {
|
||||||
|
readonly property bool actionNavActive: notificationDelegate.isFocused && root.actionIndex !== -1
|
||||||
|
readonly property bool isSelected: actionNavActive && root.actionIndex === index
|
||||||
|
// Capture modelData in a property to avoid reference errors
|
||||||
|
property var actionData: modelData
|
||||||
|
|
||||||
|
text: modelData.text
|
||||||
|
fontSize: Style.fontSizeS
|
||||||
|
backgroundColor: isSelected ? Colors.mSecondary : Colors.mPrimary
|
||||||
|
textColor: isSelected ? Colors.mOnSecondary : Colors.mOnPrimary
|
||||||
|
outlined: false
|
||||||
|
implicitHeight: 24
|
||||||
|
onHoveredChanged: {
|
||||||
|
if (hovered)
|
||||||
|
root.focusIndex = notificationDelegate.listIndex;
|
||||||
|
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
NotificationService.focusSenderWindow(notificationDelegate.appName);
|
||||||
|
NotificationService.invokeAction(notificationDelegate.notificationId, actionData.identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: notificationDelegate.buttonClusterWidth
|
||||||
|
height: notificationDelegate.actionButtonSize
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
id: expandButton
|
||||||
|
|
||||||
|
iconName: notificationDelegate.isExpanded ? "chevron-up" : "chevron-down"
|
||||||
|
baseSize: notificationDelegate.actionButtonSize
|
||||||
|
opacity: (notificationDelegate.canExpand || notificationDelegate.isExpanded) ? 1 : 0
|
||||||
|
enabled: notificationDelegate.canExpand || notificationDelegate.isExpanded
|
||||||
|
onClicked: {
|
||||||
|
notificationDelegate.pendingLink = "";
|
||||||
|
historyInteractionArea.cursorShape = Qt.ArrowCursor;
|
||||||
|
if (scrollView.expandedId === notificationId)
|
||||||
|
scrollView.expandedId = "";
|
||||||
|
else
|
||||||
|
scrollView.expandedId = notificationId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete button
|
||||||
|
UIconButton {
|
||||||
|
iconName: "trash"
|
||||||
|
baseSize: notificationDelegate.actionButtonSize
|
||||||
|
colorFg: Colors.mError
|
||||||
|
onClicked: {
|
||||||
|
NotificationService.removeFromHistory(notificationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
x: notificationDelegate.swipeOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on swipeOffset {
|
||||||
|
enabled: !notificationDelegate.isSwiping
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
duration: notificationDelegate.removeAnimationDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
enabled: notificationDelegate.isRemoving
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
duration: notificationDelegate.removeAnimationDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
enabled: notificationDelegate.isRemoving
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
duration: notificationDelegate.removeAnimationDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
enabled: notificationDelegate.isRemoving
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
duration: notificationDelegate.removeAnimationDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.UPower
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
UBox {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: layout.implicitHeight + Style.marginL * 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginL
|
||||||
|
spacing: Style.marginL
|
||||||
|
|
||||||
|
// Card Header
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
RowLayout {
|
||||||
|
id: content
|
||||||
|
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
UImageRounded {
|
||||||
|
width: Style.baseWidgetSize * 2.4
|
||||||
|
height: Style.baseWidgetSize * 2.4
|
||||||
|
imagePath: Quickshell.shellDir + "/Assets/Avatar.jpg"
|
||||||
|
fallbackIcon: "person"
|
||||||
|
radius: Style.radiusL
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: `${HostService.username} @ ${HostService.hostname}`
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
font.pointSize: Style.fontSizeL
|
||||||
|
font.capitalization: Font.Capitalize
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Uptime: " + HostService.uptimeText
|
||||||
|
font.pointSize: Style.fontSizeM
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: profileLayout
|
||||||
|
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
// Performance
|
||||||
|
UIconButton {
|
||||||
|
iconName: PowerService.getIcon(PowerProfile.Performance)
|
||||||
|
enabled: PowerService.available
|
||||||
|
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||||
|
colorFg: Colors.mRed
|
||||||
|
radius: height / 2
|
||||||
|
alwaysHover: enabled && PowerService.profile === PowerProfile.Performance
|
||||||
|
onClicked: PowerService.setProfile(PowerProfile.Performance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Balanced
|
||||||
|
UIconButton {
|
||||||
|
iconName: PowerService.getIcon(PowerProfile.Balanced)
|
||||||
|
enabled: PowerService.available
|
||||||
|
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||||
|
colorFg: Colors.mBlue
|
||||||
|
radius: height / 2
|
||||||
|
alwaysHover: enabled && PowerService.profile === PowerProfile.Balanced
|
||||||
|
onClicked: PowerService.setProfile(PowerProfile.Balanced)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eco
|
||||||
|
UIconButton {
|
||||||
|
iconName: PowerService.getIcon(PowerProfile.PowerSaver)
|
||||||
|
enabled: PowerService.available
|
||||||
|
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||||
|
colorFg: Colors.mGreen
|
||||||
|
radius: height / 2
|
||||||
|
alwaysHover: enabled && PowerService.profile === PowerProfile.PowerSaver
|
||||||
|
onClicked: PowerService.setProfile(PowerProfile.PowerSaver)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UDivider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action Tiles
|
||||||
|
GridLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columns: 3
|
||||||
|
rowSpacing: Style.marginM
|
||||||
|
columnSpacing: Style.marginM
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: [{
|
||||||
|
"name": "Lock",
|
||||||
|
"icon": "lock",
|
||||||
|
"isError": false,
|
||||||
|
"clicked": function() {
|
||||||
|
PowerService.lock();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "Logout",
|
||||||
|
"icon": "logout",
|
||||||
|
"isError": false,
|
||||||
|
"clicked": function() {
|
||||||
|
PowerService.logout();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "Suspend",
|
||||||
|
"icon": "moon",
|
||||||
|
"isError": false,
|
||||||
|
"clicked": function() {
|
||||||
|
PowerService.suspend();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "Hibernate",
|
||||||
|
"icon": "bed",
|
||||||
|
"isError": false,
|
||||||
|
"clicked": function() {
|
||||||
|
PowerService.hibernate();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "Reboot",
|
||||||
|
"icon": "refresh",
|
||||||
|
"isError": false,
|
||||||
|
"clicked": function() {
|
||||||
|
PowerService.reboot();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "Shutdown",
|
||||||
|
"icon": "power",
|
||||||
|
"isError": true,
|
||||||
|
"clicked": function() {
|
||||||
|
PowerService.shutdown();
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: tile
|
||||||
|
|
||||||
|
readonly property bool hovered: mouseArea.containsMouse
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Style.baseWidgetSize * 2.2
|
||||||
|
radius: Style.radiusL
|
||||||
|
color: hovered ? (modelData.isError ? Colors.mError : Colors.mPrimary) : Colors.mSurface
|
||||||
|
border.color: hovered ? "transparent" : Colors.mOutline
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
iconName: modelData.icon
|
||||||
|
iconSize: Style.fontSizeXL * 1.1
|
||||||
|
color: tile.hovered ? (modelData.isError ? Colors.mOnError : Colors.mOnPrimary) : (modelData.isError ? Colors.mError : Colors.mOnSurface)
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: modelData.name
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
font.weight: Style.fontWeightMedium
|
||||||
|
color: tile.hovered ? (modelData.isError ? Colors.mOnError : Colors.mOnPrimary) : Colors.mOnSurfaceVariant
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onClicked: {
|
||||||
|
modelData.clicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Services
|
||||||
|
import qs.Utils
|
||||||
|
|
||||||
|
// Weather overview card (placeholder data)
|
||||||
|
UBox {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int forecastDays: 6
|
||||||
|
property bool showLocation: true
|
||||||
|
property bool showEffects: true
|
||||||
|
readonly property bool weatherReady: LocationService.data.weather !== null
|
||||||
|
// Test mode: set to "clear_day", "clear_night", "rain", "snow", "cloud" or "fog"
|
||||||
|
property string testEffects: ""
|
||||||
|
// Weather condition detection
|
||||||
|
readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0
|
||||||
|
readonly property bool isDayTime: weatherReady ? LocationService.data.weather.current_weather.is_day : true
|
||||||
|
readonly property bool isRaining: testEffects === "rain" || (testEffects === "" && ((currentWeatherCode >= 51 && currentWeatherCode <= 67) || (currentWeatherCode >= 80 && currentWeatherCode <= 82)))
|
||||||
|
readonly property bool isSnowing: testEffects === "snow" || (testEffects === "" && ((currentWeatherCode >= 71 && currentWeatherCode <= 77) || (currentWeatherCode >= 85 && currentWeatherCode <= 86)))
|
||||||
|
readonly property bool isCloudy: testEffects === "cloud" || (testEffects === "" && (currentWeatherCode === 3))
|
||||||
|
readonly property bool isFoggy: testEffects === "fog" || (testEffects === "" && (currentWeatherCode >= 40 && currentWeatherCode <= 49))
|
||||||
|
readonly property bool isClearDay: testEffects === "clear_day" || (testEffects === "" && (currentWeatherCode === 0 && isDayTime))
|
||||||
|
readonly property bool isClearNight: testEffects === "clear_night" || (testEffects === "" && (currentWeatherCode === 0 && !isDayTime))
|
||||||
|
|
||||||
|
implicitHeight: Math.max(100, content.implicitHeight + Style.marginXL * 2)
|
||||||
|
|
||||||
|
// Weather effect layer (rain/snow)
|
||||||
|
Loader {
|
||||||
|
id: weatherEffectLoader
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.showEffects && (root.isRaining || root.isSnowing || root.isCloudy || root.isFoggy || root.isClearDay || root.isClearNight)
|
||||||
|
|
||||||
|
sourceComponent: Item {
|
||||||
|
// Animated time for shaders
|
||||||
|
property real shaderTime: 0
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ShaderEffect {
|
||||||
|
id: weatherEffect
|
||||||
|
|
||||||
|
property var source
|
||||||
|
property real time: parent.shaderTime
|
||||||
|
property real itemWidth: weatherEffect.width
|
||||||
|
property real itemHeight: weatherEffect.height
|
||||||
|
property color bgColor: root.color
|
||||||
|
property real cornerRadius: root.isRaining ? 0 : (root.radius - root.border.width)
|
||||||
|
property real alternative: root.isFoggy
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
// Rain matches content margins, everything else fills the box
|
||||||
|
anchors.margins: root.isRaining ? Style.marginXL : root.border.width
|
||||||
|
fragmentShader: {
|
||||||
|
let shaderName;
|
||||||
|
if (root.isSnowing)
|
||||||
|
shaderName = "weather_snow";
|
||||||
|
else if (root.isRaining)
|
||||||
|
shaderName = "weather_rain";
|
||||||
|
else if (root.isCloudy || root.isFoggy)
|
||||||
|
shaderName = "weather_cloud";
|
||||||
|
else if (root.isClearDay)
|
||||||
|
shaderName = "weather_sun";
|
||||||
|
else if (root.isClearNight)
|
||||||
|
shaderName = "weather_stars";
|
||||||
|
else
|
||||||
|
shaderName = "";
|
||||||
|
return Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/" + shaderName + ".frag.qsb");
|
||||||
|
}
|
||||||
|
|
||||||
|
source: ShaderEffectSource {
|
||||||
|
sourceItem: content
|
||||||
|
hideSource: root.isRaining // Only hide for rain (distortion), show for snow
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation on shaderTime {
|
||||||
|
loops: Animation.Infinite
|
||||||
|
from: 0
|
||||||
|
to: 1000
|
||||||
|
duration: 100000
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: content
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginXL
|
||||||
|
spacing: Style.marginM
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: Style.marginXXS
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: Style.marginL
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
iconName: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode, LocationService.data.weather.current_weather.is_day) : "weather-cloud-off"
|
||||||
|
iconSize: Style.fontSizeXXXL * 1.75
|
||||||
|
color: Colors.mPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Style.marginXXS
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: {
|
||||||
|
// Ensure the name is not too long if one had to specify the country
|
||||||
|
const loc = SettingsService.location || "Unknown Location";
|
||||||
|
const chunks = loc.split(",");
|
||||||
|
return chunks[0];
|
||||||
|
}
|
||||||
|
pointSize: Style.fontSizeL
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
visible: showLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
UText {
|
||||||
|
visible: weatherReady
|
||||||
|
text: {
|
||||||
|
if (!weatherReady)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var temp = LocationService.data.weather.current_weather.temperature;
|
||||||
|
var suffix = "C";
|
||||||
|
temp = Math.round(temp);
|
||||||
|
return `${temp}°${suffix}`;
|
||||||
|
}
|
||||||
|
pointSize: showLocation ? Style.fontSizeXL : Style.fontSizeXL * 1.6
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: weatherReady ? `(${LocationService.data.weather.timezone_abbreviation})` : ""
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
visible: LocationService.data.weather && showLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UDivider {
|
||||||
|
visible: weatherReady
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
visible: weatherReady
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: weatherReady ? Math.min(root.forecastDays, LocationService.data.weather.daily.time.length) : 0
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||||
|
text: {
|
||||||
|
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
|
||||||
|
// return I18n.locale.toString(weatherDate, "ddd");
|
||||||
|
return Qt.formatDate(weatherDate, "ddd");
|
||||||
|
}
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
}
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||||
|
iconName: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
|
||||||
|
iconSize: Style.fontSizeXXL * 1.6
|
||||||
|
color: Colors.mPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: {
|
||||||
|
var max = LocationService.data.weather.daily.temperature_2m_max[index];
|
||||||
|
var min = LocationService.data.weather.daily.temperature_2m_min[index];
|
||||||
|
max = Math.round(max);
|
||||||
|
min = Math.round(min);
|
||||||
|
return `${max}°/${min}°`;
|
||||||
|
}
|
||||||
|
pointSize: Style.fontSizeXS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,559 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Components
|
||||||
|
import qs.Services
|
||||||
|
import qs.Utils
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
property string passwordSsid: ""
|
||||||
|
property string passwordInput: ""
|
||||||
|
property string expandedSsid: ""
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Rectangle {
|
||||||
|
visible: NetworkService.lastError.length > 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: errorRow.implicitHeight + (Style.marginS * 2)
|
||||||
|
color: Qt.rgba(Colors.mError.r, Colors.mError.g, Colors.mError.b, 0.1)
|
||||||
|
radius: Style.radiusS
|
||||||
|
border.width: Math.max(1, Style.borderS)
|
||||||
|
border.color: Colors.mError
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: errorRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginS
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
iconName: "warning"
|
||||||
|
iconSize: Style.fontSizeL
|
||||||
|
color: Colors.mError
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: NetworkService.lastError
|
||||||
|
color: Colors.mError
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "close"
|
||||||
|
baseSize: Style.baseWidgetSize * 0.6
|
||||||
|
onClicked: NetworkService.lastError = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main content area
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
color: Colors.transparent
|
||||||
|
|
||||||
|
// WiFi disabled state
|
||||||
|
ColumnLayout {
|
||||||
|
visible: !SettingsService.wifiEnabled
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
iconName: "wifi-off"
|
||||||
|
iconSize: 64
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Wi-Fi Disabled"
|
||||||
|
pointSize: Style.fontSizeL
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Please enable Wi-Fi to connect to a network."
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanning state
|
||||||
|
ColumnLayout {
|
||||||
|
visible: SettingsService.wifiEnabled && NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginL
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UBusyIndicator {
|
||||||
|
running: true
|
||||||
|
color: Colors.mPrimary
|
||||||
|
size: Style.baseWidgetSize
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Searching for networks..."
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Networks list container
|
||||||
|
UScrollView {
|
||||||
|
visible: SettingsService.wifiEnabled && (!NetworkService.scanning || Object.keys(NetworkService.networks).length > 0)
|
||||||
|
anchors.fill: parent
|
||||||
|
horizontalPolicy: ScrollBar.AlwaysOff
|
||||||
|
verticalPolicy: ScrollBar.AsNeeded
|
||||||
|
clip: true
|
||||||
|
contentWidth: availableWidth
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
// Network list
|
||||||
|
Repeater {
|
||||||
|
model: {
|
||||||
|
if (!SettingsService.wifiEnabled)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
const nets = Object.values(NetworkService.networks);
|
||||||
|
return nets.sort((a, b) => {
|
||||||
|
if (a.connected !== b.connected)
|
||||||
|
return a.connected ? -1 : 1;
|
||||||
|
|
||||||
|
return b.signal - a.signal;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
UBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: netColumn.implicitHeight + (Style.marginS * 2)
|
||||||
|
compact: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: netColumn
|
||||||
|
|
||||||
|
width: parent.width - (Style.marginS * 2)
|
||||||
|
x: Style.marginS
|
||||||
|
y: Style.marginS
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
// Main row
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
iconName: NetworkService.signalIcon(modelData.signal)
|
||||||
|
iconSize: Style.fontSizeXXL
|
||||||
|
color: modelData.connected ? Colors.mPrimary : Colors.mOnSurface
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: modelData.ssid
|
||||||
|
pointSize: Style.fontSizeM
|
||||||
|
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: Style.marginXS
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: `${modelData.signal}%`
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "•"
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open"
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: Style.marginXXS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the status badges area (around line 237)
|
||||||
|
Rectangle {
|
||||||
|
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||||
|
color: Colors.mPrimary
|
||||||
|
radius: height * 0.5
|
||||||
|
width: connectedText.implicitWidth + (Style.marginS * 2)
|
||||||
|
height: connectedText.implicitHeight + (Style.marginXXS * 2)
|
||||||
|
|
||||||
|
UText {
|
||||||
|
id: connectedText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "Connected"
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
color: Colors.mOnPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: NetworkService.disconnectingFrom === modelData.ssid
|
||||||
|
color: Colors.mError
|
||||||
|
radius: height * 0.5
|
||||||
|
width: disconnectingText.implicitWidth + (Style.marginS * 2)
|
||||||
|
height: disconnectingText.implicitHeight + (Style.marginXXS * 2)
|
||||||
|
|
||||||
|
UText {
|
||||||
|
id: disconnectingText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "disconnecting"
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
color: Colors.mOnPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: NetworkService.forgettingNetwork === modelData.ssid
|
||||||
|
color: Colors.mError
|
||||||
|
radius: height * 0.5
|
||||||
|
width: forgettingText.implicitWidth + (Style.marginS * 2)
|
||||||
|
height: forgettingText.implicitHeight + (Style.marginXXS * 2)
|
||||||
|
|
||||||
|
UText {
|
||||||
|
id: forgettingText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "forgetting"
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
color: Colors.mOnPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: modelData.cached && !modelData.connected && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||||
|
color: Colors.transparent
|
||||||
|
border.color: Colors.mOutline
|
||||||
|
border.width: Math.max(1, Style.borderS)
|
||||||
|
radius: height * 0.5
|
||||||
|
width: savedText.implicitWidth + (Style.marginS * 2)
|
||||||
|
height: savedText.implicitHeight + (Style.marginXXS * 2)
|
||||||
|
|
||||||
|
UText {
|
||||||
|
id: savedText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "saved"
|
||||||
|
pointSize: Style.fontSizeXXS
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action area
|
||||||
|
RowLayout {
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
UBusyIndicator {
|
||||||
|
visible: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid
|
||||||
|
running: visible
|
||||||
|
color: Colors.mPrimary
|
||||||
|
size: Style.baseWidgetSize * 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||||
|
iconName: "trash"
|
||||||
|
baseSize: Style.baseWidgetSize * 0.8
|
||||||
|
onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid
|
||||||
|
}
|
||||||
|
|
||||||
|
UButton {
|
||||||
|
visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && passwordSsid !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||||
|
text: {
|
||||||
|
if (modelData.existing || modelData.cached)
|
||||||
|
return "Connect";
|
||||||
|
|
||||||
|
if (!NetworkService.isSecured(modelData.security))
|
||||||
|
return "Connect";
|
||||||
|
|
||||||
|
return "Enter Password";
|
||||||
|
}
|
||||||
|
outlined: false
|
||||||
|
fontSize: Style.fontSizeXS
|
||||||
|
enabled: !NetworkService.connecting
|
||||||
|
onClicked: {
|
||||||
|
if (modelData.existing || modelData.cached || !NetworkService.isSecured(modelData.security)) {
|
||||||
|
NetworkService.connect(modelData.ssid);
|
||||||
|
} else {
|
||||||
|
passwordSsid = modelData.ssid;
|
||||||
|
passwordInput = "";
|
||||||
|
expandedSsid = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UButton {
|
||||||
|
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||||
|
text: "Disconnect"
|
||||||
|
outlined: false
|
||||||
|
fontSize: Style.fontSizeXS
|
||||||
|
backgroundColor: Colors.mError
|
||||||
|
onClicked: NetworkService.disconnect(modelData.ssid)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password input
|
||||||
|
Rectangle {
|
||||||
|
visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: passwordRow.implicitHeight + Style.marginS * 2
|
||||||
|
color: Colors.mSurfaceVariant
|
||||||
|
border.color: Colors.mOutline
|
||||||
|
border.width: Math.max(1, Style.borderS)
|
||||||
|
radius: Style.radiusS
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: passwordRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginS
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
radius: Style.radiusXS
|
||||||
|
color: Colors.mSurface
|
||||||
|
border.color: pwdInput.activeFocus ? Colors.mSecondary : Colors.mOutline
|
||||||
|
border.width: Math.max(1, Style.borderS)
|
||||||
|
|
||||||
|
TextInput {
|
||||||
|
id: pwdInput
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.margins: Style.marginS
|
||||||
|
text: passwordInput
|
||||||
|
font.family: Fonts.sans
|
||||||
|
font.pointSize: Style.fontSizeS
|
||||||
|
color: Colors.mOnSurface
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
selectByMouse: true
|
||||||
|
focus: visible
|
||||||
|
passwordCharacter: "●"
|
||||||
|
onTextChanged: passwordInput = text
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible)
|
||||||
|
forceActiveFocus();
|
||||||
|
|
||||||
|
}
|
||||||
|
onAccepted: {
|
||||||
|
if (text && !NetworkService.connecting) {
|
||||||
|
NetworkService.connect(passwordSsid, text);
|
||||||
|
passwordSsid = "";
|
||||||
|
passwordInput = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
visible: parent.text.length === 0
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "Enter Password"
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UButton {
|
||||||
|
text: "Connect"
|
||||||
|
fontSize: Style.fontSizeXXS
|
||||||
|
enabled: passwordInput.length > 0 && !NetworkService.connecting
|
||||||
|
outlined: false
|
||||||
|
onClicked: {
|
||||||
|
NetworkService.connect(passwordSsid, passwordInput);
|
||||||
|
passwordSsid = "";
|
||||||
|
passwordInput = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "close"
|
||||||
|
baseSize: Style.baseWidgetSize * 0.8
|
||||||
|
onClicked: {
|
||||||
|
passwordSsid = "";
|
||||||
|
passwordInput = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forget network
|
||||||
|
Rectangle {
|
||||||
|
visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: forgetRow.implicitHeight + Style.marginS * 2
|
||||||
|
color: Colors.mSurfaceVariant
|
||||||
|
radius: Style.radiusS
|
||||||
|
border.width: Math.max(1, Style.borderS)
|
||||||
|
border.color: Colors.mOutline
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: forgetRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginS
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
UIcon {
|
||||||
|
iconName: "trash"
|
||||||
|
iconSize: Style.fontSizeL
|
||||||
|
color: Colors.mError
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "Forget this network?"
|
||||||
|
pointSize: Style.fontSizeS
|
||||||
|
color: Colors.mError
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UButton {
|
||||||
|
id: forgetButton
|
||||||
|
|
||||||
|
text: "Forget"
|
||||||
|
fontSize: Style.fontSizeXXS
|
||||||
|
backgroundColor: Colors.mError
|
||||||
|
outlined: false
|
||||||
|
onClicked: {
|
||||||
|
NetworkService.forget(modelData.ssid);
|
||||||
|
expandedSsid = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UIconButton {
|
||||||
|
iconName: "close"
|
||||||
|
baseSize: Style.baseWidgetSize * 0.8
|
||||||
|
onClicked: expandedSsid = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smooth opacity animation
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty state when no networks
|
||||||
|
ColumnLayout {
|
||||||
|
visible: SettingsService.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Style.marginL
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
UIcon {
|
||||||
|
iconName: "search"
|
||||||
|
iconSize: 64
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UText {
|
||||||
|
text: "No networks found"
|
||||||
|
pointSize: Style.fontSizeL
|
||||||
|
color: Colors.mOnSurfaceVariant
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
UButton {
|
||||||
|
text: "Scan Again"
|
||||||
|
icon: "refresh"
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
onClicked: NetworkService.scan()
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.Constants
|
||||||
|
import qs.Modules.Sidebar.Components
|
||||||
|
import qs.Modules.Sidebar.Modules
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property var modelData
|
||||||
|
|
||||||
|
SidebarWrap {
|
||||||
|
screen: modelData
|
||||||
|
isLeft: true
|
||||||
|
|
||||||
|
contentComponent: ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginM
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
WeatherCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CalendarMonthCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
LyricsCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
LyricsControl {
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionCard {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarWrap {
|
||||||
|
screen: modelData
|
||||||
|
isLeft: false
|
||||||
|
|
||||||
|
contentComponent: ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginM
|
||||||
|
spacing: Style.marginM
|
||||||
|
|
||||||
|
PowerMenuCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationHistoryCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
|
|
||||||
// Rounded group container using the variant surface color.
|
|
||||||
// To be used in side panels and settings panes to group fields or buttons.
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property bool compact: false
|
|
||||||
|
|
||||||
implicitWidth: childrenRect.width
|
|
||||||
implicitHeight: childrenRect.height
|
|
||||||
color: compact ? Color.transparent : Color.mSurfaceVariant
|
|
||||||
radius: Style.radiusM
|
|
||||||
layer.enabled: !compact
|
|
||||||
|
|
||||||
layer.effect: DropShadow {
|
|
||||||
horizontalOffset: 6
|
|
||||||
verticalOffset: 6
|
|
||||||
radius: 8
|
|
||||||
samples: 12
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// Public properties
|
|
||||||
property string text: ""
|
|
||||||
property string icon: ""
|
|
||||||
property string tooltipText
|
|
||||||
property color backgroundColor: Color.mPrimary
|
|
||||||
property color textColor: Color.mOnPrimary
|
|
||||||
property color hoverColor: Color.mTertiary
|
|
||||||
property bool enabled: true
|
|
||||||
property real fontSize: Style.fontSizeM
|
|
||||||
property int fontWeight: Style.fontWeightBold
|
|
||||||
property string fontFamily: Fonts.primary
|
|
||||||
property real iconSize: Style.fontSizeL
|
|
||||||
property bool outlined: false
|
|
||||||
// Internal properties
|
|
||||||
property bool hovered: false
|
|
||||||
property bool pressed: false
|
|
||||||
|
|
||||||
// Signals
|
|
||||||
signal clicked()
|
|
||||||
signal rightClicked()
|
|
||||||
signal middleClicked()
|
|
||||||
|
|
||||||
// Dimensions
|
|
||||||
implicitWidth: contentRow.implicitWidth + (Style.marginL * 2)
|
|
||||||
implicitHeight: Math.max(Style.baseWidgetSize, contentRow.implicitHeight + (Style.marginM))
|
|
||||||
// Appearance
|
|
||||||
radius: Style.radiusS
|
|
||||||
color: {
|
|
||||||
if (!enabled)
|
|
||||||
return outlined ? Color.transparent : Qt.lighter(Color.mSurfaceVariant, 1.2);
|
|
||||||
|
|
||||||
if (hovered)
|
|
||||||
return hoverColor;
|
|
||||||
|
|
||||||
return outlined ? Color.transparent : backgroundColor;
|
|
||||||
}
|
|
||||||
border.width: outlined ? Math.max(1, Style.borderS) : 0
|
|
||||||
border.color: {
|
|
||||||
if (!enabled)
|
|
||||||
return Color.mOutline;
|
|
||||||
|
|
||||||
if (pressed || hovered)
|
|
||||||
return backgroundColor;
|
|
||||||
|
|
||||||
return outlined ? backgroundColor : Color.transparent;
|
|
||||||
}
|
|
||||||
opacity: enabled ? 1 : 0.6
|
|
||||||
|
|
||||||
// Content
|
|
||||||
RowLayout {
|
|
||||||
id: contentRow
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Style.marginXS
|
|
||||||
|
|
||||||
// Icon (optional)
|
|
||||||
NIcon {
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
visible: root.icon !== ""
|
|
||||||
icon: root.icon
|
|
||||||
pointSize: root.iconSize
|
|
||||||
color: {
|
|
||||||
if (!root.enabled)
|
|
||||||
return Color.mOnSurfaceVariant;
|
|
||||||
|
|
||||||
if (root.outlined) {
|
|
||||||
if (root.pressed || root.hovered)
|
|
||||||
return root.backgroundColor;
|
|
||||||
|
|
||||||
return root.backgroundColor;
|
|
||||||
}
|
|
||||||
return root.textColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text
|
|
||||||
NText {
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
visible: root.text !== ""
|
|
||||||
text: root.text
|
|
||||||
pointSize: root.fontSize
|
|
||||||
font.weight: root.fontWeight
|
|
||||||
family: root.fontFamily
|
|
||||||
color: {
|
|
||||||
if (!root.enabled)
|
|
||||||
return Color.mOnSurfaceVariant;
|
|
||||||
|
|
||||||
if (root.outlined) {
|
|
||||||
if (root.hovered)
|
|
||||||
return root.textColor;
|
|
||||||
|
|
||||||
return root.backgroundColor;
|
|
||||||
}
|
|
||||||
return root.textColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mouse interaction
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.enabled
|
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
||||||
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
onEntered: {
|
|
||||||
root.hovered = true;
|
|
||||||
if (tooltipText)
|
|
||||||
TooltipService.show(Screen, root, root.tooltipText);
|
|
||||||
|
|
||||||
}
|
|
||||||
onExited: {
|
|
||||||
root.hovered = false;
|
|
||||||
if (tooltipText)
|
|
||||||
TooltipService.hide();
|
|
||||||
|
|
||||||
}
|
|
||||||
onPressed: (mouse) => {
|
|
||||||
if (tooltipText)
|
|
||||||
TooltipService.hide();
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton)
|
|
||||||
root.clicked();
|
|
||||||
else if (mouse.button == Qt.RightButton)
|
|
||||||
root.rightClicked();
|
|
||||||
else if (mouse.button == Qt.MiddleButton)
|
|
||||||
root.middleClicked();
|
|
||||||
}
|
|
||||||
onCanceled: {
|
|
||||||
root.hovered = false;
|
|
||||||
if (tooltipText)
|
|
||||||
TooltipService.hide();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
import qs.Utils
|
|
||||||
|
|
||||||
// Compact circular statistic display using Layout management
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property real value: 0 // 0..100 (or any range visually mapped)
|
|
||||||
property string icon: ""
|
|
||||||
property string suffix: "%"
|
|
||||||
// When nested inside a parent group (NBox), you can make it flat
|
|
||||||
property bool flat: false
|
|
||||||
// Scales the internal content (labels, gauge, icon) without changing the
|
|
||||||
// outer width/height footprint of the component
|
|
||||||
property real contentScale: 1
|
|
||||||
|
|
||||||
width: 68
|
|
||||||
height: 92
|
|
||||||
color: flat ? Color.transparent : Color.mSurface
|
|
||||||
radius: Style.radiusS
|
|
||||||
border.color: flat ? Color.transparent : Color.mSurfaceVariant
|
|
||||||
border.width: flat ? 0 : Math.max(1, Style.borderS)
|
|
||||||
// Repaint gauge when the bound value changes
|
|
||||||
onValueChanged: gauge.requestPaint()
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: mainLayout
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginS * contentScale
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
// Main gauge container
|
|
||||||
Item {
|
|
||||||
id: gaugeContainer
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.alignment: Qt.AlignCenter
|
|
||||||
Layout.preferredWidth: 68 * contentScale
|
|
||||||
Layout.preferredHeight: 68 * contentScale
|
|
||||||
|
|
||||||
Canvas {
|
|
||||||
// 390° (equivalent to 30°)
|
|
||||||
|
|
||||||
id: gauge
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
renderStrategy: Canvas.Cooperative
|
|
||||||
onPaint: {
|
|
||||||
const ctx = getContext("2d");
|
|
||||||
const w = width, h = height;
|
|
||||||
const cx = w / 2, cy = h / 2;
|
|
||||||
const r = Math.min(w, h) / 2 - 5 * contentScale;
|
|
||||||
// Rotated 90° to the right: gap at the bottom
|
|
||||||
// Start at 150° and end at 390° (30°) → bottom opening
|
|
||||||
const start = Math.PI * 5 / 6;
|
|
||||||
// 150°
|
|
||||||
const endBg = Math.PI * 13 / 6;
|
|
||||||
ctx.reset();
|
|
||||||
ctx.lineWidth = 6 * contentScale;
|
|
||||||
// Track uses surfaceVariant for stronger contrast
|
|
||||||
ctx.strokeStyle = Color.mSurface;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(cx, cy, r, start, endBg);
|
|
||||||
ctx.stroke();
|
|
||||||
// Value arc
|
|
||||||
const ratio = Math.max(0, Math.min(1, root.value / 100));
|
|
||||||
const end = start + (endBg - start) * ratio;
|
|
||||||
ctx.strokeStyle = Color.mPrimary;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(cx, cy, r, start, end);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Percent centered in the circle
|
|
||||||
NText {
|
|
||||||
id: valueLabel
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
anchors.verticalCenterOffset: -4 * contentScale
|
|
||||||
text: `${root.value}${root.suffix}`
|
|
||||||
pointSize: Style.fontSizeM * contentScale
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Color.mOnSurface
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tiny circular badge for the icon, positioned inside below the percentage
|
|
||||||
Rectangle {
|
|
||||||
id: iconBadge
|
|
||||||
|
|
||||||
width: iconText.implicitWidth + Style.marginXXS
|
|
||||||
height: width
|
|
||||||
radius: width / 2
|
|
||||||
color: Color.mPrimary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: valueLabel.bottom
|
|
||||||
anchors.topMargin: 8 * contentScale
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
id: iconText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
icon: root.icon
|
|
||||||
color: Color.mOnPrimary
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string icon: Icons.defaultIcon
|
|
||||||
property real pointSize: Style.fontSizeL
|
|
||||||
|
|
||||||
visible: (icon !== undefined) && (icon !== "")
|
|
||||||
text: {
|
|
||||||
if ((icon === undefined) || (icon === ""))
|
|
||||||
return "";
|
|
||||||
|
|
||||||
if (Icons.get(icon) === undefined) {
|
|
||||||
Logger.warn("Icon", `"${icon}"`, "doesn't exist in the icons font");
|
|
||||||
Logger.callStack();
|
|
||||||
return Icons.get(Icons.defaultIcon);
|
|
||||||
}
|
|
||||||
return Icons.get(icon);
|
|
||||||
}
|
|
||||||
font.family: Icons.fontFamily
|
|
||||||
font.pointSize: root.pointSize
|
|
||||||
color: Color.mOnSurface
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property real baseSize: Style.baseWidgetSize
|
|
||||||
property string icon
|
|
||||||
property bool enabled: true
|
|
||||||
property bool allowClickWhenDisabled: false
|
|
||||||
property bool hovering: false
|
|
||||||
property bool compact: false
|
|
||||||
property color colorBg: Color.mSurfaceVariant
|
|
||||||
property color colorFg: Color.mPrimary
|
|
||||||
property color colorBgHover: Color.mTertiary
|
|
||||||
property color colorFgHover: Color.mOnTertiary
|
|
||||||
property color colorBorder: Color.transparent
|
|
||||||
property color colorBorderHover: Color.transparent
|
|
||||||
|
|
||||||
signal entered()
|
|
||||||
signal exited()
|
|
||||||
signal clicked()
|
|
||||||
signal rightClicked()
|
|
||||||
signal middleClicked()
|
|
||||||
|
|
||||||
implicitWidth: Math.round(baseSize)
|
|
||||||
implicitHeight: Math.round(baseSize)
|
|
||||||
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
|
|
||||||
color: root.enabled && root.hovering ? colorBgHover : colorBg
|
|
||||||
radius: width * 0.5
|
|
||||||
border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder
|
|
||||||
border.width: Math.max(1, Style.borderS)
|
|
||||||
|
|
||||||
NIcon {
|
|
||||||
icon: root.icon
|
|
||||||
pointSize: Math.max(1, root.compact ? root.width * 0.65 : root.width * 0.48)
|
|
||||||
color: root.enabled && root.hovering ? colorFgHover : colorFg
|
|
||||||
// Center horizontally
|
|
||||||
x: (root.width - width) / 2
|
|
||||||
// Center vertically accounting for font metrics
|
|
||||||
y: (root.height - height) / 2 + (height - contentHeight) / 2
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
// Always enabled to allow hover/tooltip even when the button is disabled
|
|
||||||
enabled: true
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
||||||
hoverEnabled: true
|
|
||||||
onEntered: {
|
|
||||||
hovering = root.enabled ? true : false;
|
|
||||||
root.entered();
|
|
||||||
}
|
|
||||||
onExited: {
|
|
||||||
hovering = false;
|
|
||||||
root.exited();
|
|
||||||
}
|
|
||||||
onClicked: function(mouse) {
|
|
||||||
if (!root.enabled && !allowClickWhenDisabled)
|
|
||||||
return ;
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton)
|
|
||||||
root.clicked();
|
|
||||||
else if (mouse.button === Qt.RightButton)
|
|
||||||
root.rightClicked();
|
|
||||||
else if (mouse.button === Qt.MiddleButton)
|
|
||||||
root.middleClicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationNormal
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string imagePath: ""
|
|
||||||
property color borderColor: Color.transparent
|
|
||||||
property real borderWidth: 0
|
|
||||||
property string fallbackIcon: ""
|
|
||||||
property real fallbackIconSize: Style.fontSizeXXL
|
|
||||||
|
|
||||||
color: Color.transparent
|
|
||||||
radius: parent.width * 0.5
|
|
||||||
anchors.margins: Style.marginXXS
|
|
||||||
|
|
||||||
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 {
|
|
||||||
property var source
|
|
||||||
property real imageOpacity: root.opacity
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/circled_image.frag.qsb")
|
|
||||||
supportsAtlasTextures: false
|
|
||||||
blending: true
|
|
||||||
|
|
||||||
source: ShaderEffectSource {
|
|
||||||
sourceItem: img
|
|
||||||
hideSource: true
|
|
||||||
live: true
|
|
||||||
recursive: false
|
|
||||||
format: ShaderEffectSource.RGBA
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback icon
|
|
||||||
Loader {
|
|
||||||
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
sourceComponent: NIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
icon: fallbackIcon
|
|
||||||
pointSize: fallbackIconSize
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Constants
|
|
||||||
import qs.Noctalia
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string imagePath: ""
|
|
||||||
property color borderColor: Color.transparent
|
|
||||||
property real borderWidth: 0
|
|
||||||
property real imageRadius: width * 0.5
|
|
||||||
property string fallbackIcon: ""
|
|
||||||
property real fallbackIconSize: Style.fontSizeXXL
|
|
||||||
property real scaledRadius: imageRadius
|
|
||||||
|
|
||||||
signal statusChanged(int status)
|
|
||||||
|
|
||||||
color: Color.transparent
|
|
||||||
radius: scaledRadius
|
|
||||||
anchors.margins: Style.marginXXS
|
|
||||||
|
|
||||||
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
|
|
||||||
onStatusChanged: root.statusChanged(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderEffect {
|
|
||||||
property var source
|
|
||||||
// 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
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/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: Color.transparent
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
source: ShaderEffectSource {
|
|
||||||
sourceItem: img
|
|
||||||
hideSource: true
|
|
||||||
live: true
|
|
||||||
recursive: false
|
|
||||||
format: ShaderEffectSource.RGBA
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback icon
|
|
||||||
Loader {
|
|
||||||
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
sourceComponent: NIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
icon: fallbackIcon
|
|
||||||
pointSize: fallbackIconSize
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Constants
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string label: ""
|
|
||||||
property string description: ""
|
|
||||||
property color labelColor: Color.mOnSurface
|
|
||||||
property color descriptionColor: Color.mOnSurfaceVariant
|
|
||||||
|
|
||||||
spacing: Style.marginXXS
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: label
|
|
||||||
pointSize: Style.fontSizeL
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: labelColor
|
|
||||||
visible: label !== ""
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: description
|
|
||||||
pointSize: Style.fontSizeS
|
|
||||||
color: descriptionColor
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
visible: description !== ""
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user