Initial commit

remove syncthing folder

bar/popops: fix menu window anims and positioning

bar/popops: change anims a little and add dropshadow

Update README.md

widgets/coloredicon: move to colorization, looks worse but..., yea

bar/popops: make popup window dissapear on menu close

README: add todo list, and brief desc

Update README.md

Update README.md

Update README.md

bar/systray: issue recreate on interact

bar/systray: hide popup on interact

bar/systray: add arrow for entries with children

bar/battery: start of battery widget

wallpaper/matugen: add foot template

extra sizing conditions for sys tray

bar/systray: add some more margin to text

update settings schema

bar/workspaces: filter by monitor, switch to scriptmodel

settings: fix settings lol

bar/systray: fix right item

feat: screenshot tool

clipboard one day...

feat: init lockscreen

mpris: add ipc handler for multimedia keys

mpris stuff

save progress

put shell in subdir, and add nix package

move readme back woops

bar/volume: make tool bar smaller

greeter: init greeter

greeter: fixed resource links

readme: update checklist

progress maybe, maybe not

fix: fixed screenshot tool not working

fix: bar layout issues

progress save

progress update

track styled popup

still broken but getting there

still broken but getting there

fix: gitignore qmlls.ini

fix: remove qmlls.ini

progress save

new popup system

new popup system

new popup system

more work on popups

fix: mask issues on popups

update readme
This commit is contained in:
kossLAN 2025-06-07 04:01:14 -04:00
commit 9e44812e93
Signed by: kossLAN
SSH key fingerprint: SHA256:bdV0x+wdQHGJ6LgmstH3KV8OpWY+OOFmJcPcB0wQPV8
102 changed files with 4592 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
shell/.qmlls.ini

16
README.md Normal file
View file

@ -0,0 +1,16 @@
# kossLAN's personal quickshell dots, for personal use.
The idea is to eventually be minimal but also use material 3 design language, low padding, low margin, and not distracting.
## TODO List
- [x] Custom Popup Window Surface for smooth anims on top bar
- [x] Lockscreen (WIP)
- [x] Make volume mixer
- [x] Screenshot tool (WIP - kinda scuffed, but is functional)
- [ ] Recording/Clip widget with gpuscreenrecorder
- [x] Session Manager
- [ ] Battery Profile Popup
- [ ] REDO Volume OSD (WIP)
- [ ] REDO Launcher (wallpaper picker, calculator, commands, etc...)
- [ ] Music Player Popup V4 lol
- [ ] Add bluetooth module support

26
flake.lock generated Normal file
View file

@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1750178436,
"narHash": "sha256-t1lcWocjeNT3kYqxYUj3R/O/9PbNsvYFzW50NRkx6X4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "479251543fd2a1256569108a5dee5c79e6caf8bd",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

31
flake.nix Normal file
View file

@ -0,0 +1,31 @@
{
description = "kossLAN's quickshell dots";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
};
outputs = {
self,
nixpkgs,
}: let
forEachSystem = fn:
nixpkgs.lib.genAttrs
["x86_64-linux" "aarch64-linux"]
(system: fn system nixpkgs.legacyPackages.${system});
in {
packages = forEachSystem (system: pkgs: rec {
default = minmat;
minmat = pkgs.stdenv.mkDerivation {
pname = "minmat";
version = "0.1.0";
src = ./shell;
installPhase = ''
mkdir -p $out/etc/quickshell
cp -r * $out/etc/quickshell
'';
};
});
};
}

46
shell/ShellSettings.qml Normal file
View file

@ -0,0 +1,46 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
property alias settings: jsonAdapter.settings
property alias sizing: jsonAdapter.sizing
property QtObject colors: QtObject {
property color surface: Qt.rgba(1.0, 1.0, 1.0, 1.0)
property color surface_translucent: Qt.rgba(0.0, 0.0, 0.0, 0.15)
property color surface_container: Qt.rgba(0.25, 0.25, 0.25, 1.0)
property color surface_container_translucent: Qt.rgba(0.25, 0.25, 0.25, 0.25)
property color highlight: Qt.rgba(1.0, 1.0, 1.0, 0.85)
// property color primary: "#2EADC6"
property color active: Qt.rgba(1.0, 1.0, 1.0, 1.0)
property color active_translucent: Qt.rgba(1.0, 1.0, 1.0, 0.15)
property color border_translucent: Qt.rgba(1.0, 1.0, 1.0, 0.05)
property color inactive: Qt.rgba(0.25, 0.25, 0.25, 1.0)
property color inactive_translucent: Qt.rgba(0.25, 0.25, 0.25, 0.15)
}
FileView {
path: `${Quickshell.dataPath("settings")}/quickshell/settings.json`
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: writeAdapter()
blockLoading: true
JsonAdapter {
id: jsonAdapter
property JsonObject settings: JsonObject {
property string wallpaperUrl: Qt.resolvedUrl("root:resources/wallpapers/pixelart0.jpg")
property string screenshotPath: "/home/koss/Pictures"
property real opacity: 0.55
}
property JsonObject sizing: JsonObject {
property int barHeight: 25
}
}
}
}

View file

@ -0,0 +1,12 @@
import QtQuick
import Quickshell.Wayland
import ".."
Text {
id: windowText
text: ToplevelManager.activeToplevel?.title ?? ""
color: ShellSettings.colors.active
font.pointSize: 11
visible: text !== ""
elide: Text.ElideRight
}

101
shell/bar/Bar.qml Normal file
View file

@ -0,0 +1,101 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import "power"
// import "volume"
import "systray"
// import qs.widgets
import qs
Variants {
model: Quickshell.screens
delegate: PanelWindow {
id: root
color: ShellSettings.colors.surface_translucent
implicitHeight: ShellSettings.sizing.barHeight
screen: modelData
required property var modelData
anchors {
top: true
left: true
right: true
}
readonly property Popup popup: Popup {
bar: root
}
RowLayout {
spacing: 0
anchors {
fill: parent
leftMargin: 5
rightMargin: 5
}
// Left side of bar
RowLayout {
spacing: 15
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignLeft
Workspaces {
screen: root.screen
Layout.fillHeight: true
}
ActiveWindow {
id: activeWindow
Layout.preferredWidth: 400
}
}
// PowerMenu {
// bar: root
// Layout.fillHeight: true
// }
// Right side of bar
RowLayout {
spacing: 10
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignRight
SysTray {
bar: root
Layout.fillHeight: true
}
// VolumeIndicator {
// id: volumeIndicator
// popup: root.popup
// Layout.preferredWidth: this.height
// Layout.fillHeight: true
// Layout.topMargin: 2
// Layout.bottomMargin: 2
// }
// PowerMenu {
// bar: root
// Layout.fillHeight: true
// }
// Widgets.Separator {
// Layout.leftMargin: 5
// Layout.rightMargin: 5
// }
Clock {
id: clock
color: ShellSettings.colors.active
}
}
}
}
}

21
shell/bar/Clock.qml Normal file
View file

@ -0,0 +1,21 @@
import QtQuick
import Quickshell
Text {
property string ap: sysClock.hours >= 12 ? "PM" : "AM"
property string minutes: sysClock.minutes.toString().padStart(2, '0')
property string hours: {
var value = sysClock.hours % 12;
if (value === 0)
return 12;
return value;
}
SystemClock {
id: sysClock
enabled: true
}
text: `${hours}:${minutes} ${ap}`
font.pointSize: 11
}

197
shell/bar/Popup.qml Normal file
View file

@ -0,0 +1,197 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Hyprland
import qs.widgets
Scope {
id: root
required property var bar
property real gaps: 5
property Item parentItem
property PopupItem activeItem
property PopupItem lastActiveItem
property PopupItem shownItem: activeItem ?? lastActiveItem
onActiveItemChanged: {
if (activeItem != null) {
activeItem.targetVisible = true;
if (parentItem) {
activeItem.parent = parentItem;
}
}
if (lastActiveItem != null && lastActiveItem != activeItem) {
lastActiveItem.targetVisible = false;
}
if (activeItem != null) {
lastActiveItem = activeItem;
}
}
function setItem(item: PopupItem) {
activeItem = item;
}
function removeItem(item: PopupItem) {
if (activeItem == item) {
activeItem = null;
}
}
function onHidden(item: PopupItem) {
if (item == lastActiveItem) {
console.log("triggered");
lastActiveItem = null;
}
}
property real scaleMul: lastActiveItem && lastActiveItem.targetVisible ? 1 : 0
Behavior on scaleMul {
SmoothedAnimation {
velocity: 5
}
}
LazyLoader {
id: popupLoader
activeAsync: root.shownItem != null
PopupWindow {
id: popup
visible: true
color: "transparent"
implicitWidth: root.bar.width
implicitHeight: Math.max(800, parentItem.targetHeight)
anchor {
window: root.bar
rect: Qt.rect(0, 0, root.bar.width, root.bar.height)
edges: Edges.Bottom | Edges.Left
gravity: Edges.Bottom | Edges.Right
adjustment: PopupAdjustment.None
}
mask: Region {
item: parentItem
}
HyprlandFocusGrab {
id: grab
active: true
windows: [popup, root.bar]
onCleared: {
if (!active) {
root.shownItem.closed();
}
}
}
// HyprlandWindow.opacity: root.scaleMul
HyprlandWindow.visibleMask: popup.mask
Connections {
target: root
function onScaleMulChanged() {
popup.mask.changed();
}
}
StyledRectangle {
id: parentItem
width: targetWidth
height: targetHeight
x: targetX
y: root.gaps
transform: Scale {
origin.x: parentItem.targetX
origin.y: 0
xScale: 1
yScale: root.scaleMul
}
readonly property var targetWidth: root.shownItem?.implicitWidth ?? 0
readonly property var targetHeight: root.shownItem?.implicitHeight ?? 0
readonly property var targetX: {
if (root.shownItem == null) {
return 0;
}
let owner = root.shownItem.owner;
let bar = root.bar;
let isCentered = root.shownItem.centered;
let xPos = owner.mapToItem(bar.contentItem, 0, bar.height, owner.width, 0).x;
let rightEdge = xPos + targetWidth;
let maxRightEdge = popup.width;
if (isCentered) {
return xPos - (targetWidth / 2) + (owner.width / 2);
}
if (rightEdge > maxRightEdge) {
// touching right edge, reposition
// console.log("touching right edge");
return maxRightEdge - targetWidth - root.gaps;
}
return xPos;
}
Component.onCompleted: {
root.parentItem = this;
if (root.activeItem) {
root.activeItem.parent = this;
}
}
// TODO: Make a close animation, a little complicated, will need to track if an animation is running
// and stop unload from occuring until its done, in the LazyLoader.
Behavior on x {
enabled: root.lastActiveItem != null
SmoothedAnimation {
duration: 200
easing.type: Easing.InOutQuad
}
}
Behavior on width {
enabled: root.lastActiveItem != null
SmoothedAnimation {
duration: 200
easing.type: Easing.InOutQuad
}
}
Behavior on height {
enabled: root.lastActiveItem != null
SmoothedAnimation {
duration: 200
easing.type: Easing.InOutQuad
}
}
// SmoothedAnimation on height {
// duration: 200
// easing.type: Easing.InOutQuad
// to: parentItem.targetHeight
// onToChanged: restart()
// }
}
}
}
}

66
shell/bar/PopupItem.qml Normal file
View file

@ -0,0 +1,66 @@
import QtQuick
import qs.widgets
Item {
id: root
visible: false
opacity: root.targetOpacity
onShowChanged: {
if (show) {
popup.setItem(this);
} else {
popup.removeItem(this);
}
}
onTargetVisibleChanged: {
if (targetVisible) {
visible = true;
targetOpacity = 1;
} else {
console.log("closed");
closed();
targetOpacity = 0;
}
}
onTargetOpacityChanged: {
if (!targetVisible && targetOpacity == 0) {
visible = false;
this.parent = null;
if (popup)
popup.onHidden(this);
}
}
readonly property alias contentItem: contentItem
default property alias data: contentItem.data
readonly property Item item: contentItem
Item {
id: contentItem
anchors.fill: parent
// anchors.margins: 5
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
}
required property var popup
required property var owner
property bool centered: false
property bool show: false
signal closed
property bool targetVisible: false
property real targetOpacity: 0
Behavior on targetOpacity {
id: opacityAnimation
SmoothedAnimation {
velocity: 5
}
}
}

72
shell/bar/Workspaces.qml Normal file
View file

