1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

Add DankModal and SystemLogo widgets

This commit is contained in:
bbedward
2025-07-22 21:12:19 -04:00
parent 5cf04aa941
commit f1833a81a0
19 changed files with 1038 additions and 1646 deletions

View File

@@ -35,6 +35,7 @@ Singleton {
property string iconTheme: "System Default"
property var availableIconThemes: ["System Default"]
property string systemDefaultIconTheme: "Adwaita"
property bool useOSLogo: false
function loadSettings() {
parseSettings(settingsFile.text());
@@ -67,6 +68,7 @@ Singleton {
spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list";
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto";
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false;
applyStoredTheme();
detectAvailableIconThemes();
updateGtkIconTheme(iconTheme);
@@ -103,7 +105,8 @@ Singleton {
"appLauncherViewMode": appLauncherViewMode,
"spotlightLauncherViewMode": spotlightLauncherViewMode,
"networkPreference": networkPreference,
"iconTheme": iconTheme
"iconTheme": iconTheme,
"useOSLogo": useOSLogo
}, null, 2));
}
@@ -378,6 +381,11 @@ gtk-application-prefer-dark-theme=true`;
}
}
function setUseOSLogo(enabled) {
useOSLogo = enabled;
saveSettings();
}
Component.onCompleted: loadSettings()
onShowSystemResourcesChanged: {
if (typeof SystemMonitorService !== 'undefined')

View File

@@ -81,7 +81,9 @@ PanelWindow {
if (viewMode === "grid") {
// Grid navigation: move by columns
var columnsCount = appGrid.columns || 4;
selectedIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1);
var newIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1);
console.log("Grid navigation DOWN: from", selectedIndex, "to", newIndex, "columns:", columnsCount);
selectedIndex = newIndex;
} else {
// List navigation: next item
selectedIndex = (selectedIndex + 1) % filteredModel.count;
@@ -94,7 +96,9 @@ PanelWindow {
if (viewMode === "grid") {
// Grid navigation: move by columns
var columnsCount = appGrid.columns || 4;
selectedIndex = Math.max(selectedIndex - columnsCount, 0);
var newIndex = Math.max(selectedIndex - columnsCount, 0);
console.log("Grid navigation UP: from", selectedIndex, "to", newIndex, "columns:", columnsCount);
selectedIndex = newIndex;
} else {
// List navigation: previous item
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1;
@@ -145,9 +149,6 @@ PanelWindow {
searchField.enabled = true;
searchDebounceTimer.stop(); // Stop any pending search
updateFilteredModel();
Qt.callLater(function() {
searchField.forceActiveFocus();
});
}
function hide() {
@@ -368,6 +369,12 @@ PanelWindow {
Item {
anchors.fill: parent
focus: true
Component.onCompleted: {
if (launcher.isVisible) {
forceActiveFocus();
}
}
// Handle keyboard shortcuts
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
@@ -388,6 +395,11 @@ PanelWindow {
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
launchSelected();
event.accepted = true;
} else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\s]/)) {
// User started typing, focus search field and pass the character
searchField.forceActiveFocus();
searchField.text = event.text;
event.accepted = true;
}
}
@@ -441,14 +453,14 @@ PanelWindow {
leftIconFocusedColor: Theme.primary
showClearButton: true
font.pixelSize: Theme.fontSizeLarge
focus: launcher.isVisible
enabled: launcher.isVisible
placeholderText: "Search applications..."
onTextEdited: {
searchDebounceTimer.restart();
}
Keys.onPressed: function(event) {
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && filteredModel.count) {
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && filteredModel.count && text.length > 0) {
// Launch first app when typing in search field
var firstApp = filteredModel.get(0);
if (firstApp.desktopEntry) {
Prefs.addRecentApp(firstApp.desktopEntry);
@@ -458,9 +470,12 @@ PanelWindow {
}
launcher.hide();
event.accepted = true;
} else if (event.key === Qt.Key_Escape) {
launcher.hide();
event.accepted = true;
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up ||
(event.key === Qt.Key_Left && viewMode === "grid") ||
(event.key === Qt.Key_Right && viewMode === "grid") ||
((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
// Pass navigation keys and enter (when not searching) to main handler
event.accepted = false;
}
}
}
@@ -592,6 +607,7 @@ PanelWindow {
itemHeight: 72
iconSize: 56
showDescription: true
hoverUpdatesSelection: false
onItemClicked: function(index, modelData) {
if (modelData.desktopEntry) {
Prefs.addRecentApp(modelData.desktopEntry);
@@ -616,6 +632,7 @@ PanelWindow {
columns: 4
adaptiveColumns: false
currentIndex: selectedIndex
hoverUpdatesSelection: false
onItemClicked: function(index, modelData) {
if (modelData.desktopEntry) {
Prefs.addRecentApp(modelData.desktopEntry);

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,9 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Widgets
PanelWindow {
DankModal {
id: inputDialog
property bool dialogVisible: false
@@ -39,298 +36,118 @@ PanelWindow {
}
visible: dialogVisible
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: dialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
color: "transparent"
size: "medium"
keyboardFocus: "exclusive"
onVisibleChanged: {
if (visible) {
textInput.enabled = true;
Qt.callLater(function() {
textInput.forceActiveFocus();
textInput.text = inputValue;
});
} else {
textInput.enabled = false;
}
}
anchors {
top: true
left: true
right: true
bottom: true
onBackgroundClicked: {
hideDialog();
cancelled();
}
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
opacity: dialogVisible ? 1 : 0
MouseArea {
anchors.fill: parent
onClicked: {
textInput.enabled = false; // Disable before hiding to prevent Wayland warnings
inputDialog.cancelled();
hideDialog();
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.standardEasing
}
}
}
Rectangle {
width: Math.min(400, parent.width - Theme.spacingL * 2)
height: Math.min(250, parent.height - Theme.spacingL * 2)
anchors.centerIn: parent
color: Theme.surfaceContainer
radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
opacity: dialogVisible ? 1 : 0
scale: dialogVisible ? 1 : 0.9
content: Component {
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingL
// Header
Row {
width: parent.width
Column {
width: parent.width - 40
spacing: Theme.spacingXS
Text {
text: dialogTitle
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
Text {
text: dialogSubtitle
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
width: parent.width
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 2
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
onClicked: {
inputDialog.cancelled();
hideDialog();
}
}
Text {
text: dialogTitle
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
// Text input
Rectangle {
Text {
text: dialogSubtitle
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
border.color: textInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: textInput.activeFocus ? 2 : 1
DankTextField {
id: textInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
echoMode: isPassword && !showPasswordCheckbox.checked ? TextInput.Password : TextInput.Normal
enabled: dialogVisible
placeholderText: inputPlaceholder
backgroundColor: "transparent"
normalBorderColor: "transparent"
focusedBorderColor: "transparent"
onTextEdited: {
inputValue = text;
}
onAccepted: {
inputDialog.confirmed(inputValue);
hideDialog();
}
Component.onCompleted: {
if (dialogVisible)
forceActiveFocus();
}
}
horizontalAlignment: Text.AlignHCenter
}
DankTextField {
id: textInput
width: parent.width
placeholderText: inputPlaceholder
text: inputValue
echoMode: isPassword ? TextInput.Password : TextInput.Normal
onTextChanged: inputValue = text
onAccepted: {
hideDialog();
confirmed(text);
}
}
// Show password checkbox (only visible for password inputs)
Row {
spacing: Theme.spacingS
visible: isPassword
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
Rectangle {
id: showPasswordCheckbox
width: 120
height: 40
radius: Theme.cornerRadius
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
property bool checked: false
width: 20
height: 20
radius: 4
color: checked ? Theme.primary : "transparent"
border.color: checked ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5)
border.width: 2
DankIcon {
Text {
text: cancelButtonText
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.centerIn: parent
name: "check"
size: 12
color: Theme.background
visible: parent.checked
}
MouseArea {
id: cancelButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
hideDialog();
cancelled();
}
}
}
Text {
text: "Show password"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
// Buttons
Item {
width: parent.width
height: 40
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Text {
id: cancelText
anchors.centerIn: parent
text: cancelButtonText
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
textInput.enabled = false; // Disable before hiding to prevent Wayland warnings
inputDialog.cancelled();
hideDialog();
}
}
Rectangle {
width: 120
height: 40
radius: Theme.cornerRadius
color: confirmButton.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
Text {
text: confirmButtonText
font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText
font.weight: Font.Medium
anchors.centerIn: parent
}
Rectangle {
width: Math.max(80, confirmText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: confirmArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: inputValue.length > 0
opacity: enabled ? 1 : 0.5
Text {
id: confirmText
anchors.centerIn: parent
text: confirmButtonText
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
MouseArea {
id: confirmButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
hideDialog();
confirmed(textInput.text);
}
MouseArea {
id: confirmArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: {
inputDialog.confirmed(inputValue);
hideDialog();
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
}
}

View File

@@ -1,12 +1,10 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Widgets
PanelWindow {
DankModal {
id: root
property bool powerConfirmVisible: false
@@ -37,173 +35,13 @@ PanelWindow {
}
}
// DankModal configuration
visible: powerConfirmVisible
implicitWidth: 400
implicitHeight: 300
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
// Darkened background
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.5
}
Rectangle {
width: Math.min(400, parent.width - Theme.spacingL * 2)
height: Math.min(200, parent.height - Theme.spacingL * 2)
anchors.centerIn: parent
color: Theme.surfaceContainer
radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
opacity: powerConfirmVisible ? 1 : 0
scale: powerConfirmVisible ? 1 : 0.9
Column {
anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingL
// Title
Text {
text: powerConfirmTitle
font.pixelSize: Theme.fontSizeLarge
color: {
switch (powerConfirmAction) {
case "poweroff":
return Theme.error;
case "reboot":
return Theme.warning;
default:
return Theme.surfaceText;
}
}
font.weight: Font.Medium
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
// Message
Text {
text: powerConfirmMessage
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
Item {
height: Theme.spacingL
}
// Buttons
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
// Cancel button
Rectangle {
width: 120
height: 40
radius: Theme.cornerRadius
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text {
text: "Cancel"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
id: cancelButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerConfirmVisible = false;
}
}
}
// Confirm button
Rectangle {
width: 120
height: 40
radius: Theme.cornerRadius
color: {
let baseColor;
switch (powerConfirmAction) {
case "poweroff":
baseColor = Theme.error;
break;
case "reboot":
baseColor = Theme.warning;
break;
default:
baseColor = Theme.primary;
break;
}
return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor;
}
Text {
text: "Confirm"
font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
id: confirmButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerConfirmVisible = false;
executePowerAction(powerConfirmAction);
}
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
size: "small"
keyboardFocus: "ondemand"
enableShadow: false
onBackgroundClicked: {
powerConfirmVisible = false;
}
Process {
@@ -217,4 +55,130 @@ PanelWindow {
}
}
content: Component {
Item {
anchors.fill: parent
Column {
anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingL
// Title
Text {
text: powerConfirmTitle
font.pixelSize: Theme.fontSizeLarge
color: {
switch (powerConfirmAction) {
case "poweroff":
return Theme.error;
case "reboot":
return Theme.warning;
default:
return Theme.surfaceText;
}
}
font.weight: Font.Medium
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
// Message
Text {
text: powerConfirmMessage
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
Item {
height: Theme.spacingL
}
// Buttons
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
// Cancel button
Rectangle {
width: 120
height: 40
radius: Theme.cornerRadius
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text {
text: "Cancel"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
id: cancelButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerConfirmVisible = false;
}
}
}
// Confirm button
Rectangle {
width: 120
height: 40
radius: Theme.cornerRadius
color: {
let baseColor;
switch (powerConfirmAction) {
case "poweroff":
baseColor = Theme.error;
break;
case "reboot":
baseColor = Theme.warning;
break;
default:
baseColor = Theme.primary;
break;
}
return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor;
}
Text {
text: "Confirm"
font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
id: confirmButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
powerConfirmVisible = false;
executePowerAction(powerConfirmAction);
}
}
}
}
}
}
}
}

View File

@@ -2121,4 +2121,4 @@ PanelWindow {
target: "processlist"
}
}
}

View File

@@ -1,73 +1,35 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Widgets
import qs.Modules.Settings
PanelWindow {
DankModal {
id: settingsPopup
property bool settingsVisible: false
signal closingPopup()
onSettingsVisibleChanged: {
if (!settingsVisible) {
onVisibleChanged: {
if (!visible) {
closingPopup();
// Hide any open dropdown when settings close
if (typeof globalDropdownWindow !== 'undefined') {
globalDropdownWindow.hide();
}
}
}
// DankModal configuration
visible: settingsVisible
implicitWidth: 600
implicitHeight: 700
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent"
size: "extra-large"
keyboardFocus: "ondemand"
enableShadow: true
anchors {
top: true
left: true
right: true
bottom: true
onBackgroundClicked: {
settingsVisible = false;
}
// Darkened background
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.5
MouseArea {
anchors.fill: parent
onClicked: settingsPopup.settingsVisible = false
}
}
// Main settings panel - spotlight-like centered appearance
Rectangle {
id: mainPanel
width: Math.min(600, parent.width - Theme.spacingXL * 2)
height: Math.min(700, parent.height - Theme.spacingXL * 2)
anchors.centerIn: parent
color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
// Simple opacity and scale control tied directly to settingsVisible
opacity: settingsPopup.settingsVisible ? 1 : 0
scale: settingsPopup.settingsVisible ? 1 : 0.95
// Add shadow effect
layer.enabled: true
content: Component {
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
@@ -111,40 +73,19 @@ PanelWindow {
}
// Settings sections
Flickable {
ScrollView {
id: settingsScrollView
width: parent.width
height: parent.height - 80
height: parent.height - 50
clip: true
contentHeight: settingsColumn.height
boundsBehavior: Flickable.DragAndOvershootBounds
flickDeceleration: 8000
maximumFlickVelocity: 15000
property real wheelStepSize: 60
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
onWheel: (wheel) => {
var delta = wheel.angleDelta.y
var steps = delta / 120
settingsScrollView.contentY -= steps * settingsScrollView.wheelStepSize
// Keep within bounds
if (settingsScrollView.contentY < 0)
settingsScrollView.contentY = 0
else if (settingsScrollView.contentY > settingsScrollView.contentHeight - settingsScrollView.height)
settingsScrollView.contentY = Math.max(0, settingsScrollView.contentHeight - settingsScrollView.height)
}
}
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
Column {
id: settingsColumn
width: parent.width
width: settingsScrollView.width - 20
spacing: Theme.spacingL
bottomPadding: Theme.spacingL
// Profile Settings
SettingsSection {
@@ -193,32 +134,6 @@ PanelWindow {
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 8
shadowBlur: 1
shadowColor: Qt.rgba(0, 0, 0, 0.3)
shadowOpacity: 0.3
}
}
// Keyboard focus and shortcuts

View File

@@ -59,4 +59,13 @@ Column {
return Prefs.setShowSystemTray(checked);
}
}
DankToggle {
text: "Use OS Logo for App Launcher"
description: "Display operating system logo instead of apps icon"
checked: Prefs.useOSLogo
onToggled: (checked) => {
return Prefs.setUseOSLogo(checked);
}
}
}

View File

@@ -3,13 +3,11 @@ import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
PanelWindow {
DankModal {
id: spotlightLauncher
property bool spotlightOpen: false
@@ -28,26 +26,20 @@ PanelWindow {
}
property string selectedCategory: "All"
property string viewMode: Prefs.spotlightLauncherViewMode // "list" or "grid"
property int gridColumns: 4
// ...existing code...
function show() {
console.log("SpotlightLauncher: show() called");
spotlightOpen = true;
searchField.enabled = true;
console.log("SpotlightLauncher: spotlightOpen set to", spotlightOpen);
searchDebounceTimer.stop(); // Stop any pending search
updateFilteredApps(); // Immediate update when showing
Qt.callLater(function() {
searchField.forceActiveFocus();
searchField.selectAll();
});
}
function hide() {
searchField.enabled = false; // Disable before hiding to prevent Wayland warnings
spotlightOpen = false;
searchDebounceTimer.stop(); // Stop any pending search
searchField.text = "";
searchQuery = "";
selectedIndex = 0;
selectedCategory = "All";
updateFilteredApps();
@@ -60,11 +52,13 @@ PanelWindow {
show();
}
property string searchQuery: ""
property bool shouldFocusSearch: false
function updateFilteredApps() {
filteredApps = [];
selectedIndex = 0;
var apps = [];
var searchQuery = searchField.text;
if (searchQuery.length === 0) {
// Show apps from category
if (selectedCategory === "All") {
@@ -156,58 +150,95 @@ PanelWindow {
}
function selectNext() {
if (filteredApps.length > 0) {
if (filteredModel.count > 0) {
if (viewMode === "grid") {
// Grid navigation: move by columns
var columnsCount = resultsGrid.columns || 6;
selectedIndex = Math.min(selectedIndex + columnsCount, filteredApps.length - 1);
// Grid navigation: move DOWN by one row (gridColumns positions)
var columnsCount = gridColumns;
var newIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1);
selectedIndex = newIndex;
} else {
// List navigation: next item
selectedIndex = (selectedIndex + 1) % filteredApps.length;
selectedIndex = (selectedIndex + 1) % filteredModel.count;
}
}
}
function selectPrevious() {
if (filteredApps.length > 0) {
if (filteredModel.count > 0) {
if (viewMode === "grid") {
// Grid navigation: move by columns
var columnsCount = resultsGrid.columns || 6;
selectedIndex = Math.max(selectedIndex - columnsCount, 0);
// Grid navigation: move UP by one row (gridColumns positions)
var columnsCount = gridColumns;
var newIndex = Math.max(selectedIndex - columnsCount, 0);
selectedIndex = newIndex;
} else {
// List navigation: previous item
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredApps.length - 1;
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1;
}
}
}
function selectNextInRow() {
if (filteredApps.length > 0 && viewMode === "grid")
selectedIndex = Math.min(selectedIndex + 1, filteredApps.length - 1);
if (filteredModel.count > 0 && viewMode === "grid") {
// Grid navigation: move RIGHT by one position
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1);
}
}
function selectPreviousInRow() {
if (filteredApps.length > 0 && viewMode === "grid")
if (filteredModel.count > 0 && viewMode === "grid") {
// Grid navigation: move LEFT by one position
selectedIndex = Math.max(selectedIndex - 1, 0);
}
}
function launchSelected() {
if (filteredApps.length > 0 && selectedIndex >= 0 && selectedIndex < filteredApps.length)
launchApp(filteredApps[selectedIndex]);
if (filteredModel.count > 0 && selectedIndex >= 0 && selectedIndex < filteredModel.count) {
var selectedApp = filteredModel.get(selectedIndex);
launchApp(selectedApp);
}
}
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: spotlightOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
WlrLayershell.namespace: "quickshell-spotlight"
// DankModal configuration
visible: spotlightOpen
size: "custom"
customWidth: 600
customHeight: {
// Fixed height to prevent shrinking - consistent experience
let baseHeight = Theme.spacingXL * 2 + Theme.spacingL * 3;
// Add category section height if visible
if (categories.length > 1 || filteredModel.count > 0)
baseHeight += 36 * 2 + Theme.spacingS + Theme.spacingM;
// Add search field height
baseHeight += 56;
// Add fixed results height for consistent size
let fixedResultsHeight = 400;
// Always same height regardless of content
baseHeight += fixedResultsHeight;
// Ensure reasonable bounds
return Math.min(Math.max(baseHeight, 500), 800);
}
keyboardFocus: "exclusive"
backgroundColor: Theme.popupBackground()
cornerRadius: Theme.cornerRadiusXLarge
borderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
borderWidth: 1
enableShadow: true
onVisibleChanged: {
console.log("SpotlightLauncher visibility changed to:", visible);
if (visible && !spotlightOpen) {
show();
}
}
color: "transparent"
onOpened: {
shouldFocusSearch = true;
}
onBackgroundClicked: {
spotlightOpen = false;
}
Component.onCompleted: {
console.log("SpotlightLauncher: Component.onCompleted called - component loaded successfully!");
var allCategories = AppSearchService.getAllCategories().filter((cat) => {
@@ -223,19 +254,11 @@ PanelWindow {
// Search debouncing
Timer {
id: searchDebounceTimer
interval: 50
repeat: false
onTriggered: updateFilteredApps()
}
anchors {
top: true
left: true
right: true
bottom: true
}
ListModel {
id: filteredModel
}
@@ -253,72 +276,59 @@ PanelWindow {
}));
if (spotlightOpen)
updateFilteredApps();
}
}
target: AppSearchService
}
// Dimmed overlay background
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.4)
opacity: spotlightOpen ? 1 : 0
MouseArea {
content: Component {
Item {
anchors.fill: parent
enabled: spotlightOpen
onClicked: hide()
}
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
focus: true
Component.onCompleted: {
forceActiveFocus();
}
onVisibleChanged: {
if (visible) {
forceActiveFocus();
}
}
// Handle keyboard shortcuts
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
hide();
event.accepted = true;
} else if (event.key === Qt.Key_Down) {
selectNext();
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
selectPrevious();
event.accepted = true;
} else if (event.key === Qt.Key_Right && viewMode === "grid") {
selectNextInRow();
event.accepted = true;
} else if (event.key === Qt.Key_Left && viewMode === "grid") {
selectPreviousInRow();
event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
launchSelected();
event.accepted = true;
} else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
// User started typing, focus search field and pass the character
searchField.forceActiveFocus();
searchField.text = event.text;
event.accepted = true;
}
}
}
}
// Main container with search and results
Rectangle {
// Margins and spacing
// Categories (2 rows)
id: mainContainer
width: 600
height: {
// Fixed height to prevent shrinking - consistent experience
let baseHeight = Theme.spacingXL * 2 + Theme.spacingL * 3;
// Add category section height if visible
if (categories.length > 1 || filteredModel.count > 0)
baseHeight += 36 * 2 + Theme.spacingS + Theme.spacingM;
// Add search field height
baseHeight += 56;
// Add fixed results height for consistent size
let fixedResultsHeight = 400;
// Always same height regardless of content
baseHeight += fixedResultsHeight;
// Ensure reasonable bounds
return Math.min(Math.max(baseHeight, 500), parent.height - 40);
}
anchors.centerIn: parent
color: Theme.popupBackground()
radius: Theme.cornerRadiusXLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
layer.enabled: true
opacity: spotlightOpen ? 1 : 0
scale: spotlightOpen ? 1 : 0.96
Column {
anchors.fill: parent
anchors.margins: Theme.spacingXL
spacing: Theme.spacingL
// Combined row for categories and view mode toggle
Column {
@@ -368,11 +378,8 @@ PanelWindow {
updateFilteredApps();
}
}
}
}
}
// Bottom row: Media, Office, Settings, System, Utilities (5 items)
@@ -412,15 +419,10 @@ PanelWindow {
updateFilteredApps();
}
}
}
}
}
}
}
// Search field with view toggle buttons
@@ -444,31 +446,51 @@ PanelWindow {
showClearButton: true
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
focus: spotlightOpen
focus: false
enabled: spotlightOpen
placeholderText: "Search applications..."
text: searchQuery
onTextEdited: {
searchQuery = text;
searchDebounceTimer.restart();
}
Connections {
target: spotlightLauncher
function onShouldFocusSearchChanged() {
if (shouldFocusSearch) {
Qt.callLater(function() {
searchField.forceActiveFocus();
searchField.selectAll();
shouldFocusSearch = false;
});
}
}
}
onActiveFocusChanged: {
if (!activeFocus && searchQuery.length === 0) {
// If search field loses focus and there's no search text, give focus back to main handler
parent.parent.forceActiveFocus();
}
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
hide();
event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
launchSelected();
event.accepted = true;
} else if (event.key === Qt.Key_Down) {
selectNext();
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
selectPrevious();
event.accepted = true;
} else if (event.key === Qt.Key_Right && viewMode === "grid") {
selectNextInRow();
event.accepted = true;
} else if (event.key === Qt.Key_Left && viewMode === "grid") {
selectPreviousInRow();
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && searchQuery.length > 0) {
// Launch first app when typing in search field
if (filteredApps.length > 0) {
launchApp(filteredApps[0]);
}
event.accepted = true;
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up ||
(event.key === Qt.Key_Left && viewMode === "grid") ||
(event.key === Qt.Key_Right && viewMode === "grid") ||
((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && searchQuery.length === 0)) {
// Pass navigation keys and enter (when not searching) to main handler
event.accepted = false;
}
}
}
@@ -506,7 +528,6 @@ PanelWindow {
Prefs.setSpotlightLauncherViewMode("list");
}
}
}
// Grid view button
@@ -536,11 +557,8 @@ PanelWindow {
Prefs.setSpotlightLauncherViewMode("grid");
}
}
}
}
}
// Results container
@@ -562,6 +580,7 @@ PanelWindow {
itemHeight: 60
iconSize: 40
showDescription: true
hoverUpdatesSelection: false
onItemClicked: function(index, modelData) {
launchApp(modelData);
}
@@ -577,13 +596,14 @@ PanelWindow {
anchors.fill: parent
visible: viewMode === "grid"
model: filteredModel
columns: 6
adaptiveColumns: true
columns: 4
adaptiveColumns: false
minCellWidth: 120
maxCellWidth: 160
iconSizeRatio: 0.55
maxIconSize: 48
currentIndex: selectedIndex
hoverUpdatesSelection: false
onItemClicked: function(index, modelData) {
launchApp(modelData);
}
@@ -591,37 +611,9 @@ PanelWindow {
selectedIndex = index;
}
}
}
}
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 8
shadowBlur: 1 // radius/32
shadowColor: Qt.rgba(0, 0, 0, 0.3)
shadowOpacity: 0.3
}
// Center-screen fade with subtle scale
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
IpcHandler {
@@ -645,5 +637,4 @@ PanelWindow {
target: "spotlight"
}
}
}

View File

@@ -10,25 +10,21 @@ Rectangle {
property bool isActive: false
readonly property bool nerdFontAvailable: Qt.fontFamilies()
.indexOf("Symbols Nerd Font") !== -1
width: 40
height: 30
radius: Theme.cornerRadius
color: launcherArea.containsMouse || isActive ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
Text {
visible: nerdFontAvailable && OSDetectorService.osLogo
SystemLogo {
visible: Prefs.useOSLogo
anchors.centerIn: parent
text: OSDetectorService.osLogo
font.family: "Symbols Nerd Font"
font.pixelSize: Theme.iconSize - 6
width: Theme.iconSize - 6
height: Theme.iconSize - 6
color: Theme.surfaceText
}
DankIcon {
visible: !nerdFontAvailable || !OSDetectorService.osLogo
visible: !Prefs.useOSLogo
anchors.centerIn: parent
name: "apps"
size: Theme.iconSize - 6

View File

@@ -1,13 +1,10 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
PanelWindow {
DankModal {
id: root
property bool wifiPasswordDialogVisible: false
@@ -15,75 +12,31 @@ PanelWindow {
property string wifiPasswordInput: ""
visible: wifiPasswordDialogVisible
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: wifiPasswordDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
color: "transparent"
size: "medium"
keyboardFocus: "exclusive"
onVisibleChanged: {
if (visible) {
passwordInput.enabled = true;
Qt.callLater(function() {
passwordInput.forceActiveFocus();
});
} else {
passwordInput.enabled = false;
if (!visible) {
wifiPasswordInput = "";
}
}
anchors {
top: true
left: true
right: true
bottom: true
onBackgroundClicked: {
wifiPasswordDialogVisible = false;
wifiPasswordInput = "";
}
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
opacity: wifiPasswordDialogVisible ? 1 : 0
MouseArea {
anchors.fill: parent
onClicked: {
passwordInput.enabled = false; // Disable before hiding to prevent Wayland warnings
wifiPasswordDialogVisible = false;
wifiPasswordInput = "";
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.standardEasing
}
}
}
Rectangle {
width: Math.min(400, parent.width - Theme.spacingL * 2)
height: Math.min(250, parent.height - Theme.spacingL * 2)
anchors.centerIn: parent
color: Theme.surfaceContainer
radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
opacity: wifiPasswordDialogVisible ? 1 : 0
scale: wifiPasswordDialogVisible ? 1 : 0.9
// Prevent clicks inside dialog from closing it
MouseArea {
anchors.fill: parent
onClicked: {
// Do nothing - prevent propagation to background
}
}
content: Component {
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
Component.onCompleted: {
Qt.callLater(function() {
passwordInput.forceActiveFocus();
});
}
// Header
Row {
@@ -107,7 +60,6 @@ PanelWindow {
width: parent.width
elide: Text.ElideRight
}
}
DankActionButton {
@@ -116,12 +68,10 @@ PanelWindow {
iconColor: Theme.surfaceText
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
onClicked: {
passwordInput.enabled = false; // Disable before hiding to prevent Wayland warnings
wifiPasswordDialogVisible = false;
wifiPasswordInput = "";
}
}
}
// Password input
@@ -135,13 +85,11 @@ PanelWindow {
DankTextField {
id: passwordInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
enabled: wifiPasswordDialogVisible
placeholderText: "Enter password"
backgroundColor: "transparent"
normalBorderColor: "transparent"
@@ -150,20 +98,12 @@ PanelWindow {
wifiPasswordInput = text;
}
onAccepted: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
// Close dialog immediately after pressing Enter
passwordInput.enabled = false;
WifiService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text);
wifiPasswordDialogVisible = false;
wifiPasswordInput = "";
passwordInput.text = "";
}
Component.onCompleted: {
if (wifiPasswordDialogVisible)
forceActiveFocus();
}
}
}
// Show password checkbox
@@ -172,9 +112,7 @@ PanelWindow {
Rectangle {
id: showPasswordCheckbox
property bool checked: false
width: 20
height: 20
radius: 4
@@ -198,7 +136,6 @@ PanelWindow {
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
}
}
}
Text {
@@ -207,7 +144,6 @@ PanelWindow {
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
// Buttons
@@ -230,7 +166,6 @@ PanelWindow {
Text {
id: cancelText
anchors.centerIn: parent
text: "Cancel"
font.pixelSize: Theme.fontSizeMedium
@@ -240,17 +175,14 @@ PanelWindow {
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
passwordInput.enabled = false; // Disable before hiding to prevent Wayland warnings
wifiPasswordDialogVisible = false;
wifiPasswordInput = "";
}
}
}
Rectangle {
@@ -258,12 +190,11 @@ PanelWindow {
height: 36
radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: wifiPasswordInput.length > 0
enabled: passwordInput.text.length > 0
opacity: enabled ? 1 : 0.5
Text {
id: connectText
anchors.centerIn: parent
text: "Connect"
font.pixelSize: Theme.fontSizeMedium
@@ -273,17 +204,15 @@ PanelWindow {
MouseArea {
id: connectArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
// Close dialog immediately after clicking connect
passwordInput.enabled = false;
WifiService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text);
wifiPasswordDialogVisible = false;
wifiPasswordInput = "";
passwordInput.text = "";
}
}
@@ -292,33 +221,11 @@ PanelWindow {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
// Auto-reopen dialog on invalid password
@@ -333,5 +240,4 @@ PanelWindow {
}
}
}
}
}

View File

@@ -34,7 +34,10 @@ paru -S quickshell-git
```bash
# Arch
paru -S nerd-fonts ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil swaybg
paru -S ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil swaybg
```
**Note on networking:**
3. Configure SwayBG (If Desired)

View File

@@ -1,112 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property string osLogo: ""
property string osName: ""
// OS Detection using /etc/os-release
Process {
id: osDetector
command: ["sh", "-c", "grep '^ID=' /etc/os-release | cut -d'=' -f2 | tr -d '\"'"]
running: true
onExited: (exitCode) => {
if (exitCode !== 0) {
// Ultimate fallback - use generic apps icon (empty logo means fallback to "apps")
root.osLogo = "";
root.osName = "Linux";
console.log("OS detection failed, using generic icon");
}
}
stdout: SplitParser {
splitMarker: "\n"
onRead: (data) => {
if (data.trim()) {
let osId = data.trim().toLowerCase();
console.log("Detected OS from /etc/os-release:", osId);
// Set OS-specific Nerd Font icons and names
switch (osId) {
case "arch":
root.osLogo = "\uf303"; // Arch Linux Nerd Font icon
root.osName = "Arch Linux";
break;
case "ubuntu":
root.osLogo = "\uf31b"; // Ubuntu Nerd Font icon
root.osName = "Ubuntu";
break;
case "fedora":
root.osLogo = "\uf30a"; // Fedora Nerd Font icon
root.osName = "Fedora";
break;
case "debian":
root.osLogo = "\uf306"; // Debian Nerd Font icon
root.osName = "Debian";
break;
case "opensuse":
case "opensuse-leap":
case "opensuse-tumbleweed":
root.osLogo = "\uef6d"; // openSUSE Nerd Font icon
root.osName = "openSUSE";
break;
case "manjaro":
root.osLogo = "\uf312"; // Manjaro Nerd Font icon
root.osName = "Manjaro";
break;
case "nixos":
root.osLogo = "\uf313"; // NixOS Nerd Font icon
root.osName = "NixOS";
break;
case "rocky":
root.osLogo = "\uf32b"; // Rocky Linux Nerd Font icon
root.osName = "Rocky Linux";
break;
case "almalinux":
root.osLogo = "\uf31d"; // AlmaLinux Nerd Font icon
root.osName = "AlmaLinux";
break;
case "centos":
root.osLogo = "\uf304"; // CentOS Nerd Font icon
root.osName = "CentOS";
break;
case "rhel":
case "redhat":
root.osLogo = "\uf316"; // Red Hat Nerd Font icon
root.osName = "Red Hat";
break;
case "gentoo":
root.osLogo = "\uf30d"; // Gentoo Nerd Font icon
root.osName = "Gentoo";
break;
case "mint":
case "linuxmint":
root.osLogo = "\uf30e"; // Linux Mint Nerd Font icon
root.osName = "Linux Mint";
break;
case "elementary":
root.osLogo = "\uf309"; // Elementary OS Nerd Font icon
root.osName = "Elementary OS";
break;
case "pop":
root.osLogo = "\uf32a"; // Pop!_OS Nerd Font icon
root.osName = "Pop!_OS";
break;
default:
root.osLogo = "\uf17c"; // Generic Linux Nerd Font icon
root.osName = "Linux";
}
console.log("Set OS logo:", root.osLogo, "Name:", root.osName);
}
}
}
}
}

View File

@@ -18,6 +18,7 @@ ScrollView {
property int maxIconSize: 56
property int minIconSize: 32
property real wheelStepSize: 60
property bool hoverUpdatesSelection: true
signal itemClicked(int index, var modelData)
signal itemHovered(int index)
@@ -136,7 +137,9 @@ ScrollView {
cursorShape: Qt.PointingHandCursor
z: 10
onEntered: {
currentIndex = index;
if (hoverUpdatesSelection) {
currentIndex = index;
}
itemHovered(index);
}
onClicked: {

View File

@@ -14,6 +14,7 @@ ScrollView {
property real wheelStepSize: 60
property bool showDescription: true
property int itemSpacing: Theme.spacingS
property bool hoverUpdatesSelection: true
signal itemClicked(int index, var modelData)
signal itemHovered(int index)
@@ -133,7 +134,9 @@ ScrollView {
cursorShape: Qt.PointingHandCursor
z: 10
onEntered: {
listView.currentIndex = index;
if (hoverUpdatesSelection) {
listView.currentIndex = index;
}
itemHovered(index);
}
onClicked: {

View File

@@ -162,7 +162,7 @@ Item {
}
}
onActiveFocusChanged: (hasFocus) => {
onFocusStateChanged: (hasFocus) => {
if (hasFocus) {
dropdownHideTimer.stop()
if (text.length <= 2) {

235
Widgets/DankModal.qml Normal file
View File

@@ -0,0 +1,235 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Widgets
PanelWindow {
id: root
// Core properties
property alias content: contentLoader.sourceComponent
// Sizing
property string size: "medium" // "small", "medium", "large", "extra-large", "auto", "custom"
property real customWidth: 400
property real customHeight: 300
// Background behavior
property bool showBackground: true
property real backgroundOpacity: 0.5
// Positioning
property string positioning: "center" // "center", "top-right", "custom"
property point customPosition: Qt.point(0, 0)
// Focus management
property string keyboardFocus: "ondemand" // "ondemand", "exclusive", "none"
property bool closeOnEscapeKey: true
property bool closeOnBackgroundClick: true
// Animation
property string animationType: "scale" // "scale", "slide", "fade"
property int animationDuration: Theme.mediumDuration
property var animationEasing: Theme.emphasizedEasing
// Styling
property color backgroundColor: Theme.surfaceContainer
property color borderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
property real borderWidth: 1
property real cornerRadius: Theme.cornerRadiusLarge
property bool enableShadow: false
// Signals
signal opened()
signal dialogClosed()
signal backgroundClicked()
// Internal properties
readonly property var sizePresets: ({
"small": { width: 350, height: 200 },
"medium": { width: 500, height: 400 },
"large": { width: 600, height: 500 },
"extra-large": { width: 700, height: 600 },
"fit-content": { width: 600, height: 500 }
})
readonly property real contentWidth: {
if (size === "custom") return customWidth
if (size === "auto") return Math.min(contentLoader.item ? contentLoader.item.implicitWidth || 400 : 400, parent.width - Theme.spacingL * 2)
return sizePresets[size] ? sizePresets[size].width : sizePresets["medium"].width
}
readonly property real contentHeight: {
if (size === "custom") return customHeight
if (size === "auto") return Math.min(contentLoader.item ? contentLoader.item.implicitHeight || 300 : 300, parent.height - Theme.spacingL * 2)
return sizePresets[size] ? sizePresets[size].height : sizePresets["medium"].height
}
// PanelWindow configuration
// visible property is inherited from PanelWindow
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
switch (root.keyboardFocus) {
case "exclusive": return WlrKeyboardFocus.Exclusive
case "none": return WlrKeyboardFocus.None
default: return WlrKeyboardFocus.OnDemand
}
}
onVisibleChanged: {
if (root.visible) {
opened()
} else {
dialogClosed()
}
}
// Background overlay
Rectangle {
id: background
anchors.fill: parent
color: "black"
opacity: root.showBackground ? (root.visible ? root.backgroundOpacity : 0) : 0
visible: root.showBackground
Behavior on opacity {
NumberAnimation {
duration: root.animationDuration
easing.type: root.animationEasing
}
}
MouseArea {
anchors.fill: parent
enabled: root.closeOnBackgroundClick
onClicked: (mouse) => {
var localPos = mapToItem(contentContainer, mouse.x, mouse.y)
if (localPos.x < 0 || localPos.x > contentContainer.width ||
localPos.y < 0 || localPos.y > contentContainer.height) {
root.backgroundClicked()
}
}
}
}
// Main content container
Rectangle {
id: contentContainer
width: root.contentWidth
height: root.contentHeight
// Positioning
anchors.centerIn: positioning === "center" ? parent : undefined
x: {
if (positioning === "top-right") {
return Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
} else if (positioning === "custom") {
return root.customPosition.x
}
return 0 // Will be overridden by anchors.centerIn when positioning === "center"
}
y: {
if (positioning === "top-right") {
return Theme.barHeight + Theme.spacingXS
} else if (positioning === "custom") {
return root.customPosition.y
}
return 0 // Will be overridden by anchors.centerIn when positioning === "center"
}
color: root.backgroundColor
radius: root.cornerRadius
border.color: root.borderColor
border.width: root.borderWidth
layer.enabled: root.enableShadow
// Animation properties
opacity: root.visible ? 1 : 0
scale: {
if (root.animationType === "scale") {
return root.visible ? 1 : 0.9
}
return 1
}
// Transform for slide animation
transform: root.animationType === "slide" ? slideTransform : null
Translate {
id: slideTransform
x: root.visible ? 0 : 15
y: root.visible ? 0 : -30
}
// Content area
Loader {
id: contentLoader
anchors.fill: parent
active: true
asynchronous: false
}
// Animations
Behavior on opacity {
NumberAnimation {
duration: root.animationDuration
easing.type: root.animationEasing
}
}
Behavior on scale {
enabled: root.animationType === "scale"
NumberAnimation {
duration: root.animationDuration
easing.type: root.animationEasing
}
}
// Shadow effect
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 8
shadowBlur: 1
shadowColor: Qt.rgba(0, 0, 0, 0.3)
shadowOpacity: 0.3
}
}
// Keyboard handling
FocusScope {
anchors.fill: parent
focus: visible && root.closeOnEscapeKey
Keys.onEscapePressed: {
if (root.closeOnEscapeKey) {
visible = false
}
}
}
// Convenience functions
function open() {
visible = true
}
function close() {
visible = false
}
function toggle() {
visible = !visible
}
}

View File

@@ -43,7 +43,7 @@ Rectangle {
signal textEdited()
signal editingFinished()
signal accepted()
signal activeFocusChanged(bool hasFocus)
signal focusStateChanged(bool hasFocus)
// Access to inner TextInput properties via functions
function getActiveFocus() {
@@ -120,7 +120,7 @@ Rectangle {
onTextChanged: root.textEdited()
onEditingFinished: root.editingFinished()
onAccepted: root.accepted()
onActiveFocusChanged: root.activeFocusChanged(activeFocus)
onActiveFocusChanged: root.focusStateChanged(activeFocus)
MouseArea {
anchors.fill: parent

39
Widgets/SystemLogo.qml Normal file
View File

@@ -0,0 +1,39 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.Common
Item {
id: root
property color color: Theme.surfaceText
IconImage {
id: iconImage
anchors.fill: parent
smooth: true
asynchronous: true
Process {
running: true
command: ["sh", "-c", ". /etc/os-release && echo $LOGO"]
stdout: StdioCollector {
onStreamFinished: () => {
iconImage.source = Quickshell.iconPath(this.text.trim());
}
}
}
}
MultiEffect {
source: iconImage
anchors.fill: iconImage
colorization: 1.0
colorizationColor: root.color
brightness: 0.5
saturation: 0.0
visible: iconImage.status === Image.Ready
}
}