put shell in subdir, and add nix package

This commit is contained in:
kossLAN 2025-06-17 12:50:08 -04:00
parent c45c04e9ac
commit f41ea4b1cb
Signed by: kossLAN
SSH key fingerprint: SHA256:bdV0x+wdQHGJ6LgmstH3KV8OpWY+OOFmJcPcB0wQPV8
100 changed files with 57 additions and 126 deletions

18
shell/README.md Normal file
View file

@ -0,0 +1,18 @@
# 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
- [ ] Lockscreen (WIP)
- [ ] Port visuals to more material 3 aethestic (WIP)
- [x] Screenshot tool (WIP - kinda scuffed, but is functional)
- [ ] Recording/Clip widget with gpuscreenrecorder
- [ ] Session Manager
- [ ] Battery Profile Popup
- [ ] REDO Volume OSD (WIP)
- [ ] REDO Launcher (wallpaper picker, calculator, commands, etc...)
- [ ] Music Player Popup V4 lol
### Optionals
- [ ] Add toggle for floating or screen corners

82
shell/ShellSettings.qml Normal file
View file

@ -0,0 +1,82 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
property alias settings: jsonAdapter.settings
property alias colors: jsonAdapter.colors
FileView {
path: `${Quickshell.env("XDG_DATA_HOME")}/quickshell/settings.json`
watchChanges: true
// onFileChanged: reload()
onAdapterUpdated: writeAdapter()
blockLoading: true
JsonAdapter {
id: jsonAdapter
property JsonObject settings: JsonObject {
property int barHeight: 25
property string wallpaperUrl: Qt.resolvedUrl("root:resources/wallpapers/pixelart0.jpg")
property string colorScheme: "scheme-fruit-salad"
property string screenshotPath: "/home/koss/Pictures"
}
property var colors: {
"background": "#131313",
"error": "#ffb4ab",
"error_container": "#93000a",
"inverse_on_surface": "#303030",
"inverse_primary": "#9c4236",
"inverse_surface": "#e2e2e2",
"on_background": "#e2e2e2",
"on_error": "#690005",
"on_error_container": "#ffdad6",
"on_primary": "#5f150d",
"on_primary_container": "#ffdad4",
"on_primary_fixed": "#410000",
"on_primary_fixed_variant": "#7d2b21",
"on_secondary": "#442925",
"on_secondary_container": "#ffdad4",
"on_secondary_fixed": "#2c1512",
"on_secondary_fixed_variant": "#5d3f3b",
"on_surface": "#e2e2e2",
"on_surface_variant": "#c6c6c6",
"on_tertiary": "#3e2e04",
"on_tertiary_container": "#fbdfa6",
"on_tertiary_fixed": "#251a00",
"on_tertiary_fixed_variant": "#564419",
"outline": "#919191",
"outline_variant": "#474747",
"primary": "#ffb4a8",
"primary_container": "#7d2b21",
"primary_fixed": "#ffdad4",
"primary_fixed_dim": "#ffb4a8",
"scrim": "#000000",
"secondary": "#e7bdb6",
"secondary_container": "#5d3f3b",
"secondary_fixed": "#ffdad4",
"secondary_fixed_dim": "#e7bdb6",
"shadow": "#000000",
"source_color": "#df4332",
"surface": "#131313",
"surface_bright": "#393939",
"surface_container": "#1f1f1f",
"surface_container_high": "#2a2a2a",
"surface_container_highest": "#353535",
"surface_container_low": "#1b1b1b",
"surface_container_lowest": "#0e0e0e",
"surface_dim": "#131313",
"surface_tint": "#ffb4a8",
"surface_variant": "#474747",
"tertiary": "#dec38c",
"tertiary_container": "#564419",
"tertiary_fixed": "#fbdfa6",
"tertiary_fixed_dim": "#dec38c"
}
}
}
}

View file

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

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

@ -0,0 +1,130 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import "power"
import "volume"
import "systray" as SysTray
import "popups" as Popup
import "mpris" as Mpris
import "../widgets" as Widgets
import ".."
PanelWindow {
id: root
color: ShellSettings.colors["surface"]
implicitHeight: ShellSettings.settings.barHeight
property alias popup: popupWindow
anchors {
top: true
left: true
right: true
}
// Popup window for all popups
Popup.MenuWindow {
id: popupWindow
bar: root
}
RowLayout {
spacing: 0
anchors {
fill: parent
leftMargin: 5
rightMargin: 5
}
// Left side of bar
Item {
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
spacing: 10
anchors.fill: parent
HyprWorkspaces {
screen: root.screen
Layout.fillHeight: true
Layout.leftMargin: 4
}
Widgets.Separator {
visible: activeWindow.visible
Layout.leftMargin: 5
Layout.rightMargin: 5
}
ActiveWindow {
id: activeWindow
Layout.preferredWidth: 400
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
// Center of bar
WrapperItem {
topMargin: 2
bottomMargin: 2
Layout.fillHeight: true
Mpris.Button {
bar: root
}
}
// Right side of bar
Item {
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
anchors.fill: parent
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
SysTray.SysTray {
id: sysTray
popup: root.popup
Layout.fillHeight: true
}
VolumeIndicator {
id: volumeIndicator
popup: root.popup
Layout.preferredWidth: this.height
Layout.fillHeight: true
Layout.topMargin: 2
Layout.bottomMargin: 2
}
BatteryIndicator {
id: batteryIndicator
popup: root.popup
Layout.fillHeight: true
}
Widgets.Separator {
Layout.leftMargin: 5
Layout.rightMargin: 5
}
Clock {
id: clock
color: ShellSettings.colors["inverse_surface"]
}
}
}
}
}

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
}

