1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 23: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 string iconTheme: "System Default"
property var availableIconThemes: ["System Default"] property var availableIconThemes: ["System Default"]
property string systemDefaultIconTheme: "Adwaita" property string systemDefaultIconTheme: "Adwaita"
property bool useOSLogo: false
function loadSettings() { function loadSettings() {
parseSettings(settingsFile.text()); parseSettings(settingsFile.text());
@@ -67,6 +68,7 @@ Singleton {
spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list"; spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list";
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto"; networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto";
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default"; iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false;
applyStoredTheme(); applyStoredTheme();
detectAvailableIconThemes(); detectAvailableIconThemes();
updateGtkIconTheme(iconTheme); updateGtkIconTheme(iconTheme);
@@ -103,7 +105,8 @@ Singleton {
"appLauncherViewMode": appLauncherViewMode, "appLauncherViewMode": appLauncherViewMode,
"spotlightLauncherViewMode": spotlightLauncherViewMode, "spotlightLauncherViewMode": spotlightLauncherViewMode,
"networkPreference": networkPreference, "networkPreference": networkPreference,
"iconTheme": iconTheme "iconTheme": iconTheme,
"useOSLogo": useOSLogo
}, null, 2)); }, null, 2));
} }
@@ -378,6 +381,11 @@ gtk-application-prefer-dark-theme=true`;
} }
} }
function setUseOSLogo(enabled) {
useOSLogo = enabled;
saveSettings();
}
Component.onCompleted: loadSettings() Component.onCompleted: loadSettings()
onShowSystemResourcesChanged: { onShowSystemResourcesChanged: {
if (typeof SystemMonitorService !== 'undefined') if (typeof SystemMonitorService !== 'undefined')

View File

@@ -81,7 +81,9 @@ PanelWindow {
if (viewMode === "grid") { if (viewMode === "grid") {
// Grid navigation: move by columns // Grid navigation: move by columns
var columnsCount = appGrid.columns || 4; 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 { } else {
// List navigation: next item // List navigation: next item
selectedIndex = (selectedIndex + 1) % filteredModel.count; selectedIndex = (selectedIndex + 1) % filteredModel.count;
@@ -94,7 +96,9 @@ PanelWindow {
if (viewMode === "grid") { if (viewMode === "grid") {
// Grid navigation: move by columns // Grid navigation: move by columns
var columnsCount = appGrid.columns || 4; 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 { } else {
// List navigation: previous item // List navigation: previous item
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1; selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1;
@@ -145,9 +149,6 @@ PanelWindow {
searchField.enabled = true; searchField.enabled = true;
searchDebounceTimer.stop(); // Stop any pending search searchDebounceTimer.stop(); // Stop any pending search
updateFilteredModel(); updateFilteredModel();
Qt.callLater(function() {
searchField.forceActiveFocus();
});
} }
function hide() { function hide() {
@@ -368,6 +369,12 @@ PanelWindow {
Item { Item {
anchors.fill: parent anchors.fill: parent
focus: true focus: true
Component.onCompleted: {
if (launcher.isVisible) {
forceActiveFocus();
}
}
// Handle keyboard shortcuts // Handle keyboard shortcuts
Keys.onPressed: function(event) { Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
@@ -388,6 +395,11 @@ PanelWindow {
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
launchSelected(); launchSelected();
event.accepted = true; 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 leftIconFocusedColor: Theme.primary
showClearButton: true showClearButton: true
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
focus: launcher.isVisible
enabled: launcher.isVisible enabled: launcher.isVisible
placeholderText: "Search applications..." placeholderText: "Search applications..."
onTextEdited: { onTextEdited: {
searchDebounceTimer.restart(); searchDebounceTimer.restart();
} }
Keys.onPressed: function(event) { 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); var firstApp = filteredModel.get(0);
if (firstApp.desktopEntry) { if (firstApp.desktopEntry) {
Prefs.addRecentApp(firstApp.desktopEntry); Prefs.addRecentApp(firstApp.desktopEntry);
@@ -458,9 +470,12 @@ PanelWindow {
} }
launcher.hide(); launcher.hide();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Escape) { } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up ||
launcher.hide(); (event.key === Qt.Key_Left && viewMode === "grid") ||
event.accepted = true; (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 itemHeight: 72
iconSize: 56 iconSize: 56
showDescription: true showDescription: true
hoverUpdatesSelection: false
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
if (modelData.desktopEntry) { if (modelData.desktopEntry) {
Prefs.addRecentApp(modelData.desktopEntry); Prefs.addRecentApp(modelData.desktopEntry);
@@ -616,6 +632,7 @@ PanelWindow {
columns: 4 columns: 4
adaptiveColumns: false adaptiveColumns: false
currentIndex: selectedIndex currentIndex: selectedIndex
hoverUpdatesSelection: false
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
if (modelData.desktopEntry) { if (modelData.desktopEntry) {
Prefs.addRecentApp(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
import QtQuick.Controls import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
PanelWindow { DankModal {
id: inputDialog id: inputDialog
property bool dialogVisible: false property bool dialogVisible: false
@@ -39,298 +36,118 @@ PanelWindow {
} }
visible: dialogVisible visible: dialogVisible
WlrLayershell.layer: WlrLayershell.Overlay size: "medium"
WlrLayershell.exclusiveZone: -1 keyboardFocus: "exclusive"
WlrLayershell.keyboardFocus: dialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
color: "transparent"
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
textInput.enabled = true; textInput.enabled = true;
Qt.callLater(function() { Qt.callLater(function() {
textInput.forceActiveFocus(); textInput.forceActiveFocus();
textInput.text = inputValue;
}); });
} else { } else {
textInput.enabled = false; textInput.enabled = false;
} }
} }
anchors { onBackgroundClicked: {
top: true hideDialog();
left: true cancelled();
right: true
bottom: true
} }
Rectangle { content: Component {
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
Column { Column {
anchors.fill: parent anchors.centerIn: parent
anchors.margins: Theme.spacingL width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingL spacing: Theme.spacingL
// Header Text {
Row { text: dialogTitle
width: parent.width font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
Column { font.weight: Font.Medium
width: parent.width - 40 anchors.horizontalCenter: parent.horizontalCenter
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 input Text {
Rectangle { text: dialogSubtitle
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: parent.width width: parent.width
height: 50 horizontalAlignment: Text.AlignHCenter
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) DankTextField {
border.width: textInput.activeFocus ? 2 : 1 id: textInput
width: parent.width
DankTextField { placeholderText: inputPlaceholder
id: textInput text: inputValue
echoMode: isPassword ? TextInput.Password : TextInput.Normal
anchors.fill: parent onTextChanged: inputValue = text
font.pixelSize: Theme.fontSizeMedium onAccepted: {
textColor: Theme.surfaceText hideDialog();
echoMode: isPassword && !showPasswordCheckbox.checked ? TextInput.Password : TextInput.Normal confirmed(text);
enabled: dialogVisible }
placeholderText: inputPlaceholder
backgroundColor: "transparent"
normalBorderColor: "transparent"
focusedBorderColor: "transparent"
onTextEdited: {
inputValue = text;
}
onAccepted: {
inputDialog.confirmed(inputValue);
hideDialog();
}
Component.onCompleted: {
if (dialogVisible)
forceActiveFocus();
}
}
} }
// Show password checkbox (only visible for password inputs)
Row { Row {
spacing: Theme.spacingS anchors.horizontalCenter: parent.horizontalCenter
visible: isPassword spacing: Theme.spacingM
Rectangle { 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 Text {
text: cancelButtonText
width: 20 font.pixelSize: Theme.fontSizeMedium
height: 20 color: Theme.surfaceText
radius: 4 font.weight: Font.Medium
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 {
anchors.centerIn: parent anchors.centerIn: parent
name: "check"
size: 12
color: Theme.background
visible: parent.checked
} }
MouseArea { MouseArea {
id: cancelButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
showPasswordCheckbox.checked = !showPasswordCheckbox.checked; hideDialog();
cancelled();
} }
} }
} }
Text { Rectangle {
text: "Show password" width: 120
font.pixelSize: Theme.fontSizeMedium height: 40
color: Theme.surfaceText radius: Theme.cornerRadius
anchors.verticalCenter: parent.verticalCenter color: confirmButton.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
}
}
// 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();
}
}
Text {
text: confirmButtonText
font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText
font.weight: Font.Medium
anchors.centerIn: parent
} }
Rectangle { MouseArea {
width: Math.max(80, confirmText.contentWidth + Theme.spacingM * 2) id: confirmButton
height: 36 anchors.fill: parent
radius: Theme.cornerRadius hoverEnabled: true
color: confirmArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary cursorShape: Qt.PointingHandCursor
enabled: inputValue.length > 0 onClicked: {
opacity: enabled ? 1 : 0.5 hideDialog();
confirmed(textInput.text);
Text {
id: confirmText
anchors.centerIn: parent
text: confirmButtonText
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
} }
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
import QtQuick.Controls import QtQuick.Controls
import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Widgets
PanelWindow { DankModal {
id: root id: root
property bool powerConfirmVisible: false property bool powerConfirmVisible: false
@@ -37,173 +35,13 @@ PanelWindow {
} }
} }
// DankModal configuration
visible: powerConfirmVisible visible: powerConfirmVisible
implicitWidth: 400 size: "small"
implicitHeight: 300 keyboardFocus: "ondemand"
WlrLayershell.layer: WlrLayershell.Overlay enableShadow: false
WlrLayershell.exclusiveZone: -1 onBackgroundClicked: {
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand powerConfirmVisible = false;
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
}
}
} }
Process { 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" target: "processlist"
} }
} }

View File

@@ -1,73 +1,35 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
import qs.Modules.Settings
PanelWindow { DankModal {
id: settingsPopup id: settingsPopup
property bool settingsVisible: false property bool settingsVisible: false
signal closingPopup() signal closingPopup()
onSettingsVisibleChanged: { onVisibleChanged: {
if (!settingsVisible) { if (!visible) {
closingPopup(); closingPopup();
// Hide any open dropdown when settings close
if (typeof globalDropdownWindow !== 'undefined') { if (typeof globalDropdownWindow !== 'undefined') {
globalDropdownWindow.hide(); globalDropdownWindow.hide();
} }
} }
} }
// DankModal configuration
visible: settingsVisible visible: settingsVisible
implicitWidth: 600 size: "extra-large"
implicitHeight: 700 keyboardFocus: "ondemand"
WlrLayershell.layer: WlrLayershell.Overlay enableShadow: true
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent"
anchors { onBackgroundClicked: {
top: true settingsVisible = false;
left: true
right: true
bottom: true
} }
// Darkened background content: Component {
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
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
@@ -111,40 +73,19 @@ PanelWindow {
} }
// Settings sections // Settings sections
Flickable { ScrollView {
id: settingsScrollView id: settingsScrollView
width: parent.width width: parent.width
height: parent.height - 80 height: parent.height - 50
clip: true clip: true
contentHeight: settingsColumn.height ScrollBar.vertical.policy: ScrollBar.AsNeeded
boundsBehavior: Flickable.DragAndOvershootBounds ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
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)
}
}
Column { Column {
id: settingsColumn id: settingsColumn
width: parent.width width: settingsScrollView.width - 20
spacing: Theme.spacingL spacing: Theme.spacingL
bottomPadding: Theme.spacingL
// Profile Settings // Profile Settings
SettingsSection { 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 // Keyboard focus and shortcuts

View File

@@ -59,4 +59,13 @@ Column {
return Prefs.setShowSystemTray(checked); 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 QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
PanelWindow { DankModal {
id: spotlightLauncher id: spotlightLauncher
property bool spotlightOpen: false property bool spotlightOpen: false
@@ -28,26 +26,20 @@ PanelWindow {
} }
property string selectedCategory: "All" property string selectedCategory: "All"
property string viewMode: Prefs.spotlightLauncherViewMode // "list" or "grid" property string viewMode: Prefs.spotlightLauncherViewMode // "list" or "grid"
property int gridColumns: 4
// ...existing code...
function show() { function show() {
console.log("SpotlightLauncher: show() called"); console.log("SpotlightLauncher: show() called");
spotlightOpen = true; spotlightOpen = true;
searchField.enabled = true;
console.log("SpotlightLauncher: spotlightOpen set to", spotlightOpen); console.log("SpotlightLauncher: spotlightOpen set to", spotlightOpen);
searchDebounceTimer.stop(); // Stop any pending search searchDebounceTimer.stop(); // Stop any pending search
updateFilteredApps(); // Immediate update when showing updateFilteredApps(); // Immediate update when showing
Qt.callLater(function() {
searchField.forceActiveFocus();
searchField.selectAll();
});
} }
function hide() { function hide() {
searchField.enabled = false; // Disable before hiding to prevent Wayland warnings
spotlightOpen = false; spotlightOpen = false;
searchDebounceTimer.stop(); // Stop any pending search searchDebounceTimer.stop(); // Stop any pending search
searchField.text = ""; searchQuery = "";
selectedIndex = 0; selectedIndex = 0;
selectedCategory = "All"; selectedCategory = "All";
updateFilteredApps(); updateFilteredApps();
@@ -60,11 +52,13 @@ PanelWindow {
show(); show();
} }
property string searchQuery: ""
property bool shouldFocusSearch: false
function updateFilteredApps() { function updateFilteredApps() {
filteredApps = []; filteredApps = [];
selectedIndex = 0; selectedIndex = 0;
var apps = []; var apps = [];
var searchQuery = searchField.text;
if (searchQuery.length === 0) { if (searchQuery.length === 0) {
// Show apps from category // Show apps from category
if (selectedCategory === "All") { if (selectedCategory === "All") {
@@ -156,58 +150,95 @@ PanelWindow {
} }
function selectNext() { function selectNext() {
if (filteredApps.length > 0) { if (filteredModel.count > 0) {
if (viewMode === "grid") { if (viewMode === "grid") {
// Grid navigation: move by columns // Grid navigation: move DOWN by one row (gridColumns positions)
var columnsCount = resultsGrid.columns || 6; var columnsCount = gridColumns;
selectedIndex = Math.min(selectedIndex + columnsCount, filteredApps.length - 1); var newIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1);
selectedIndex = newIndex;
} else { } else {
// List navigation: next item // List navigation: next item
selectedIndex = (selectedIndex + 1) % filteredApps.length; selectedIndex = (selectedIndex + 1) % filteredModel.count;
} }
} }
} }
function selectPrevious() { function selectPrevious() {
if (filteredApps.length > 0) { if (filteredModel.count > 0) {
if (viewMode === "grid") { if (viewMode === "grid") {
// Grid navigation: move by columns // Grid navigation: move UP by one row (gridColumns positions)
var columnsCount = resultsGrid.columns || 6; var columnsCount = gridColumns;
selectedIndex = Math.max(selectedIndex - columnsCount, 0); var newIndex = Math.max(selectedIndex - columnsCount, 0);
selectedIndex = newIndex;
} else { } else {
// List navigation: previous item // List navigation: previous item
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredApps.length - 1; selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1;
} }
} }
} }
function selectNextInRow() { function selectNextInRow() {
if (filteredApps.length > 0 && viewMode === "grid") if (filteredModel.count > 0 && viewMode === "grid") {
selectedIndex = Math.min(selectedIndex + 1, filteredApps.length - 1); // Grid navigation: move RIGHT by one position
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1);
}
} }
function selectPreviousInRow() { 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); selectedIndex = Math.max(selectedIndex - 1, 0);
}
} }
function launchSelected() { function launchSelected() {
if (filteredApps.length > 0 && selectedIndex >= 0 && selectedIndex < filteredApps.length) if (filteredModel.count > 0 && selectedIndex >= 0 && selectedIndex < filteredModel.count) {
launchApp(filteredApps[selectedIndex]); var selectedApp = filteredModel.get(selectedIndex);
launchApp(selectedApp);
}
} }
WlrLayershell.layer: WlrLayershell.Overlay // DankModal configuration
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: spotlightOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
WlrLayershell.namespace: "quickshell-spotlight"
visible: spotlightOpen 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: { onVisibleChanged: {
console.log("SpotlightLauncher visibility changed to:", visible); console.log("SpotlightLauncher visibility changed to:", visible);
if (visible && !spotlightOpen) {
show();
}
} }
color: "transparent"
onOpened: {
shouldFocusSearch = true;
}
onBackgroundClicked: {
spotlightOpen = false;
}
Component.onCompleted: { Component.onCompleted: {
console.log("SpotlightLauncher: Component.onCompleted called - component loaded successfully!"); console.log("SpotlightLauncher: Component.onCompleted called - component loaded successfully!");
var allCategories = AppSearchService.getAllCategories().filter((cat) => { var allCategories = AppSearchService.getAllCategories().filter((cat) => {
@@ -223,19 +254,11 @@ PanelWindow {
// Search debouncing // Search debouncing
Timer { Timer {
id: searchDebounceTimer id: searchDebounceTimer
interval: 50 interval: 50
repeat: false repeat: false
onTriggered: updateFilteredApps() onTriggered: updateFilteredApps()
} }
anchors {
top: true
left: true
right: true
bottom: true
}
ListModel { ListModel {
id: filteredModel id: filteredModel
} }
@@ -253,72 +276,59 @@ PanelWindow {
})); }));
if (spotlightOpen) if (spotlightOpen)
updateFilteredApps(); updateFilteredApps();
} }
} }
target: AppSearchService target: AppSearchService
} }
// Dimmed overlay background content: Component {
Rectangle { Item {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.4)
opacity: spotlightOpen ? 1 : 0
MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: spotlightOpen focus: true
onClicked: hide()
} Component.onCompleted: {
forceActiveFocus();
Behavior on opacity { }
NumberAnimation {
duration: Theme.shortDuration onVisibleChanged: {
easing.type: Theme.standardEasing 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 { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingXL anchors.margins: Theme.spacingXL
spacing: Theme.spacingL spacing: Theme.spacingL
// Combined row for categories and view mode toggle // Combined row for categories and view mode toggle
Column { Column {
@@ -368,11 +378,8 @@ PanelWindow {
updateFilteredApps(); updateFilteredApps();
} }
} }
} }
} }
} }
// Bottom row: Media, Office, Settings, System, Utilities (5 items) // Bottom row: Media, Office, Settings, System, Utilities (5 items)
@@ -412,15 +419,10 @@ PanelWindow {
updateFilteredApps(); updateFilteredApps();
} }
} }
} }
} }
} }
} }
} }
// Search field with view toggle buttons // Search field with view toggle buttons
@@ -444,31 +446,51 @@ PanelWindow {
showClearButton: true showClearButton: true
textColor: Theme.surfaceText textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
focus: spotlightOpen focus: false
enabled: spotlightOpen enabled: spotlightOpen
placeholderText: "Search applications..." placeholderText: "Search applications..."
text: searchQuery
onTextEdited: { onTextEdited: {
searchQuery = text;
searchDebounceTimer.restart(); 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) => { Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
hide(); hide();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && searchQuery.length > 0) {
launchSelected(); // Launch first app when typing in search field
event.accepted = true; if (filteredApps.length > 0) {
} else if (event.key === Qt.Key_Down) { launchApp(filteredApps[0]);
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; 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"); Prefs.setSpotlightLauncherViewMode("list");
} }
} }
} }
// Grid view button // Grid view button
@@ -536,11 +557,8 @@ PanelWindow {
Prefs.setSpotlightLauncherViewMode("grid"); Prefs.setSpotlightLauncherViewMode("grid");
} }
} }
} }
} }
} }
// Results container // Results container
@@ -562,6 +580,7 @@ PanelWindow {
itemHeight: 60 itemHeight: 60
iconSize: 40 iconSize: 40
showDescription: true showDescription: true
hoverUpdatesSelection: false
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
launchApp(modelData); launchApp(modelData);
} }
@@ -577,13 +596,14 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
visible: viewMode === "grid" visible: viewMode === "grid"
model: filteredModel model: filteredModel
columns: 6 columns: 4
adaptiveColumns: true adaptiveColumns: false
minCellWidth: 120 minCellWidth: 120
maxCellWidth: 160 maxCellWidth: 160
iconSizeRatio: 0.55 iconSizeRatio: 0.55
maxIconSize: 48 maxIconSize: 48
currentIndex: selectedIndex currentIndex: selectedIndex
hoverUpdatesSelection: false
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
launchApp(modelData); launchApp(modelData);
} }
@@ -591,37 +611,9 @@ PanelWindow {
selectedIndex = index; 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 { IpcHandler {
@@ -645,5 +637,4 @@ PanelWindow {
target: "spotlight" target: "spotlight"
} }
}
}

View File

@@ -10,25 +10,21 @@ Rectangle {
property bool isActive: false property bool isActive: false
readonly property bool nerdFontAvailable: Qt.fontFamilies()
.indexOf("Symbols Nerd Font") !== -1
width: 40 width: 40
height: 30 height: 30
radius: Theme.cornerRadius 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) 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 { SystemLogo {
visible: nerdFontAvailable && OSDetectorService.osLogo visible: Prefs.useOSLogo
anchors.centerIn: parent anchors.centerIn: parent
text: OSDetectorService.osLogo width: Theme.iconSize - 6
font.family: "Symbols Nerd Font" height: Theme.iconSize - 6
font.pixelSize: Theme.iconSize - 6
color: Theme.surfaceText color: Theme.surfaceText
} }
DankIcon { DankIcon {
visible: !nerdFontAvailable || !OSDetectorService.osLogo visible: !Prefs.useOSLogo
anchors.centerIn: parent anchors.centerIn: parent
name: "apps" name: "apps"
size: Theme.iconSize - 6 size: Theme.iconSize - 6

View File

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

View File

@@ -34,7 +34,10 @@ paru -S quickshell-git
```bash ```bash
# Arch # 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) 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 maxIconSize: 56
property int minIconSize: 32 property int minIconSize: 32
property real wheelStepSize: 60 property real wheelStepSize: 60
property bool hoverUpdatesSelection: true
signal itemClicked(int index, var modelData) signal itemClicked(int index, var modelData)
signal itemHovered(int index) signal itemHovered(int index)
@@ -136,7 +137,9 @@ ScrollView {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
z: 10 z: 10
onEntered: { onEntered: {
currentIndex = index; if (hoverUpdatesSelection) {
currentIndex = index;
}
itemHovered(index); itemHovered(index);
} }
onClicked: { onClicked: {

View File

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

View File

@@ -162,7 +162,7 @@ Item {
} }
} }
onActiveFocusChanged: (hasFocus) => { onFocusStateChanged: (hasFocus) => {
if (hasFocus) { if (hasFocus) {
dropdownHideTimer.stop() dropdownHideTimer.stop()
if (text.length <= 2) { 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 textEdited()
signal editingFinished() signal editingFinished()
signal accepted() signal accepted()
signal activeFocusChanged(bool hasFocus) signal focusStateChanged(bool hasFocus)
// Access to inner TextInput properties via functions // Access to inner TextInput properties via functions
function getActiveFocus() { function getActiveFocus() {
@@ -120,7 +120,7 @@ Rectangle {
onTextChanged: root.textEdited() onTextChanged: root.textEdited()
onEditingFinished: root.editingFinished() onEditingFinished: root.editingFinished()
onAccepted: root.accepted() onAccepted: root.accepted()
onActiveFocusChanged: root.activeFocusChanged(activeFocus) onActiveFocusChanged: root.focusStateChanged(activeFocus)
MouseArea { MouseArea {
anchors.fill: parent 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
}
}