mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 17:47:09 +00:00
quickshell and hyprland additions
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls
|
||||
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.modules.functions
|
||||
|
||||
StyledRect {
|
||||
id: root
|
||||
property bool hovered: false
|
||||
property bool selected: false
|
||||
|
||||
required property int parentWidth
|
||||
|
||||
width: parentWidth
|
||||
height: 50
|
||||
color: {
|
||||
if (selected || hovered)
|
||||
return Appearance.m3colors.m3surfaceContainerHigh
|
||||
else
|
||||
return Appearance.m3colors.m3surface
|
||||
}
|
||||
radius: Metrics.radius(15)
|
||||
|
||||
Behavior on color {
|
||||
PropertyAnimation {
|
||||
duration: Metrics.chronoDuration(200)
|
||||
easing.type: Easing.InSine
|
||||
}
|
||||
}
|
||||
|
||||
ClippingWrapperRectangle {
|
||||
id: entryIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Metrics.margin(10)
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (parent.height / 2) - (size / 2)
|
||||
|
||||
property int size: 25
|
||||
height: size
|
||||
width: size
|
||||
radius: Metrics.radius(1000)
|
||||
|
||||
color: "transparent"
|
||||
|
||||
child: Image {
|
||||
source: Quickshell.iconPath(modelData.icon, "application-x-executable")
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect { // Tint if needed, ngl this looks fucking cool when you use monochrome
|
||||
saturation: (Config.runtime.appearance.tintIcons || (Config.runtime.appearance.colors.matugenScheme === "scheme-monochrome" && Config.runtime.appearance.colors.autogenerated) || Config.runtime.appearance.colors.scheme.toLowerCase() === "monochrome") ? -1.0 : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.left: entryIcon.right
|
||||
anchors.leftMargin: Metrics.margin(10)
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (parent.height / 2) - (height / 2)
|
||||
|
||||
height: 40
|
||||
spacing: Metrics.spacing(-5)
|
||||
|
||||
StyledText {
|
||||
font.weight: 400
|
||||
text: modelData.name
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
color: {
|
||||
if (root.hovered || root.selected)
|
||||
return Appearance.m3colors.m3onSurface
|
||||
else
|
||||
return Appearance.colors.colOutline
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
PropertyAnimation {
|
||||
duration: Metrics.chronoDuration(200)
|
||||
easing.type: Easing.InSine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
font.weight: 400
|
||||
text: StringUtils.shortText(modelData.comment, 65) // Limit maximum chars to 65
|
||||
font.pixelSize: Metrics.fontSize(12)
|
||||
color: {
|
||||
if (root.hovered || root.selected)
|
||||
return Qt.alpha(Appearance.m3colors.m3onSurface, 0.7)
|
||||
else
|
||||
return Qt.alpha(Appearance.colors.colOutline, 0.7)
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
PropertyAnimation {
|
||||
duration: Metrics.chronoDuration(200)
|
||||
easing.type: Easing.InSine
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onEntered: root.hovered = true
|
||||
onExited: root.hovered = false
|
||||
onClicked: {
|
||||
modelData.execute()
|
||||
IPCLoader.toggleLauncher()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls
|
||||
|
||||
import qs.modules.components
|
||||
import qs.modules.functions
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
PanelWindow {
|
||||
id: launcherWindow
|
||||
|
||||
readonly property bool launcherOpen: Globals.visiblility.launcher
|
||||
|
||||
visible: launcherOpen
|
||||
focusable: true
|
||||
aboveWindows: true // btw I never knew this was a property (read docs)
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
exclusionMode: ExclusionMode.Ignore // why this? idk but it works atleast
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
ScrollView {
|
||||
id: maskId
|
||||
|
||||
implicitHeight: DisplayMetrics.scaledHeight(0.623)
|
||||
implicitWidth: DisplayMetrics.scaledWidth(0.3)
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (parent.width / 2) - (implicitWidth / 2)
|
||||
anchors.topMargin: (parent.height / 2) - (implicitHeight / 2)
|
||||
|
||||
clip: true
|
||||
focus: true
|
||||
|
||||
Rectangle {
|
||||
id: launcher
|
||||
property string currentSearch: ""
|
||||
property int entryIndex: 0
|
||||
property list<DesktopEntry> appList: Apps.list
|
||||
|
||||
Connections {
|
||||
target: launcherWindow
|
||||
function onLauncherOpenChanged() {
|
||||
if (!launcherWindow.launcherOpen) {
|
||||
launcher.currentSearch = ""
|
||||
launcher.entryIndex = 0
|
||||
launcher.appList = Apps.list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
color: Appearance.m3colors.m3surface
|
||||
radius: Metrics.radius(21)
|
||||
|
||||
StyledRect {
|
||||
id: searchBox
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Metrics.margin(10)
|
||||
|
||||
color: Appearance.m3colors.m3surfaceContainerLow
|
||||
width: parent.width - 20
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (parent.width / 2) - (width / 2)
|
||||
height: 45
|
||||
radius: Metrics.radius(15)
|
||||
z: 2
|
||||
|
||||
focus: true
|
||||
|
||||
Keys.onDownPressed: launcher.entryIndex += 1
|
||||
Keys.onUpPressed: {
|
||||
if (launcher.entryIndex != 0)
|
||||
launcher.entryIndex -= 1
|
||||
}
|
||||
Keys.onEscapePressed: Globals.visiblility.launcher = false
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
|
||||
launcher.appList[launcher.entryIndex].execute()
|
||||
Globals.visiblility.launcher = false
|
||||
} else if (event.key === Qt.Key_Backspace) {
|
||||
launcher.currentSearch = launcher.currentSearch.slice(0, -1)
|
||||
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
|
||||
launcher.currentSearch += event.text
|
||||
}
|
||||
|
||||
launcher.appList = Apps.fuzzyQuery(launcher.currentSearch)
|
||||
launcher.entryIndex = 0
|
||||
}
|
||||
|
||||
MaterialSymbol {
|
||||
id: iconText
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Metrics.margin(10)
|
||||
icon: "search"
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.weight: 600
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (parent.height / 2) - ((font.pixelSize + 5) / 2)
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: placeHolderText
|
||||
anchors.left: iconText.right
|
||||
anchors.leftMargin: Metrics.margin(10)
|
||||
color: (launcher.currentSearch != "") ? Appearance.m3colors.m3onSurface : Appearance.colors.colOutline
|
||||
text: (launcher.currentSearch != "") ? launcher.currentSearch : "Start typing to search ..."
|
||||
font.pixelSize: Metrics.fontSize(13)
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (parent.height / 2) - ((font.pixelSize + 5) / 2)
|
||||
animate: false
|
||||
opacity: 0.8
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
anchors.top: searchBox.bottom
|
||||
anchors.topMargin: Metrics.margin(10)
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (parent.width / 2) - (width / 2)
|
||||
width: parent.width - 20
|
||||
height: parent.height - searchBox.height - 20
|
||||
|
||||
ListView {
|
||||
id: appList
|
||||
anchors.fill: parent
|
||||
spacing: Metrics.spacing(10)
|
||||
anchors.bottomMargin: Metrics.margin(4)
|
||||
|
||||
model: launcher.appList
|
||||
currentIndex: launcher.entryIndex
|
||||
|
||||
delegate: AppItem {
|
||||
required property int index
|
||||
required property DesktopEntry modelData
|
||||
selected: index === launcher.entryIndex
|
||||
parentWidth: appList.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function toggle() {
|
||||
Globals.visiblility.launcher = !Globals.visiblility.launcher;
|
||||
}
|
||||
|
||||
target: "launcher"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.config
|
||||
import qs.modules.functions
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
/*
|
||||
|
||||
This LauncherContent has been depricated.
|
||||
And yet not used. (4/3/26)
|
||||
|
||||
*/
|
||||
|
||||
Item {
|
||||
id: content
|
||||
|
||||
property int selectedIndex: -1
|
||||
property string searchQuery: ""
|
||||
property var calcVars: ({})
|
||||
|
||||
property alias listView: listView
|
||||
property alias filteredModel: filteredModel
|
||||
|
||||
function launchCurrent() {
|
||||
launchApp(listView.currentIndex)
|
||||
}
|
||||
|
||||
function webSearchUrl(query) {
|
||||
const engine = (Config.runtime.launcher.webSearchEngine || "").toLowerCase()
|
||||
if (engine.startsWith("http"))
|
||||
return engine.replace("%s", encodeURIComponent(query))
|
||||
|
||||
const engines = {
|
||||
"google": "https://www.google.com/search?q=%s",
|
||||
"duckduckgo": "https://duckduckgo.com/?q=%s",
|
||||
"brave": "https://search.brave.com/search?q=%s",
|
||||
"bing": "https://www.bing.com/search?q=%s",
|
||||
"startpage": "https://www.startpage.com/search?q=%s"
|
||||
}
|
||||
const template = engines[engine] || engines["duckduckgo"]
|
||||
return template.replace("%s", encodeURIComponent(query))
|
||||
}
|
||||
|
||||
function moveSelection(delta) {
|
||||
if (filteredModel.count === 0) return
|
||||
|
||||
selectedIndex = Math.max(0, Math.min(selectedIndex + delta, filteredModel.count - 1))
|
||||
listView.currentIndex = selectedIndex
|
||||
listView.positionViewAtIndex(selectedIndex, ListView.Contain)
|
||||
}
|
||||
|
||||
function fuzzyMatch(text, pattern) {
|
||||
text = text.toLowerCase()
|
||||
pattern = pattern.toLowerCase()
|
||||
let ti = 0, pi = 0
|
||||
while (ti < text.length && pi < pattern.length) {
|
||||
if (text[ti] === pattern[pi]) pi++
|
||||
ti++
|
||||
}
|
||||
return pi === pattern.length
|
||||
}
|
||||
|
||||
function evalExpression(expr) {
|
||||
try {
|
||||
const fn = new Function("vars", `
|
||||
with (vars) { with (Math) { return (${expr}); } }
|
||||
`)
|
||||
const res = fn(calcVars)
|
||||
if (res === undefined || Number.isNaN(res)) return null
|
||||
return res
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilter() {
|
||||
filteredModel.clear()
|
||||
const query = searchQuery.toLowerCase().trim()
|
||||
|
||||
const calcVal = evalExpression(query)
|
||||
if (calcVal !== null && query !== "") {
|
||||
filteredModel.append({
|
||||
name: String(calcVal),
|
||||
displayName: String(calcVal),
|
||||
comment: "Calculation",
|
||||
icon: "",
|
||||
exec: "",
|
||||
isCalc: true,
|
||||
isWeb: false
|
||||
})
|
||||
}
|
||||
|
||||
const sourceApps = AppRegistry.apps
|
||||
|
||||
if (query === "") {
|
||||
for (let app of sourceApps) {
|
||||
filteredModel.append({
|
||||
name: app.name,
|
||||
displayName: app.name,
|
||||
comment: app.comment,
|
||||
icon: AppRegistry.iconForDesktopIcon(app.icon),
|
||||
exec: app.exec,
|
||||
isCalc: false,
|
||||
isWeb: false
|
||||
})
|
||||
}
|
||||
selectedIndex = filteredModel.count > 0 ? 0 : -1
|
||||
listView.currentIndex = selectedIndex
|
||||
return
|
||||
}
|
||||
|
||||
let exactMatches = []
|
||||
let startsWithMatches = []
|
||||
let containsMatches = []
|
||||
let fuzzyMatches = []
|
||||
|
||||
for (let app of sourceApps) {
|
||||
const name = app.name ? app.name.toLowerCase() : ""
|
||||
const comment = app.comment ? app.comment.toLowerCase() : ""
|
||||
|
||||
if (name === query) exactMatches.push(app)
|
||||
else if (name.startsWith(query)) startsWithMatches.push(app)
|
||||
else if (name.includes(query) || comment.includes(query)) containsMatches.push(app)
|
||||
else if (Config.runtime.launcher.fuzzySearchEnabled && fuzzyMatch(name, query)) fuzzyMatches.push(app)
|
||||
}
|
||||
|
||||
const sortedResults = [
|
||||
...exactMatches,
|
||||
...startsWithMatches,
|
||||
...containsMatches,
|
||||
...fuzzyMatches
|
||||
]
|
||||
|
||||
for (let app of sortedResults) {
|
||||
filteredModel.append({
|
||||
name: app.name,
|
||||
displayName: app.name,
|
||||
comment: app.comment,
|
||||
icon: AppRegistry.iconForDesktopIcon(app.icon),
|
||||
exec: app.exec,
|
||||
isCalc: false,
|
||||
isWeb: false
|
||||
})
|
||||
}
|
||||
|
||||
if (filteredModel.count === 0 && query !== "") {
|
||||
filteredModel.append({
|
||||
name: query,
|
||||
displayName: "Search the web for \"" + query + "\"",
|
||||
comment: "Web search",
|
||||
icon: "public",
|
||||
exec: webSearchUrl(query),
|
||||
isCalc: false,
|
||||
isWeb: true
|
||||
})
|
||||
}
|
||||
|
||||
selectedIndex = filteredModel.count > 0 ? 0 : -1
|
||||
listView.currentIndex = selectedIndex
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
|
||||
function launchApp(idx) {
|
||||
if (idx < 0 || idx >= filteredModel.count) return
|
||||
|
||||
const app = filteredModel.get(idx)
|
||||
if (app.isCalc) return
|
||||
if (app.isWeb)
|
||||
Quickshell.execDetached(["xdg-open", app.exec])
|
||||
else
|
||||
Quickshell.execDetached(["bash", "-c", app.exec + " &"])
|
||||
|
||||
closeLauncher()
|
||||
}
|
||||
|
||||
function closeLauncher() {
|
||||
Globals.visiblility.launcher = false
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
searchQuery = ""
|
||||
updateFilter()
|
||||
selectedIndex = -1
|
||||
listView.currentIndex = -1
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AppRegistry
|
||||
function onReady() {
|
||||
updateFilter()
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
opacity: Globals.visiblility.launcher ? 1 : 0
|
||||
anchors.margins: Metrics.margin(10)
|
||||
|
||||
ListModel { id: filteredModel }
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin(16)
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
model: filteredModel
|
||||
spacing: Metrics.spacing(8)
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
highlightRangeMode: ListView.StrictlyEnforceRange
|
||||
preferredHighlightBegin: 0
|
||||
preferredHighlightEnd: height
|
||||
highlightMoveDuration: 120
|
||||
currentIndex: selectedIndex
|
||||
|
||||
delegate: Rectangle {
|
||||
property bool isSelected: listView.currentIndex === index
|
||||
|
||||
width: listView.width
|
||||
height: 60
|
||||
radius: Appearance.rounding.normal
|
||||
color: isSelected ? Appearance.m3colors.m3surfaceContainerHighest : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin(10)
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
Item {
|
||||
width: 32
|
||||
height: 32
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
visible: !model.isCalc && !model.isWeb
|
||||
smooth: true
|
||||
mipmap: true
|
||||
antialiasing: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: 128
|
||||
sourceSize.height: 128
|
||||
source: model.icon
|
||||
}
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
visible: model.isCalc
|
||||
icon: "calculate"
|
||||
iconSize: Metrics.iconSize(28)
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
}
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
visible: model.isWeb
|
||||
icon: "public"
|
||||
iconSize: Metrics.iconSize(28)
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: listView.width - 120
|
||||
spacing: Metrics.spacing(4)
|
||||
|
||||
Text {
|
||||
text: model.displayName
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
Text {
|
||||
text: model.comment
|
||||
font.pixelSize: Metrics.fontSize(11)
|
||||
elide: Text.ElideRight
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: launchApp(index)
|
||||
onEntered: listView.currentIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: Config.runtime.appearance.animations.enabled
|
||||
NumberAnimation {
|
||||
duration: Metrics.chronoDuration(400)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.animation.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user