Merge branch 'end-4:main' into parallax

This commit is contained in:
Ivan Rosinskii
2025-12-10 19:03:44 +01:00
committed by GitHub
76 changed files with 2987 additions and 714 deletions
@@ -138,6 +138,7 @@ Singleton {
}
property JsonObject palette: JsonObject {
property string type: "auto" // Allowed: auto, scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot
property string accentColor: ""
}
}
@@ -153,6 +154,7 @@ Singleton {
property JsonObject apps: JsonObject {
property string bluetooth: "kcmshell6 kcm_bluetooth"
property string changePassword: "kitty -1 --hold=yes fish -i -c 'passwd'"
property string network: "kcmshell6 kcm_networkmanagement"
property string manageUser: "kcmshell6 kcm_users"
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
@@ -346,6 +348,10 @@ Singleton {
}
}
property JsonObject launcher: JsonObject {
property list<string> pinnedApps: [ "org.kde.dolphin", "kitty", "cmake-gui"]
}
property JsonObject light: JsonObject {
property JsonObject night: JsonObject {
property bool automatic: true
@@ -432,6 +438,9 @@ Singleton {
property int strokeWidth: 6
property int padding: 10
}
property JsonObject annotation: JsonObject {
property bool useSatty: false
}
}
property JsonObject resources: JsonObject {
@@ -12,6 +12,10 @@ Singleton {
});
}
function changePassword() {
Quickshell.execDetached(["bash", "-c", `${Config.options.apps.changePassword}`]);
}
function lock() {
Quickshell.execDetached(["loginctl", "lock-session"]);
}
@@ -0,0 +1,157 @@
pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: root
required property Component lockSurface
property alias context: lockContext
property Component sessionLockSurface: WlSessionLockSurface {
id: sessionLockSurface
color: "transparent"
Loader {
active: GlobalStates.screenLocked
anchors.fill: parent
opacity: active ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
sourceComponent: root.lockSurface
}
}
Process {
id: unlockKeyringProc
onExited: (exitCode, exitStatus) => {
KeyringStorage.fetchKeyringData();
}
}
function unlockKeyring() {
unlockKeyringProc.exec({
environment: ({
"UNLOCK_PASSWORD": lockContext.currentText
}),
command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")]
})
}
// This stores all the information shared between the lock surfaces on each screen.
// https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen
LockContext {
id: lockContext
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (GlobalStates.screenLocked) {
lockContext.reset();
lockContext.tryFingerUnlock();
}
}
}
onUnlocked: (targetAction) => {
// Perform the target action if it's not just unlocking
if (targetAction == LockContext.ActionEnum.Poweroff) {
Session.poweroff();
return;
} else if (targetAction == LockContext.ActionEnum.Reboot) {
Session.reboot();
return;
}
// Unlock the keyring if configured to do so
if (Config.options.lock.security.unlockKeyring) root.unlockKeyring(); // Async
// Unlock the screen before exiting, or the compositor will display a
// fallback lock you can't interact with.
GlobalStates.screenLocked = false;
// Refocus last focused window on unlock (hack)
Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`])
// Reset
lockContext.reset();
// Post-unlock actions
if (lockContext.alsoInhibitIdle) {
lockContext.alsoInhibitIdle = false;
Idle.toggleInhibit(true);
}
}
}
WlSessionLock {
id: lock
locked: GlobalStates.screenLocked
surface: root.sessionLockSurface
}
function lock() {
if (Config.options.lock.useHyprlock) {
Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]);
return;
}
GlobalStates.screenLocked = true;
}
IpcHandler {
target: "lock"
function activate(): void {
root.lock();
}
function focus(): void {
lockContext.shouldReFocus();
}
}
GlobalShortcut {
name: "lock"
description: "Locks the screen"
onPressed: {
root.lock()
}
}
GlobalShortcut {
name: "lockFocus"
description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason"
+ "decides to keyboard-unfocus the lock screen"
onPressed: {
lockContext.shouldReFocus();
}
}
function initIfReady() {
if (!Config.ready || !Persistent.ready) return;
if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) {
root.lock();
} else {
KeyringStorage.fetchKeyringData();
}
}
Connections {
target: Config
function onReadyChanged() {
root.initIfReady();
}
}
Connections {
target: Persistent
function onReadyChanged() {
root.initIfReady();
}
}
}
@@ -0,0 +1,44 @@
pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import Quickshell
import Quickshell.Wayland
Scope {
id: root
required property Component contentComponent
Loader {
active: PolkitService.active
sourceComponent: Variants {
model: Quickshell.screens
delegate: PanelWindow {
id: panelWindow
required property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
bottom: true
}
color: "transparent"
WlrLayershell.namespace: "quickshell:polkit"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
WlrLayershell.layer: WlrLayer.Overlay
exclusionMode: ExclusionMode.Ignore
Loader {
anchors.fill: parent
sourceComponent: root.contentComponent
}
}
}
}
}
@@ -30,7 +30,11 @@ MouseArea {
height: batteryProgress.valueBarHeight
RowLayout {
anchors.centerIn: parent
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: (parent.height - height) / 2
}
spacing: 0
MaterialSymbol {
@@ -3,116 +3,39 @@ import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.panels.lock
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
LockScreen {
id: root
Process {
id: unlockKeyringProc
onExited: (exitCode, exitStatus) => {
KeyringStorage.fetchKeyringData();
}
}
function unlockKeyring() {
unlockKeyringProc.exec({
environment: ({
"UNLOCK_PASSWORD": lockContext.currentText
}),
command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")]
})
lockSurface: LockSurface {
context: root.context
}
// Push everything down
property var windowData: []
function saveWindowPositionAndTile() {
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "true"])
root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id))
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "true"]);
root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id));
root.windowData.forEach(w => {
Hyprland.dispatch(`pseudo address:${w.address}`)
Hyprland.dispatch(`settiled address:${w.address}`)
Hyprland.dispatch(`movetoworkspacesilent ${w.workspace.id},address:${w.address}`)
})
Hyprland.dispatch(`pseudo address:${w.address}`);
Hyprland.dispatch(`settiled address:${w.address}`);
Hyprland.dispatch(`movetoworkspacesilent ${w.workspace.id},address:${w.address}`);
});
}
function restoreWindowPositionAndTile() {
root.windowData.forEach(w => {
Hyprland.dispatch(`setfloating address:${w.address}`)
Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`)
Hyprland.dispatch(`pseudo address:${w.address}`)
})
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "false"])
Hyprland.dispatch(`setfloating address:${w.address}`);
Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`);
Hyprland.dispatch(`pseudo address:${w.address}`);
});
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "false"]);
}
// This stores all the information shared between the lock surfaces on each screen.
// https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen
LockContext {
id: lockContext
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (GlobalStates.screenLocked) {
lockContext.reset();
lockContext.tryFingerUnlock();
}
}
}
onUnlocked: (targetAction) => {
// Perform the target action if it's not just unlocking
if (targetAction == LockContext.ActionEnum.Poweroff) {
Session.poweroff();
return;
} else if (targetAction == LockContext.ActionEnum.Reboot) {
Session.reboot();
return;
}
// Unlock the keyring if configured to do so
if (Config.options.lock.security.unlockKeyring) root.unlockKeyring(); // Async
// Unlock the screen before exiting, or the compositor will display a
// fallback lock you can't interact with.
GlobalStates.screenLocked = false;
// Refocus last focused window on unlock (hack)
Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`])
// Reset
lockContext.reset();
// Post-unlock actions
if (lockContext.alsoInhibitIdle) {
lockContext.alsoInhibitIdle = false;
Idle.toggleInhibit(true);
}
}
}
WlSessionLock {
id: lock
locked: GlobalStates.screenLocked
WlSessionLockSurface {
color: "transparent"
Loader {
active: GlobalStates.screenLocked
anchors.fill: parent
opacity: active ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
sourceComponent: LockSurface {
context: lockContext
}
}
}
}
// Blur layer hack
Variants {
model: Quickshell.screens
delegate: Scope {
@@ -124,71 +47,12 @@ Scope {
onShouldPushChanged: {
if (shouldPush) {
root.saveWindowPositionAndTile();
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`])
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`]);
} else {
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`])
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`]);
root.restoreWindowPositionAndTile();
}
}
}
}
function lock() {
if (Config.options.lock.useHyprlock) {
Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]);
return;
}
GlobalStates.screenLocked = true;
}
IpcHandler {
target: "lock"
function activate(): void {
root.lock();
}
function focus(): void {
lockContext.shouldReFocus();
}
}
GlobalShortcut {
name: "lock"
description: "Locks the screen"
onPressed: {
root.lock()
}
}
GlobalShortcut {
name: "lockFocus"
description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason"
+ "decides to keyboard-unfocus the lock screen"
onPressed: {
lockContext.shouldReFocus();
}
}
function initIfReady() {
if (!Config.ready || !Persistent.ready) return;
if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) {
root.lock();
} else {
KeyringStorage.fetchKeyringData();
}
}
Connections {
target: Config
function onReadyChanged() {
root.initIfReady();
}
}
Connections {
target: Persistent
function onReadyChanged() {
root.initIfReady();
}
}
}
@@ -7,6 +7,7 @@ import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.common.panels.lock
import qs.modules.ii.bar as Bar
import Quickshell
import Quickshell.Services.SystemTray
@@ -6,37 +6,10 @@ import qs.modules.common.functions
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
FullscreenPolkitWindow {
id: root
Loader {
active: PolkitService.active
sourceComponent: Variants {
model: Quickshell.screens
delegate: PanelWindow {
id: panelWindow
required property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
bottom: true
}
color: "transparent"
WlrLayershell.namespace: "quickshell:polkit"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
WlrLayershell.layer: WlrLayer.Overlay
exclusionMode: ExclusionMode.Ignore
PolkitContent {
anchors.fill: parent
}
}
}
contentComponent: Component {
PolkitContent {}
}
}
@@ -66,12 +66,7 @@ Item {
WindowDialogParagraph {
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
text: {
if (!PolkitService.flow) return;
return PolkitService.flow.message.endsWith(".")
? PolkitService.flow.message.slice(0, -1)
: PolkitService.flow.message
}
text: PolkitService.cleanMessage
}
MaterialTextField {
@@ -79,11 +74,7 @@ Item {
Layout.fillWidth: true
focus: true
enabled: PolkitService.interactionAvailable
placeholderText: {
const inputPrompt = PolkitService.flow?.inputPrompt.trim() ?? "";
const cleanedInputPrompt = inputPrompt.endsWith(":") ? inputPrompt.slice(0, -1) : inputPrompt;
return cleanedInputPrompt || (root.usePasswordChars ? Translation.tr("Password") : Translation.tr("Input"))
}
placeholderText: PolkitService.cleanPrompt
echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal
onAccepted: root.submit();
@@ -95,7 +86,7 @@ Item {
}
WindowDialogButtonRow {
Layout.bottomMargin: 10 // I honestly don't know why this is necessary
Item {
Layout.fillWidth: true
}
@@ -261,6 +261,7 @@ PanelWindow {
const uploadAndGetUrl = (filePath) => {
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
}
const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? "satty" : "swappy"} -f -`;
switch (root.action) {
case RegionSelection.SnipAction.Copy:
if (saveScreenshotDir === "") {
@@ -282,7 +283,7 @@ PanelWindow {
break;
case RegionSelection.SnipAction.Edit:
snipProc.command = ["bash", "-c", `${cropToStdout} | swappy -f - && ${cleanup}`]
snipProc.command = ["bash", "-c", `${cropToStdout} | ${annotationCommand} && ${cleanup}`]
break;
case RegionSelection.SnipAction.Search:
snipProc.command = ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(root.screenshotPath)})" && ${cleanup}`]
@@ -14,61 +14,13 @@ import Quickshell.Hyprland
Scope {
id: root
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
property bool packageManagerRunning: false
property bool downloadRunning: false
component DescriptionLabel: Rectangle {
id: descriptionLabel
property string text
property color textColor: Appearance.colors.colOnTooltip
color: Appearance.colors.colTooltip
clip: true
radius: Appearance.rounding.normal
implicitHeight: descriptionLabelText.implicitHeight + 10 * 2
implicitWidth: descriptionLabelText.implicitWidth + 15 * 2
Behavior on implicitWidth {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
StyledText {
id: descriptionLabelText
anchors.centerIn: parent
color: descriptionLabel.textColor
text: descriptionLabel.text
}
}
function detectRunningStuff() {
packageManagerRunning = false;
downloadRunning = false;
detectPackageManagerProc.running = false;
detectPackageManagerProc.running = true;
detectDownloadProc.running = false;
detectDownloadProc.running = true;
}
Process {
id: detectPackageManagerProc
command: ["bash", "-c", "pidof pacman yay paru dnf zypper apt apx xbps flatpak snap apk yum epsi pikman"]
onExited: (exitCode, exitStatus) => {
root.packageManagerRunning = (exitCode === 0);
}
}
Process {
id: detectDownloadProc
command: ["bash", "-c", "pidof curl wget aria2c yt-dlp || ls ~/Downloads | grep -E '\.crdownload$|\.part$'"]
onExited: (exitCode, exitStatus) => {
root.downloadRunning = (exitCode === 0);
}
}
Loader {
id: sessionLoader
active: GlobalStates.sessionOpen
onActiveChanged: {
if (sessionLoader.active) root.detectRunningStuff();
if (sessionLoader.active)
SessionWarnings.refresh();
}
Connections {
@@ -84,7 +36,7 @@ Scope {
id: sessionRoot
visible: sessionLoader.active
property string subtitle
function hide() {
GlobalStates.sessionOpen = false;
}
@@ -110,7 +62,7 @@ Scope {
id: sessionMouseArea
anchors.fill: parent
onClicked: {
sessionRoot.hide()
sessionRoot.hide();
}
}
@@ -119,7 +71,7 @@ Scope {
anchors.centerIn: parent
spacing: 15
Keys.onPressed: (event) => {
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
sessionRoot.hide();
}
@@ -128,7 +80,8 @@ Scope {
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 0
StyledText { // Title
StyledText {
// Title
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font {
@@ -139,7 +92,8 @@ Scope {
text: Translation.tr("Session")
}
StyledText { // Small instruction
StyledText {
// Small instruction
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.normal
@@ -157,8 +111,14 @@ Scope {
focus: sessionRoot.visible
buttonIcon: "lock"
buttonText: Translation.tr("Lock")
onClicked: { Session.lock(); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
onClicked: {
Session.lock();
sessionRoot.hide();
}
onFocusChanged: {
if (focus)
sessionRoot.subtitle = buttonText;
}
KeyNavigation.right: sessionSleep
KeyNavigation.down: sessionHibernate
}
@@ -166,8 +126,14 @@ Scope {
id: sessionSleep
buttonIcon: "dark_mode"
buttonText: Translation.tr("Sleep")
onClicked: { Session.suspend(); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
onClicked: {
Session.suspend();
sessionRoot.hide();
}
onFocusChanged: {
if (focus)
sessionRoot.subtitle = buttonText;
}
KeyNavigation.left: sessionLock
KeyNavigation.right: sessionLogout
KeyNavigation.down: sessionShutdown
@@ -176,8 +142,14 @@ Scope {
id: sessionLogout
buttonIcon: "logout"
buttonText: Translation.tr("Logout")
onClicked: { Session.logout(); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
onClicked: {
Session.logout();
sessionRoot.hide();
}
onFocusChanged: {
if (focus)
sessionRoot.subtitle = buttonText;
}
KeyNavigation.left: sessionSleep
KeyNavigation.right: sessionTaskManager
KeyNavigation.down: sessionReboot
@@ -186,8 +158,14 @@ Scope {
id: sessionTaskManager
buttonIcon: "browse_activity"
buttonText: Translation.tr("Task Manager")
onClicked: { Session.launchTaskManager(); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
onClicked: {
Session.launchTaskManager();
sessionRoot.hide();
}
onFocusChanged: {
if (focus)
sessionRoot.subtitle = buttonText;
}
KeyNavigation.left: sessionLogout
KeyNavigation.down: sessionFirmwareReboot
}
@@ -196,8 +174,14 @@ Scope {
id: sessionHibernate
buttonIcon: "downloading"
buttonText: Translation.tr("Hibernate")
onClicked: { Session.hibernate(); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
onClicked: {
Session.hibernate();
sessionRoot.hide();
}
onFocusChanged: {
if (focus)
sessionRoot.subtitle = buttonText;
}
KeyNavigation.up: sessionLock
KeyNavigation.right: sessionShutdown
}
@@ -205,8 +189,14 @@ Scope {
id: sessionShutdown
buttonIcon: "power_settings_new"
buttonText: Translation.tr("Shutdown")
onClicked: { Session.poweroff(); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
onClicked: {
Session.poweroff();
sessionRoot.hide();
}
onFocusChanged: {
if (focus)
sessionRoot.subtitle = buttonText;
}
KeyNavigation.left: sessionHibernate
KeyNavigation.right: sessionReboot
KeyNavigation.up: sessionSleep
@@ -215,8 +205,14 @@ Scope {
id: sessionReboot
buttonIcon: "restart_alt"
buttonText: Translation.tr("Reboot")
onClicked: { Session.reboot(); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
onClicked: {
Session.reboot();
sessionRoot.hide();
}
onFocusChanged: {
if (focus)
sessionRoot.subtitle = buttonText;
}
KeyNavigation.left: sessionShutdown
KeyNavigation.right: sessionFirmwareReboot
KeyNavigation.up: sessionLogout
@@ -225,8 +221,14 @@ Scope {
id: sessionFirmwareReboot
buttonIcon: "settings_applications"
buttonText: Translation.tr("Reboot to firmware settings")
onClicked: { Session.rebootToFirmware(); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
onClicked: {
Session.rebootToFirmware();
sessionRoot.hide();
}
onFocusChanged: {
if (focus)
sessionRoot.subtitle = buttonText;
}
KeyNavigation.up: sessionTaskManager
KeyNavigation.left: sessionReboot
}
@@ -247,7 +249,7 @@ Scope {
spacing: 10
Loader {
active: root.packageManagerRunning
active: SessionWarnings.packageManagerRunning
visible: active
sourceComponent: DescriptionLabel {
text: Translation.tr("Your package manager is running")
@@ -256,7 +258,7 @@ Scope {
}
}
Loader {
active: root.downloadRunning
active: SessionWarnings.downloadRunning
visible: active
sourceComponent: DescriptionLabel {
text: Translation.tr("There might be a download in progress")
@@ -268,6 +270,28 @@ Scope {
}
}
component DescriptionLabel: Rectangle {
id: descriptionLabel
property string text
property color textColor: Appearance.colors.colOnTooltip
color: Appearance.colors.colTooltip
clip: true
radius: Appearance.rounding.normal
implicitHeight: descriptionLabelText.implicitHeight + 10 * 2
implicitWidth: descriptionLabelText.implicitWidth + 15 * 2
Behavior on implicitWidth {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
StyledText {
id: descriptionLabelText
anchors.centerIn: parent
color: descriptionLabel.textColor
text: descriptionLabel.text
}
}
IpcHandler {
target: "session"
@@ -276,11 +300,11 @@ Scope {
}
function close(): void {
GlobalStates.sessionOpen = false
GlobalStates.sessionOpen = false;
}
function open(): void {
GlobalStates.sessionOpen = true
GlobalStates.sessionOpen = true;
}
}
@@ -298,7 +322,7 @@ Scope {
description: "Opens session screen on press"
onPressed: {
GlobalStates.sessionOpen = true
GlobalStates.sessionOpen = true;
}
}
@@ -307,8 +331,7 @@ Scope {
description: "Closes session screen on press"
onPressed: {
GlobalStates.sessionOpen = false
GlobalStates.sessionOpen = false;
}
}
}
@@ -99,7 +99,7 @@ Item {
WPanelSeparator {}
FooterRectangle {
FooterMoreButton {
WTextButton {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
@@ -87,38 +87,16 @@ Item {
}
}
Column {
VerticalPageIndicator {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 6
spacing: 6
NavigationArrow {
down: false
}
Repeater {
model: root.pages
delegate: MouseArea {
id: pageIndicator
required property int index
hoverEnabled: true
onClicked: root.currentPage = index
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: 6
implicitHeight: 6
Circle {
anchors.centerIn: parent
diameter: (index === root.currentPage || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4
color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg
}
}
}
NavigationArrow {
down: true
}
currentIndex: root.currentPage
count: root.pages
onClicked: (index) => root.currentPage = index
onIncreasePage: root.increasePage();
onDecreasePage: root.decreasePage();
}
FocusedScrollMouseArea {
@@ -126,25 +104,7 @@ Item {
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: false
onScrollUp: decreasePage();
onScrollDown: increasePage();
}
component NavigationArrow: FluentIcon {
id: navArrow
required property bool down
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: 12
implicitWidth: 12 - (2 * upArea.containsPress)
icon: down ? "caret-down" : "caret-up"
color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg
filled: true
opacity: ((down && root.currentPage < root.pages - 1) || (!down && root.currentPage > 0)) ? 1 : 0
MouseArea {
id: upArea
anchors.fill: parent
hoverEnabled: true
onClicked: navArrow.down ? root.increasePage() : root.decreasePage();
}
onScrollUp: root.decreasePage();
onScrollDown: root.increasePage();
}
}
@@ -89,7 +89,7 @@ Item {
WPanelSeparator {}
FooterRectangle {
FooterMoreButton {
WTextButton {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
@@ -12,9 +12,9 @@ AppButton {
iconName: checked ? "system-search-checked" : "system-search"
separateLightDark: true
checked: GlobalStates.overviewOpen
checked: GlobalStates.searchOpen && LauncherSearch.query !== ""
onClicked: {
GlobalStates.overviewOpen = !GlobalStates.overviewOpen; // For now...
GlobalStates.searchOpen = !GlobalStates.searchOpen; // For now...
}
BarToolTip {
@@ -14,7 +14,7 @@ AppButton {
leftInset: Config.options.waffles.bar.leftAlignApps ? 12 : 0
iconName: down ? "start-here-pressed" : "start-here"
checked: GlobalStates.searchOpen
checked: GlobalStates.searchOpen && LauncherSearch.query === ""
onClicked: {
GlobalStates.searchOpen = !GlobalStates.searchOpen;
}
@@ -67,7 +67,7 @@ Button {
}
}
CloseButton {
WindowCloseButton {
id: closeButton
}
}
@@ -91,46 +91,14 @@ Button {
}
}
component CloseButton: Button {
id: reusableCloseButton
component WindowCloseButton: CloseButton {
visible: root.hovered
Layout.leftMargin: 4
implicitHeight: 30
implicitWidth: 30
radius: Looks.radius.large - root.padding
onClicked: {
root.toplevel.close();
}
Rectangle {
z: 0
color: "transparent"
anchors.fill: closeButtonBg
anchors.margins: -1
opacity: closeButtonBg.opacity
border.width: 1
radius: closeButtonBg.radius + 1
border.color: Looks.colors.bg2Border
}
background: Rectangle {
id: closeButtonBg
z: 1
opacity: reusableCloseButton.hovered ? 1 : 0
radius: Looks.radius.large - root.padding
color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger
Behavior on opacity {
animation: Looks.transition.opacity.createObject(this)
}
Behavior on color {
animation: Looks.transition.color.createObject(this)
}
}
contentItem: FluentIcon {
z: 2
anchors.centerIn: parent
icon: "dismiss"
implicitSize: 10
}
}
}
@@ -0,0 +1,344 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.common.panels.lock
import qs.modules.waffle.looks
import qs.modules.waffle.sessionScreen as SessionScreen
LockScreen {
id: root
property bool passwordView: false
lockSurface: Item {
id: lockSurfaceItem
Component.onCompleted: {
root.passwordView = false;
lockSurfaceItem.forceActiveFocus();
}
Keys.onPressed: {
interactables.switchToFocusedView();
}
Image {
id: bg
z: 0
anchors.fill: parent
sourceSize: Qt.size(lockSurfaceItem.width, lockSurfaceItem.height)
source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop
}
GaussianBlur {
z: 1
anchors.fill: parent
source: bg
radius: 100
samples: radius * 2 + 1
scale: root.passwordView ? 1.1 : 1
opacity: root.passwordView ? 1 : 0
Behavior on opacity {
animation: Looks.transition.opacity.createObject(this)
}
Behavior on scale {
NumberAnimation {
duration: 400
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
}
Interactables {
id: interactables
z: 2
anchors.fill: parent
}
}
component Interactables: Rectangle {
id: interactablesComponent
color: ColorUtils.transparentize("#000000", 0.8)
// Button {
// onClicked: {
// root.context.unlocked(LockContext.ActionEnum.Unlock);
// GlobalStates.screenLocked = false;
// }
// text: "woah it doesnt work let me out pls uwu colon three"
// }
function switchToFocusedView() {
root.passwordView = true;
}
Item {
id: unfocusedContent
anchors.fill: parent
visible: !root.passwordView
ClockTextGroup {
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: interactablesComponent.height * 0.1
}
}
RowLayout {
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: 21
rightMargin: 31
}
IconIndicator {
baseIcon: "wifi-1"
icon: WIcons.internetIcon
}
IconIndicator {
baseIcon: WIcons.batteryIcon
icon: WIcons.batteryLevelIcon
}
}
}
Item {
id: focusedContent
anchors.fill: parent
visible: root.passwordView
PasswordGroup {
visible: root.passwordView
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
}
RowLayout {
visible: root.passwordView
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: 21
rightMargin: 31
}
SessionScreen.PowerButton {
id: powerButton
}
}
}
}
component IconIndicator: Item {
id: iconIndicator
required property string baseIcon
required property string icon
default property alias data: iconWidget.data
implicitWidth: 40
implicitHeight: 40
FluentIcon {
id: iconWidget
anchors.centerIn: parent
icon: iconIndicator.baseIcon
color: Looks.darkColors.inactiveIcon
implicitSize: 20
FluentIcon {
anchors.fill: parent
icon: iconIndicator.icon
}
}
}
component ClockTextGroup: Column {
id: clockTextGroup
spacing: -3
WText {
anchors.horizontalCenter: parent.horizontalCenter
color: Looks.darkColors.fg
font.pixelSize: 133
font.weight: Looks.font.weight.strong
text: {
// Don't take am/pm
// Match groups of digits separated by non-digit chars (e.g., "12:34", "12.34", "12-34")
let match = DateTime.time.match(/(\d{1,2})\D+(\d{2})/);
return match ? `${match[1]}${DateTime.time.match(/\D+/)[0]}${match[2]}` : DateTime.time;
}
}
WText {
id: dateLabel
color: Looks.darkColors.fg
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 28
font.weight: Looks.font.weight.strong
text: DateTime.collapsedCalendarFormat
}
}
component PasswordGroup: ColumnLayout {
id: passwordGroup
spacing: 15
WUserAvatar {
Layout.alignment: Qt.AlignHCenter
sourceSize: Qt.size(192, 192)
}
WText {
Layout.alignment: Qt.AlignHCenter
text: SystemInfo.username
color: Looks.darkColors.fg
font.pixelSize: 26
font.weight: Looks.font.weight.strong
}
Rectangle {
id: passwordInputWrapper
Layout.topMargin: 10
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 132
color: "transparent"
implicitWidth: 296
implicitHeight: 36
border.width: 2
border.color: Looks.applyContentTransparency(Looks.darkColors.bg1Border)
radius: Looks.radius.medium
Rectangle {
id: passwordInputBackground
anchors.fill: parent
anchors.margins: 2
radius: Looks.radius.small + 1
color: passwordInput.focus ? Looks.applyBackgroundTransparency(Looks.darkColors.bg1Base) : Looks.applyContentTransparency(Looks.darkColors.bg1)
RowLayout {
anchors.fill: parent
anchors.margins: 6
spacing: 3
WTextInput {
id: passwordInput
Layout.fillHeight: true
Layout.fillWidth: true
verticalAlignment: TextInput.AlignVCenter
inputMethodHints: Qt.ImhSensitiveData
echoMode: passwordVisibilityButton.pressed ? TextInput.Normal : TextInput.Password
color: Looks.darkColors.fg
font.pixelSize: 12
WText {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
visible: passwordInput.text.length === 0
text: Translation.tr("Password")
font.pixelSize: Looks.font.pixelSize.large
color: Looks.darkColors.fg
opacity: 0.8
}
onTextChanged: root.context.currentText = this.text
onAccepted: {
root.context.tryUnlock();
}
Connections {
target: root.context
function onCurrentTextChanged() {
passwordInput.text = root.context.currentText;
}
}
Connections {
target: root
function onPasswordViewChanged() {
passwordInput.forceActiveFocus();
}
}
Keys.onPressed: event => {
root.context.resetClearTimer();
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
}
}
PasswordBoxButton {
id: passwordVisibilityButton
property bool passwordVisible: false
visible: passwordInput.text.length > 0
onPressed: passwordVisible = true
onReleased: passwordVisible = false
icon.name: passwordVisible ? "eye-off" : "eye"
}
PasswordBoxButton {
onClicked: {
root.context.tryUnlock();
}
icon.name: "arrow-right"
}
}
}
Rectangle {
id: activeIndicatorLine
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
implicitHeight: 2
color: passwordInput.focus ? Looks.colors.accent : Looks.applyContentTransparency(Looks.darkColors.bg2Border)
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: passwordInputWrapper.width
height: passwordInputWrapper.height
radius: passwordInputWrapper.radius
}
}
}
Item {}
}
component PasswordBoxButton: WButton {
id: pwBoxBtn
implicitWidth: 28
implicitHeight: 22
property color colBackground: ColorUtils.transparentize(Looks.darkColors.bg1)
property color colBackgroundHover: ColorUtils.transparentize(Looks.darkColors.bg2Hover)
property color colBackgroundActive: ColorUtils.transparentize(Looks.darkColors.bg2Active)
fgColor: checked ? Looks.colors.accentFg : Looks.darkColors.fg
checked: hovered
contentItem: Item {
FluentIcon {
color: pwBoxBtn.fgColor
anchors.centerIn: parent
icon: pwBoxBtn.icon.name
implicitSize: 16
}
}
}
}
@@ -0,0 +1,48 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
import qs.modules.waffle.bar
import Quickshell
Button {
id: reusableCloseButton
implicitHeight: 30
implicitWidth: 30
property alias radius: closeButtonBg.radius
Rectangle {
z: 0
color: "transparent"
anchors.fill: closeButtonBg
anchors.margins: -1
opacity: closeButtonBg.opacity
border.width: 1
radius: closeButtonBg.radius + 1
border.color: Looks.colors.bg2Border
}
background: Rectangle {
id: closeButtonBg
z: 1
opacity: reusableCloseButton.hovered ? 1 : 0
color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger
Behavior on opacity {
animation: Looks.transition.opacity.createObject(this)
}
Behavior on color {
animation: Looks.transition.color.createObject(this)
}
}
contentItem: FluentIcon {
z: 2
anchors.centerIn: parent
icon: "dismiss"
implicitSize: 10
}
}
@@ -96,12 +96,12 @@ Singleton {
property color bg0Opaque: root.dark ? root.darkColors.bg0 : root.lightColors.bg0
property color bg0: ColorUtils.transparentize(bg0Opaque, root.backgroundTransparency)
property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency)
property color bg1Base: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base, root.backgroundTransparency)
property color bg1Base: root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base
property color bg1: ColorUtils.transparentize(root.dark ? root.darkColors.bg1 : root.lightColors.bg1, root.contentTransparency)
property color bg1Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, root.contentTransparency)
property color bg1Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, root.contentTransparency)
property color bg1Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Border : root.lightColors.bg1Border, root.contentTransparency)
property color bg2Base: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Base : root.lightColors.bg2Base, root.backgroundTransparency)
property color bg2Base: root.dark ? root.darkColors.bg2Base : root.lightColors.bg2Base
property color bg2: ColorUtils.transparentize(root.dark ? root.darkColors.bg2 : root.lightColors.bg2, root.contentTransparency)
property color bg2Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Hover : root.lightColors.bg2Hover, root.contentTransparency)
property color bg2Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Active : root.lightColors.bg2Active, root.contentTransparency)
@@ -146,7 +146,8 @@ Singleton {
property int thin: Font.Normal
property int regular: Font.Medium
property int strong: Font.DemiBold
property int stronger: Font.Bold
property int stronger: (Font.DemiBold + 2*Font.Bold) / 3
property int strongest: Font.Bold
}
property QtObject pixelSize: QtObject {
property real normal: 11
@@ -154,6 +155,11 @@ Singleton {
property real larger: 15
property real xlarger: 17
}
property QtObject variableAxes: QtObject {
property var ui: ({
"wdth": 25
})
}
}
transition: QtObject {
@@ -0,0 +1,73 @@
pragma ComponentBehavior: Bound
import Qt.labs.synchronizer
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.waffle.looks
Column {
id: root
property bool showArrows: true
property int currentIndex: 0
property int count: 1
signal clicked(int index)
signal increasePage()
signal decreasePage()
visible: count > 1
spacing: 6
NavigationArrow {
visible: root.showArrows
down: false
}
Repeater {
model: root.count
delegate: MouseArea {
id: pageIndicator
required property int index
hoverEnabled: true
onClicked: root.clicked(index);
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: 6
implicitHeight: 6
Circle {
anchors.centerIn: parent
diameter: (index === root.currentIndex || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4
color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg
}
}
}
NavigationArrow {
visible: root.showArrows
down: true
}
component NavigationArrow: FluentIcon {
id: navArrow
required property bool down
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: 12
implicitWidth: 12 - (2 * upArea.containsPress)
icon: down ? "caret-down" : "caret-up"
color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg
filled: true
opacity: ((down && root.currentIndex < root.count - 1) || (!down && root.currentIndex > 0)) ? 1 : 0
MouseArea {
id: upArea
anchors.fill: parent
hoverEnabled: true
onClicked: navArrow.down ? root.increasePage() : root.decreasePage();
}
}
}
@@ -17,4 +17,6 @@ Kirigami.Icon {
roundToIconSize: false
fallback: root.iconName
source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback
color: Looks.colors.fg
}
@@ -14,6 +14,9 @@ Menu {
property bool downDirection: false
property bool hasIcons: false // TODO: implement
property color color: Looks.colors.bg1Base
property alias backgroundPane: bgPane
implicitWidth: background.implicitWidth + margins * 2
implicitHeight: background.implicitHeight + margins * 2
margins: 10
@@ -58,7 +61,7 @@ Menu {
bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin
}
contentItem: Rectangle {
color: Looks.colors.bg1Base
color: root.color
implicitWidth: menuListView.implicitWidth + root.padding * 2
implicitHeight: root.contentItem.implicitHeight + root.padding * 2
}
@@ -66,6 +69,10 @@ Menu {
}
}
Component.onCompleted: {
menuListView.itemAtIndex(0)?.forceActiveFocus();
}
contentItem: Item {
implicitWidth: menuListView.implicitWidth
implicitHeight: menuListView.implicitHeight
@@ -91,6 +98,5 @@ Menu {
delegate: WMenuItem {
id: menuItemDelegate
width: ListView.view?.width
}
}
@@ -55,6 +55,9 @@ MenuItem {
rightInset: inset
horizontalPadding: 11
width: ListView.view?.width
height: visible ? implicitHeight : 0
background: Rectangle {
id: backgroundRect
radius: Looks.radius.medium
@@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Effects
import qs.modules.common
import qs.modules.common.widgets
Item {
default property Item contentItem
property Item shadow: WRectangularShadow {
target: contentItem
}
implicitWidth: contentItem.implicitWidth
implicitHeight: contentItem.implicitHeight
children: [shadow, contentItem]
}
@@ -8,10 +8,11 @@ Text {
color: Looks.colors.fg
font {
hintingPreference: Font.PreferFullHinting
hintingPreference: Font.PreferDefaultHinting
family: Looks.font.family.ui
pixelSize: Looks.font.pixelSize.normal
weight: Looks.font.weight.regular
variableAxes: Looks.font.variableAxes.ui
}
linkColor: Looks.colors.link
@@ -0,0 +1,31 @@
import qs.modules.common
import QtQuick
import QtQuick.Controls.FluentWinUI3
import QtQuick.Controls
TextField {
id: root
clip: true
renderType: Text.NativeRendering
verticalAlignment: Text.AlignVCenter
color: Looks.colors.fg
palette {
active: Looks.colors.accent
}
font {
hintingPreference: Font.PreferDefaultHinting
family: Looks.font.family.ui
pixelSize: Looks.font.pixelSize.normal
weight: Looks.font.weight.regular
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: true
cursorShape: Qt.IBeamCursor
}
}
@@ -0,0 +1,208 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
Rectangle {
id: root
color: "#000000"
readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true
Keys.onPressed: event => { // Esc to close
if (event.key === Qt.Key_Escape) {
PolkitService.cancel();
}
}
StyledImage {
anchors.fill: parent
source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop
Rectangle {
anchors.fill: parent
color: ColorUtils.transparentize("#000000", 0.31)
PolkitDialog {
id: dialog
DragHandler {
target: null
property real startX: dialog.x
property real startY: dialog.y
onActiveChanged: {
if (!active) return;
startX = dialog.x;
startY = dialog.y;
}
xAxis.onActiveValueChanged: {
dialog.x = Math.round(startX + xAxis.activeValue);
}
yAxis.onActiveValueChanged: {
dialog.y = Math.round(startY + yAxis.activeValue);
}
}
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
}
}
}
component PolkitDialog: WPane {
borderColor: Looks.colors.ambientShadow
contentItem: WPanelPageColumn {
PolkitDialogHeader {
Layout.fillWidth: true
}
BodyRectangle {
id: dialogBody
implicitHeight: bodyContent.implicitHeight + 48
implicitWidth: 434
color: Looks.colors.bg1Base
ColumnLayout {
id: bodyContent
anchors.fill: parent
anchors.margins: 24
spacing: 20
RowLayout {
Layout.fillWidth: true
spacing: 15
WAppIcon {
iconName: PolkitService.flow?.iconName ?? "window-shield"
fallback: PolkitService.flow?.iconName == "" ? `${Looks.iconsPath}/window-shield` : PolkitService.flow.iconName
isMask: PolkitService.flow?.iconName === ""
tryCustomIcon: false
}
WText {
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
font.pixelSize: Looks.font.pixelSize.larger
font.weight: Looks.font.weight.strongest
text: {
const iconName = PolkitService.flow?.iconName ?? "";
if (iconName === "")
return Translation.tr("Command-line-invoked Action");
const desktopEntry = DesktopEntries.applications.values.find(entry => {
return entry.icon == iconName;
});
return desktopEntry ? desktopEntry.name : Translation.tr("Unknown Application");
}
}
}
WText {
Layout.fillWidth: true
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignLeft
text: PolkitService.cleanMessage
}
WTextField {
id: inputField
Layout.fillWidth: true
focus: true
enabled: PolkitService.interactionAvailable
placeholderText: PolkitService.cleanPrompt
echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal
onAccepted: PolkitService.submit(inputField.text)
Keys.onPressed: event => { // Esc to close
if (event.key === Qt.Key_Escape) {
PolkitService.cancel();
}
}
Component.onCompleted: forceActiveFocus()
Connections {
target: PolkitService
function onInteractionAvailableChanged() {
if (!PolkitService.interactionAvailable)
return;
inputField.text = "";
inputField.forceActiveFocus();
}
}
}
}
}
BodyRectangle {
implicitHeight: 80
color: Looks.colors.bgPanelFooterBase
RowLayout {
anchors.fill: parent
anchors.margins: 24
spacing: 8
uniformCellSizes: true
WButton {
Layout.fillWidth: true
implicitHeight: 32
colBackground: Looks.colors.bg1
horizontalAlignment: Text.AlignHCenter
text: Translation.tr("Yes")
onClicked: PolkitService.submit(inputField.text)
}
WButton {
Layout.fillWidth: true
implicitHeight: 32
horizontalAlignment: Text.AlignHCenter
checked: true
text: Translation.tr("No")
onClicked: PolkitService.cancel()
}
}
}
}
}
component PolkitDialogHeader: BodyRectangle {
implicitHeight: headerContent.implicitHeight
color: Looks.colors.bg2Base
CloseButton {
anchors {
top: parent.top
right: parent.right
}
radius: 0
implicitWidth: 32
implicitHeight: 32
onClicked: {
PolkitService.cancel();
}
}
ColumnLayout {
id: headerContent
anchors.fill: parent
anchors.leftMargin: 24
anchors.rightMargin: 24
spacing: 18
WText {
Layout.topMargin: 20
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
text: Translation.tr("Polkit")
}
WText {
Layout.fillWidth: true
Layout.bottomMargin: 12
horizontalAlignment: Text.AlignLeft
wrapMode: Text.Wrap
text: Translation.tr("Do you want to allow this app to make changes to your device?")
font.pixelSize: Looks.font.pixelSize.xlarger
font.weight: Looks.font.weight.strongest
}
}
}
}
@@ -0,0 +1,15 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import Quickshell
import Quickshell.Wayland
FullscreenPolkitWindow {
id: root
contentComponent: Component {
WPolkitContent {}
}
}
@@ -0,0 +1,76 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.waffle.looks
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
WSessionScreenTextButton {
id: root
implicitWidth: 40
implicitHeight: 40
focusRingRadius: Looks.radius.large
colBackground: ColorUtils.transparentize(Looks.darkColors.bg2)
colBackgroundHover: Looks.applyContentTransparency(Looks.darkColors.bg2Hover)
colBackgroundActive: Looks.applyContentTransparency(Looks.darkColors.bg2Active)
property color color: {
if (root.down) {
return root.colBackgroundActive;
} else if (root.hovered) {
return root.colBackgroundHover;
} else {
return root.colBackground;
}
}
background: Rectangle {
id: background
radius: Looks.radius.medium
color: root.color
}
contentItem: Item {
FluentIcon {
anchors.centerIn: parent
implicitSize: 20
icon: "power"
color: root.fgColor
}
}
onClicked: {
powerMenu.visible = !powerMenu.visible;
}
WMenu {
id: powerMenu
x: -powerMenu.implicitWidth / 2 + root.implicitWidth / 2
y: -powerMenu.implicitHeight
color: Looks.darkColors.bg1Base
Component.onCompleted: {
powerMenu.backgroundPane.borderColor = Looks.applyContentTransparency(Looks.darkColors.bg2Border);
}
delegate: WMenuItem {
id: menuItemDelegate
colBackground: ColorUtils.transparentize(Looks.darkColors.bg1Base)
colBackgroundHover: Looks.applyContentTransparency(Looks.darkColors.bg2Hover)
colBackgroundActive: Looks.applyContentTransparency(Looks.darkColors.bg2Active)
colForeground: Looks.darkColors.fg
}
Action {
icon.name: "power"
text: Translation.tr("Shut down")
onTriggered: Session.poweroff()
}
Action {
icon.name: "arrow-counterclockwise"
text: Translation.tr("Restart")
onTriggered: Session.reboot()
}
}
}
@@ -0,0 +1,139 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.waffle.looks
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Item {
id: root
Component.onCompleted: {
lockButton.forceActiveFocus();
}
ColumnLayout {
anchors.centerIn: parent
spacing: 4
WSessionScreenTextButton {
id: lockButton
focus: true
text: Translation.tr("Lock")
onClicked: {
GlobalStates.sessionOpen = false;
Session.lock();
}
KeyNavigation.up: powerButton
KeyNavigation.down: signOutButton
}
WSessionScreenTextButton {
id: signOutButton
focus: true
text: Translation.tr("Sign out")
onClicked: {
GlobalStates.sessionOpen = false;
Session.logout();
}
KeyNavigation.up: lockButton
KeyNavigation.down: changePasswordButton
}
WSessionScreenTextButton {
id: changePasswordButton
focus: true
text: Translation.tr("Change password")
onClicked: {
GlobalStates.sessionOpen = false;
Session.changePassword();
}
KeyNavigation.up: signOutButton
KeyNavigation.down: taskManagerButton
}
WSessionScreenTextButton {
id: taskManagerButton
focus: true
text: Translation.tr("Task Manager")
onClicked: {
GlobalStates.sessionOpen = false;
Session.launchTaskManager();
}
KeyNavigation.up: signOutButton
KeyNavigation.down: cancelButton
}
CancelButton {
id: cancelButton
Layout.fillWidth: true
Layout.leftMargin: 5
Layout.rightMargin: 5
Layout.topMargin: 38
onClicked: GlobalStates.sessionOpen = false
KeyNavigation.up: taskManagerButton
KeyNavigation.down: powerButton
}
}
RowLayout {
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: 21
rightMargin: 31
}
PowerButton {
id: powerButton
KeyNavigation.up: cancelButton
KeyNavigation.down: lockButton
}
}
component CancelButton: WBorderlessButton {
id: root
implicitHeight: 32
colBackground: Looks.darkColors.bg1Base
colBackgroundHover: Qt.lighter(Looks.darkColors.bg1Base, 1.2)
colBackgroundActive: Qt.lighter(Looks.darkColors.bg1Base, 1.1)
colForeground: Looks.darkColors.fg
property bool keyboardDown: false
Keys.onPressed: event => {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
keyboardDown = true;
event.accepted = true;
}
}
Keys.onReleased: event => {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
keyboardDown = false;
root.clicked();
event.accepted = true;
}
}
contentItem: WText {
text: Translation.tr("Cancel")
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Looks.font.pixelSize.large
color: root.colForeground
}
Rectangle {
visible: cancelButton.focus
anchors {
fill: parent
margins: -3
}
radius: cancelButton.background.radius + 4
color: "transparent"
border.width: 2
border.color: "#ffffff"
}
}
}
@@ -0,0 +1,55 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs
import qs.modules.waffle.looks
WTextButton {
id: root
implicitWidth: 135
implicitHeight: 40
horizontalPadding: 5
property bool keyboardDown: false
property alias focusRingRadius: focusRing.radius
fgColor: (root.pressed || root.keyboardDown) ? Looks.darkColors.fg1 : Looks.darkColors.fg
Keys.onPressed: event => {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
keyboardDown = true;
event.accepted = true;
}
}
Keys.onReleased: event => {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
keyboardDown = false;
root.clicked();
event.accepted = true;
}
}
contentItem: Item {
id: contentItem
implicitWidth: buttonText.implicitWidth
WText {
id: buttonText
anchors.fill: parent
color: root.fgColor
text: root.text
font.pixelSize: Looks.font.pixelSize.large
}
}
Rectangle {
id: focusRing
visible: root.focus
anchors {
fill: parent
margins: -4
}
color: "transparent"
border.width: 2
border.color: "#ffffff"
}
}
@@ -0,0 +1,116 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: root
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
Loader {
id: sessionLoader
active: GlobalStates.sessionOpen
onActiveChanged: {
if (sessionLoader.active) SessionWarnings.refresh();
}
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (GlobalStates.screenLocked) {
GlobalStates.sessionOpen = false;
}
}
}
sourceComponent: PanelWindow { // Session menu
id: sessionRoot
visible: sessionLoader.active
property string subtitle
function hide() {
GlobalStates.sessionOpen = false;
}
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "quickshell:session"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
// This is a big surface so we needa carefully choose the transparency,
// or we'll get a large scary rgb blob
color: "#000000"
anchors {
top: true
left: true
right: true
bottom: true
}
Item {
anchors.fill: parent
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
sessionRoot.hide();
}
}
SessionScreenContent {
anchors.fill: parent
}
}
}
}
IpcHandler {
target: "session"
function toggle(): void {
GlobalStates.sessionOpen = !GlobalStates.sessionOpen;
}
function close(): void {
GlobalStates.sessionOpen = false
}
function open(): void {
GlobalStates.sessionOpen = true
}
}
GlobalShortcut {
name: "sessionToggle"
description: "Toggles session screen on press"
onPressed: {
GlobalStates.sessionOpen = !GlobalStates.sessionOpen;
}
}
GlobalShortcut {
name: "sessionOpen"
description: "Opens session screen on press"
onPressed: {
GlobalStates.sessionOpen = true
}
}
GlobalShortcut {
name: "sessionClose"
description: "Closes session screen on press"
onPressed: {
GlobalStates.sessionOpen = false
}
}
}
@@ -9,6 +9,8 @@ import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
import qs.modules.waffle.startMenu.startPage
import qs.modules.waffle.startMenu.searchPage
WBarAttachedPanelContent {
id: root
@@ -99,7 +101,7 @@ WBarAttachedPanelContent {
}
}
Item {
implicitHeight: root.searching ? 736 : 736 // TODO: Make sizes naturally inferred
implicitHeight: root.searching ? 800 : 800 // TODO: Make sizes naturally inferred
Layout.fillWidth: true
Loader {
id: pageContentLoader
@@ -1,227 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
WPanelPageColumn {
id: root
WPanelSeparator {}
BodyRectangle {
Layout.fillHeight: true
}
WPanelSeparator {}
StartFooter {
Layout.fillWidth: true
}
component StartFooter: FooterRectangle {
implicitHeight: 63
UserButton {
anchors {
left: parent.left
leftMargin: 52
bottom: parent.bottom
bottomMargin: 12
}
}
PowerButton {
anchors {
right: parent.right
rightMargin: 52
bottom: parent.bottom
bottomMargin: 12
}
}
}
component UserButton: WBorderlessButton {
id: userButton
implicitWidth: userButtonRow.implicitWidth + 12 * 2
implicitHeight: 40
contentItem: Item {
RowLayout {
id: userButtonRow
anchors.centerIn: parent
spacing: 12
WUserAvatar {
sourceSize: Qt.size(32, 32)
}
WText {
Layout.alignment: Qt.AlignVCenter
text: SystemInfo.username
}
}
}
onClicked: {
userMenu.open();
}
WToolTip {
text: SystemInfo.username
}
Popup {
id: userMenu
x: -51
y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10
background: null
WToolTipContent {
id: popupContent
horizontalPadding: 10
verticalPadding: 7
radius: Looks.radius.large
realContentItem: Item {
implicitWidth: userMenuContentLayout.implicitWidth
implicitHeight: userMenuContentLayout.implicitHeight
ColumnLayout {
id: userMenuContentLayout
anchors {
fill: parent
leftMargin: popupContent.horizontalPadding
rightMargin: popupContent.horizontalPadding
topMargin: popupContent.verticalPadding
bottomMargin: popupContent.verticalPadding
}
spacing: 5
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 6
FluentIcon {
Layout.alignment: Qt.AlignVCenter
implicitSize: 22
icon: "corporation"
monochrome: false
}
WText {
Layout.alignment: Qt.AlignVCenter
text: "Megahard"
font.pixelSize: Looks.font.pixelSize.large
font.weight: Looks.font.weight.strong
}
Item { Layout.fillWidth: true }
WBorderlessButton {
Layout.alignment: Qt.AlignVCenter
implicitHeight: 36
implicitWidth: textItem.implicitWidth + 10 * 2
contentItem: WText {
id: textItem
text: Translation.tr("Sign out")
font.pixelSize: Looks.font.pixelSize.large
}
onClicked: Session.logout()
}
}
Item { // Force min width 360 (using min on the item somehow doesn't work)
implicitWidth: 334
}
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: 7
Layout.leftMargin: 6
spacing: 12
WUserAvatar {
sourceSize: Qt.size(58, 58)
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 2
WText {
text: SystemInfo.username
font.pixelSize: Looks.font.pixelSize.larger
font.weight: Looks.font.weight.strong
}
WText {
color: Looks.colors.fg1
text: Translation.tr("Local account")
}
WText {
color: Looks.colors.accent
text: Translation.tr("Manage my account")
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser])
GlobalStates.searchOpen = false;
}
}
}
}
}
}
}
}
}
}
component PowerButton: WBorderlessButton {
id: powerButton
implicitWidth: 40
implicitHeight: 40
contentItem: Item {
FluentIcon {
anchors.centerIn: parent
icon: "power"
implicitSize: 20
}
}
WToolTip {
extraVisibleCondition: !powerMenu.visible
text: qsTr("Power")
}
onClicked: {
powerMenu.open()
}
WMenu {
id: powerMenu
x: -powerMenu.implicitWidth / 2 + powerButton.implicitWidth / 2
y: -powerMenu.implicitHeight - 4
Action {
icon.name: "lock-closed"
text: Translation.tr("Lock")
onTriggered: Session.lock()
}
Action {
icon.name: "weather-moon"
text: Translation.tr("Sleep")
onTriggered: Session.suspend()
}
Action {
icon.name: "power"
text: Translation.tr("Shut down")
onTriggered: Session.poweroff()
}
Action {
icon.name: "arrow-counterclockwise"
text: Translation.tr("Restart")
onTriggered: Session.reboot()
}
}
}
}
@@ -70,6 +70,19 @@ Scope {
}
}
function toggleClipboard() {
if (LauncherSearch.query.startsWith(Config.options.search.prefix.clipboard) || !GlobalStates.searchOpen) {
GlobalStates.searchOpen = !GlobalStates.searchOpen;
}
LauncherSearch.ensurePrefix(Config.options.search.prefix.clipboard);
}
function toggleEmojis() {
if (LauncherSearch.query.startsWith(Config.options.search.prefix.emojis) || !GlobalStates.searchOpen) {
GlobalStates.searchOpen = !GlobalStates.searchOpen;
}
LauncherSearch.ensurePrefix(Config.options.search.prefix.emojis);
}
IpcHandler {
target: "search"
@@ -119,4 +132,22 @@ Scope {
GlobalStates.superReleaseMightTrigger = false;
}
}
GlobalShortcut {
name: "overviewClipboardToggle"
description: "Toggle clipboard query on overview widget"
onPressed: {
root.toggleClipboard();
}
}
GlobalShortcut {
name: "overviewEmojiToggle"
description: "Toggle emoji query on overview widget"
onPressed: {
root.toggleEmojis();
}
}
}
@@ -5,6 +5,7 @@ import qs.modules.common
import qs.modules.waffle.looks
import qs.modules.common.functions
import qs.modules.common.models
import qs.modules.waffle.startMenu
import Quickshell
import QtQuick.Layouts
import QtQuick.Controls
@@ -119,7 +120,7 @@ RowLayout {
onModelChanged: {
root.focusFirstItem();
}
delegate: WSearchResultButton {
delegate: SearchResultButton {
required property int index
required property var modelData
entry: modelData
@@ -189,6 +190,7 @@ RowLayout {
const isAppEntry = resultPreview.entry.type === Translation.tr("App");
const appId = isAppEntry ? resultPreview.entry.id : "";
const pinned = isAppEntry ? (Config.options.dock.pinnedApps.includes(appId)) : false;
const startPinned = isAppEntry ? (Config.options.launcher.pinnedApps.includes(appId)) : false;
var result = [
searchResultComp.createObject(null, {
name: resultPreview.entry.verb,
@@ -198,6 +200,16 @@ RowLayout {
resultPreview.entry.execute();
}
}),
...(isAppEntry ? [
searchResultComp.createObject(null, {
name: startPinned ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start"),
iconName: startPinned ? "keep_off" : "keep",
iconType: LauncherSearchResult.IconType.Material,
execute: () => {
LauncherApps.togglePin(appId);
}
})
] : []),
...(isAppEntry ? [
searchResultComp.createObject(null, {
name: pinned ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar"),
@@ -207,7 +219,7 @@ RowLayout {
TaskbarApps.togglePin(appId);
}
})
] : [])
] : []),
];
result = result.concat(resultPreview.entry.actions);
return result;
@@ -8,6 +8,7 @@ import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
import qs.modules.waffle.startMenu
RowLayout {
id: root
@@ -65,8 +66,8 @@ RowLayout {
WMenu {
id: accountsMenu
x: -accountsMenu.implicitWidth + optionsButton.implicitWidth
y: optionsButton.height + 10
x: -accountsMenu.implicitWidth + optionsButton.implicitWidth + 10
y: optionsButton.height
downDirection: true
Action {
icon.name: "people-settings"
@@ -0,0 +1,7 @@
import QtQuick
import qs.services
QtObject {
property string name
property list<string> categories
}
@@ -0,0 +1,84 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
GridLayout {
id: root
columns: 4
Component {
id: aggAppCatComp
AggregatedAppCategoryModel {}
}
property list<AggregatedAppCategoryModel> aggregatedCategories: [
aggAppCatComp.createObject(null, {
name: Translation.tr("Productivity"),
categories: ["Development", "Education", "Network", "Office"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Utilities & Tools"),
categories: ["Utility", "Science"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Creativity"),
categories: ["AudioVideo", "Graphics"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Other"),
categories: ["Game"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("System"),
categories: ["Settings", "System"]
})
]
Repeater {
model: root.aggregatedCategories
delegate: AppCategory {
required property var modelData
aggregatedCategory: modelData
}
}
columnSpacing: 27
rowSpacing: 12
component AppCategory: Item {
id: categoryItem
property AggregatedAppCategoryModel aggregatedCategory
implicitWidth: categoryLayout.implicitWidth
implicitHeight: categoryLayout.implicitHeight
ColumnLayout {
id: categoryLayout
anchors.fill: parent
spacing: 4
AppCategoryGrid {
id: categoryGrid
Layout.fillWidth: true
aggregatedCategory: categoryItem.aggregatedCategory
}
WButton {
id: categoryButton
Layout.fillWidth: true
implicitHeight: 32
contentItem: WText {
id: categoryButtonText
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
text: categoryItem.aggregatedCategory.name
}
onClicked: {
categoryGrid.openCategoryFolder();
}
}
}
}
}
@@ -0,0 +1,341 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
Rectangle {
id: root
property AggregatedAppCategoryModel aggregatedCategory
property list<DesktopEntry> desktopEntries: [...DesktopEntries.applications.values.filter(app => {
const appCategories = app.categories;
const gridCategories = root.aggregatedCategory.categories;
return appCategories.some(cat => gridCategories.indexOf(cat) !== -1);
})].sort((a, b) => a.name.localeCompare(b.name));
property Item windowRootItem: {
var item = root;
// print("FINDING ROOT")
while (item.parent != null) {
if (item.parent.toString().includes("ProxyWindow"))
break;
item = item.parent;
}
// print(item.width, item.height)
return item;
}
function openCategoryFolder() {
categoryFolderPopup.open();
}
radius: Looks.radius.large
color: Looks.colors.bg1
border.width: 1
border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, 0.7)
implicitWidth: 156
implicitHeight: 156
GridLayout {
id: categoryAppsGrid
anchors.fill: parent
anchors.margins: 10
columns: 2
rows: 2
columnSpacing: 0
rowSpacing: 0
uniformCellHeights: true
uniformCellWidths: true
Repeater {
model: ScriptModel {
values: root.desktopEntries.slice(0, 3)
}
delegate: SmallGridAppButton {
required property DesktopEntry modelData
desktopEntry: modelData
}
}
Loader {
id: categoryOpenButtonLoader
// It's like this on the real thing - you get an invisible button if there's not enough items
opacity: root.desktopEntries.length > 3 ? 1 : 0
active: true
sourceComponent: CategoryOpenButton {
aggregatedCategory: root.aggregatedCategory
}
}
}
Popup {
id: categoryFolderPopup
// I don't even know what the fuck is going on at this point
// I hate point mapping
property point originPoint: categoryOpenButtonLoader.mapToItem(root, categoryOpenButtonLoader.width / 2, categoryOpenButtonLoader.height / 2)
property point windowCenterPoint: {
const rootContentItem = root.windowRootItem;
const canvasPosInRoot = root.mapFromItem(rootContentItem, rootContentItem.width / 2, rootContentItem.height / 2);
const sectionItem = root.parent.parent.parent;
const positionInSection = sectionItem.mapFromItem(categoryOpenButtonLoader, categoryOpenButtonLoader.x, categoryOpenButtonLoader.y);
const targetY = Math.max(-positionInSection.y + 212, canvasPosInRoot.y);
return Qt.point(canvasPosInRoot.x, targetY);
}
enter: Transition {
NumberAnimation {
target: categoryFolderPopup
property: "x"
from: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 5 / 2
to: categoryFolderPopup.windowCenterPoint.x - categoryFolderPopup.width / 2
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
NumberAnimation {
target: categoryFolderPopup
property: "y"
from: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height * 3 / 2
to: categoryFolderPopup.windowCenterPoint.y - categoryFolderPopup.height / 2
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
NumberAnimation {
target: categoryFolderPopup
property: "scale"
from: 0
to: 1
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
exit: Transition {
NumberAnimation {
target: categoryFolderPopup
property: "x"
to: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 5 / 2
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut
}
NumberAnimation {
target: categoryFolderPopup
property: "y"
to: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height * 3 / 2
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut
}
NumberAnimation {
target: categoryFolderPopup
property: "scale"
from: 1
to: 0
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut
}
}
background: null
Loader {
id: folderContentLoader
active: categoryFolderPopup.visible
sourceComponent: WRectangularShadowThis {
CategoryFolderContent {
title: root.aggregatedCategory.name
desktopEntries: root.desktopEntries
}
}
}
}
component CategoryFolderContent: WToolTipContent {
id: categoryFolderContent
property string title
property list<DesktopEntry> desktopEntries: root.desktopEntries
horizontalPadding: 0
verticalPadding: 0
radius: Looks.radius.large
realContentItem: Item {
implicitWidth: 448
implicitHeight: 376
ColumnLayout {
anchors {
fill: parent
leftMargin: 32
rightMargin: 32
topMargin: 40
bottomMargin: 32
}
spacing: 28
WText {
Layout.fillWidth: true
text: categoryFolderContent.title
font.pixelSize: Looks.font.pixelSize.xlarger
font.weight: Looks.font.weight.stronger
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
SwipeView {
id: categoryFolderSwipeView
anchors.fill: parent
orientation: Qt.Vertical
clip: true
Repeater {
model: Math.ceil(root.desktopEntries.length / 12)
delegate: Item {
id: folderPage
required property int index
width: SwipeView.view.width
height: SwipeView.view.height
BigAppGrid {
anchors {
top: parent.top
left: parent.left
}
columns: 4
rows: 3
desktopEntries: root.desktopEntries.slice(folderPage.index * 12, (folderPage.index + 1) * 12)
}
}
}
}
VerticalPageIndicator {
anchors.verticalCenter: parent.verticalCenter
anchors.right: categoryFolderSwipeView.right
anchors.rightMargin: -19
showArrows: false
currentIndex: categoryFolderSwipeView.currentIndex
count: Math.ceil(root.desktopEntries.length / 12)
onClicked: index => categoryFolderSwipeView.currentIndex = index
}
}
}
FocusedScrollMouseArea {
z: 999
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: false
onScrollUp: categoryFolderSwipeView.decrementCurrentIndex()
onScrollDown: categoryFolderSwipeView.incrementCurrentIndex()
}
}
}
component CategoryOpenButton: SmallGridButton {
id: categoryOpenButton
property AggregatedAppCategoryModel aggregatedCategory
onClicked: root.openCategoryFolder()
contentItem: Item {
Behavior on scale {
NumberAnimation {
id: scaleAnim
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
GridLayout {
anchors.centerIn: parent
rows: 2
columns: 2
rowSpacing: 2
columnSpacing: 2
Repeater {
model: root.desktopEntries.slice(3, 7)
delegate: WAppIcon {
required property DesktopEntry modelData
tryCustomIcon: false
iconName: modelData.icon
implicitSize: 16
}
}
}
}
}
component SmallGridAppButton: SmallGridButton {
id: smallGridAppButton
property DesktopEntry desktopEntry
property bool pinnedStart: LauncherApps.isPinned(smallGridAppButton.desktopEntry.id);
property bool pinnedTaskbar: TaskbarApps.isPinned(smallGridAppButton.desktopEntry.id);
onClicked: {
GlobalStates.searchOpen = false;
desktopEntry.execute();
}
contentItem: Item {
Behavior on scale {
NumberAnimation {
id: scaleAnim
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
WAppIcon {
anchors.centerIn: parent
tryCustomIcon: false
iconName: smallGridAppButton.desktopEntry.icon
implicitSize: 34
}
}
WToolTip {
text: smallGridAppButton.desktopEntry.name
}
altAction: () => {
appMenu.popup();
}
WMenu {
id: appMenu
downDirection: true
WMenuItem {
icon.name: smallGridAppButton.pinnedStart ? "pin-off" : "pin"
text: smallGridAppButton.pinnedStart ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start")
onTriggered: {
LauncherApps.togglePin(smallGridAppButton.desktopEntry.id);
}
}
WMenuItem {
icon.name: smallGridAppButton.pinnedTaskbar ? "pin-off" : "pin"
text: smallGridAppButton.pinnedTaskbar ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar")
onTriggered: {
TaskbarApps.togglePin(smallGridAppButton.desktopEntry.id);
}
}
}
}
component SmallGridButton: WButton {
id: root
implicitWidth: 68
implicitHeight: 68
property real pressedScale: 5 / 6
onDownChanged: {
contentItem.scale = root.down ? root.pressedScale : 1; // If/When we do dragging, the scale is 1.25
}
}
}
@@ -0,0 +1,37 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
GridLayout {
id: root
property list<var> desktopEntries: []
columnSpacing: 0
rowSpacing: 0
uniformCellHeights: true
uniformCellWidths: true
Repeater {
model: root.desktopEntries
delegate: StartAppButton {
id: pinnedAppButton
required property var modelData
desktopEntry: modelData
onClicked: {
GlobalStates.searchOpen = false;
desktopEntry.execute();
}
}
}
}
@@ -0,0 +1,98 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
WButton {
id: root
required property DesktopEntry desktopEntry
property bool pinnedStart: LauncherApps.isPinned(root.desktopEntry.id);
property bool pinnedTaskbar: TaskbarApps.isPinned(root.desktopEntry.id);
implicitWidth: 96
implicitHeight: 84
horizontalPadding: 0
verticalPadding: 0
contentItem: ColumnLayout {
spacing: 3
WAppIcon {
Layout.topMargin: 12
Layout.alignment: Qt.AlignHCenter
iconName: root.desktopEntry.icon
implicitSize: 34
tryCustomIcon: false
}
WText {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: 8
Layout.rightMargin: 8
text: root.desktopEntry.name
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
}
}
WToolTip {
text: root.desktopEntry.name
}
altAction: () => {
appMenu.popup()
}
WMenu {
id: appMenu
downDirection: true
WMenuItem {
visible: root.pinnedStart
icon.name: "arrow-up-left"
text: Translation.tr("Move to front")
onTriggered: {
LauncherApps.moveToFront(root.desktopEntry.id);
}
}
WMenuItem {
visible: root.pinnedStart
icon.name: "arrow-left"
text: Translation.tr("Move left")
onTriggered: {
LauncherApps.moveLeft(root.desktopEntry.id);
}
}
WMenuItem {
visible: root.pinnedStart
icon.name: "arrow-right"
text: Translation.tr("Move right")
onTriggered: {
LauncherApps.moveRight(root.desktopEntry.id);
}
}
WMenuItem {
icon.name: root.pinnedStart ? "pin-off" : "pin"
text: root.pinnedStart ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start")
onTriggered: {
LauncherApps.togglePin(root.desktopEntry.id);
}
}
WMenuItem {
icon.name: root.pinnedTaskbar ? "pin-off" : "pin"
text: root.pinnedTaskbar ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar")
onTriggered: {
TaskbarApps.togglePin(root.desktopEntry.id);
}
}
}
}
@@ -0,0 +1,76 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
BodyRectangle {
id: root
ColumnLayout {
anchors {
fill: parent
leftMargin: 32
rightMargin: 32
topMargin: 25
bottomMargin: 30
}
spacing: 26
PinnedApps {
Layout.fillWidth: true
}
AllApps {
implicitHeight: 300 // for now
}
}
component PinnedApps: PageSection {
title: Translation.tr("Pinned")
BigAppGrid {
Layout.fillWidth: true
columns: 8
desktopEntries: Config.options.launcher.pinnedApps.map(appId => DesktopEntries.byId(appId))
}
}
component AllApps: PageSection {
title: Translation.tr("All")
// TODO: Do we wanna also implement list view and grid view?
// (instead of only category view)
AllAppsGrid {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 32
Layout.rightMargin: 32
}
}
component PageSection: ColumnLayout {
id: pageSection
required property string title
default property alias data: pageSectionContentArea.data
spacing: 16
WText {
Layout.leftMargin: 32
text: pageSection.title
font.pixelSize: Looks.font.pixelSize.large
font.weight: Looks.font.weight.stronger
}
ColumnLayout {
id: pageSectionContentArea
Layout.fillWidth: true
}
}
}
@@ -0,0 +1,99 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
WPanelPageColumn {
id: root
WPanelSeparator {}
StartPageApps {
Layout.fillHeight: true
}
WPanelSeparator {}
StartFooter {
Layout.fillWidth: true
}
component StartFooter: FooterRectangle {
implicitHeight: 63
StartUserButton {
anchors {
left: parent.left
leftMargin: 52
bottom: parent.bottom
bottomMargin: 12
}
}
PowerButton {
anchors {
right: parent.right
rightMargin: 52
bottom: parent.bottom
bottomMargin: 12
}
}
}
component PowerButton: WBorderlessButton {
id: powerButton
implicitWidth: 40
implicitHeight: 40
contentItem: Item {
FluentIcon {
anchors.centerIn: parent
icon: "power"
implicitSize: 20
}
}
WToolTip {
extraVisibleCondition: !powerMenu.visible
text: qsTr("Power")
}
onClicked: {
powerMenu.open()
}
WMenu {
id: powerMenu
x: -powerMenu.implicitWidth / 2 + powerButton.implicitWidth / 2
y: -powerMenu.implicitHeight - 4
Action {
icon.name: "lock-closed"
text: Translation.tr("Lock")
onTriggered: Session.lock()
}
Action {
icon.name: "weather-moon"
text: Translation.tr("Sleep")
onTriggered: Session.suspend()
}
Action {
icon.name: "power"
text: Translation.tr("Shut down")
onTriggered: Session.poweroff()
}
Action {
icon.name: "arrow-counterclockwise"
text: Translation.tr("Restart")
onTriggered: Session.reboot()
}
}
}
}
@@ -0,0 +1,140 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
WBorderlessButton {
id: userButton
implicitWidth: userButtonRow.implicitWidth + 12 * 2
implicitHeight: 40
contentItem: Item {
RowLayout {
id: userButtonRow
anchors.centerIn: parent
spacing: 12
WUserAvatar {
sourceSize: Qt.size(32, 32)
}
WText {
Layout.alignment: Qt.AlignVCenter
text: SystemInfo.username
}
}
}
onClicked: {
userMenu.open();
}
WToolTip {
text: SystemInfo.username
}
Popup {
id: userMenu
x: -51
y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10
background: null
WToolTipContent {
id: popupContent
horizontalPadding: 10
verticalPadding: 7
radius: Looks.radius.large
realContentItem: Item {
implicitWidth: userMenuContentLayout.implicitWidth
implicitHeight: userMenuContentLayout.implicitHeight
ColumnLayout {
id: userMenuContentLayout
anchors {
fill: parent
leftMargin: popupContent.horizontalPadding
rightMargin: popupContent.horizontalPadding
topMargin: popupContent.verticalPadding
bottomMargin: popupContent.verticalPadding
}
spacing: 5
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 6
FluentIcon {
Layout.alignment: Qt.AlignVCenter
implicitSize: 22
icon: "corporation"
monochrome: false
}
WText {
Layout.alignment: Qt.AlignVCenter
text: "Megahard"
font.pixelSize: Looks.font.pixelSize.large
font.weight: Looks.font.weight.strong
}
Item { Layout.fillWidth: true }
WBorderlessButton {
Layout.alignment: Qt.AlignVCenter
implicitHeight: 36
implicitWidth: textItem.implicitWidth + 10 * 2
contentItem: WText {
id: textItem
text: Translation.tr("Sign out")
font.pixelSize: Looks.font.pixelSize.large
}
onClicked: Session.logout()
}
}
Item { // Force min width 360 (using min on the item somehow doesn't work)
implicitWidth: 334
}
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: 7
Layout.leftMargin: 6
spacing: 12
WUserAvatar {
sourceSize: Qt.size(58, 58)
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 2
WText {
text: SystemInfo.username
font.pixelSize: Looks.font.pixelSize.larger
font.weight: Looks.font.weight.strong
}
WText {
color: Looks.colors.fg1
text: Translation.tr("Local account")
}
WText {
color: Looks.colors.accent
text: Translation.tr("Manage my account")
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser])
GlobalStates.searchOpen = false;
}
}
}
}
}
}
}
}
}
}