10
shell/bar/Controller.qml Normal file
View file

@ -0,0 +1,10 @@
import Quickshell
Variants {
model: Quickshell.screens
Bar {
required property var modelData
screen: modelData
}
}

View file

@ -0,0 +1,73 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import ".."
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 {
required property var modelData
radius: height / 2
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 12
Layout.preferredWidth: {
if (Hyprland.focusedMonitor?.activeWorkspace?.id === modelData?.id)
return 25;
return 12;
}
color: {
let value = Qt.color(ShellSettings.colors["secondary"]).darker(2);
if (!modelData?.id || !Hyprland.focusedMonitor?.activeWorkspace?.id)
return value;
if (workspaceButton.containsMouse) {
value = ShellSettings.colors["on_primary"];
} else if (Hyprland.focusedMonitor.activeWorkspace.id === modelData.id) {
value = ShellSettings.colors["primary"];
}
return value;
}
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,33 @@
import Quickshell
import QtQuick
import "../../widgets/" as Widgets
Widgets.IconButton {
id: root
required property var bar
required property var screen
implicitSize: 20
source: "root:/resources/general/nixos.svg"
padding: 2
onClicked: {
if (controlPanel.visible) {
controlPanel.hide();
} else {
controlPanel.show();
}
}
ControlPanel {
id: controlPanel
anchor {
window: root.screen
onAnchoring: {
anchor.rect = mapToItem(root.screen.contentItem, 0, root.screen.height, width, 0);
}
}
}
}

View file

@ -0,0 +1,308 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Services.Mpris
import Qt5Compat.GraphicalEffects
import "volume" as Volume
import "../../widgets/" as Widgets
import "../.."
// Change to PopupWindow
PopupWindow {
id: root
implicitWidth: 400
implicitHeight: container.height + 10
color: "transparent"
visible: container.opacity > 0
anchor.rect.x: 0
anchor.rect.y: parentWindow.implicitHeight
// anchors {
// top: true
// left: true
// }
function show() {
container.opacity = 1;
grab.active = true;
}
function hide() {
container.opacity = 0;
grab.active = false;
}
HyprlandFocusGrab {
id: grab
windows: [root]
onCleared: {
root.hide();
}
}
// Add drop shadow effect
// Rectangle {
// id: shadowSource
// color: ShellSettings.colors["surface"]
// radius: 8
// opacity: container.opacity
// width: container.width
// height: container.height
//
// anchors {
// top: parent.top
// left: parent.left
// margins: 5
// }
//
// layer.enabled: true
// layer.effect: DropShadow {
// horizontalOffset: 0
// verticalOffset: 2
// radius: 8.0
// samples: 17
// color: Qt.rgba(0, 0, 0, 0.5)
// transparentBorder: true
// }
// visible: false // Hide the source rectangle
// }
Item {
id: shadowItem
anchors.fill: container
z: container.z - 1
opacity: container.opacity
Rectangle {
id: shadowRect
anchors.fill: parent
color: "transparent"
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 2
radius: 8.0
samples: 17
color: Qt.rgba(0, 0, 0, 0.5)
source: container
}
}
}
Rectangle {
id: container
color: ShellSettings.colors["surface"]
radius: 18
opacity: 0
width: parent.width - 10
height: contentColumn.implicitHeight + 20
anchors {
top: parent.top
left: parent.left
margins: 5
}
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
ColumnLayout {
id: contentColumn
spacing: 10
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 10
}
// RowLayout {
// Layout.fillWidth: true
// Layout.preferredHeight: 40
//
// Rectangle {
// radius: 20
// color: ShellSettings.colors["surface_container_high"]
// Layout.fillWidth: true
// Layout.fillHeight: true
//
// RowLayout {
// anchors {
// fill: parent
// leftMargin: 6
// }
//
// ProfileImage {
// id: profileImage
// Layout.preferredWidth: 25
// Layout.preferredHeight: 25
// // implicitWidth: 30
// // implicitHeight: 30
// }
//
// Text {
// text: "kossLAN"
// color: ShellSettings.colors["inverse_surface"]
// font.pointSize: 12
// verticalAlignment: Text.AlignVCenter
// Layout.fillWidth: true
// Layout.fillHeight: true
// Layout.margins: 4
// }
// }
// }
//
// Rectangle {
// radius: 20
// color: ShellSettings.colors["surface_container_high"]
// Layout.preferredWidth: powerButtons.implicitWidth + 10
// Layout.fillHeight: true
//
// RowLayout {
// id: powerButtons
// spacing: 10
//
// anchors {
// fill: parent
// leftMargin: 5
// rightMargin: 5
// }
//
// Widgets.IconButton {
// id: sleepButton
// implicitSize: 24
// radius: 20
// source: "root:resources/control/sleep.svg"
// onClicked: sleepProcess.running = true
// }
//
// Process {
// id: sleepProcess
// running: false
// command: ["hyprctl", "dispatch", "dpms", "off"]
// }
//
// Rectangle {
// radius: 20
// color: ShellSettings.colors["surface_bright"]
// Layout.preferredWidth: 2
// Layout.fillHeight: true
// Layout.topMargin: 4
// Layout.bottomMargin: 4
// }
//
// Widgets.IconButton {
// id: powerButton
// implicitSize: 24
// radius: 20
// source: "root:resources/control/shutdown.svg"
// }
// }
// }
// }
RowLayout {
spacing: 15
Layout.fillWidth: true
Rectangle {
color: ShellSettings.colors["surface_container_high"]
radius: 12
Layout.fillWidth: true
Layout.preferredHeight: 30
}
}
RowLayout {
spacing: 15
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Repeater {
model: [1, 2, 3, 4, 5]
delegate: Rectangle {
color: ShellSettings.colors["surface_container_high"]
radius: width / 2
Layout.preferredWidth: 45
Layout.preferredHeight: 45
}
}
}
ColumnLayout {
spacing: 10
Layout.fillWidth: true
RowLayout {
spacing: 10
Layout.fillWidth: true
Layout.preferredHeight: 55
Rectangle {
color: ShellSettings.colors["primary"]
radius: width / 2
Layout.fillWidth: true
Layout.fillHeight: true
}
Rectangle {
color: ShellSettings.colors["primary"]
radius: width / 2
Layout.fillWidth: true
Layout.fillHeight: true
}
}
RowLayout {
spacing: 10
Layout.fillWidth: true
Layout.preferredHeight: 55
Rectangle {
color: ShellSettings.colors["surface_container_high"]
radius: width / 2
Layout.fillWidth: true
Layout.fillHeight: true
}
Rectangle {
color: ShellSettings.colors["surface_container_high"]
radius: width / 2
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
Volume.Mixer {
id: sinkMixer
isSink: true
Layout.fillWidth: true
}
Volume.Mixer {
id: sourceMixer
isSink: false
Layout.fillWidth: true
}
MediaPlayer {
player: Mpris.players?.values[0]
visible: Mpris.players?.values.length != 0
Layout.fillWidth: true
Layout.preferredHeight: 150
}
}
}
}

View file

@ -0,0 +1,248 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../.."
import "../../widgets" as Widgets
Item {
id: root
required property var player
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: root.width
height: root.height
radius: 14
color: "black"
}
}
ColorQuantizer {
id: gradientQuantizer
source: root.player?.trackArtUrl ?? ""
depth: 2
rescaleSize: 64
}
ColorQuantizer {
id: accentQuantizer
source: root.player?.trackArtUrl ?? ""
depth: 0
rescaleSize: 64
}
ShaderEffect {
property color topLeftColor: gradientQuantizer?.colors[0] ?? "white"
property color topRightColor: gradientQuantizer?.colors[1] ?? "black"
property color bottomLeftColor: gradientQuantizer?.colors[2] ?? "white"
property color bottomRightColor: gradientQuantizer?.colors[3] ?? "black"
anchors.fill: parent
fragmentShader: "root:/shaders/vertexgradient.frag.qsb"
vertexShader: "root:/shaders/vertexgradient.vert.qsb"
Behavior on topLeftColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on topRightColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on bottomLeftColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Behavior on bottomRightColor {
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
}
RowLayout {
id: cardLayout
spacing: 15
anchors {
fill: parent
margins: 10
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 5
RowLayout {
Rectangle {
id: mprisImage
color: "transparent"
radius: 10
width: 50
height: 50
Layout.alignment: Qt.AlignVCenter
visible: true
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
spread: 0.02
samples: 25
color: "#80000000"
}
Image {
anchors.fill: parent
source: root.player?.trackArtUrl ?? ""
sourceSize.width: 1024
sourceSize.height: 1024
fillMode: Image.PreserveAspectFit
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: mprisImage.width
height: mprisImage.height
radius: 10
color: "white"
}
maskSource: Rectangle {
width: mprisImage.width
height: mprisImage.height
radius: 10
color: "black"
}
}
}
}
ColumnLayout {
Layout.leftMargin: 7.5
Layout.alignment: Qt.AlignBottom
Text {
text: root.player?.trackArtist ?? "NA"
color: "white"
font.pointSize: 13
font.bold: true
horizontalAlignment: Text.AlignLeft
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
text: root.player?.trackTitle ?? "NA"
color: "white"
font.pointSize: 13
horizontalAlignment: Text.AlignLeft
Layout.fillWidth: true
elide: Text.ElideRight
}
}
}
RowLayout {
spacing: 6
Text {
text: timeStr(root.player?.position)
color: "white"
font {
pointSize: 9
bold: true
}
}
FrameAnimation {
running: root.player?.playbackState == MprisPlaybackState.Playing
onTriggered: root.player?.positionChanged()
}
Widgets.RoundSlider {
id: positionSlider
implicitHeight: 7
from: 0
to: root.player?.length
accentColor: accentQuantizer.colors[0]?.darker(1.2) ?? "purple"
value: root.player?.position ?? 0
Layout.fillWidth: true
onMoved: {
if (root.player == null)
return;
root.player.position = value;
}
}
Text {
text: timeStr(root.player?.length)
color: "white"
font {
pointSize: 9
bold: true
}
}
}
// Music Controls
RowLayout {
spacing: 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Widgets.IconButton {
implicitSize: 40
activeRectangle: false
padding: 4
source: "root:resources/mpris/previous.svg"
onClicked: root.player?.previous()
}
Widgets.IconButton {
implicitSize: 40
activeRectangle: false
padding: 4
source: root.player?.isPlaying ? "root:resources/mpris/pause.svg" : "root:resources/mpris/play.svg"
onClicked: {
if (!root.player?.canPlay)
return;
player.isPlaying ? player.pause() : player.play();
}
}
Widgets.IconButton {
implicitSize: 40
activeRectangle: false
padding: 4
source: "root:resources/mpris/next.svg"
onClicked: root.player?.next()
}
}
}
}
function timeStr(time: int): string {
const seconds = time % 60;
const minutes = Math.floor(time / 60);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
}