@ -0,0 +1,72 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import qs
RowLayout {
spacing: 6
visible: Hyprland.monitors.values.length != 0
required property var screen
Repeater {
id: workspaceButtons
model: ScriptModel {
values: Hyprland.workspaces.values.slice().filter(workspace => workspace.monitor === Hyprland.monitorFor(screen))
}
Rectangle {
radius: height / 2
color: {
let value = ShellSettings.colors.active_translucent;
if (!modelData?.id || !Hyprland.focusedMonitor?.activeWorkspace?.id)
return value;
if (workspaceButton.containsMouse) {
value = ShellSettings.colors.highlight;
} else if (Hyprland.focusedMonitor.activeWorkspace.id === modelData.id) {
value = ShellSettings.colors.highlight;
}
return value;
}
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 12
Layout.preferredWidth: {
if (Hyprland.focusedMonitor?.activeWorkspace?.id === modelData?.id)
return 25;
return 12;
}
required property var modelData
Behavior on Layout.preferredWidth {
SmoothedAnimation {
duration: 150
velocity: 200
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 100
easing.type: Easing.OutQuad
}
}
MouseArea {
id: workspaceButton
anchors.fill: parent
hoverEnabled: true
onPressed: Hyprland.dispatch(`workspace ${parent.modelData.id}`)
}
}
}
}

View file

@ -0,0 +1,42 @@
import QtQuick
import "../../mpris" as Mpris
import "../../widgets" as Widgets
import "../.."
Widgets.MaterialButton {
id: root
radius: 6
implicitWidth: mediaInfo.contentWidth + 8
implicitHeight: parent.height
// onClicked: {
// popup.visible = !popup.visible;
// }
required property var bar
property var player: Mpris.Controller.trackedPlayer
Text {
id: mediaInfo
text: root.player?.trackTitle ?? ""
color: root.containsMouse ? ShellSettings.colors["inverse_primary"] : ShellSettings.colors["inverse_surface"]
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pointSize: 11
anchors.centerIn: parent
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}
// WidgetWindow {
// id: popup
// visible: false
// parentWindow: root.bar
//
// // anchor.window: root.bar
// }

View file

@ -0,0 +1,9 @@
import Quickshell
PopupWindow {
id: root
color: "red"
implicitWidth: 500
implicitHeight: 500
}

View file

@ -0,0 +1,70 @@
pragma ComponentBehavior: Bound
import QtQuick
import Qt5Compat.GraphicalEffects
import Quickshell.Widgets
import "../.."
Item {
id: root
required property var bar
property var implicitSize: 0
readonly property real actualSize: Math.min(root.width, root.height)
implicitWidth: parent.height
implicitHeight: parent.height
NotificationCenter {
id: notificationCenter
}
Rectangle {
color: mouseArea.containsMouse ? ShellSettings.colors["primary"] : "transparent"
radius: 5
anchors {
fill: parent
margins: 1
}
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onPressed: {
if (notificationCenter.visible) {
notificationCenter.hide();
} else {
notificationCenter.show();
}
}
}
Item {
implicitWidth: root.implicitSize
implicitHeight: root.implicitSize
anchors.centerIn: parent
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: root.actualSize
height: root.actualSize
color: "white"
}
maskSource: IconImage {
implicitSize: root.actualSize
source: "root:resources/general/notification.svg"
}
}
Rectangle {
color: mouseArea.containsMouse ? ShellSettings.colors["inverse_primary"] : ShellSettings.colors["inverse_surface"]
anchors.fill: parent
}
}
// TODO: notification number overlay
}

View file

@ -0,0 +1,72 @@
pragma ComponentBehavior: Bound
import QtQuick
import Qt5Compat.GraphicalEffects
import Quickshell.Widgets
import Quickshell.Services.UPower
import qs.widgets
import qs.bar
import qs
// todo: redo the tray icon handling
StyledMouseArea {
id: root
implicitWidth: height + 8 // for margin
visible: UPower.displayDevice.isLaptopBattery
onClicked: showMenu = !showMenu
required property var bar
property bool showMenu: false
Item {
implicitWidth: parent.height
implicitHeight: parent.height
anchors.centerIn: parent
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: root.width
height: root.height
color: "white"
}
maskSource: IconImage {
implicitSize: root.width
source: "root:resources/battery/battery.svg"
}
}
Rectangle {
id: batteryBackground
color: Qt.color(ShellSettings.colors["surface"]).lighter(4)
opacity: 0.75
anchors {
fill: parent
margins: 2
}
}
Rectangle {
id: batteryPercentage
width: (parent.width - 4) * UPower.displayDevice.percentage
color: ShellSettings.colors["inverse_surface"]
anchors {
left: batteryBackground.left
top: batteryBackground.top
bottom: batteryBackground.bottom
}
}
}
property PopupItem menu: PopupItem {
owner: root
popup: root.bar.popup
show: root.showMenu
onClosed: root.showMenu = false
centered: true
implicitWidth: 250
implicitHeight: 250
}
}

View file

