1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -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

@@ -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 {
}
}
}
}
}