View file

@ -0,0 +1,33 @@
pragma ComponentBehavior: Bound
import QtQuick
import Qt5Compat.GraphicalEffects
Rectangle {
id: profileImage
color: "transparent"
Image {
anchors.fill: parent
source: "root:resources/general/pfp.png"
sourceSize.width: 100
sourceSize.height: 100
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: profileImage.width
height: profileImage.height
radius: 10
color: "white"
}
maskSource: Rectangle {
width: profileImage.width
height: profileImage.height
radius: 10
color: "black"
}
}
}
}

View file

@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import "../../.."
import "../../../widgets" as Widgets
Rectangle {
id: root
required property PwNode node
required property var isSink
color: ShellSettings.colors["surface_container_high"]
PwObjectTracker {
id: defaultSourceTracker
objects: [root.node]
}
RowLayout {
anchors.fill: parent
spacing: 8
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 2
spacing: 10
Text {
color: ShellSettings.colors["inverse_surface"]
text: {
// Taken from quickshell-examples
const app = root.node?.properties["application.name"] ?? (root.node?.description != "" ? root.node?.description : root.node?.name);
const media = root.node?.properties["media.name"];
const title = media != undefined ? `${app} - ${media}` : app;
return title != undefined ? title : "null";
}
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
Layout.topMargin: 5
Layout.rightMargin: 5
}
Widgets.RoundSlider {
implicitHeight: 7
from: 0
to: 1
value: root.node?.audio.volume ?? 0
onValueChanged: root.node.audio.volume = value
Layout.fillWidth: true
Layout.bottomMargin: 7.5
}
}
Widgets.IconButton {
source: {
if (!root.isSink)
return root.node?.audio.muted ? "root:resources/volume/microphone-mute.svg" : "root:resources/volume/microphone-full.svg";
return root.node?.audio.muted ? "root:resources/volume/volume-mute.svg" : "root:resources/volume/volume-full.svg";
}
implicitSize: 36
padding: 4
radius: implicitSize / 2
Layout.rightMargin: 10
Layout.alignment: Qt.AlignLeft
onClicked: {
root.node.audio.muted = !root.node.audio.muted;
}
}
}
}