@ -0,0 +1,96 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import "../../widgets"
import ".."
// TODO:
// 1. Get rid of leftItem/rightItem properties on menu
// 2. Load menu properly, right now its pretty buggy
// 3. Fix bug that causes close on update (nm-applet wifi networks updating)
RowLayout {
id: root
spacing: 5
visible: SystemTray.items.values.length > 0
required property var bar
Repeater {
id: repeater
model: SystemTray.items
delegate: StyledMouseArea {
id: button
Layout.preferredWidth: parent.height
Layout.fillHeight: true
required property SystemTrayItem modelData
property bool showMenu: false
onClicked: {
menuOpener.menu = modelData.menu;
showMenu = !showMenu;
}
IconImage {
id: trayIcon
anchors.fill: parent
source: {
// console.log(trayField.modelData.id);
switch (button.modelData.id) {
case "obs":
return "image://icon/obs-tray";
default:
return button.modelData.icon;
}
}
}
QsMenuOpener {
id: menuOpener
}
property PopupItem menu: PopupItem {
id: menu
owner: button
popup: root.bar.popup
show: button.showMenu
onClosed: button.showMenu = false
implicitWidth: content.implicitWidth + (2 * 8)
implicitHeight: content.implicitHeight + (2 * 8)
property var leftItem: false
property var rightItem: false
ColumnLayout {
id: content
spacing: 2
anchors.centerIn: parent
Repeater {
model: menuOpener.children
delegate: TrayMenuItem {
id: sysTrayContent
Layout.fillWidth: true
Layout.fillHeight: true
rootMenu: menu
onInteracted: {
button.showMenu = false;
menuOpener.menu = null;
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,177 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../../widgets" as Widgets
import "../.."
ColumnLayout {
id: root
required property QsMenuEntry menuData
required property var rootMenu
signal interacted
Component.onCompleted: {
if (menuData?.buttonType !== QsMenuButtonType.None || menuData?.icon != "") {
rootMenu.leftItem = true;
}
if (menuData?.hasChildren) {
rootMenu.rightItem = true;
}
}
WrapperRectangle {
Layout.fillWidth: true
Layout.preferredHeight: 25
radius: 4
color: {
if (!root.menuData?.enabled)
return "transparent";
if (entryArea.containsMouse) {
return ShellSettings.colors.active_translucent;
}
return "transparent";
}
WrapperMouseArea {
id: entryArea
hoverEnabled: true
anchors.fill: parent
onClicked: {
if (!root.menuData?.enabled)
return;
if (root.menuData?.hasChildren) {
subTrayMenu.visible = !subTrayMenu.visible;
return;
}
root.menuData?.triggered();
root.interacted();
}
RowLayout {
id: menuEntry
spacing: 5
Layout.fillWidth: true
Item {
visible: root.rootMenu.leftItem
Layout.preferredWidth: 20
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 5
RadioButton {
id: radioButton
visible: (root.menuData?.buttonType === QsMenuButtonType.RadioButton) ?? false
checked: (root.menuData?.checkState) ?? false
anchors.centerIn: parent
}
CheckBox {
id: checkBox
visible: (root.menuData?.buttonType === QsMenuButtonType.CheckBox) ?? false
checked: (root.menuData?.checkState) ?? false
anchors.centerIn: parent
}
IconImage {
id: entryImage
visible: (root.menuData?.buttonType === QsMenuButtonType.None && root.menuData?.icon !== "") ?? false
source: (root.menuData?.icon) ?? ""
anchors.fill: parent
}
}
Text {
id: text
text: root.menuData?.text ?? ""
verticalAlignment: Text.AlignVCenter
color: {
let color = Qt.color(ShellSettings.colors.active);
if (!root.menuData?.enabled)
return color.darker(2);
// if (entryArea.containsMouse)
// return Qt.color(ShellSettings.colors["inverse_primary"]);
return color;
}
Layout.fillWidth: true
Layout.fillHeight: true
}
Item {
visible: root.rootMenu.rightItem
Layout.preferredHeight: 20
Layout.preferredWidth: 20
Layout.rightMargin: 5
Widgets.IconButton {
id: arrowButton
visible: root.menuData?.hasChildren ?? false
activeRectangle: false
source: "root:resources/general/right-arrow.svg"
rotation: subTrayMenu.visible ? 90 : 0
anchors.fill: parent
Behavior on rotation {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
onClicked: {
root.expanded = !root.expanded;
}
}
}
}
}
}
WrapperRectangle {
id: subTrayMenu
color: ShellSettings.colors.surface_container_translucent
radius: 8
visible: false
border {
width: 1
color: ShellSettings.colors.active_translucent
}
Layout.fillWidth: true
QsMenuOpener {
id: menuOpener
menu: root.menuData
}
ColumnLayout {
id: subTrayContainer
spacing: 2
Layout.fillWidth: true
Repeater {
model: menuOpener.children
delegate: BoundComponent {
id: subMenuEntry
source: "TrayMenuItem.qml"
Layout.fillWidth: true
required property var modelData
property var rootMenu: root.rootMenu
}
}
}
}
}

View file

@ -0,0 +1,30 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs
ColumnLayout {
id: root
required property QsMenuEntry modelData
required property var rootMenu
property var leftItem
signal interacted
Rectangle {
visible: (root.modelData?.isSeparator ?? false)
color: ShellSettings.colors.inactive_translucent
// opacity: 0.1
Layout.fillWidth: true
Layout.preferredHeight: 2
Layout.leftMargin: 8
Layout.rightMargin: 8
}
TrayMenuEntry {
visible: !root.modelData?.isSeparator
rootMenu: root.rootMenu
menuData: root.modelData
Layout.fillWidth: true
onInteracted: root.interacted()
}
}

View file

@ -0,0 +1,70 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import "../../widgets/" as Widgets
import "../.."
ColumnLayout {
id: root
Loader {
id: sinkLoader
active: sink
property PwNode sink: Pipewire.defaultAudioSink
sourceComponent: WrapperItem {
PwNodeLinkTracker {
id: linkTracker
node: sinkLoader.sink
}
ColumnLayout {
Repeater {
model: linkTracker.linkGroups
delegate: Loader {
id: nodeLoader
active: modelData.source !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
required property PwLinkGroup modelData
sourceComponent: VolumeCard {
id: nodeCard
node: nodeLoader.modelData.source
text: node.properties["media.name"] ?? ""
// if icon-name is undefined, just gonna fallback on the application name
icon: IconImage {
source: {
if (nodeCard.node.properties["application.icon-name"] !== undefined)
return `image://icon/${nodeCard.node.properties["application.icon-name"]}`;
let applicationName = nodeCard.node.properties["application.name"];
return `image://icon/${applicationName?.toLowerCase() ?? "image-missing"}`;
}
}
button: Widgets.FontIconButton {
hoverEnabled: false
iconName: nodeCard.node.audio.muted ? "volume_off" : "volume_up"
checked: !nodeCard.node.audio.muted
inactiveColor: ShellSettings.colors["surface_container_highest"]
onClicked: {
nodeCard.node.audio.muted = !nodeCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
}
}
}
}
}

View file

@ -0,0 +1,64 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.Pipewire
import "../../widgets/" as Widgets
import "../.."
ColumnLayout {
id: root
// headphones
// don't load until the node is not null
Loader {
id: sinkLoader
active: sink !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
property PwNode sink: Pipewire.defaultAudioSink
sourceComponent: VolumeCard {
id: sinkCard
node: sinkLoader.sink
button: Widgets.FontIconButton {
hoverEnabled: false
iconName: sinkCard.node.audio.muted ? "volume_off" : "volume_up"
checked: !sinkCard.node.audio.muted
inactiveColor: ShellSettings.colors["surface_container_highest"]
onClicked: {
sinkCard.node.audio.muted = !sinkCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
// microphone, same as above
Loader {
id: sourceLoader
active: source !== null
Layout.preferredWidth: 350
Layout.preferredHeight: 45
property PwNode source: Pipewire.defaultAudioSource
sourceComponent: VolumeCard {
id: sourceCard
node: sourceLoader.source
button: Widgets.FontIconButton {
hoverEnabled: false
iconName: sourceCard.node.audio.muted ? "mic_off" : "mic"
checked: !sourceCard.node.audio.muted
inactiveColor: ShellSettings.colors["surface_container_highest"]
onClicked: {
sourceCard.node.audio.muted = !sourceCard.node.audio.muted;
}
}
anchors.fill: parent
}
}
}

View file

@ -0,0 +1,50 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import "../../widgets/" as Widgets
import "../.."
WrapperRectangle {
id: root
color: ShellSettings.colors["surface_container"]
radius: width / 2
margin: 6
required property PwNode node
property string text
property Component button
property Component icon
PwObjectTracker {
id: tracker
objects: [root.node]
}
RowLayout {
Widgets.MaterialSlider {
value: root.node.audio.volume ?? 0
text: root.text
icon: root.icon
onValueChanged: {
// only allow changes when the node is ready other wise you will combust
if (!root.node.ready)
return;
root.node.audio.volume = value;
}
Layout.fillWidth: true
Layout.fillHeight: true
}
Loader {
id: buttonLoader
sourceComponent: root.button
Layout.preferredWidth: this.height
Layout.fillHeight: true
}
}
}

View file

@ -0,0 +1,34 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import "../../widgets/" as Widgets
WrapperItem {
id: root
visible: false
ColumnLayout {
spacing: 10
Widgets.TabBar {
id: tabBar
model: ["headphones", "tune"]
Layout.fillWidth: true
Layout.preferredHeight: 35
}
StackLayout {
id: page
currentIndex: tabBar.currentIndex
Layout.fillWidth: true
Layout.preferredHeight: currentItem ? currentItem.implicitHeight : 0
readonly property Item currentItem: children[currentIndex]
DeviceMixer {}
ApplicationMixer {}
}
}
}

View file

@ -0,0 +1,27 @@
import QtQuick
import "../../widgets/" as Widgets
Item {
id: root
required property var popup
Widgets.FontIconButton {
id: button
iconName: "volume_up"
anchors.fill: parent
onClicked: {
if (root.popup.content == volumeMenu) {
root.popup.hide();
return;
}
root.popup.set(this, volumeMenu);
root.popup.show();
}
}
VolumeControl {
id: volumeMenu
}
}

34
shell/greeter.qml Normal file
View file

@ -0,0 +1,34 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
import "lockscreen"
ShellRoot {
id: root
GreeterContext {
id: context
onLaunch: {
lock.locked = false;
Greetd.launch(["hyprland"]);
}
}
WlSessionLock {
id: lock
locked: true
WlSessionLockSurface {
LockSurface {
state: context.state
// TODO: env var for wallpaper
wallpaper: "root:resources/wallpapers/wallhaven-96y9qk.jpg"
anchors.fill: parent
}
}
}
}

View file

@ -0,0 +1,313 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import ".."
Singleton {
PersistentProperties {
id: persist
property bool launcherOpen: false
}
IpcHandler {
target: "launcher"
function open(): void {
persist.launcherOpen = true;
}
function close(): void {
persist.launcherOpen = false;
}
function toggle(): void {
persist.launcherOpen = !persist.launcherOpen;
}
}
LazyLoader {
id: loader
activeAsync: persist.launcherOpen
PanelWindow {
implicitWidth: 500
implicitHeight: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10
color: "transparent"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.namespace: "shell:launcher"
Rectangle {
id: container
color: ShellSettings.colors["surface"]
radius: 18
anchors {
fill: parent
margins: 10
}
Behavior on height {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 7
anchors.topMargin: 10
anchors.bottomMargin: 0
spacing: 0
Rectangle {
id: searchContainer
Layout.fillWidth: true
implicitHeight: searchbox.implicitHeight + 15
radius: 10
color: ShellSettings.colors["surface_container"]
border.color: ShellSettings.colors["secondary"]
RowLayout {
id: searchbox
anchors.fill: parent
anchors.margins: 5
TextInput {
id: search
Layout.fillWidth: true
color: ShellSettings.colors["inverse_surface"]
focus: true
Keys.forwardTo: [list]
Keys.onEscapePressed: persist.launcherOpen = false
Keys.onPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
if (event.key == Qt.Key_J) {
list.currentIndex = list.currentIndex == list.count - 1 ? 0 : list.currentIndex + 1;
event.accepted = true;
} else if (event.key == Qt.Key_K) {
list.currentIndex = list.currentIndex == 0 ? list.count - 1 : list.currentIndex - 1;
event.accepted = true;
}
}
}
onAccepted: {
if (list.currentItem) {
list.currentItem.clicked(null);
}
}
onTextChanged: {
list.currentIndex = 0;
}
}
}
}
ListView {
id: list
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
cacheBuffer: 0 // works around QTBUG-131106
//reuseItems: true
model: ScriptModel {
values: DesktopEntries.applications.values.map(object => {
const stxt = search.text.toLowerCase();
const ntxt = object.name.toLowerCase();
let si = 0;
let ni = 0;
let matches = [];
let startMatch = -1;
for (let si = 0; si != stxt.length; ++si) {
const sc = stxt[si];
while (true) {
// Drop any entries with letters that don't exist in order
if (ni == ntxt.length)
return null;
const nc = ntxt[ni++];
if (nc == sc) {
if (startMatch == -1)
startMatch = ni;
break;
} else {
if (startMatch != -1) {
matches.push({
index: startMatch,
length: ni - startMatch
});
startMatch = -1;
}
}
}
}
if (startMatch != -1) {
matches.push({
index: startMatch,
length: ni - startMatch + 1
});
}
return {
object: object,
matches: matches
};
}).filter(entry => entry !== null).sort((a, b) => {
let ai = 0;
let bi = 0;
let s = 0;
while (ai != a.matches.length && bi != b.matches.length) {
const am = a.matches[ai];
const bm = b.matches[bi];
s = bm.length - am.length;
if (s != 0)
return s;
s = am.index - bm.index;
if (s != 0)
return s;
++ai;
++bi;
}
s = a.matches.length - b.matches.length;
if (s != 0)
return s;
s = a.object.name.length - b.object.name.length;
if (s != 0)
return s;
return a.object.name.localeCompare(b.object.name);
}).map(entry => entry.object)
onValuesChanged: list.currentIndex = 0
}
topMargin: 7
bottomMargin: list.count == 0 ? 0 : 7
add: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 100
}
}
displaced: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 100
}
}
move: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 100
}
}
remove: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 0
duration: 100
}
}
highlight: Rectangle {
radius: 12
color: ShellSettings.colors["primary"]
}
keyNavigationEnabled: true
keyNavigationWraps: true
highlightMoveVelocity: -1
highlightMoveDuration: 100
preferredHighlightBegin: list.topMargin
preferredHighlightEnd: list.height - list.bottomMargin
highlightRangeMode: ListView.ApplyRange
snapMode: ListView.SnapToItem
readonly property real delegateHeight: 44
delegate: MouseArea {
required property DesktopEntry modelData
implicitHeight: list.delegateHeight
implicitWidth: ListView.view.width
onClicked: {
modelData.execute();
persist.launcherOpen = false;
}
RowLayout {
id: delegateLayout
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: 5
}
IconImage {
Layout.alignment: Qt.AlignVCenter
asynchronous: true
implicitSize: 30
source: Quickshell.iconPath(modelData.icon)
}
Text {
text: modelData.name
color: ShellSettings.colors["inverse_surface"]
Layout.alignment: Qt.AlignVCenter
}
}
}
}
}
}
}
}
function init() {
}
}

View file

@ -0,0 +1,53 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import ".."
Singleton {
id: root
PersistentProperties {
id: persist
property bool locked: false
}
IpcHandler {
target: "lockscreen"
function lock(): void {
persist.locked = true;
}
}
LockContext {
id: context
Connections {
target: context.state
function onUnlocked() {
persist.locked = false;
}
}
}
WlSessionLock {
id: lock
locked: persist.locked
WlSessionLockSurface {
LockSurface {
state: context.state
wallpaper: ShellSettings.settings.wallpaperUrl
anchors.fill: parent
}
}
}
function init() {
}
}

View file

@ -0,0 +1,39 @@
import QtQuick
import Quickshell
import Quickshell.Services.Greetd
import "../lockscreen"
Scope {
id: root
signal launch
property LockState state: LockState {
onTryUnlock: {
this.unlockInProgress = true;
// TODO: env var for user
Greetd.createSession("koss");
}
}
Connections {
target: Greetd
function onAuthMessage(message: string, error: bool, responseRequired: bool, echoResponse: bool) {
if (responseRequired) {
Greetd.respond(root.state.currentText);
} // else ignore - only supporting passwords
}
function onAuthFailure() {
root.state.currentText = "";
root.state.failed();
root.state.unlockInProgress = false;
}
function onReadyToLaunch() {
root.state.unlockInProgress = false;
root.launch();
}
}
}

View file

@ -0,0 +1,48 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Services.Pam
Scope {
id: root
property LockState state: LockState {
onTryUnlock: {
if (this.currentText === "")
return;
this.unlockInProgress = true;
pam.start();
}
}
PamContext {
id: pam
// Its best to have a custom pam config for quickshell, as the system one
// might not be what your interface expects, and break in some way.
// This particular example only supports passwords.
configDirectory: "pam"
config: "password.conf"
// pam_unix will ask for a response for the password prompt
onPamMessage: {
if (this.responseRequired) {
this.respond(root.state.currentText);
}
}
// pam_unix won't send any important messages so all we need is the completion status.
onCompleted: result => {
if (result == PamResult.Success) {
root.state.unlocked();
root.state.currentText = "";
} else {
root.state.showFailure = true;
}
root.state.unlockInProgress = false;
}
}
}

View file

@ -0,0 +1,12 @@
import Quickshell
Scope {
property string currentText: ""
property bool unlockInProgress: false
property bool showFailure: false
signal unlocked
signal failed
signal tryUnlock
onCurrentTextChanged: showFailure = false
}

View file

@ -0,0 +1,197 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
Item {
id: root
required property LockState state
required property string wallpaper
Item {
anchors.fill: parent
Image {
id: bgImage
source: root.wallpaper
fillMode: Image.PreserveAspectCrop
anchors.fill: parent
visible: false
}
FastBlur {
anchors.fill: bgImage
source: bgImage
radius: 80
transparentBorder: false
}
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.3
}
Rectangle {
anchors.fill: parent
color: "transparent"
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.rgba(0, 0, 0, 0.2)
}
GradientStop {
position: 0.5
color: Qt.rgba(0, 0, 0, 0.1)
}
GradientStop {
position: 1.0
color: Qt.rgba(0, 0, 0, 0.4)
}
}
}
}
// Date and time display
ColumnLayout {
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: 120
}
spacing: 10
Text {
id: clock
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
font.pointSize: 72
font.weight: Font.Light
color: "white"
text: {
const now = this.date;
let hours = now.getHours();
const minutes = now.getMinutes().toString().padStart(2, '0');
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12; // 0 should be 12
return `${hours}:${minutes}`;
}
property var date: new Date()
Layout.alignment: Qt.AlignHCenter
Timer {
running: true
repeat: true
interval: 1000
onTriggered: clock.date = new Date()
}
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 0
radius: 20
samples: 41
color: Qt.rgba(1, 1, 1, 0.3)
}
}
}
// login section
ColumnLayout {
visible: Window.active
anchors.centerIn: parent
spacing: 30
Rectangle {
id: profileImage
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 120
Layout.preferredHeight: 120
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: profileImage.width
height: profileImage.height
radius: width / 2
color: "black"
}
}
Image {
source: "root:resources/general/pfp.png"
anchors.fill: parent
}
}
// password input, should probably split this out into a seperate comp
LoginField {
id: passwordBox
enabled: !root.state.unlockInProgress
Layout.preferredWidth: 250
Layout.preferredHeight: 30
Layout.maximumHeight: 30
Layout.alignment: Qt.AlignHCenter
onTextChanged: root.state.currentText = this.text
onAccepted: root.state.tryUnlock()
Connections {
target: root.state
function onCurrentTextChanged() {
if (!passwordBox.shaking) {
passwordBox.text = root.state.currentText;
}
}
function onShowFailureChanged() {
if (root.state.showFailure && !passwordBox.shaking) {
passwordBox.shaking = true;
}
}
}
}
}
// hint text
Text {
text: "Press Enter to unlock"
color: Qt.rgba(1, 1, 1, 0.5)
font.pointSize: 12
horizontalAlignment: Text.AlignHCenter
opacity: passwordBox.text.length > 0 ? 1.0 : 0.0
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 60
}
Behavior on opacity {
NumberAnimation {
duration: 300
}
}
}
// testing button
Button {
visible: false
text: "Emergency Unlock"
onClicked: root.state.unlocked()
anchors {
right: parent.right
bottom: parent.bottom
margins: 20
}
}
}

View file

