mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
Add DankModal and SystemLogo widgets
This commit is contained in:
@@ -35,6 +35,7 @@ Singleton {
|
||||
property string iconTheme: "System Default"
|
||||
property var availableIconThemes: ["System Default"]
|
||||
property string systemDefaultIconTheme: "Adwaita"
|
||||
property bool useOSLogo: false
|
||||
|
||||
function loadSettings() {
|
||||
parseSettings(settingsFile.text());
|
||||
@@ -67,6 +68,7 @@ Singleton {
|
||||
spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list";
|
||||
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto";
|
||||
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
|
||||
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false;
|
||||
applyStoredTheme();
|
||||
detectAvailableIconThemes();
|
||||
updateGtkIconTheme(iconTheme);
|
||||
@@ -103,7 +105,8 @@ Singleton {
|
||||
"appLauncherViewMode": appLauncherViewMode,
|
||||
"spotlightLauncherViewMode": spotlightLauncherViewMode,
|
||||
"networkPreference": networkPreference,
|
||||
"iconTheme": iconTheme
|
||||
"iconTheme": iconTheme,
|
||||
"useOSLogo": useOSLogo
|
||||
}, null, 2));
|
||||
}
|
||||
|
||||
@@ -378,6 +381,11 @@ gtk-application-prefer-dark-theme=true`;
|
||||
}
|
||||
}
|
||||
|
||||
function setUseOSLogo(enabled) {
|
||||
useOSLogo = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
Component.onCompleted: loadSettings()
|
||||
onShowSystemResourcesChanged: {
|
||||
if (typeof SystemMonitorService !== 'undefined')
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2121,4 +2121,4 @@ PanelWindow {
|
||||
target: "processlist"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,10 @@ paru -S quickshell-git
|
||||
|
||||
```bash
|
||||
# Arch
|
||||
paru -S nerd-fonts ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil swaybg
|
||||
paru -S ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil swaybg
|
||||
```
|
||||
|
||||
**Note on networking:**
|
||||
|
||||
3. Configure SwayBG (If Desired)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ ScrollView {
|
||||
property int maxIconSize: 56
|
||||
property int minIconSize: 32
|
||||
property real wheelStepSize: 60
|
||||
property bool hoverUpdatesSelection: true
|
||||
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemHovered(int index)
|
||||
@@ -136,7 +137,9 @@ ScrollView {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 10
|
||||
onEntered: {
|
||||
currentIndex = index;
|
||||
if (hoverUpdatesSelection) {
|
||||
currentIndex = index;
|
||||
}
|
||||
itemHovered(index);
|
||||
}
|
||||
onClicked: {
|
||||
|
||||
@@ -14,6 +14,7 @@ ScrollView {
|
||||
property real wheelStepSize: 60
|
||||
property bool showDescription: true
|
||||
property int itemSpacing: Theme.spacingS
|
||||
property bool hoverUpdatesSelection: true
|
||||
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemHovered(int index)
|
||||
@@ -133,7 +134,9 @@ ScrollView {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 10
|
||||
onEntered: {
|
||||
listView.currentIndex = index;
|
||||
if (hoverUpdatesSelection) {
|
||||
listView.currentIndex = index;
|
||||
}
|
||||
itemHovered(index);
|
||||
}
|
||||
onClicked: {
|
||||
|
||||
@@ -162,7 +162,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
onActiveFocusChanged: (hasFocus) => {
|
||||
onFocusStateChanged: (hasFocus) => {
|
||||
if (hasFocus) {
|
||||
dropdownHideTimer.stop()
|
||||
if (text.length <= 2) {
|
||||
|
||||
235
Widgets/DankModal.qml
Normal file
235
Widgets/DankModal.qml
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ Rectangle {
|
||||
signal textEdited()
|
||||
signal editingFinished()
|
||||
signal accepted()
|
||||
signal activeFocusChanged(bool hasFocus)
|
||||
signal focusStateChanged(bool hasFocus)
|
||||
|
||||
// Access to inner TextInput properties via functions
|
||||
function getActiveFocus() {
|
||||
@@ -120,7 +120,7 @@ Rectangle {
|
||||
onTextChanged: root.textEdited()
|
||||
onEditingFinished: root.editingFinished()
|
||||
onAccepted: root.accepted()
|
||||
onActiveFocusChanged: root.activeFocusChanged(activeFocus)
|
||||
onActiveFocusChanged: root.focusStateChanged(activeFocus)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
39
Widgets/SystemLogo.qml
Normal file
39
Widgets/SystemLogo.qml
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user