View file

@ -0,0 +1,176 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import "../../../widgets/" as Widgets
import "../../.."
// TODO: refactor this trash
Rectangle {
id: root
required property var isSink
color: "transparent"
radius: 10
property bool expanded: false
property int baseHeight: 60
property int contentHeight: expanded ? (applicationVolumes.count * baseHeight) : 0
implicitHeight: baseHeight + contentHeight
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: root.width
height: root.height
radius: root.baseHeight / 2
color: "black"
}
}
Item {
id: headerSection
width: parent.width
height: root.baseHeight
anchors.top: parent.top
RowLayout {
spacing: 0
anchors.fill: parent
Rectangle {
color: ShellSettings.colors["surface_container_high"]
Widgets.IconButton {
id: arrowButton
implicitSize: 44
activeRectangle: false
source: "root:resources/general/right-arrow.svg"
padding: 4
rotation: root.expanded ? 90 : 0
anchors.centerIn: parent
Behavior on rotation {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
onClicked: {
root.expanded = !root.expanded;
}
}
Layout.preferredWidth: 40
Layout.preferredHeight: root.baseHeight
}
Card {
node: root.isSink ? Pipewire.defaultAudioSink : Pipewire.defaultAudioSource
isSink: root.isSink
Layout.fillWidth: true
Layout.preferredHeight: root.baseHeight
}
}
}
Rectangle {
id: divider
color: ShellSettings.colors["surface_bright"]
height: 2
width: parent.width
anchors.top: headerSection.bottom
opacity: root.expanded ? 1.0 : 0.0
// Behavior on opacity {
// NumberAnimation {
// duration: 150
// easing.type: Easing.OutCubic
// }
// }
}
Item {
id: contentSection
width: parent.width
anchors.top: divider.bottom
height: root.contentHeight
clip: true
// Behavior on height {
// SmoothedAnimation {
// duration: 150
// velocity: 200
// easing.type: Easing.OutCubic
// }
// }
Column {
id: applicationsColumn
width: parent.width
anchors.top: parent.top
opacity: root.expanded ? 1.0 : 0.0
// Behavior on opacity {
// NumberAnimation {
// duration: 100
// easing.type: Easing.OutCubic
// }
// }
PwNodeLinkTracker {
id: linkTracker
node: root.isSink ? Pipewire.defaultAudioSink : Pipewire.defaultAudioSource
}
Repeater {
id: applicationVolumes
model: linkTracker.linkGroups
delegate: RowLayout {
id: cardRow
required property PwLinkGroup modelData
spacing: 0
width: applicationsColumn.width
height: root.baseHeight
Rectangle {
color: ShellSettings.colors["surface_container_high"]
IconImage {
implicitSize: 32
source: {
if (cardRow.modelData.source?.properties["application.icon-name"] == null) {
return "root:resources/general/placeholder.svg";
}
return `image://icon/${cardRow.modelData.source?.properties["application.icon-name"]}`;
}
anchors {
fill: parent
leftMargin: 8
rightMargin: 8
}
}
Layout.preferredWidth: 40
Layout.preferredHeight: root.baseHeight
}
Card {
node: cardRow.modelData.source
isSink: root.isSink
Layout.fillWidth: true
Layout.preferredHeight: root.baseHeight
}
}
}
}
}
}

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,191 @@
import Quickshell
import Quickshell.Hyprland
import Quickshell.Widgets
import QtQuick
import QtQuick.Effects
import "../.."
PopupWindow {
id: root
color: "transparent"
implicitWidth: bar.width
implicitHeight: Math.max(popupContainer.height, 800) + 20
mask: Region {
item: popupContainer
}
anchor {
window: bar
rect: Qt.rect(0, 0, bar.width, bar.height)
edges: Edges.Bottom | Edges.Left
gravity: Edges.Bottom | Edges.Right
adjustment: PopupAdjustment.None
}
required property var bar
property var isOpen: false
property var padding: 5
property var item
property var content
function set(item, content) {
root.item = item;
root.content = content;
popupContent.data = content;
let itemPos = item.mapToItem(root.bar.contentItem, 0, root.bar.height, item.width, 0).x;
position(itemPos);
popupContainer.opacity = 0;
popupContent.opacity = 0;
}
function position(itemPos) {
if (itemPos === undefined)
return;
let rightEdge = itemPos + popupContainer.implicitWidth;
let maxRightEdge = root.width - padding;
let isTouchingRightEdge = rightEdge > maxRightEdge;
if (isTouchingRightEdge) {
// touching right edge, reposition
// console.log("touching right edge");
popupContainer.x = maxRightEdge - popupContainer.implicitWidth;
popupContainer.y = padding;
} else {
// not touching right edge
popupContainer.x = itemPos;
popupContainer.y = padding;
}
}
function show() {
grab.active = true;
isOpen = true;
root.visible = true; // set and leave open
root.content.visible = true;
popupContainer.opacity = 1;
popupContent.opacity = 1;
}
function hide() {
grab.active = false;
isOpen = false;
popupContainer.opacity = 0;
popupContent.opacity = 0;
root.item = undefined;
root.content = undefined;
popupContent.data = [];
}
function toggle() {
if (isOpen) {
hide();
} else {
show();
}
}
RectangularShadow {
radius: popupContainer.radius
anchors.fill: popupContainer
opacity: popupContainer.opacity
visible: popupContainer.visible
blur: 10
spread: 2
}
WrapperRectangle {
id: popupContainer
color: ShellSettings.colors["surface"]
radius: 12
margin: 8
clip: true
opacity: 0
visible: opacity > 0
x: root.bar.width
// spooky, likely to cause problems lol
width: implicitWidth
height: implicitHeight
onVisibleChanged: root.visible = visible
// needed to handle occurences where items are resized while open
onImplicitWidthChanged: {
if (root.isOpen && popupContent.data !== []) {
// console.log("repositioning popup");
let itemPos = root.item.mapToItem(root.bar.contentItem, 0, root.bar.height, root.item.width, 0).x;
root.position(itemPos);
}
}
Item {
id: popupContent
implicitWidth: Math.max(root.content?.width, 60)
implicitHeight: Math.max(childrenRect.height, 60)
Behavior on opacity {
NumberAnimation {
duration: 200
easing.type: Easing.Linear
from: 0
to: 1
}
}
}
// broken for elements in the popup that have hover/mousearea's
// HoverHandler {
// id: hover
// enabled: true
// acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
// onHoveredChanged: {
// if (hovered == false)
// root.hide();
// }
// }
HyprlandFocusGrab {
id: grab
windows: [root, root.bar]
onCleared: {
root.hide();
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
easing.type: Easing.Linear
}
}
Behavior on width {
enabled: root.isOpen
SmoothedAnimation {
duration: 200
easing.type: Easing.Linear
}
}
Behavior on height {
SmoothedAnimation {
duration: 200
easing.type: Easing.Linear
}
}
Behavior on x {
enabled: root.isOpen
SmoothedAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
}
}

View file

@ -0,0 +1,102 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Widgets
import Quickshell.Services.UPower
import "../../widgets" as Widgets
import "../.."
// todo: redo the tray icon handling
Item {
id: root
implicitWidth: height + 8 // for margin
visible: UPower.displayDevice.isLaptopBattery
required property var popup
Widgets.MaterialButton {
id: batteryButton
hoverEnabled: true
onClicked: {
if (root.popup.content == powerMenu) {
root.popup.hide();
return;
}
root.popup.set(this, powerMenu);
root.popup.show();
}
anchors {
fill: parent
margins: 1
}
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
}
}
}
}
Item {
id: powerMenu
visible: false
implicitWidth: 250
implicitHeight: 80
RowLayout {
anchors.fill: parent
// ComboBox {
// model: ScriptModel {
// values: ["Power Save", "Balanced", "Performance"]
// }
//
// currentIndex: PowerProfiles.profile
// onCurrentIndexChanged: {
// PowerProfiles.profile = this.currentIndex;
// console.log(PowerProfile.toString(PowerProfiles.profile));
// }
// }
}
}
}

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" as Widgets
RowLayout {
id: root
spacing: 5
visible: SystemTray.items.values.length > 0
required property var popup
Repeater {
model: SystemTray.items
delegate: Item {
id: trayField
Layout.preferredWidth: parent.height
Layout.fillHeight: true
required property SystemTrayItem modelData
Widgets.MaterialButton {
id: trayButton
hoverEnabled: true
onClicked: {
menuOpener.menu = trayField.modelData.menu;
if (root.popup.content == trayMenu) {
root.popup.hide();
return;
}
root.popup.set(this, trayMenu);
root.popup.show();
}
anchors {
fill: parent
margins: 2
}
IconImage {
id: trayIcon
anchors.fill: parent
source: {
// console.log(trayField.modelData.id);
switch (trayField.modelData.id) {
case "obs":
return "image://icon/obs-tray";
default:
return trayField.modelData.icon;
}
}
}
}
QsMenuOpener {
id: menuOpener
}
WrapperItem {
id: trayMenu
visible: false
property var leftItem: false
property var rightItem: false
ColumnLayout {
id: menuContainer
spacing: 2
Repeater {
model: menuOpener.children
delegate: TrayMenuItem {
id: sysTrayContent
Layout.fillWidth: true
Layout.fillHeight: true
rootMenu: trayMenu
onInteracted: {
root.popup.hide();
menuOpener.menu = null;
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,170 @@
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: 6
color: {
if (!root.menuData?.enabled)
return "transparent";
if (entryArea.containsMouse)
return ShellSettings.colors["primary"];
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["inverse_surface"]);
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"]
radius: 8
visible: false
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,29 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import "../.."
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["surface_container_high"]
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,76 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import "../../widgets/" as Widgets
import "../.."
WrapperItem {
id: root
visible: false
ColumnLayout {
spacing: 5
// Toolbar
Rectangle {
id: toolbar
color: ShellSettings.colors["surface_container_highest"]
radius: 10
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: toolbar.width
height: toolbar.height
radius: toolbar.radius
color: "black"
}
}
Layout.fillWidth: true
Layout.preferredHeight: 35
RowLayout {
spacing: 0
anchors.fill: parent
Widgets.FontIconButton {
hoverEnabled: false
iconName: "headphones"
radius: 0
checked: page.currentIndex === 0
onClicked: page.currentIndex = 0
Layout.fillWidth: true
Layout.fillHeight: true
}
Widgets.FontIconButton {
hoverEnabled: false
iconName: "tune"
radius: 0
checked: page.currentIndex === 1
onClicked: page.currentIndex = 1
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
StackLayout {
id: page
currentIndex: 0
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
}
}

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,44 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
Singleton {
id: root
PersistentProperties {
id: persist
property bool locked: false
}
IpcHandler {
target: "lockscreen"
function lock(): void {
persist.locked = true;
}
}
LockContext {
id: passwordContext
onUnlocked: persist.locked = false
}
WlSessionLock {
id: lock
locked: persist.locked
WlSessionLockSurface {
LockSurface {
anchors.fill: parent
context: passwordContext
}
}
}
function init() {}
}

View file

@ -0,0 +1,53 @@
import QtQuick
import Quickshell
import Quickshell.Services.Pam
Scope {
id: root
property string currentText: ""
property bool unlockInProgress: false
property bool showFailure: false
signal unlocked
signal failed
// Clear the failure text once the user starts typing.
onCurrentTextChanged: showFailure = false
function tryUnlock() {
if (currentText === "")
return;
root.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.currentText);
}
}
// pam_unix won't send any important messages so all we need is the completion status.
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked();
root.currentText = "";
} else {
root.showFailure = true;
}
root.unlockInProgress = false;
}
}
}

View file

@ -0,0 +1,198 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import ".."
Rectangle {
id: root
color: Window.active ? ShellSettings.colors["surface"] : ShellSettings.colors["surface_dim"]
required property LockContext context
Item {
anchors.fill: parent
Image {
id: bgImage
source: ShellSettings.settings.wallpaperUrl
fillMode: Image.PreserveAspectCrop
anchors.fill: parent
visible: false
}
FastBlur {
anchors.fill: bgImage
source: bgImage
radius: 80
transparentBorder: true
}
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.context.unlockInProgress
Layout.preferredWidth: 250
Layout.preferredHeight: 30
Layout.maximumHeight: 30
Layout.alignment: Qt.AlignHCenter
onTextChanged: root.context.currentText = this.text
onAccepted: root.context.tryUnlock()
Connections {
target: root.context
function onCurrentTextChanged() {
if (!passwordBox.shaking) {
passwordBox.text = root.context.currentText;
}
}
function onShowFailureChanged() {
if (root.context.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.context.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,102 @@
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;
}
}
// Just use this window to grab screen context
LazyLoader {
activeAsync: 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
// to get a freeze frame for now
ScreencopyView {
id: screenView
captureSource: focusedScreen.screen
anchors.fill: parent
SelectionRectangle {
id: selection
anchors.fill: parent
property string position
property bool running: false
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);
position = `${x},${y} ${width}x${height}`;
running = true;
}
LazyLoader {
activeAsync: selection.running
Process {
id: grim
running: true
property var path: `${ShellSettings.settings.screenshotPath}/screenshot.png`
command: ["grim", "-g", selection.position, path]
onRunningChanged: {
if (!running) {
// Quickshell.clipboardText = `image://${path}`;
root.windowOpen = false;
}
}
stderr: SplitParser {
onRead: data => console.log(`line read: ${data}`)
}
}
}
}
}
}
}
}
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,309 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Qt.labs.folderlistmodel
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 {
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
ListView {
id: horizontalList
orientation: ListView.Horizontal
model: ["scheme-content", "scheme-expressive", "scheme-fidelity", "scheme-fruit-salad", "scheme-monochrome", "scheme-neutral", "scheme-rainbow", "scheme-tonal-spot", "scheme-vibrant"]
spacing: 10
clip: true
Layout.fillWidth: true
Layout.preferredHeight: 100
Layout.margins: 10
delegate: Rectangle {
id: paletteCell
required property string modelData
property string matugenConf: Qt.resolvedUrl("root:wallpaper/matugen.toml").toString().replace("file://", "")
property var colors: {
"primary": "white",
"secondary": "gray",
"tertiary": "lightgrey",
"container": "black"
}
width: 100
height: 100
color: paletteSelect.containsMouse ? ShellSettings.colors["surface_container_highest"] : ShellSettings.colors["surface_container_high"]
radius: 20
MouseArea {
id: paletteSelect
hoverEnabled: true
anchors.fill: parent
onPressed: {
ShellSettings.settings.colorScheme = paletteCell.modelData;
}
}
Item {
id: paletteContainer
width: 80
height: 80
anchors.centerIn: parent
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: paletteContainer.width
height: paletteContainer.height
radius: 20
}
}
Rectangle {
id: topLeft
color: paletteCell.colors["primary"] ?? "white"
width: parent.width / 2
height: parent.height / 2
}
Rectangle {
id: topRight
color: paletteCell.colors["secondary"] ?? "gray"
width: parent.width / 2
height: parent.height / 2
anchors.left: topLeft.right
}
Rectangle {
id: bottomLeft
color: paletteCell.colors["tertiary"] ?? "lightgrey"
width: parent.width / 2
height: parent.height / 2
anchors.top: topLeft.bottom
}
Rectangle {
id: bottomRight
color: paletteCell.colors["surface"] ?? "black"
width: parent.width / 2
height: parent.height / 2
anchors {
top: topRight.bottom
left: bottomLeft.right
}
}
}
Connections {
target: ShellSettings.settings
function onWallpaperUrlChanged() {
matugen.running = true;
}
}
Process {
id: matugen
running: true
command: ["matugen", "image", ShellSettings.settings.wallpaperUrl.replace("file://", ""), "--type", paletteCell.modelData, "--json", "hex", "--config", paletteCell.matugenConf, "--dry-run"]
stdout: SplitParser {
onRead: data => {
try {
paletteCell.colors = JSON.parse(data)['colors']['dark'];
} catch (e) {
console.error("Error parsing JSON:", e);
}
}
}
stderr: SplitParser {
onRead: data => console.log(`line read: ${data}`)
}
}
}
}
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;
}
}
}
}
}
}
}
}
}
function init() {
}
}

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" as 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.Controller {}
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,174 @@
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
}
// The OSD window will be created and destroyed based on shouldShowOsd.
// PanelWindow.visible could be set instead of using a loader, but using
// a loader will reduce the memory overhead when the window isn't open.
LazyLoader {
active: root.shouldShowOsd
PanelWindow {
anchors.right: true
margins.right: 5
implicitWidth: 50
implicitHeight: 275
color: "transparent"
// An empty click mask prevents the window from blocking mouse events.
mask: Region {}
Rectangle {
anchors.fill: parent
radius: width / 2
color: ShellSettings.colors["surface"]
ColumnLayout {
spacing: 10
anchors {
fill: parent
margins: 8
}
Rectangle {
radius: width / 2
Layout.fillWidth: true
Layout.preferredHeight: width
}
Rectangle {
id: sliderContainer
color: "gray"
Layout.fillWidth: true
Layout.fillHeight: true
layer.enabled: true
layer.effect: OpacityMask {
source: Rectangle {
width: sliderContainer.width
height: sliderContainer.height
radius: sliderContainer.width / 2
color: "white"
}
maskSource: Rectangle {
width: sliderContainer.width
height: sliderContainer.height
radius: sliderContainer.width / 2
color: "black"
}
}
Rectangle {
radius: width / 2
color: ShellSettings.colors["primary"]
implicitHeight: Math.max(parent.width, parent.height * (Pipewire.defaultAudioSink?.audio.volume ?? 0))
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
// replace with material icon
// IconImage {
// implicitSize: parent.width - 4
// source: "root:resources/volume/volume-full.svg"
//
// anchors {
// horizontalCenter: parent.horizontalCenter
// }
// }
}
}
}
}
// 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,84 @@
import Quickshell
import Quickshell.Io
import QtQuick
import ".."
Scope {
id: root
property string matugenConf: Qt.resolvedUrl("matugen.toml").toString().replace("file://", "")
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);
matugen.running = true;
}
function onColorSchemeChanged() {
console.log("Switching color scheme: " + ShellSettings.settings.colorScheme);
matugen.running = true;
}
}
Process {
id: matugen
running: false
// Don't format this lol
command: [
"matugen",
"image",
ShellSettings.settings.wallpaperUrl.replace("file://", ""),
"--type",
ShellSettings.settings.colorScheme,
"--json",
"hex",
"--config",
root.matugenConf
]
stdout: SplitParser {
onRead: data => {
console.log(ShellSettings.settings.colorScheme);
try {
ShellSettings.colors = JSON.parse(data)['colors']['dark'];
} catch (e) {}
}
}
stderr: SplitParser {
onRead: data => console.log(`line read: ${data}`)
}
}
}
}
}

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,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,52 @@
import QtQuick
import Quickshell.Widgets
import Qt5Compat.GraphicalEffects
import ".."
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["inverse_surface"]
property var activeColor: ShellSettings.colors["inverse_primary"]
signal clicked
implicitWidth: implicitSize
implicitHeight: implicitSize
Rectangle {
id: iconBackground
color: ShellSettings.colors["primary"]
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,24 @@
import QtQuick
import ".."
MouseArea {
id: root
hoverEnabled: true
property real radius: width / 2
property bool checked: false
property var activeColor: ShellSettings.colors["primary"]
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,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["primary"]
radius: 5
width: 7.5
height: 7.5
}