@ -0,0 +1,92 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
TextField {
id: root
color: "white"
scale: activeFocus ? 1.05 : 1.0
padding: 8
focus: true
echoMode: TextInput.Password
inputMethodHints: Qt.ImhSensitiveData
font.pointSize: 11
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
color: Qt.rgba(1, 1, 1, 0.1)
border.color: root.activeFocus ? Qt.rgba(1, 1, 1, 0.5) : Qt.rgba(1, 1, 1, 0.2)
border.width: 1
radius: 8
layer.enabled: true
layer.effect: FastBlur {
radius: 10
transparentBorder: true
}
}
transform: Translate {
id: shakeTransform
x: 0
}
property bool shaking: false
onShakingChanged: {
if (shaking)
shakeAnimation.start();
}
Behavior on scale {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
SequentialAnimation {
id: shakeAnimation
NumberAnimation {
target: shakeTransform
property: "x"
to: -8
duration: 50
easing.type: Easing.OutQuad
}
NumberAnimation {
target: shakeTransform
property: "x"
to: 8
duration: 100
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: shakeTransform
property: "x"
to: -6
duration: 80
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: shakeTransform
property: "x"
to: 6
duration: 80
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: shakeTransform
property: "x"
to: -3
duration: 60
easing.type: Easing.InOutQuad
}
onFinished: {
root.shaking = false;
root.text = "";
}
}
}

View file

@ -0,0 +1 @@
auth sufficient pam_fprintd.so

View file

@ -0,0 +1 @@
auth required pam_unix.so

View file

@ -0,0 +1,77 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
Singleton {
id: root
property MprisPlayer trackedPlayer
IpcHandler {
target: "mpris"
function next(): void {
root.trackedPlayer.next();
}
function prev(): void {
root.trackedPlayer.previous();
}
function play(): void {
root.trackedPlayer.play();
}
function pause(): void {
root.trackedPlayer.pause();
}
function play_pause(): void {
if (root.trackedPlayer.isPlaying) {
root.trackedPlayer.pause();
} else {
root.trackedPlayer.play();
}
}
}
Instantiator {
model: Mpris.players
Connections {
required property MprisPlayer modelData
target: modelData
Component.onCompleted: {
if (root.trackedPlayer == null || modelData.isPlaying) {
root.trackedPlayer = modelData;
}
}
Component.onDestruction: {
if (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) {
for (const player of Mpris.players.values) {
if (player.playbackState.isPlaying) {
root.trackedPlayer = player;
break;
}
}
if (root.trackedPlayer == null && Mpris.players.values.length != 0) {
root.trackedPlayer = Mpris.players.values[0];
}
}
}
function onPlaybackStateChanged() {
if (root.trackedPlayer !== modelData)
root.trackedPlayer = modelData;
}
}
}
function init() {}
}

View file

@ -0,0 +1,218 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
import "../widgets/" as Widgets
import ".."
import "../.."
Item {
id: root
required property var notification
signal expired(Notification notification)
signal closed(Notification notification)
width: parent.width
height: Math.min(row.implicitHeight + 30, 400)
Rectangle {
id: container
radius: 10
color: ShellSettings.colors["surface_container"]
anchors.fill: parent
Item {
id: timerController
property int totalDuration: 5000
property int remainingTime: totalDuration
property bool isRunning: false
property real lastTime: 0
Timer {
id: internalTimer
interval: 16
repeat: true
running: timerController.isRunning
onTriggered: {
var currentTime = Date.now();
if (timerController.lastTime > 0) {
var delta = currentTime - timerController.lastTime;
timerController.remainingTime -= delta;
if (timerController.remainingTime <= 0) {
timerController.isRunning = false;
root.expired(root.notification);
}
}
timerController.lastTime = currentTime;
}
}
function start() {
if (!isRunning) {
lastTime = Date.now();
isRunning = true;
}
}
function pause() {
isRunning = false;
lastTime = 0;
}
Component.onCompleted: {
start();
}
}
MouseArea {
id: notificationArea
hoverEnabled: true
anchors.fill: parent
onContainsMouseChanged: {
progressAnimation.paused = containsMouse;
if (containsMouse) {
timerController.pause();
} else {
timerController.start();
}
}
}
RowLayout {
id: row
spacing: 5
anchors {
fill: parent
margins: 15
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
id: topRow
spacing: 10
Layout.fillWidth: true
IconImage {
visible: root.notification.appIcon != ""
source: Quickshell.iconPath(root.notification.appIcon)
implicitSize: 24
}
RowLayout {
Layout.fillWidth: true
Text {
id: appName
text: root.notification.appName
color: ShellSettings.colors["inverse_surface"]
font.pointSize: 11
font.bold: true
elide: Text.ElideRight
maximumLineCount: 1
Layout.preferredWidth: implicitWidth
Layout.maximumWidth: topRow.width * 0.3
}
Widgets.Separator {}
Text {
id: summaryText
text: root.notification.summary
color: ShellSettings.colors["inverse_surface"]
font.pointSize: 11
elide: Text.ElideRight
maximumLineCount: 1
Layout.fillWidth: true
}
}
Item {
id: closeButton
width: 24
height: 24
Layout.alignment: Qt.AlignTop
Canvas {
id: progressCircle
anchors.fill: parent
antialiasing: true
property real progress: 1.0
onProgressChanged: requestPaint()
onPaint: {
var ctx = getContext("2d");
ctx.reset();
var centerX = width / 2;
var centerY = height / 2;
var radius = Math.min(width, height) / 2 - 2;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + 2 * Math.PI * progress);
ctx.strokeStyle = ShellSettings.colors["primary"];
ctx.lineWidth = 2;
ctx.stroke();
}
}
NumberAnimation {
id: progressAnimation
target: progressCircle
property: "progress"
from: 1.0
to: 0.0
duration: 5000
running: true
easing.type: Easing.Linear
}
Rectangle {
id: closeButtonBg
anchors.centerIn: parent
width: 16
height: 16
color: "#FF474D"
radius: 10
visible: closeButtonArea.containsMouse
}
MouseArea {
id: closeButtonArea
hoverEnabled: true
anchors.fill: parent
onPressed: root.closed(root.notification)
}
IconImage {
source: "image://icon/window-close"
implicitSize: 16
anchors.centerIn: parent
}
}
}
ColumnLayout {
Layout.fillWidth: true
Text {
id: bodyText
text: root.notification.body
color: ShellSettings.colors["inverse_surface"]
font.pointSize: 11
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 10
Layout.fillWidth: true
}
}
}
}
}
}

View file

