all: misc

This commit is contained in:
outfoxxed 2026-01-04 00:20:14 -08:00
parent c6eef9f01c
commit f22dee69f0
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
5 changed files with 375 additions and 230 deletions

View file

@ -8,8 +8,7 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Services.SystemTray import Quickshell.Hyprland
import ".."
Singleton { Singleton {
PersistentProperties { PersistentProperties {
@ -38,240 +37,37 @@ Singleton {
activeAsync: persist.launcherOpen activeAsync: persist.launcherOpen
PanelWindow { PanelWindow {
width: 450 id: launcherWindow
height: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10 //anchors { left: true; right: true; top: true; bottom: true }
color: "transparent" color: "transparent"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive implicitWidth: content.width
implicitHeight: content.height
//color: "#20ff0000"
//WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.namespace: "shell:launcher" WlrLayershell.namespace: "shell:launcher"
Rectangle { /*HyprlandWindow.visibleMask: Region {
//anchors.fill: parent item: content
height: 7 + searchContainer.implicitHeight + list.topMargin + list.bottomMargin + Math.min(list.contentHeight, list.delegateHeight * 10) }*/
Behavior on height { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
width: 450
color: ShellGlobals.colors.bar
radius: 5
border.color: ShellGlobals.colors.barOutline
border.width: 1
ColumnLayout { HyprlandFocusGrab {
anchors.fill: parent windows: [launcherWindow]
anchors.margins: 7 active: true
anchors.bottomMargin: 0 //onCleared: console.log("cleared")
spacing: 0 onCleared: persist.launcherOpen = false
}
Rectangle { MouseArea {
id: searchContainer anchors.fill: parent
Layout.fillWidth: true
implicitHeight: searchbox.implicitHeight + 10
color: "#30c0ffff"
radius: 3
border.color: "#50ffffff"
RowLayout { onPressed: persist.launcherOpen = false
id: searchbox
anchors.fill: parent
anchors.margins: 5
IconImage { MouseArea {
implicitSize: parent.height anchors.centerIn: parent
source: "root:icons/magnifying-glass.svg" width: content.width
} height: content.height
TextInput { LaunchContent { id: content }
id: search
Layout.fillWidth: true
color: "white"
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: 5
color: "#20e0ffff"
border.color: "#30ffffff"
border.width: 1
}
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: "#f0f0f0"
Layout.alignment: Qt.AlignVCenter
}
}
}
}
} }
} }
} }

View file

@ -0,0 +1,331 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import qs
Item {
id: root
height: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10;
width: 450
Rectangle {
id: content
height: 7 + searchContainer.implicitHeight + list.topMargin + list.bottomMargin + Math.min(list.contentHeight, list.delegateHeight * 10)
Behavior on height { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
width: 450
color: ShellGlobals.colors.bar
radius: 5
border.color: ShellGlobals.colors.barOutline
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 7
anchors.bottomMargin: 0
spacing: 0
Rectangle {
id: searchContainer
Layout.fillWidth: true
implicitHeight: searchbox.implicitHeight + 10
color: "#30c0ffff"
radius: 3
border.color: "#50ffffff"
RowLayout {
id: searchbox
anchors.fill: parent
anchors.margins: 5
IconImage {
implicitSize: parent.height
source: "root:icons/magnifying-glass.svg"
}
TextInput {
id: search
Layout.fillWidth: true
color: "white"
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: 5
color: "#20e0ffff"
border.color: "#30ffffff"
border.width: 1
}
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: "#f0f0f0"
Layout.alignment: Qt.AlignVCenter
}
}
}
}
}
}
Rectangle {
id: preview
anchors {
left: content.right
leftMargin: 5
}
property real listYOffset: 7 + searchContainer.implicitHeight
property real yCenter: list.contentY
y: listYOffset - yCenter - height / 2 + (list.currentItem?.y ?? 0) + list.delegateHeight / 2
implicitWidth: previewContent.implicitWidth + previewContent.anchors.margins * 2
implicitHeight: previewContent.implicitHeight + previewContent.anchors.margins * 2
color: ShellGlobals.colors.bar
radius: 5
border.color: ShellGlobals.colors.barOutline
border.width: 1
property DesktopEntry entry: list.currentItem?.modelData ?? null
property var toplevels: !entry ? []: ToplevelManager.toplevels.values
.filter(toplevel => toplevel.appId.toLowerCase() == entry.id.toLowerCase())
// waits for hasContent before showing
// cant use visible because layout wont run
property real scaleMul: previewLayout.implicitWidth != 0 ? 1 : 0;
Behavior on scaleMul { SmoothedAnimation { velocity: 5 } }
opacity: scaleMul
transform: Scale {
origin.x: 0
origin.y: preview.height / 2
xScale: 0.9 + preview.scaleMul * 0.1
yScale: xScale
}
Item {
id: previewContent
anchors.fill: parent
anchors.margins: 10
implicitWidth: previewLayout.implicitWidth
implicitHeight: previewLayout.implicitHeight
RowLayout {
id: previewLayout
Repeater {
model: preview.toplevels
Rectangle {
id: delegate
required property Toplevel modelData;
color: "transparent"
visible: view.hasContent
implicitWidth: previewContent.implicitWidth
implicitHeight: previewContent.implicitHeight
ColumnLayout {
id: previewContent
anchors.centerIn: parent
width: 240
ScreencopyView {
id: view
implicitWidth: 240
implicitHeight: 200
captureSource: modelData
live: true
}
Label {
text: modelData.title
Layout.fillWidth: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,5 @@
import QtQuick
Item {
property real yCenter: 0
}

View file

@ -4,6 +4,8 @@ import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
PanelWindow { PanelWindow {
id: root
WlrLayershell.namespace: "shell:notifications" WlrLayershell.namespace: "shell:notifications"
exclusionMode: ExclusionMode.Ignore exclusionMode: ExclusionMode.Ignore
color: "transparent" color: "transparent"
@ -16,6 +18,9 @@ PanelWindow {
right: true right: true
} }
property var bar: null
//property var fullscreen: Hyprland.monitorFor(root.screen)?.activeWorkspace?.hasFullscreen ?? false
property Component notifComponent: DaemonNotification {} property Component notifComponent: DaemonNotification {}
NotificationDisplay { NotificationDisplay {
@ -23,8 +28,8 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
stack.y: 5 + 55//(NotificationManager.showTrayNotifs ? 55 : 0) stack.y: root.bar.compactState * 10 + 50//5 + 55//(NotificationManager.showTrayNotifs ? 55 : 0)
stack.x: 72 stack.x: root.bar.leftMargin + root.bar.width - 10
} }
visible: display.stack.children.length != 0 visible: display.stack.children.length != 0

View file

@ -1,4 +1,5 @@
//@ pragma ShellId shell //@ pragma ShellId shell
//@ pragma IgnoreSystemSettings
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@ -13,6 +14,8 @@ import "launcher" as Launcher
import "background" import "background"
ShellRoot { ShellRoot {
id: root
Component.onCompleted: [Lock.Controller, Launcher.Controller.init()] Component.onCompleted: [Lock.Controller, Launcher.Controller.init()]
Process { Process {
@ -38,8 +41,11 @@ ShellRoot {
Notifs.NotificationOverlay { Notifs.NotificationOverlay {
screen: Quickshell.screens.find(s => s.name == "DP-1") screen: Quickshell.screens.find(s => s.name == "DP-1")
bar: root.bars.find(b => b.screen == screen)
} }
property var bars: []
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
@ -48,6 +54,8 @@ ShellRoot {
Bar.Bar { Bar.Bar {
screen: modelData screen: modelData
Component.onCompleted: root.bars = [...root.bars, this]
Component.onDestruction: root.bars = root.bars.filter(b => b == this)
} }
PanelWindow { PanelWindow {