@ -0,0 +1,98 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import ".."
Scope {
id: root
Connections {
target: Notifications.notificationServer
function onNotification(notification) {
notificationLoader.item.visible = true;
}
}
LazyLoader {
id: notificationLoader
loading: true
PanelWindow {
id: notificationWindow
property var visibleCount: {
let count = 0;
for (let i = 0; i < toastList.count; i++) {
let item = toastList.itemAt(i);
if (item && item.visible) {
count++;
}
}
return count;
}
onVisibleCountChanged: visible = visibleCount != 0
color: "transparent"
implicitWidth: 525
visible: false
exclusionMode: ExclusionMode.Normal
mask: Region {
item: notifLayout
}
anchors {
top: true
bottom: true
right: true
}
Text {
text: "length: " + notificationWindow.visibleCount
}
ColumnLayout {
id: notifLayout
spacing: 15
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 5
}
Repeater {
id: toastList
model: ScriptModel {
values: Notifications.notificationServer.trackedNotifications.values.concat()
}
delegate: ActiveToast {
id: toast
required property var modelData
notification: modelData
Connections {
target: toast
function onExpired(notification) {
toast.visible = false;
}
function onClosed(notification) {
notification.dismiss();
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,278 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Qt5Compat.GraphicalEffects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import "../widgets" as Widgets
import ".."
Singleton {
PersistentProperties {
id: persist
property bool notificationsOpen: false
}
IpcHandler {
id: ipc
target: "notifications"
function open(): void {
persist.notificationsOpen = true;
}
function close(): void {
persist.notificationsOpen = false;
}
function toggle(): void {
persist.notificationsOpen = !persist.notificationsOpen;
}
}
LazyLoader {
id: loader
activeAsync: persist.notificationsOpen
PanelWindow {
id: notificationPanel
color: "red"
implicitWidth: 500
exclusionMode: ExclusionMode.Ignore
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
anchors {
top: true
right: true
bottom: true
}
ColumnLayout {
spacing: 10
anchors {
fill: parent
margins: 10
}
Text {
text: "Notifications: " + toastList.count
Layout.fillWidth: true
}
ListView {
id: toastList
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 5
model: ScriptModel {
values: {
const notifications = Notifications.notificationServer.trackedNotifications.values.concat();
const groupedByApp = notifications.reduce((groups, notification) => {
const appName = notification.appName;
if (!groups[appName]) {
groups[appName] = {
appName: appName,
summaryGroups: {}
};
}
const summary = notification.summary;
const image = notification.image;
if (!groups[appName].summaryGroups[summary]) {
groups[appName].summaryGroups[summary] = {
summary: summary,
image: image,
notifications: []
};
}
groups[appName].summaryGroups[summary].notifications.push(notification);
return groups;
}, {});
return Object.values(groupedByApp).map(appGroup => {
return {
appName: appGroup.appName,
summaryGroups: Object.values(appGroup.summaryGroups)
};
});
}
}
delegate: Item {
id: toastWrapper
required property var modelData
width: ListView.view.width
height: toastContent.height
Item {
id: toastContent
width: parent.width
height: contentColumn.implicitHeight
anchors.centerIn: parent
ColumnLayout {
id: contentColumn
spacing: 2
anchors {
fill: parent
margins: 0
}
// Notification content
Repeater {
model: toastWrapper.modelData.summaryGroups
delegate: Rectangle {
id: summaryGroup
required property var modelData
required property int index
Layout.fillWidth: true
Layout.preferredHeight: groupContent.implicitHeight + 24
color: ShellSettings.colors["surface_container"]
antialiasing: true
topLeftRadius: index === 0 ? 25 : 5
topRightRadius: index === 0 ? 25 : 5
bottomLeftRadius: index === (toastWrapper.modelData.summaryGroups.length - 1) ? 25 : 5
bottomRightRadius: index === (toastWrapper.modelData.summaryGroups.length - 1) ? 25 : 5
ColumnLayout {
id: groupContent
spacing: 8
anchors {
fill: parent
margins: 12
}
RowLayout {
spacing: 12
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
ColumnLayout {
Layout.alignment: Qt.AlignTop
spacing: 0
Item {
id: imageContainer
Layout.preferredWidth: 36
Layout.preferredHeight: 36
visible: summaryGroup.modelData.image != ""
antialiasing: true
Image {
id: notificationImage
anchors.fill: parent
source: summaryGroup.modelData.image
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: notificationImage.width
height: notificationImage.height
radius: notificationImage.width / 2
antialiasing: true
}
}
}
}
}
// Content column
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 8
// Header row
RowLayout {
Layout.fillWidth: true
spacing: 8
Text {
text: summaryGroup.modelData.summary
font.pixelSize: 16
font.weight: Font.Medium
color: ShellSettings.colors["on_surface"]
wrapMode: Text.WordWrap
maximumLineCount: 2
elide: Text.ElideRight
}
Widgets.Separator {}
Text {
text: "now"
font.pixelSize: 14
color: ShellSettings.colors["on_surface_variant"]
Layout.alignment: Qt.AlignVCenter
}
}
// Notification bodies
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Repeater {
model: summaryGroup.modelData.notifications
delegate: ColumnLayout {
id: bodyDelegate
required property var modelData
required property int index
Layout.fillWidth: true
spacing: 0
Text {
Layout.fillWidth: true
text: bodyDelegate.modelData.body
font.pixelSize: 14
color: ShellSettings.colors["on_surface_variant"]
wrapMode: Text.WordWrap
maximumLineCount: 4
elide: Text.ElideRight
lineHeight: 1.3
visible: bodyDelegate.modelData.body != ""
}
}
}
}
}
}
}
}
}
}
}
}
}
}
// HyprlanFocusGrab {
// id: grab
// windows: [notificationPanel]
// onCleared: {
// ipc.hide();
// }
// }
}
}
function init() {
}
}

View file

@ -0,0 +1,23 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
Singleton {
property alias notificationServer: notifServer
NotificationServer {
id: notifServer
actionsSupported: true
persistenceSupported: true
}
Connections {
target: notifServer
function onNotification(notification) {
notification.tracked = true;
}
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M24,0V24H0V0Z" fill="none"/>
<path d="M20,10V8.33A1.34,1.34,0,0,0,18.67,7H3.34A1.34,1.34,0,0,0,2,8.33v7.33A1.34,1.34,0,0,0,3.33,17H18.67A1.34,1.34,0,0,0,20,15.67V14h2V10Zm-8.5,3v2L4,11H9.5V9L17,13Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 448 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M20,15.67V14h2V10H20V8.33A1.34,1.34,0,0,0,18.67,7H3.34A1.34,1.34,0,0,0,2,8.33v7.33A1.34,1.34,0,0,0,3.33,17H18.67A1.34,1.34,0,0,0,20,15.67Z"/>
<path d="M20,15.67V14h2V10H20V8.33A1.34,1.34,0,0,0,18.67,7H3.34A1.34,1.34,0,0,0,2,8.33v7.33A1.34,1.34,0,0,0,3.33,17H18.67A1.34,1.34,0,0,0,20,15.67Z" fill="white"/>
<path d="M24,0V24H0V0Z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 581 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="64px" viewBox="0 0 16 16" width="64px" xmlns="http://www.w3.org/2000/svg">
<g fill="white">
<path d="m 4.550781 1 c -1.9375 0 -3.5 1.5625 -3.5 3.5 s 1.5625 3.5 3.5 3.5 h 7 c 1.941407 0 3.5 -1.5625 3.5 -3.5 s -1.558593 -3.5 -3.5 -3.5 z m 7 1 c 1.386719 0 2.5 1.113281 2.5 2.5 c 0 1.382812 -1.113281 2.5 -2.5 2.5 c -1.382812 0 -2.5 -1.117188 -2.5 -2.5 c 0 -1.386719 1.117188 -2.5 2.5 -2.5 z m 0 0"/>
<path d="m 4.550781 9 c -1.9375 0 -3.5 1.5625 -3.5 3.5 s 1.5625 3.5 3.5 3.5 h 7 c 1.941407 0 3.5 -1.5625 3.5 -3.5 s -1.558593 -3.5 -3.5 -3.5 z m 0 1 c 1.386719 0 2.5 1.113281 2.5 2.5 c 0 1.382812 -1.113281 2.5 -2.5 2.5 c -1.382812 0 -2.5 -1.117188 -2.5 -2.5 c 0 -1.386719 1.117188 -2.5 2.5 -2.5 z m 0 0" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 805 B

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="white" fill-rule="evenodd" d="M4 5.014v-.93c0-1.078.417-2.114 1.165-2.881A3.96 3.96 0 018 0a3.96 3.96 0 012.835 1.203A4.127 4.127 0 0112 4.083v.93a2.25 2.25 0 012 2.237v5.5A2.25 2.25 0 0111.75 15h-7.5A2.25 2.25 0 012 12.75v-5.5a2.25 2.25 0 012-2.236zM6.239 2.25A2.46 2.46 0 018 1.5c.657 0 1.29.267 1.761.75.471.483.739 1.142.739 1.833V5h-5v-.917c0-.69.268-1.35.739-1.833zM8 9.25a.75.75 0 00-.75.75v1a.75.75 0 001.5 0v-1A.75.75 0 008 9.25z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 700 B

View file

@ -0,0 +1 @@
<svg class="svg-icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M746.089 221.476q39.902 28.374 70.936 64.286t53.202 77.587 33.695 88.226 11.527 94.433q0 82.463-31.478 154.729t-85.123 125.911-125.911 85.123-154.729 31.478q-81.577 0-153.842-31.478t-126.355-85.123-85.123-125.911-31.034-154.729q0-46.995 11.084-92.216t31.478-86.01 50.542-76.256 67.389-63.842q19.507-14.187 42.118-10.64t36.798 22.168 10.64 41.675-22.168 37.241q-55.863 40.789-85.567 100.197t-29.705 127.685q0 58.522 22.168 110.394t60.739 90.442 90.443 61.182 110.394 22.611 110.394-22.611 90.442-61.182 61.182-90.443 22.611-110.394q0-69.163-31.921-130.788t-89.558-101.527q-19.507-13.3-23.497-36.355t9.31-42.562q13.3-18.621 36.355-22.611t42.562 9.31zM518.207 548.668q-23.055 0-39.458-16.404t-16.404-39.458l0-336.945q0-23.055 16.404-39.902t39.458-16.847q23.941 0 40.344 16.847t16.404 39.902l0 336.945q0 23.055-16.404 39.458t-40.344 16.403z" /></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1 @@
<svg class="svg-icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M635.882496 934.139904C406.065152 934.139904 219.092992 744.753152 219.092992 511.978496 219.092992 279.202816 406.065152 89.833472 635.882496 89.833472 688.43008 89.833472 739.864576 99.736576 788.757504 119.264256 798.143488 123.024384 804.473856 132.043776 804.888576 142.27456 805.285888 152.505344 799.68768 162.01216 790.617088 166.513664 659.37408 231.692288 577.837056 364.069888 577.837056 511.978496 577.837056 659.877888 659.37408 792.255488 790.617088 857.459712 799.705088 861.969408 805.285888 871.477248 804.888576 881.707008 804.473856 891.938816 798.143488 900.957184 788.757504 904.710144 739.864576 924.246016 688.43008 934.139904 635.882496 934.139904Z" /></svg>

After

Width:  |  Height:  |  Size: 879 B

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 10L12 15L17 10" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 343 B

View file

@ -0,0 +1 @@
<svg height="512" width="512" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd" transform="matrix(1.27565325 0 0 -1.27565325 -.000008 478.03446375)"><path d="m122.453 169.761 97.758-169.34-44.926-.422-26.101 45.496-26.286-45.25-22.32.008-11.433 19.75 37.449 64.394-26.582 46.258z" fill="#ffffff"/><path d="m157.738 239.515-97.777-169.332-22.828 38.699 26.351 45.347-52.332.137-11.152 19.336 11.391 19.777 74.488-.234 26.769 46.152z" fill="#ffffff"/><path d="m165.238 104.155 195.532-.012-22.098-39.117-52.449.145 26.047-45.387-11.168-19.328-22.825-.027-37.039 64.629-53.355.109z" fill="#ffffff"/><path d="m279.043 178.35-97.758 169.34 44.926.422 26.101-45.496 26.286 45.254 22.32-.008 11.434-19.754-37.45-64.39 26.582-46.262z" fill="#ffffff"/><g fill="#ffffff"><path d="m122.453 169.761 97.758-169.34-44.926-.422-26.101 45.496-26.286-45.25-22.32.008-11.433 19.75 37.449 64.394-26.582 46.258z"/><path d="m236 244.386-195.535.011 22.101 39.118 52.45-.149-26.047 45.391 11.168 19.328 22.82.023 37.043-64.625 53.352-.109z"/><path d="m243.625 108.636 97.777 169.328 22.825-38.696-26.348-45.351 52.332-.137 11.152-19.336-11.39-19.777-74.489.238-26.769-46.152z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 32 32" xml:space="preserve">
<g>
<path d="M26.8,25H5.2c-0.8,0-1.5-0.4-1.9-1.1c-0.4-0.7-0.3-1.5,0.1-2.2L4.5,20c1.8-2.7,2.7-5.8,2.7-9c0-3.7,2.4-7.1,5.9-8.3
C13.7,1.6,14.8,1,16,1s2.3,0.6,2.9,1.7c3.5,1.2,5.9,4.6,5.9,8.3c0,3.2,0.9,6.3,2.7,9l1.1,1.7c0.4,0.7,0.5,1.5,0.1,2.2
C28.4,24.6,27.6,25,26.8,25z"/>
</g>
<path d="M11.1,27c0.5,2.3,2.5,4,4.9,4s4.4-1.7,4.9-4H11.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="white" width="800px" height="800px" viewBox="0 0 256 256" id="Flat" xmlns="http://www.w3.org/2000/svg">
<path d="M228,48a20.02229,20.02229,0,0,0-20-20H48a19.89568,19.89568,0,0,0-13.18359,4.999,12.24037,12.24037,0,0,0-.959.8584,12.05585,12.05585,0,0,0-.8584.959A19.89568,19.89568,0,0,0,28,48V208a20.02229,20.02229,0,0,0,20,20H208a19.89568,19.89568,0,0,0,13.18359-4.999,11.64461,11.64461,0,0,0,1.81739-1.81739A19.89568,19.89568,0,0,0,228,208ZM204,187.0293,68.9707,52H204ZM52,68.9707,187.0293,204H52Z"/>
</svg>

After

Width:  |  Height:  |  Size: 640 B

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 7L15 12L10 17" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 343 B

BIN
shell/resources/mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -2 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>next [#998]</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-144.000000, -3805.000000)" fill="white">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M99.684,3649.69353 L95.207,3652.82453 C94.571,3653.25353 94,3652.84553 94,3652.13153 L94,3650.14053 L89.78,3652.82453 C89.145,3653.25353 88,3652.84553 88,3652.13153 L88,3645.86853 C88,3645.15453 89.145,3644.74653 89.78,3645.17453 L94,3647.85953 L94,3645.86853 C94,3645.15453 94.571,3644.74653 95.207,3645.17453 L99.516,3648.30653 C100.03,3648.65353 100.198,3649.34653 99.684,3649.69353" id="next-[#998]">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20 5L20 19C20 20.6569 18.6569 22 17 22L16 22C14.3431 22 13 20.6569 13 19L13 5C13 3.34314 14.3431 2 16 2L17 2C18.6569 2 20 3.34315 20 5Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 2C9.65685 2 11 3.34315 11 5L11 19C11 20.6569 9.65685 22 8 22L7 22C5.34315 22 4 20.6569 4 19L4 5C4 3.34314 5.34315 2 7 2L8 2Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 628 B

View file

@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M21.4086 9.35258C23.5305 10.5065 23.5305 13.4935 21.4086 14.6474L8.59662 21.6145C6.53435 22.736 4 21.2763 4 18.9671L4 5.0329C4 2.72368 6.53435 1.26402 8.59661 2.38548L21.4086 9.35258Z" fill="#ffffff"/> </g>
</svg>

After

Width:  |  Height:  |  Size: 666 B

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -2 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>previous [#999]</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-104.000000, -3805.000000)" fill="white">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M59.9990013,3645.86816 L59.9990013,3652.13116 C59.9990013,3652.84516 58.8540013,3653.25316 58.2180013,3652.82516 L53.9990013,3650.14016 L53.9990013,3652.13116 C53.9990013,3652.84516 53.4260013,3653.25316 52.7900013,3652.82516 L48.4790013,3649.69316 C47.9650013,3649.34616 47.7980013,3648.65316 48.3120013,3648.30616 L52.7900013,3645.17516 C53.4260013,3644.74616 53.9990013,3645.15416 53.9990013,3645.86816 L53.9990013,3647.85916 L58.2180013,3645.17516 C58.8540013,3644.74616 59.9990013,3645.15416 59.9990013,3645.86816" id="previous-[#999]">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="white" width="800px" height="800px" viewBox="-4 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<title>shuffle</title>
<path d="M0 20.688v2c0 0.281 0.219 0.5 0.5 0.5h2.875c1.688 0 3.094-0.781 4.25-1.969 1.188-1.188 2.156-2.781 3.125-4.313 0.781-1.25 1.563-2.438 2.375-3.344 0.781-0.938 1.563-1.5 2.5-1.5h2.656v2.281c0 0.719 0.5 0.844 1.094 0.375l4.344-3.625c0.375-0.313 0.375-0.906 0-1.219l-4.344-3.594c-0.594-0.5-1.094-0.375-1.094 0.375v2.406h-2.656c-1.719 0-3.063 0.75-4.25 1.969-1.156 1.188-2.219 2.781-3.156 4.281-0.813 1.281-1.563 2.5-2.375 3.406-0.781 0.906-1.563 1.469-2.469 1.469h-2.875c-0.281 0-0.5 0.219-0.5 0.5zM0 9.531v2c0 0.281 0.219 0.5 0.5 0.5h2.875c1.406 0 2.531 1.375 3.75 3.156 0.031-0.094 0.063-0.156 0.094-0.219 0.031-0.031 0.125-0.094 0.156-0.156 0.469-0.781 1-1.531 1.5-2.344-0.75-0.969-1.469-1.844-2.406-2.438-0.906-0.625-1.906-0.969-3.094-0.969h-2.875c-0.344 0-0.5 0.156-0.5 0.469zM18.281 20.125h-2.656c-1.375 0-2.563-1.344-3.75-3.094-0.063 0.094-0.094 0.156-0.125 0.219-0.063 0.063-0.094 0.125-0.156 0.219-0.219 0.375-0.5 0.781-0.719 1.156-0.25 0.344-0.5 0.75-0.719 1.094 0.719 0.969 1.469 1.813 2.375 2.406 0.875 0.625 1.906 1.031 3.094 1.031h2.656v2.188c0 0.719 0.5 0.875 1.094 0.375l4.344-3.656c0.375-0.313 0.375-0.875 0-1.188l-4.344-3.594c-0.594-0.469-1.094-0.375-1.094 0.375v2.469z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 18a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v12z" fill="#000000"/></svg>

After

Width:  |  Height:  |  Size: 366 B

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="white" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 16c2.206 0 4-1.794 4-4V6c0-2.217-1.785-4.021-3.979-4.021a.933.933 0 0 0-.209.025A4.006 4.006 0 0 0 8 6v6c0 2.206 1.794 4 4 4z"/><path d="M11 19.931V22h2v-2.069c3.939-.495 7-3.858 7-7.931h-2c0 3.309-2.691 6-6 6s-6-2.691-6-6H4c0 4.072 3.061 7.436 7 7.931z"/></svg>

After

Width:  |  Height:  |  Size: 497 B

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="white" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m21.707 20.293-3.4-3.4A7.93 7.93 0 0 0 20 12h-2a5.945 5.945 0 0 1-1.119 3.467l-1.449-1.45A3.926 3.926 0 0 0 16 12V6c0-2.217-1.785-4.021-3.979-4.021-.07 0-.14.009-.209.025A4.006 4.006 0 0 0 8 6v.586L3.707 2.293 2.293 3.707l18 18 1.414-1.414zM6 12H4c0 4.072 3.06 7.436 7 7.931V22h2v-2.069a7.935 7.935 0 0 0 2.241-.63l-1.549-1.548A5.983 5.983 0 0 1 12 18c-3.309 0-6-2.691-6-6z"/><path d="M8.007 12.067a3.996 3.996 0 0 0 3.926 3.926l-3.926-3.926z"/></svg>

After

Width:  |  Height:  |  Size: 682 B

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<title>volume-up-solid</title>
<g id="Layer_2" data-name="Layer 2">
<g id="invisible_box" data-name="invisible box">
<rect width="48" height="48" fill="none"/>
</g>
<g id="icons_Q2" data-name="icons Q2">
<path d="M29,4a.9.9,0,0,0-.7.3L16.7,15H8a2,2,0,0,0-2,2V31a2,2,0,0,0,2,2h8.7L28.3,43.7a.9.9,0,0,0,.7.3,1,1,0,0,0,1-1V5a1,1,0,0,0-1-1Z" fill="white"/>
<path d="M36,42a2.1,2.1,0,0,1-1.6-.8,2,2,0,0,1,.4-2.8,18,18,0,0,0,0-28.8,2,2,0,1,1,2.4-3.2A22.4,22.4,0,0,1,46,24a22.4,22.4,0,0,1-8.8,17.6A1.7,1.7,0,0,1,36,42Z" fill="white"/>
<path d="M34,15.5v17a.5.5,0,0,0,.9.3,14,14,0,0,0,0-17.6A.5.5,0,0,0,34,15.5Z" fill="white"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 906 B

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<title>volume-off-solid</title>
<g id="Layer_2" data-name="Layer 2">
<g id="invisible_box" data-name="invisible box">
<rect width="48" height="48" fill="none"/>
</g>
<g id="icons_Q2" data-name="icons Q2">
<g>
<path d="M30,22.2V5a1,1,0,0,0-1-1,1.1,1.1,0,0,0-.7.3l-8.4,7.8Z" fill="white"/>
<path d="M40.4,38.6l-32-32A2,2,0,0,0,5.6,9.4L11.2,15H8a2,2,0,0,0-2,2V31a2,2,0,0,0,2,2h8.7L28.3,43.7a1.1,1.1,0,0,0,.7.3,1,1,0,0,0,1-1V33.8l7.6,7.6a1.9,1.9,0,0,0,2.8,0A1.9,1.9,0,0,0,40.4,38.6Z" fill="white"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 797 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

View file

@ -0,0 +1,72 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import ".."
Singleton {
id: root
property bool windowOpen: false
IpcHandler {
target: "screencapture"
function screenshot(): void {
root.windowOpen = true;
}
}
LazyLoader {
active: root.windowOpen
PanelWindow {
id: focusedScreen
color: "transparent"
exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.namespace: "shell:screencapture"
anchors {
top: true
bottom: true
left: true
right: true
}
Item {
anchors.fill: parent
focus: true
Keys.onEscapePressed: root.windowOpen = false
SelectionRectangle {
id: selection
anchors.fill: parent
onAreaSelected: selection => {
let screen = focusedScreen.screen;
const x = Math.floor(selection.x) + screen.x;
const y = Math.floor(selection.y) + screen.y;
const width = Math.floor(selection.width);
const height = Math.floor(selection.height);
let position = `${x},${y} ${width}x${height}`;
let path = "/home/koss/Pictures/screenshot.png";
Quickshell.execDetached({
command: ["grim", "-g", position, path]
});
root.windowOpen = false;
}
}
}
}
}
function init() {
}
}

View file

@ -0,0 +1,3 @@
import QtQuick
Image {}

View file

@ -0,0 +1,152 @@
import QtQuick
import ".."
Canvas {
id: root
anchors.fill: parent
property color overlayColor: "#80000000"
property color borderColor: ShellSettings.colors["primary"]
property real borderWidth: 3
property real handleSize: 16
property var screen
property real centerX: width / 2
property real centerY: height / 2
property real minWidth: 400
property real minHeight: 300
// rect that holds positional data for the selection
property rect selectionRect: Qt.rect(centerX - minWidth / 2, centerY - minHeight / 2, minWidth, minHeight)
// handle positions
property point topLeftHandle: Qt.point(selectionRect.x, selectionRect.y)
property point topRightHandle: Qt.point(selectionRect.x + selectionRect.width, selectionRect.y)
property point bottomLeftHandle: Qt.point(selectionRect.x, selectionRect.y + selectionRect.height)
property point bottomRightHandle: Qt.point(selectionRect.x + selectionRect.width, selectionRect.y + selectionRect.height)
// dragging state
property int activeHandle: -1
property point dragStart: Qt.point(0, 0)
property rect initialRect: Qt.rect(0, 0, 0, 0)
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
// grey overlay
ctx.fillStyle = overlayColor;
ctx.fillRect(0, 0, width, height);
// cut out the selection rectangle
ctx.globalCompositeOperation = "destination-out";
ctx.fillRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height);
ctx.globalCompositeOperation = "source-over";
// draw border
ctx.strokeStyle = borderColor;
ctx.lineWidth = borderWidth;
ctx.beginPath();
ctx.moveTo(topLeftHandle.x, topLeftHandle.y);
ctx.lineTo(topRightHandle.x, topRightHandle.y);
ctx.lineTo(bottomRightHandle.x, bottomRightHandle.y);
ctx.lineTo(bottomLeftHandle.x, bottomLeftHandle.y);
ctx.closePath();
ctx.stroke();
// draw handles
ctx.fillStyle = borderColor;
drawHandle(ctx, topLeftHandle);
drawHandle(ctx, topRightHandle);
drawHandle(ctx, bottomLeftHandle);
drawHandle(ctx, bottomRightHandle);
}
function drawHandle(ctx, center) {
var radius = handleSize / 2;
ctx.beginPath();
ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI);
ctx.fill();
}
function getHandleAt(x, y) {
var halfSize = handleSize / 2;
var handles = [topLeftHandle, topRightHandle, bottomLeftHandle, bottomRightHandle];
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
if (x >= handle.x - halfSize && x <= handle.x + halfSize && y >= handle.y - halfSize && y <= handle.y + halfSize) {
return i;
}
}
return -1;
}
function constrainRect(rect) {
// Ensure minimum size
var width = Math.max(rect.width, minWidth);
var height = Math.max(rect.height, minHeight);
// Ensure within canvas bounds
var x = Math.max(0, Math.min(rect.x, root.width - width));
var y = Math.max(0, Math.min(rect.y, root.height - height));
return Qt.rect(x, y, width, height);
}
MouseArea {
anchors.fill: parent
onPressed: function (mouse) {
activeHandle = root.getHandleAt(mouse.x, mouse.y);
if (root.activeHandle >= 0) {
dragStart = Qt.point(mouse.x, mouse.y);
initialRect = root.selectionRect;
}
}
// kinda stupid, should maybe bind a mouse area to each handle I don't know
onPositionChanged: function (mouse) {
if (root.activeHandle < 0)
return;
var dx = mouse.x - root.dragStart.x;
var dy = mouse.y - root.dragStart.y;
var newRect;
switch (root.activeHandle) {
// top left
case 0:
var newX = Math.max(0, Math.min(root.initialRect.x + dx, root.initialRect.x + root.initialRect.width - root.minWidth));
var newY = Math.max(0, Math.min(root.initialRect.y + dy, root.initialRect.y + root.initialRect.height - minHeight));
newRect = Qt.rect(newX, newY, root.initialRect.width - (newX - root.initialRect.x), root.initialRect.height - (newY - root.initialRect.y));
break;
// top right
case 1:
var newY = Math.max(0, Math.min(root.initialRect.y + dy, root.initialRect.y + root.initialRect.height - root.minHeight));
var newWidth = Math.max(root.minWidth, Math.min(root.initialRect.width + dx, root.width - root.initialRect.x));
newRect = Qt.rect(root.initialRect.x, newY, newWidth, root.initialRect.height - (newY - root.initialRect.y));
break;
// bottom left
case 2:
var newX = Math.max(0, Math.min(root.initialRect.x + dx, root.initialRect.x + root.initialRect.width - minWidth));
var newHeight = Math.max(root.minHeight, Math.min(root.initialRect.height + dy, root.height - root.initialRect.y));
newRect = Qt.rect(newX, root.initialRect.y, root.initialRect.width - (newX - root.initialRect.x), newHeight);
break;
// bottom right
case 3:
var newWidth = Math.max(root.minWidth, Math.min(root.initialRect.width + dx, root.width - root.initialRect.x));
var newHeight = Math.max(root.minHeight, Math.min(root.initialRect.height + dy, root.height - root.initialRect.y));
newRect = Qt.rect(root.initialRect.x, root.initialRect.y, newWidth, newHeight);
break;
}
selectionRect = root.constrainRect(newRect);
root.requestPaint();
}
onReleased: {
root.activeHandle = -1;
}
}
}

View file

@ -0,0 +1,54 @@
import QtQuick
import ".."
Canvas {
id: root
property color overlayColor: "#80000000"
property color outlineColor: ShellSettings.colors["primary"]
property rect selectionRect
property point startPosition
signal areaSelected(rect selection)
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
// grey overlay
ctx.fillStyle = overlayColor;
ctx.fillRect(0, 0, width, height);
// cut out the selection rectangle
ctx.globalCompositeOperation = "destination-out";
ctx.fillRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height);
ctx.globalCompositeOperation = "source-over";
ctx.strokeStyle = outlineColor;
ctx.lineWidth = 2;
ctx.strokeRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height);
}
MouseArea {
anchors.fill: parent
onPressed: mouse => {
root.startPosition = Qt.point(mouse.x, mouse.y);
}
onPositionChanged: mouse => {
if (pressed) {
var x = Math.min(root.startPosition.x, mouse.x);
var y = Math.min(root.startPosition.y, mouse.y);
var width = Math.abs(mouse.x - root.startPosition.x);
var height = Math.abs(mouse.y - root.startPosition.y);
root.selectionRect = Qt.rect(x, y, width, height);
root.requestPaint();
}
}
onReleased: mouse => {
root.visible = false;
root.areaSelected(root.selectionRect);
}
}
}

View file

@ -0,0 +1,90 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import "../widgets/" as Widgets
import "../"
Singleton {
PersistentProperties {
id: persist
property bool windowOpen: false
}
IpcHandler {
target: "settings"
function open(): void {
persist.windowOpen = true;
}
function close(): void {
persist.windowOpen = false;
}
function toggle(): void {
persist.windowOpen = !persist.windowOpen;
}
}
LazyLoader {
id: loader
activeAsync: persist.windowOpen
FloatingWindow {
color: ShellSettings.colors["surface"]
implicitWidth: 840
implicitHeight: 845
// onWidthChanged: {
// console.log("height: " + height);
// console.log("width: " + width);
// }
maximumSize {
width: 840
height: 845
}
minimumSize {
width: 840
height: 845
}
onVisibleChanged: {
if (!visible) {
persist.windowOpen = false;
}
}
ColumnLayout {
spacing: 20
anchors.fill: parent
StackLayout {
id: page
currentIndex: topBar.currentIndex
Layout.fillWidth: true
Layout.preferredHeight: currentItem ? currentItem.implicitHeight : 0
readonly property Item currentItem: children[currentIndex]
WallpaperPicker {}
}
Widgets.TopBar {
id: topBar
model: ["headphones", "tune"]
Layout.fillWidth: true
Layout.preferredHeight: 35
}
}
}
}
function init() {
}
}

View file

@ -0,0 +1,135 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Qt5Compat.GraphicalEffects
import Qt.labs.folderlistmodel
import ".."
ColumnLayout {
id: container
spacing: 5
// anchors {
// fill: parent
// margins: 10
// }
ClippingRectangle {
radius: 20
Layout.preferredWidth: 464
Layout.preferredHeight: 261
Layout.alignment: Qt.AlignCenter
Layout.margins: 20
Image {
id: wallpaperImage
source: ShellSettings.settings.wallpaperUrl
fillMode: Image.PreserveAspectFit
anchors {
fill: parent
}
}
}
Rectangle {
color: ShellSettings.colors["surface_container"]
radius: 20
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Rectangle {
color: ShellSettings.colors["surface_container_high"]
Layout.fillWidth: true
Layout.preferredHeight: 1
}
GridView {
id: wallpaperGrid
cellWidth: 200
cellHeight: 200
clip: true
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 10
model: FolderListModel {
id: folderModel
folder: Qt.resolvedUrl("root:resources/wallpapers")
nameFilters: ["*.jpg", "*.png"]
}
delegate: Rectangle {
id: cell
required property var modelData
width: 200
height: 200
color: "transparent"
Item {
anchors.fill: parent
Rectangle {
id: border
visible: mouseArea.containsMouse
color: "transparent"
radius: 20
border {
color: ShellSettings.colors["primary"]
width: 2
}
anchors {
fill: parent
margins: 1
}
}
Image {
id: image
source: cell.modelData.fileUrl
fillMode: Image.PreserveAspectCrop
asynchronous: true
sourceSize {
height: image.height
width: image.width
}
anchors {
fill: parent
margins: 5
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: cell.width
height: cell.height
radius: 20
}
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
ShellSettings.settings.wallpaperUrl = cell.modelData.fileUrl;
}
}
}
}
}
}
}

25
shell/shaders/mask.frag Normal file
View file

@ -0,0 +1,25 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D source;
layout(binding = 2) uniform sampler2D mask;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
};
void main() {
vec4 sourceColor = texture(source, qt_TexCoord0);
vec4 maskColor = texture(mask, qt_TexCoord0);
// Use the mask's luminance to determine opacity
float maskValue = dot(maskColor.rgb, vec3(0.299, 0.587, 0.114));
// Black areas of mask = transparent, white areas = opaque
sourceColor.a *= (1.0 - maskValue) * qt_Opacity;
fragColor = sourceColor;
}

BIN
shell/shaders/mask.frag.qsb Normal file

Binary file not shown.

View file

@ -0,0 +1,18 @@
#version 440
layout(location = 0) in vec2 coord;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
vec4 topLeftColor;
vec4 topRightColor;
vec4 bottomLeftColor;
vec4 bottomRightColor;
} ubuf;
void main() {
vec4 topColor = mix(ubuf.topLeftColor, ubuf.topRightColor, coord.x);
vec4 bottomColor = mix(ubuf.bottomLeftColor, ubuf.bottomRightColor, coord.x);
fragColor = mix(topColor, bottomColor, coord.y);
}

Binary file not shown.

View file

@ -0,0 +1,21 @@
#version 440
layout(location = 0) in vec4 qt_Vertex;
layout(location = 1) in vec2 qt_MultiTexCoord0;
layout(location = 0) out vec2 coord;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
vec4 topLeftColor;
vec4 topRightColor;
vec4 bottomLeftColor;
vec4 bottomRightColor;
} ubuf;
out gl_PerVertex { vec4 gl_Position; };
void main() {
coord = qt_MultiTexCoord0;
gl_Position = ubuf.qt_Matrix * qt_Vertex;
}

Binary file not shown.

View file

@ -0,0 +1,33 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float progress;
vec2 aspectRatio;
vec2 origin;
};
layout(binding = 1) uniform sampler2D fromImage;
layout(binding = 2) uniform sampler2D toImage;
void main() {
vec2 uv = qt_TexCoord0;
vec2 scaledUV = (uv - origin) * aspectRatio;
float distance = length(scaledUV);
vec2 maxVec = max(origin, vec2(1.0) - origin) * aspectRatio;
float maxDistance = length(maxVec);
float threshold = progress * maxDistance;
if (distance < threshold) {
fragColor = texture(toImage, uv) * qt_Opacity;
} else {
fragColor = texture(fromImage, uv) * qt_Opacity;
}
}

30
shell/shell.qml Normal file
View file

@ -0,0 +1,30 @@
//@ pragma UseQApplication
//@ pragma IconTheme Papirus-Dark
import Quickshell
import QtQuick
import "bar"
import "notifications" as Notifications
import "mpris" as Mpris
import "volume-osd" as VolumeOSD
import "settings" as Settings
import "launcher" as Launcher
import "lockscreen" as LockScreen
import "wallpaper" as Wallpaper
import "screencapture" as ScreenCapture
ShellRoot {
Bar {}
Wallpaper.Controller {}
Notifications.Controller {}
VolumeOSD.Controller {}
Component.onCompleted: {
Launcher.Controller.init();
Settings.Controller.init();
ScreenCapture.Controller.init();
Mpris.Controller.init();
Notifications.NotificationCenter.init();
LockScreen.Controller.init();
}
}

View file

@ -0,0 +1,102 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import ".."
Scope {
id: root
// Bind the pipewire node so its volume will be tracked
PwObjectTracker {
objects: [Pipewire.defaultAudioSink]
}
Connections {
target: Pipewire.defaultAudioSink?.audio
function onVolumeChanged() {
root.shouldShowOsd = true;
hideTimer.restart();
}
}
property bool shouldShowOsd: false
Timer {
id: hideTimer
interval: 1000
onTriggered: root.shouldShowOsd = false
}
LazyLoader {
active: root.shouldShowOsd
PopupWindow {
implicitWidth: 50
implicitHeight: 275
color: "transparent"
// An empty click mask prevents the window from blocking mouse events.
mask: Region {}
Rectangle {
anchors.fill: parent
radius: 8
color: {
let color = ShellSettings.colors["surface"];
return Qt.rgba(color.r, color.g, color.b, 0.8);
}
RowLayout {
anchors {
fill: parent
leftMargin: 10
rightMargin: 15
}
IconImage {
implicitSize: 30
source: "root:resources/volume/volume-full.svg"
}
Rectangle {
id: sliderBackground
Layout.fillWidth: true
implicitHeight: 10
radius: 20
color: {
let color = ShellSettings.colors["inverse_surface"];
return Qt.rgba(color.r, color.g, color.b, 0.5);
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: sliderBackground.width
height: sliderBackground.height
radius: sliderBackground.radius
color: "black"
}
}
Rectangle {
color: ShellSettings.colors["primary"]
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
implicitWidth: parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0)
}
}
}
}
}
}
}

View file

@ -0,0 +1,45 @@
import Quickshell
import QtQuick
import ".."
Scope {
id: root
LazyLoader {
loading: true
Scope {
Variants {
model: Quickshell.screens
PanelWindow {
required property var modelData
color: "black"
aboveWindows: false
screen: modelData
anchors {
left: true
right: true
top: true
bottom: true
}
Image {
source: ShellSettings.settings.wallpaperUrl
fillMode: Image.PreserveAspectCrop
anchors.fill: parent
}
}
}
Connections {
target: ShellSettings.settings
function onWallpaperUrlChanged() {
console.log("Switching wallpaper: " + ShellSettings.settings.wallpaperUrl);
}
}
}
}
}

View file

@ -0,0 +1,31 @@
[config.custom_colors]
[templates.kde]
input_path = "templates/BreezeDark.colors"
mode = "Dark"
output_path = "~/.config/kdeglobals"
# post_hook = "systemctl restart --user plasma-xdg-desktop-portal-kde.service"
[templates.nvim]
input_path = "templates/nvim.json"
mode = "Dark"
output_path = "~/.local/share/nvim-colors.json"
[templates.qt5ct]
input_path = "templates/qtct-colors.conf"
mode = "Dark"
output_path = "~/.config/qt5ct/colors/matugen.conf"
[templates.qt6ct]
input_path = "templates/BreezeDark.colors"
mode = "Dark"
output_path = "~/.config/qt6ct/colors/BreezeDark.colors"
[templates.hyprland]
input_path = 'templates/hyprland-colors.conf'
output_path = '~/.config/hypr/colors.conf'
post_hook = 'hyprctl reload'
[templates.foot]
input_path = 'templates/foot.ini'
output_path = '~/.config/foot/foot.ini'

View file

@ -0,0 +1,211 @@
# SPDX-FileCopyrightText: Andrew Lake <jamboarder@gmail.com>
# SPDX-FileCopyrightText: Marco Martin <notmart@gmail.com>
# SPDX-FileCopyrightText: Nate Graham <nate@kde.org>
# SPDX-FileCopyrightText: Noah Davis <noahadvs@gmail.com>
# SPDX-FileCopyrightText: Neal Gompa <ngompa@kde.org>
# SPDX-FileCopyrightText: David Redondo <kde@david-redondo.de>
# SPDX-License-Identifier: LGPL-2.0-or-later
[ColorEffects:Disabled]
Color={{colors.outline.default.red}},{{colors.outline.default.green}},{{colors.outline.default.blue}}
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
ContrastEffect=1
IntensityAmount=0.1
IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=true
Color={{colors.outline_variant.default.red}},{{colors.outline_variant.default.green}},{{colors.outline_variant.default.blue}}
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
ContrastEffect=2
Enable=false
IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate={{colors.surface_container_high.default.red}},{{colors.surface_container_high.default.green}},{{colors.surface_container_high.default.blue}}
BackgroundNormal={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.tertiary_container.default.red}},{{colors.tertiary_container.default.green}},{{colors.tertiary_container.default.blue}}
[Colors:Complementary]
BackgroundAlternate={{colors.surface_container_high.default.red}},{{colors.surface_container_high.default.green}},{{colors.surface_container_high.default.blue}}
BackgroundNormal={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.tertiary_container.default.red}},{{colors.tertiary_container.default.green}},{{colors.tertiary_container.default.blue}}
[Colors:Header]
BackgroundAlternate={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
BackgroundNormal={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.tertiary_container.default.red}},{{colors.tertiary_container.default.green}},{{colors.tertiary_container.default.blue}}
[Colors:Header][Inactive]
BackgroundAlternate={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
BackgroundNormal={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.tertiary_container.default.red}},{{colors.tertiary_container.default.green}},{{colors.tertiary_container.default.blue}}
[Colors:Selection]
BackgroundAlternate={{colors.surface_container_high.default.red}},{{colors.surface_container_high.default.green}},{{colors.surface_container_high.default.blue}}
BackgroundNormal={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.background.default.red}},{{colors.background.default.green}},{{colors.background.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.tertiary_container.default.red}},{{colors.tertiary_container.default.green}},{{colors.tertiary_container.default.blue}}
[Colors:Tooltip]
BackgroundAlternate={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
BackgroundNormal={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.tertiary_container.default.red}},{{colors.tertiary_container.default.green}},{{colors.tertiary_container.default.blue}}
[Colors:View]
BackgroundAlternate={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
BackgroundNormal={{colors.surface.default.red}},{{colors.surface.default.green}},{{colors.surface.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.tertiary_container.default.red}},{{colors.tertiary_container.default.green}},{{colors.tertiary_container.default.blue}}
[Colors:Window]
BackgroundAlternate={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
BackgroundNormal={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
DecorationFocus={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
DecorationHover={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundActive={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundInactive={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
ForegroundLink={{colors.primary.default.red}},{{colors.primary.default.green}},{{colors.primary.default.blue}}
ForegroundNegative={{colors.error.default.red}},{{colors.error.default.green}},{{colors.error.default.blue}}
ForegroundNeutral={{colors.secondary.default.red}},{{colors.secondary.default.green}},{{colors.secondary.default.blue}}
ForegroundNormal={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
ForegroundPositive={{colors.tertiary.default.red}},{{colors.tertiary.default.green}},{{colors.tertiary.default.blue}}
ForegroundVisited={{colors.tertiary_container.default.red}},{{colors.tertiary_container.default.green}},{{colors.tertiary_container.default.blue}}
[Icons]
Theme=breeze-dark
[General]
ColorScheme=BreezeDark
Name=Breeze Dark
Name[ar]=نسيم داكن
Name[az]=Breeze - Tünd
Name[bg]=Breeze Тъмен
Name[bs]=Breeze tamna
Name[ca]=Brisa fosca
Name[ca@valencia]=Brisa fosca
Name[cs]=Breeze Tmavé
Name[da]=Breeze Dark
Name[de]=Breeze Dunkel
Name[el]=Breeze σκούρο
Name[en_GB]=Breeze Dark
Name[eo]=Breeze Dark
Name[es]=Brisa oscuro
Name[et]=Breeze tume
Name[eu]=Breeze iluna
Name[fi]=Tumma Breeze
Name[fr]=Brise sombre
Name[gl]=Brisa escura
Name[he]=בריזה כהה
Name[hi]=ब्रीज़ गहरा
Name[hu]=Breeze Dark
Name[ia]=Brisa obscure
Name[id]=Breeze Gelap
Name[is]=Breeze dökkt
Name[it]=Brezza scuro
Name[ja]=Breeze ダーク
Name[ka]=Breeze მუქი
Name[ko]=어두운 Breeze
Name[lt]=Breeze tamsus
Name[lv]=Breeze Dark
Name[nb]=Breeze mørk
Name[nl]=Breeze Dark
Name[nn]=Breeze mørk
Name[pa]=ਬਰੀਜ਼ ਗੂੜ੍ਹਾ
Name[pl]=Ciemna Bryza
Name[pt]=Brisa Escura
Name[pt_BR]=Breeze Dark
Name[ro]=Briză, întunecat
Name[ru]=Breeze, тёмный вариант
Name[sa]=वायुः अन्धकारः
Name[sk]=Tmavý vánok
Name[sl]=Sapica, temna
Name[sr]=Поветарац тамни
Name[sr@ijekavian]=Поветарац тамни
Name[sr@ijekavianlatin]=Povetarac tamni
Name[sr@latin]=Povetarac tamni
Name[sv]=Breeze mörk
Name[tg]=Насими торик
Name[tr]=Esinti Koyu
Name[uk]=Темна Breeze
Name[x-test]=xxBreeze Darkxx
Name[zh_CN]=Breeze 微风深色
Name[zh_TW]=Breeze Dark
shadeSortColumn=true
[KDE]
contrast=4
[WM]
activeBackground={{colors.surface_container.default.red}},{{colors.surface_container.default.green}},{{colors.surface_container.default.blue}}
activeBlend={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
activeForeground={{colors.on_surface.default.red}},{{colors.on_surface.default.green}},{{colors.on_surface.default.blue}}
inactiveBackground={{colors.surface_container_low.default.red}},{{colors.surface_container_low.default.green}},{{colors.surface_container_low.default.blue}}
inactiveBlend={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}
inactiveForeground={{colors.on_surface_variant.default.red}},{{colors.on_surface_variant.default.green}},{{colors.on_surface_variant.default.blue}}

View file

@ -0,0 +1,70 @@
[colors]
foreground={{colors.inverse_surface.dark.hex_stripped}}
background={{colors.surface.dark.hex_stripped}}
16=f5a97f
17=b7bdf8
alpha=1.000000
alpha-mode=matching
bright0=5b6078
bright1=ed8796
bright2=a6da95
bright3=eed49f
bright4=8aadf4
bright5=f5bde6
bright6=8bd5ca
bright7=cad3f5
jump-labels=24273a f5a97f
regular0=494d64
regular1=ed8796
regular2=a6da95
regular3=eed49f
regular4=8aadf4
regular5=f5bde6
regular6=8bd5ca
regular7=b8c0e0
search-box-match=24273a a6da95
search-box-no-match=24273a ed8796
selection-background=8aadf4
selection-foreground=24273a
urls=8aadf4
[cursor]
color=181818 cdcdcd
[key-bindings]
clipboard-copy=Control+Shift+c XF86Copy
clipboard-paste=Control+Shift+v XF86Paste
font-decrease=Control+minus Control+KP_Subtract
font-increase=Control+plus Control+equal Control+KP_Add
font-reset=Control+0 Control+KP_0
fullscreen=none
maximize=none
minimize=none
noop=none
pipe-command-output=[wl-copy] none
pipe-scrollback=[sh -c 'xurls | fuzzel | xargs -r firefox'] none
pipe-selected=[xargs -r firefox] none
pipe-visible=[sh -c 'xurls | fuzzel | xargs -r firefox'] none
primary-paste=Shift+Insert
prompt-next=Control+Shift+x
prompt-prev=Control+Shift+z
scrollback-down-half-page=none
scrollback-down-line=none
scrollback-down-page=Shift+Page_Down
scrollback-end=none
scrollback-home=none
scrollback-up-half-page=none
scrollback-up-line=none
scrollback-up-page=Shift+Page_Up
search-start=Control+Shift+r
show-urls-copy=none
show-urls-launch=Control+Shift+o
show-urls-persistent=none
spawn-terminal=Control+Shift+n
unicode-input=Control+Shift+u
[main]
font=DejaVuSansM Nerd Font:size=14
gamma-correct-blending=no
shell=zsh
term=xterm-256color

View file

@ -0,0 +1,4 @@
<* for name, value in colors *>
$image = {{image}}
${{name}} = rgba({{value.default.hex_stripped}}ff)
<* endfor *>

View file

@ -0,0 +1,20 @@
{
"colors": {
"base00": "{{colors.surface.dark.hex}}",
"base01": "{{colors.surface_container.dark.hex}}",
"base02": "{{colors.surface_container_high.dark.hex}}",
"base03": "{{colors.outline.dark.hex}}",
"base04": "{{colors.on_surface_variant.dark.hex}}",
"base05": "{{colors.on_surface.dark.hex}}",
"base06": "{{colors.inverse_surface.dark.hex}}",
"base07": "{{colors.inverse_on_surface.dark.hex}}",
"base08": "{{colors.primary_fixed.dark.hex}}",
"base09": "{{colors.tertiary.dark.hex}}",
"base0A": "{{colors.secondary.dark.hex}}",
"base0B": "{{colors.primary.dark.hex}}",
"base0C": "{{colors.tertiary.light.hex}}",
"base0D": "{{colors.primary.light.hex}}",
"base0E": "{{colors.secondary.light.hex}}",
"base0F": "{{colors.error.light.hex}}"
}
}

View file

@ -0,0 +1,4 @@
[ColorScheme]
active_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_container_high.default.hex}}, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface.default.hex}}, {{colors.scrim.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.secondary.default.hex}}
disabled_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_container_high.default.hex}}, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface.default.hex}}, {{colors.scrim.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.secondary.default.hex}}
inactive_colors={{colors.on_background.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_container_high.default.hex}}, #cacaca, #9f9f9f, #b8b8b8, {{colors.on_background.default.hex}}, #ffffff, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary_container.default.hex}}, {{colors.on_primary_container.default.hex}}, {{colors.secondary.default.hex}}, {{colors.primary.default.hex}}, {{colors.surface.default.hex}}, {{colors.scrim.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.secondary.default.hex}}

View file

@ -0,0 +1,32 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell.Widgets
import ".."
Item {
id: root
required property var source
property var implicitSize: 0
property var color: "white"
readonly property real actualSize: Math.min(root.width, root.height)
implicitWidth: implicitSize
implicitHeight: implicitSize
IconImage {
anchors.fill: parent
source: root.source
layer.enabled: true
layer.effect: MultiEffect {
colorization: 1
colorizationColor: root.color
}
}
Rectangle {
color: root.color
anchors.fill: parent
}
}

View file

@ -0,0 +1,34 @@
import QtQuick
import ".."
Text {
id: textIcon
property real fill: 0
renderType: Text.NativeRendering
textFormat: Text.PlainText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font {
family: "Material Symbols Outlined"
pointSize: Math.max(parent.height * 0.50, 11)
variableAxes: {
"FILL": fill
}
}
Behavior on fill {
NumberAnimation {
duration: 200
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}

View file

@ -0,0 +1,48 @@
import QtQuick
import ".."
MaterialButton {
id: root
property real implicitSize
property string iconName: ""
property string activeIconColor: ShellSettings.colors["inverse_primary"]
property string inactiveIconColor: ShellSettings.colors["inverse_surface"]
implicitWidth: this.implicitSize
implicitHeight: this.implicitSize
Text {
id: textIcon
text: root.iconName
renderType: Text.NativeRendering
textFormat: Text.PlainText
color: root.containsMouse || root.checked ? root.activeIconColor : root.inactiveIconColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
font {
family: "Material Symbols Outlined"
pointSize: Math.max(parent.height * 0.60, 11)
variableAxes: {
"FILL": fill
}
}
property real fill: !root.containsMouse && !root.checked ? 0 : 1
Behavior on fill {
NumberAnimation {
duration: 200
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}

View file

@ -0,0 +1,51 @@
import QtQuick
import Quickshell.Widgets
import qs
Item {
id: root
property string source
property var implicitSize: 24
property var padding: 0
property var radius: 20
property var activeRectangle: true
property var color: ShellSettings.colors.inactive_translucent
property var activeColor: ShellSettings.colors.active_translucent
signal clicked
implicitWidth: implicitSize
implicitHeight: implicitSize
Rectangle {
id: iconBackground
color: ShellSettings.colors.active_translucent
radius: root.radius
visible: iconButton.containsMouse && root.activeRectangle
anchors.fill: parent
}
// Figure out a way to color images better
IconImage {
id: iconImage
source: root.source
visible: true
// color: {
// if (!activeRectangle)
// return root.color;
//
// return iconButton.containsMouse ? root.activeColor : root.color;
// }
anchors {
fill: parent
margins: root.padding
}
}
MouseArea {
id: iconButton
hoverEnabled: true
anchors.fill: parent
onPressed: root.clicked()
}
}

View file

@ -0,0 +1,105 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import ".."
Slider {
id: root
value: 0.5
from: 0.0
to: 1.0
property string text
property Component icon
background: Rectangle {
id: background
implicitWidth: parent.width
implicitHeight: parent.height
width: root.availableWidth
height: implicitHeight
x: root.leftPadding
y: root.topPadding + root.availableHeight / 2 - height / 2
z: 0
color: ShellSettings.colors["surface_container_highest"]
radius: height / 2
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: background.implicitWidth
height: background.implicitHeight
radius: background.radius
color: "black"
}
}
Rectangle {
id: visualPos
width: root.visualPosition * (root.availableWidth - root.height) + (root.height / 2)
height: parent.height
color: ShellSettings.colors["primary"]
}
Text {
id: sliderText
text: root.text
visible: text !== ""
color: ShellSettings.colors["inverse_primary"]
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
font {
pointSize: Math.max(handle.implicitHeight * 0.35, 11)
}
anchors {
top: parent.top
bottom: parent.bottom
left: {
let visualWidth = (root.visualPosition * root.availableWidth);
if ((visualWidth / root.availableWidth) < 0.5)
return visualPos.right;
else
return parent.left;
}
right: {
let visualWidth = (root.visualPosition * root.availableWidth);
if ((visualWidth / root.availableWidth) > 0.5)
return visualPos.right;
else
return parent.right;
}
leftMargin: 20
rightMargin: 20
}
}
}
handle: Rectangle {
id: handle
color: ShellSettings.colors["primary"]
implicitWidth: root.height
implicitHeight: root.height
radius: width / 2
x: root.leftPadding + root.visualPosition * (root.availableWidth - width)
y: root.topPadding + root.availableHeight / 2 - height / 2
// icon maybe
Loader {
active: root.icon !== undefined
sourceComponent: root.icon
anchors {
fill: parent
margins: 2
}
}
}
}

View file

@ -0,0 +1,55 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import ".."
Slider {
id: slider
implicitHeight: 8
property var accentColor: ShellSettings.colors["primary"]
background: Rectangle {
id: sliderContainer
width: slider.availableWidth
height: slider.implicitHeight
color: ShellSettings.colors["inverse_surface"]
radius: 4
anchors.verticalCenter: parent.verticalCenter
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: sliderContainer.width
height: sliderContainer.height
radius: sliderContainer.radius
color: "white"
}
maskSource: Rectangle {
width: sliderContainer.width
height: sliderContainer.height
radius: sliderContainer.radius
color: "black"
}
}
Rectangle {
id: fill
width: slider.handle.width / 2 + slider.visualPosition * (sliderContainer.width - slider.handle.width)
height: sliderContainer.height
color: Qt.color(slider.accentColor ?? "purple").darker(1.2)
}
}
handle: Rectangle {
id: handleRect
x: slider.visualPosition * (slider.availableWidth - width)
y: slider.topPadding + slider.availableHeight / 2 - height / 2
width: 16
height: 16
radius: width / 2
color: slider.pressed ? Qt.color(slider.accentColor ?? "purple").darker(1.5) : slider.accentColor ?? "purple"
}
}

View file

@ -0,0 +1,9 @@
import QtQuick
import ".."
Rectangle {
color: ShellSettings.colors["active"]
radius: 5
width: 3.5
height: 15
}

View file

@ -0,0 +1,24 @@
import QtQuick
import qs
MouseArea {
id: root
hoverEnabled: true
property real radius: width / 2
property bool checked: false
property var activeColor: ShellSettings.colors.active_translucent
property var inactiveColor: "transparent"
Rectangle {
color: root.containsMouse || root.checked ? root.activeColor : root.inactiveColor
radius: root.radius
anchors.fill: parent
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}

View file

@ -0,0 +1,53 @@
import QtQuick
import Quickshell
import Quickshell.Widgets
import Quickshell.Hyprland
import ".."
PopupWindow {
id: root
color: "transparent"
implicitWidth: container.width
implicitHeight: container.height
default property alias contentItem: container.children
function open() {
// root.anchor.rect.y = -root.implicitHeight;
root.visible = true;
grab.active = true;
// slideAnimation.start();
}
function hide() {
root.visible = false;
grab.active = false;
}
// PropertyAnimation {
// id: slideAnimation
// target: root.anchor.rect
// property: "y"
// from: -root.implicitHeight // Off-screen position
// to: 0 // On-screen position
// duration: 300 // Animation duration in milliseconds
// }
HyprlandFocusGrab {
id: grab
windows: [root]
onCleared: root.hide()
}
WrapperRectangle {
id: container
margin: 5
radius: 12
color: ShellSettings.colors.surface_translucent
border {
width: 1
color: ShellSettings.colors.active_translucent
}
}
}

Some files were not shown because too many files have changed in this diff Show more