mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-29 07:52:50 -05:00
Add DankModal and SystemLogo widgets
This commit is contained in:
@@ -35,6 +35,7 @@ Singleton {
|
|||||||
property string iconTheme: "System Default"
|
property string iconTheme: "System Default"
|
||||||
property var availableIconThemes: ["System Default"]
|
property var availableIconThemes: ["System Default"]
|
||||||
property string systemDefaultIconTheme: "Adwaita"
|
property string systemDefaultIconTheme: "Adwaita"
|
||||||
|
property bool useOSLogo: false
|
||||||
|
|
||||||
function loadSettings() {
|
function loadSettings() {
|
||||||
parseSettings(settingsFile.text());
|
parseSettings(settingsFile.text());
|
||||||
@@ -67,6 +68,7 @@ Singleton {
|
|||||||
spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list";
|
spotlightLauncherViewMode = settings.spotlightLauncherViewMode !== undefined ? settings.spotlightLauncherViewMode : "list";
|
||||||
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto";
|
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto";
|
||||||
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
|
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
|
||||||
|
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false;
|
||||||
applyStoredTheme();
|
applyStoredTheme();
|
||||||
detectAvailableIconThemes();
|
detectAvailableIconThemes();
|
||||||
updateGtkIconTheme(iconTheme);
|
updateGtkIconTheme(iconTheme);
|
||||||
@@ -103,7 +105,8 @@ Singleton {
|
|||||||
"appLauncherViewMode": appLauncherViewMode,
|
"appLauncherViewMode": appLauncherViewMode,
|
||||||
"spotlightLauncherViewMode": spotlightLauncherViewMode,
|
"spotlightLauncherViewMode": spotlightLauncherViewMode,
|
||||||
"networkPreference": networkPreference,
|
"networkPreference": networkPreference,
|
||||||
"iconTheme": iconTheme
|
"iconTheme": iconTheme,
|
||||||
|
"useOSLogo": useOSLogo
|
||||||
}, null, 2));
|
}, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +381,11 @@ gtk-application-prefer-dark-theme=true`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setUseOSLogo(enabled) {
|
||||||
|
useOSLogo = enabled;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: loadSettings()
|
Component.onCompleted: loadSettings()
|
||||||
onShowSystemResourcesChanged: {
|
onShowSystemResourcesChanged: {
|
||||||
if (typeof SystemMonitorService !== 'undefined')
|
if (typeof SystemMonitorService !== 'undefined')
|
||||||
|
|||||||
@@ -81,7 +81,9 @@ PanelWindow {
|
|||||||
if (viewMode === "grid") {
|
if (viewMode === "grid") {
|
||||||
// Grid navigation: move by columns
|
// Grid navigation: move by columns
|
||||||
var columnsCount = appGrid.columns || 4;
|
var columnsCount = appGrid.columns || 4;
|
||||||
selectedIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1);
|
var newIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1);
|
||||||
|
console.log("Grid navigation DOWN: from", selectedIndex, "to", newIndex, "columns:", columnsCount);
|
||||||
|
selectedIndex = newIndex;
|
||||||
} else {
|
} else {
|
||||||
// List navigation: next item
|
// List navigation: next item
|
||||||
selectedIndex = (selectedIndex + 1) % filteredModel.count;
|
selectedIndex = (selectedIndex + 1) % filteredModel.count;
|
||||||
@@ -94,7 +96,9 @@ PanelWindow {
|
|||||||
if (viewMode === "grid") {
|
if (viewMode === "grid") {
|
||||||
// Grid navigation: move by columns
|
// Grid navigation: move by columns
|
||||||
var columnsCount = appGrid.columns || 4;
|
var columnsCount = appGrid.columns || 4;
|
||||||
selectedIndex = Math.max(selectedIndex - columnsCount, 0);
|
var newIndex = Math.max(selectedIndex - columnsCount, 0);
|
||||||
|
console.log("Grid navigation UP: from", selectedIndex, "to", newIndex, "columns:", columnsCount);
|
||||||
|
selectedIndex = newIndex;
|
||||||
} else {
|
} else {
|
||||||
// List navigation: previous item
|
// List navigation: previous item
|
||||||
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1;
|
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1;
|
||||||
@@ -145,9 +149,6 @@ PanelWindow {
|
|||||||
searchField.enabled = true;
|
searchField.enabled = true;
|
||||||
searchDebounceTimer.stop(); // Stop any pending search
|
searchDebounceTimer.stop(); // Stop any pending search
|
||||||
updateFilteredModel();
|
updateFilteredModel();
|
||||||
Qt.callLater(function() {
|
|
||||||
searchField.forceActiveFocus();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
@@ -368,6 +369,12 @@ PanelWindow {
|
|||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (launcher.isVisible) {
|
||||||
|
forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
// Handle keyboard shortcuts
|
// Handle keyboard shortcuts
|
||||||
Keys.onPressed: function(event) {
|
Keys.onPressed: function(event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
@@ -388,6 +395,11 @@ PanelWindow {
|
|||||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||||
launchSelected();
|
launchSelected();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
} else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\s]/)) {
|
||||||
|
// User started typing, focus search field and pass the character
|
||||||
|
searchField.forceActiveFocus();
|
||||||
|
searchField.text = event.text;
|
||||||
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,14 +453,14 @@ PanelWindow {
|
|||||||
leftIconFocusedColor: Theme.primary
|
leftIconFocusedColor: Theme.primary
|
||||||
showClearButton: true
|
showClearButton: true
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
focus: launcher.isVisible
|
|
||||||
enabled: launcher.isVisible
|
enabled: launcher.isVisible
|
||||||
placeholderText: "Search applications..."
|
placeholderText: "Search applications..."
|
||||||
onTextEdited: {
|
onTextEdited: {
|
||||||
searchDebounceTimer.restart();
|
searchDebounceTimer.restart();
|
||||||
}
|
}
|
||||||
Keys.onPressed: function(event) {
|
Keys.onPressed: function(event) {
|
||||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && filteredModel.count) {
|
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && filteredModel.count && text.length > 0) {
|
||||||
|
// Launch first app when typing in search field
|
||||||
var firstApp = filteredModel.get(0);
|
var firstApp = filteredModel.get(0);
|
||||||
if (firstApp.desktopEntry) {
|
if (firstApp.desktopEntry) {
|
||||||
Prefs.addRecentApp(firstApp.desktopEntry);
|
Prefs.addRecentApp(firstApp.desktopEntry);
|
||||||
@@ -458,9 +470,12 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
launcher.hide();
|
launcher.hide();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_Escape) {
|
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up ||
|
||||||
launcher.hide();
|
(event.key === Qt.Key_Left && viewMode === "grid") ||
|
||||||
event.accepted = true;
|
(event.key === Qt.Key_Right && viewMode === "grid") ||
|
||||||
|
((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||||
|
// Pass navigation keys and enter (when not searching) to main handler
|
||||||
|
event.accepted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,6 +607,7 @@ PanelWindow {
|
|||||||
itemHeight: 72
|
itemHeight: 72
|
||||||
iconSize: 56
|
iconSize: 56
|
||||||
showDescription: true
|
showDescription: true
|
||||||
|
hoverUpdatesSelection: false
|
||||||
onItemClicked: function(index, modelData) {
|
onItemClicked: function(index, modelData) {
|
||||||
if (modelData.desktopEntry) {
|
if (modelData.desktopEntry) {
|
||||||
Prefs.addRecentApp(modelData.desktopEntry);
|
Prefs.addRecentApp(modelData.desktopEntry);
|
||||||
@@ -616,6 +632,7 @@ PanelWindow {
|
|||||||
columns: 4
|
columns: 4
|
||||||
adaptiveColumns: false
|
adaptiveColumns: false
|
||||||
currentIndex: selectedIndex
|
currentIndex: selectedIndex
|
||||||
|
hoverUpdatesSelection: false
|
||||||
onItemClicked: function(index, modelData) {
|
onItemClicked: function(index, modelData) {
|
||||||
if (modelData.desktopEntry) {
|
if (modelData.desktopEntry) {
|
||||||
Prefs.addRecentApp(modelData.desktopEntry);
|
Prefs.addRecentApp(modelData.desktopEntry);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,9 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
DankModal {
|
||||||
id: inputDialog
|
id: inputDialog
|
||||||
|
|
||||||
property bool dialogVisible: false
|
property bool dialogVisible: false
|
||||||
@@ -39,298 +36,118 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visible: dialogVisible
|
visible: dialogVisible
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
size: "medium"
|
||||||
WlrLayershell.exclusiveZone: -1
|
keyboardFocus: "exclusive"
|
||||||
WlrLayershell.keyboardFocus: dialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
textInput.enabled = true;
|
textInput.enabled = true;
|
||||||
Qt.callLater(function() {
|
Qt.callLater(function() {
|
||||||
textInput.forceActiveFocus();
|
textInput.forceActiveFocus();
|
||||||
textInput.text = inputValue;
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
textInput.enabled = false;
|
textInput.enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors {
|
onBackgroundClicked: {
|
||||||
top: true
|
hideDialog();
|
||||||
left: true
|
cancelled();
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
content: Component {
|
||||||
anchors.fill: parent
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.5)
|
|
||||||
opacity: dialogVisible ? 1 : 0
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
textInput.enabled = false; // Disable before hiding to prevent Wayland warnings
|
|
||||||
inputDialog.cancelled();
|
|
||||||
hideDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.min(400, parent.width - Theme.spacingL * 2)
|
|
||||||
height: Math.min(250, parent.height - Theme.spacingL * 2)
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
|
||||||
border.width: 1
|
|
||||||
opacity: dialogVisible ? 1 : 0
|
|
||||||
scale: dialogVisible ? 1 : 0.9
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.centerIn: parent
|
||||||
anchors.margins: Theme.spacingL
|
width: parent.width - Theme.spacingL * 2
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
// Header
|
Text {
|
||||||
Row {
|
text: dialogTitle
|
||||||
width: parent.width
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
Column {
|
font.weight: Font.Medium
|
||||||
width: parent.width - 40
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: dialogTitle
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: dialogSubtitle
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
|
||||||
width: parent.width
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
maximumLineCount: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
|
||||||
onClicked: {
|
|
||||||
inputDialog.cancelled();
|
|
||||||
hideDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text input
|
Text {
|
||||||
Rectangle {
|
text: dialogSubtitle
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 50
|
horizontalAlignment: Text.AlignHCenter
|
||||||
radius: Theme.cornerRadius
|
}
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
|
||||||
border.color: textInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
DankTextField {
|
||||||
border.width: textInput.activeFocus ? 2 : 1
|
id: textInput
|
||||||
|
width: parent.width
|
||||||
DankTextField {
|
placeholderText: inputPlaceholder
|
||||||
id: textInput
|
text: inputValue
|
||||||
|
echoMode: isPassword ? TextInput.Password : TextInput.Normal
|
||||||
anchors.fill: parent
|
onTextChanged: inputValue = text
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
onAccepted: {
|
||||||
textColor: Theme.surfaceText
|
hideDialog();
|
||||||
echoMode: isPassword && !showPasswordCheckbox.checked ? TextInput.Password : TextInput.Normal
|
confirmed(text);
|
||||||
enabled: dialogVisible
|
}
|
||||||
placeholderText: inputPlaceholder
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
normalBorderColor: "transparent"
|
|
||||||
focusedBorderColor: "transparent"
|
|
||||||
onTextEdited: {
|
|
||||||
inputValue = text;
|
|
||||||
}
|
|
||||||
onAccepted: {
|
|
||||||
inputDialog.confirmed(inputValue);
|
|
||||||
hideDialog();
|
|
||||||
}
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (dialogVisible)
|
|
||||||
forceActiveFocus();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show password checkbox (only visible for password inputs)
|
|
||||||
Row {
|
Row {
|
||||||
spacing: Theme.spacingS
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: isPassword
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: showPasswordCheckbox
|
width: 120
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||||
|
|
||||||
property bool checked: false
|
Text {
|
||||||
|
text: cancelButtonText
|
||||||
width: 20
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
height: 20
|
color: Theme.surfaceText
|
||||||
radius: 4
|
font.weight: Font.Medium
|
||||||
color: checked ? Theme.primary : "transparent"
|
|
||||||
border.color: checked ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5)
|
|
||||||
border.width: 2
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "check"
|
|
||||||
size: 12
|
|
||||||
color: Theme.background
|
|
||||||
visible: parent.checked
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
id: cancelButton
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
|
hideDialog();
|
||||||
|
cancelled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Rectangle {
|
||||||
text: "Show password"
|
width: 120
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
height: 40
|
||||||
color: Theme.surfaceText
|
radius: Theme.cornerRadius
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
color: confirmButton.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: cancelArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: cancelText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: cancelButtonText
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
textInput.enabled = false; // Disable before hiding to prevent Wayland warnings
|
|
||||||
inputDialog.cancelled();
|
|
||||||
hideDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: confirmButtonText
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.primaryText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
MouseArea {
|
||||||
width: Math.max(80, confirmText.contentWidth + Theme.spacingM * 2)
|
id: confirmButton
|
||||||
height: 36
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
hoverEnabled: true
|
||||||
color: confirmArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
cursorShape: Qt.PointingHandCursor
|
||||||
enabled: inputValue.length > 0
|
onClicked: {
|
||||||
opacity: enabled ? 1 : 0.5
|
hideDialog();
|
||||||
|
confirmed(textInput.text);
|
||||||
Text {
|
|
||||||
id: confirmText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: confirmButtonText
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.background
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: confirmArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
enabled: parent.enabled
|
|
||||||
onClicked: {
|
|
||||||
inputDialog.confirmed(inputValue);
|
|
||||||
hideDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
DankModal {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool powerConfirmVisible: false
|
property bool powerConfirmVisible: false
|
||||||
@@ -37,173 +35,13 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DankModal configuration
|
||||||
visible: powerConfirmVisible
|
visible: powerConfirmVisible
|
||||||
implicitWidth: 400
|
size: "small"
|
||||||
implicitHeight: 300
|
keyboardFocus: "ondemand"
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
enableShadow: false
|
||||||
WlrLayershell.exclusiveZone: -1
|
onBackgroundClicked: {
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
powerConfirmVisible = false;
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Darkened background
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "black"
|
|
||||||
opacity: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.min(400, parent.width - Theme.spacingL * 2)
|
|
||||||
height: Math.min(200, parent.height - Theme.spacingL * 2)
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
|
||||||
border.width: 1
|
|
||||||
opacity: powerConfirmVisible ? 1 : 0
|
|
||||||
scale: powerConfirmVisible ? 1 : 0.9
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width - Theme.spacingL * 2
|
|
||||||
spacing: Theme.spacingL
|
|
||||||
|
|
||||||
// Title
|
|
||||||
Text {
|
|
||||||
text: powerConfirmTitle
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: {
|
|
||||||
switch (powerConfirmAction) {
|
|
||||||
case "poweroff":
|
|
||||||
return Theme.error;
|
|
||||||
case "reboot":
|
|
||||||
return Theme.warning;
|
|
||||||
default:
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
font.weight: Font.Medium
|
|
||||||
width: parent.width
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message
|
|
||||||
Text {
|
|
||||||
text: powerConfirmMessage
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
height: Theme.spacingL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
Row {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
// Cancel button
|
|
||||||
Rectangle {
|
|
||||||
width: 120
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Cancel"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelButton
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
powerConfirmVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm button
|
|
||||||
Rectangle {
|
|
||||||
width: 120
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
let baseColor;
|
|
||||||
switch (powerConfirmAction) {
|
|
||||||
case "poweroff":
|
|
||||||
baseColor = Theme.error;
|
|
||||||
break;
|
|
||||||
case "reboot":
|
|
||||||
baseColor = Theme.warning;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
baseColor = Theme.primary;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Confirm"
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.primaryText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: confirmButton
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
powerConfirmVisible = false;
|
|
||||||
executePowerAction(powerConfirmAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
@@ -217,4 +55,130 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content: Component {
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Theme.spacingL * 2
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
// Title
|
||||||
|
Text {
|
||||||
|
text: powerConfirmTitle
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: {
|
||||||
|
switch (powerConfirmAction) {
|
||||||
|
case "poweroff":
|
||||||
|
return Theme.error;
|
||||||
|
case "reboot":
|
||||||
|
return Theme.warning;
|
||||||
|
default:
|
||||||
|
return Theme.surfaceText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font.weight: Font.Medium
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message
|
||||||
|
Text {
|
||||||
|
text: powerConfirmMessage
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
height: Theme.spacingL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
// Cancel button
|
||||||
|
Rectangle {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Cancel"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
powerConfirmVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm button
|
||||||
|
Rectangle {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
let baseColor;
|
||||||
|
switch (powerConfirmAction) {
|
||||||
|
case "poweroff":
|
||||||
|
baseColor = Theme.error;
|
||||||
|
break;
|
||||||
|
case "reboot":
|
||||||
|
baseColor = Theme.warning;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
baseColor = Theme.primary;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Confirm"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.primaryText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: confirmButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
powerConfirmVisible = false;
|
||||||
|
executePowerAction(powerConfirmAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2121,4 +2121,4 @@ PanelWindow {
|
|||||||
target: "processlist"
|
target: "processlist"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,73 +1,35 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.Modules.Settings
|
|
||||||
|
|
||||||
PanelWindow {
|
DankModal {
|
||||||
id: settingsPopup
|
id: settingsPopup
|
||||||
|
|
||||||
property bool settingsVisible: false
|
property bool settingsVisible: false
|
||||||
|
|
||||||
signal closingPopup()
|
signal closingPopup()
|
||||||
|
|
||||||
onSettingsVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!settingsVisible) {
|
if (!visible) {
|
||||||
closingPopup();
|
closingPopup();
|
||||||
// Hide any open dropdown when settings close
|
|
||||||
if (typeof globalDropdownWindow !== 'undefined') {
|
if (typeof globalDropdownWindow !== 'undefined') {
|
||||||
globalDropdownWindow.hide();
|
globalDropdownWindow.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DankModal configuration
|
||||||
visible: settingsVisible
|
visible: settingsVisible
|
||||||
implicitWidth: 600
|
size: "extra-large"
|
||||||
implicitHeight: 700
|
keyboardFocus: "ondemand"
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
enableShadow: true
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
onBackgroundClicked: {
|
||||||
top: true
|
settingsVisible = false;
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Darkened background
|
content: Component {
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "black"
|
|
||||||
opacity: 0.5
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: settingsPopup.settingsVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main settings panel - spotlight-like centered appearance
|
|
||||||
Rectangle {
|
|
||||||
id: mainPanel
|
|
||||||
|
|
||||||
width: Math.min(600, parent.width - Theme.spacingXL * 2)
|
|
||||||
height: Math.min(700, parent.height - Theme.spacingXL * 2)
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
// Simple opacity and scale control tied directly to settingsVisible
|
|
||||||
opacity: settingsPopup.settingsVisible ? 1 : 0
|
|
||||||
scale: settingsPopup.settingsVisible ? 1 : 0.95
|
|
||||||
// Add shadow effect
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingL
|
anchors.margins: Theme.spacingL
|
||||||
@@ -111,40 +73,19 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Settings sections
|
// Settings sections
|
||||||
Flickable {
|
ScrollView {
|
||||||
id: settingsScrollView
|
id: settingsScrollView
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - 80
|
height: parent.height - 50
|
||||||
clip: true
|
clip: true
|
||||||
contentHeight: settingsColumn.height
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
flickDeceleration: 8000
|
|
||||||
maximumFlickVelocity: 15000
|
|
||||||
|
|
||||||
property real wheelStepSize: 60
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
propagateComposedEvents: true
|
|
||||||
z: -1
|
|
||||||
onWheel: (wheel) => {
|
|
||||||
var delta = wheel.angleDelta.y
|
|
||||||
var steps = delta / 120
|
|
||||||
settingsScrollView.contentY -= steps * settingsScrollView.wheelStepSize
|
|
||||||
|
|
||||||
// Keep within bounds
|
|
||||||
if (settingsScrollView.contentY < 0)
|
|
||||||
settingsScrollView.contentY = 0
|
|
||||||
else if (settingsScrollView.contentY > settingsScrollView.contentHeight - settingsScrollView.height)
|
|
||||||
settingsScrollView.contentY = Math.max(0, settingsScrollView.contentHeight - settingsScrollView.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: settingsColumn
|
id: settingsColumn
|
||||||
width: parent.width
|
width: settingsScrollView.width - 20
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
bottomPadding: Theme.spacingL
|
||||||
|
|
||||||
// Profile Settings
|
// Profile Settings
|
||||||
SettingsSection {
|
SettingsSection {
|
||||||
@@ -193,32 +134,6 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 8
|
|
||||||
shadowBlur: 1
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard focus and shortcuts
|
// Keyboard focus and shortcuts
|
||||||
|
|||||||
@@ -59,4 +59,13 @@ Column {
|
|||||||
return Prefs.setShowSystemTray(checked);
|
return Prefs.setShowSystemTray(checked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
text: "Use OS Logo for App Launcher"
|
||||||
|
description: "Display operating system logo instead of apps icon"
|
||||||
|
checked: Prefs.useOSLogo
|
||||||
|
onToggled: (checked) => {
|
||||||
|
return Prefs.setUseOSLogo(checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,11 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
DankModal {
|
||||||
id: spotlightLauncher
|
id: spotlightLauncher
|
||||||
|
|
||||||
property bool spotlightOpen: false
|
property bool spotlightOpen: false
|
||||||
@@ -28,26 +26,20 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
property string selectedCategory: "All"
|
property string selectedCategory: "All"
|
||||||
property string viewMode: Prefs.spotlightLauncherViewMode // "list" or "grid"
|
property string viewMode: Prefs.spotlightLauncherViewMode // "list" or "grid"
|
||||||
|
property int gridColumns: 4
|
||||||
|
|
||||||
// ...existing code...
|
|
||||||
function show() {
|
function show() {
|
||||||
console.log("SpotlightLauncher: show() called");
|
console.log("SpotlightLauncher: show() called");
|
||||||
spotlightOpen = true;
|
spotlightOpen = true;
|
||||||
searchField.enabled = true;
|
|
||||||
console.log("SpotlightLauncher: spotlightOpen set to", spotlightOpen);
|
console.log("SpotlightLauncher: spotlightOpen set to", spotlightOpen);
|
||||||
searchDebounceTimer.stop(); // Stop any pending search
|
searchDebounceTimer.stop(); // Stop any pending search
|
||||||
updateFilteredApps(); // Immediate update when showing
|
updateFilteredApps(); // Immediate update when showing
|
||||||
Qt.callLater(function() {
|
|
||||||
searchField.forceActiveFocus();
|
|
||||||
searchField.selectAll();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
searchField.enabled = false; // Disable before hiding to prevent Wayland warnings
|
|
||||||
spotlightOpen = false;
|
spotlightOpen = false;
|
||||||
searchDebounceTimer.stop(); // Stop any pending search
|
searchDebounceTimer.stop(); // Stop any pending search
|
||||||
searchField.text = "";
|
searchQuery = "";
|
||||||
selectedIndex = 0;
|
selectedIndex = 0;
|
||||||
selectedCategory = "All";
|
selectedCategory = "All";
|
||||||
updateFilteredApps();
|
updateFilteredApps();
|
||||||
@@ -60,11 +52,13 @@ PanelWindow {
|
|||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property string searchQuery: ""
|
||||||
|
property bool shouldFocusSearch: false
|
||||||
|
|
||||||
function updateFilteredApps() {
|
function updateFilteredApps() {
|
||||||
filteredApps = [];
|
filteredApps = [];
|
||||||
selectedIndex = 0;
|
selectedIndex = 0;
|
||||||
var apps = [];
|
var apps = [];
|
||||||
var searchQuery = searchField.text;
|
|
||||||
if (searchQuery.length === 0) {
|
if (searchQuery.length === 0) {
|
||||||
// Show apps from category
|
// Show apps from category
|
||||||
if (selectedCategory === "All") {
|
if (selectedCategory === "All") {
|
||||||
@@ -156,58 +150,95 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectNext() {
|
function selectNext() {
|
||||||
if (filteredApps.length > 0) {
|
if (filteredModel.count > 0) {
|
||||||
if (viewMode === "grid") {
|
if (viewMode === "grid") {
|
||||||
// Grid navigation: move by columns
|
// Grid navigation: move DOWN by one row (gridColumns positions)
|
||||||
var columnsCount = resultsGrid.columns || 6;
|
var columnsCount = gridColumns;
|
||||||
selectedIndex = Math.min(selectedIndex + columnsCount, filteredApps.length - 1);
|
var newIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1);
|
||||||
|
selectedIndex = newIndex;
|
||||||
} else {
|
} else {
|
||||||
// List navigation: next item
|
// List navigation: next item
|
||||||
selectedIndex = (selectedIndex + 1) % filteredApps.length;
|
selectedIndex = (selectedIndex + 1) % filteredModel.count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectPrevious() {
|
function selectPrevious() {
|
||||||
if (filteredApps.length > 0) {
|
if (filteredModel.count > 0) {
|
||||||
if (viewMode === "grid") {
|
if (viewMode === "grid") {
|
||||||
// Grid navigation: move by columns
|
// Grid navigation: move UP by one row (gridColumns positions)
|
||||||
var columnsCount = resultsGrid.columns || 6;
|
var columnsCount = gridColumns;
|
||||||
selectedIndex = Math.max(selectedIndex - columnsCount, 0);
|
var newIndex = Math.max(selectedIndex - columnsCount, 0);
|
||||||
|
selectedIndex = newIndex;
|
||||||
} else {
|
} else {
|
||||||
// List navigation: previous item
|
// List navigation: previous item
|
||||||
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredApps.length - 1;
|
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNextInRow() {
|
function selectNextInRow() {
|
||||||
if (filteredApps.length > 0 && viewMode === "grid")
|
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||||
selectedIndex = Math.min(selectedIndex + 1, filteredApps.length - 1);
|
// Grid navigation: move RIGHT by one position
|
||||||
|
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectPreviousInRow() {
|
function selectPreviousInRow() {
|
||||||
if (filteredApps.length > 0 && viewMode === "grid")
|
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||||
|
// Grid navigation: move LEFT by one position
|
||||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function launchSelected() {
|
function launchSelected() {
|
||||||
if (filteredApps.length > 0 && selectedIndex >= 0 && selectedIndex < filteredApps.length)
|
if (filteredModel.count > 0 && selectedIndex >= 0 && selectedIndex < filteredModel.count) {
|
||||||
launchApp(filteredApps[selectedIndex]);
|
var selectedApp = filteredModel.get(selectedIndex);
|
||||||
|
launchApp(selectedApp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
// DankModal configuration
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: spotlightOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
|
||||||
WlrLayershell.namespace: "quickshell-spotlight"
|
|
||||||
visible: spotlightOpen
|
visible: spotlightOpen
|
||||||
|
size: "custom"
|
||||||
|
customWidth: 600
|
||||||
|
customHeight: {
|
||||||
|
// Fixed height to prevent shrinking - consistent experience
|
||||||
|
let baseHeight = Theme.spacingXL * 2 + Theme.spacingL * 3;
|
||||||
|
// Add category section height if visible
|
||||||
|
if (categories.length > 1 || filteredModel.count > 0)
|
||||||
|
baseHeight += 36 * 2 + Theme.spacingS + Theme.spacingM;
|
||||||
|
// Add search field height
|
||||||
|
baseHeight += 56;
|
||||||
|
// Add fixed results height for consistent size
|
||||||
|
let fixedResultsHeight = 400;
|
||||||
|
// Always same height regardless of content
|
||||||
|
baseHeight += fixedResultsHeight;
|
||||||
|
// Ensure reasonable bounds
|
||||||
|
return Math.min(Math.max(baseHeight, 500), 800);
|
||||||
|
}
|
||||||
|
keyboardFocus: "exclusive"
|
||||||
|
backgroundColor: Theme.popupBackground()
|
||||||
|
cornerRadius: Theme.cornerRadiusXLarge
|
||||||
|
borderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
borderWidth: 1
|
||||||
|
enableShadow: true
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
console.log("SpotlightLauncher visibility changed to:", visible);
|
console.log("SpotlightLauncher visibility changed to:", visible);
|
||||||
|
if (visible && !spotlightOpen) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
color: "transparent"
|
|
||||||
|
onOpened: {
|
||||||
|
shouldFocusSearch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackgroundClicked: {
|
||||||
|
spotlightOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
console.log("SpotlightLauncher: Component.onCompleted called - component loaded successfully!");
|
console.log("SpotlightLauncher: Component.onCompleted called - component loaded successfully!");
|
||||||
var allCategories = AppSearchService.getAllCategories().filter((cat) => {
|
var allCategories = AppSearchService.getAllCategories().filter((cat) => {
|
||||||
@@ -223,19 +254,11 @@ PanelWindow {
|
|||||||
// Search debouncing
|
// Search debouncing
|
||||||
Timer {
|
Timer {
|
||||||
id: searchDebounceTimer
|
id: searchDebounceTimer
|
||||||
|
|
||||||
interval: 50
|
interval: 50
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: updateFilteredApps()
|
onTriggered: updateFilteredApps()
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
id: filteredModel
|
id: filteredModel
|
||||||
}
|
}
|
||||||
@@ -253,72 +276,59 @@ PanelWindow {
|
|||||||
}));
|
}));
|
||||||
if (spotlightOpen)
|
if (spotlightOpen)
|
||||||
updateFilteredApps();
|
updateFilteredApps();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: AppSearchService
|
target: AppSearchService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dimmed overlay background
|
content: Component {
|
||||||
Rectangle {
|
Item {
|
||||||
anchors.fill: parent
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.4)
|
|
||||||
opacity: spotlightOpen ? 1 : 0
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: spotlightOpen
|
focus: true
|
||||||
onClicked: hide()
|
|
||||||
}
|
Component.onCompleted: {
|
||||||
|
forceActiveFocus();
|
||||||
Behavior on opacity {
|
}
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
onVisibleChanged: {
|
||||||
easing.type: Theme.standardEasing
|
if (visible) {
|
||||||
|
forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle keyboard shortcuts
|
||||||
|
Keys.onPressed: function(event) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
hide();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Down) {
|
||||||
|
selectNext();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Up) {
|
||||||
|
selectPrevious();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Right && viewMode === "grid") {
|
||||||
|
selectNextInRow();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Left && viewMode === "grid") {
|
||||||
|
selectPreviousInRow();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||||
|
launchSelected();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
|
||||||
|
// User started typing, focus search field and pass the character
|
||||||
|
searchField.forceActiveFocus();
|
||||||
|
searchField.text = event.text;
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main container with search and results
|
|
||||||
Rectangle {
|
|
||||||
// Margins and spacing
|
|
||||||
// Categories (2 rows)
|
|
||||||
|
|
||||||
id: mainContainer
|
|
||||||
|
|
||||||
width: 600
|
|
||||||
height: {
|
|
||||||
// Fixed height to prevent shrinking - consistent experience
|
|
||||||
let baseHeight = Theme.spacingXL * 2 + Theme.spacingL * 3;
|
|
||||||
// Add category section height if visible
|
|
||||||
if (categories.length > 1 || filteredModel.count > 0)
|
|
||||||
baseHeight += 36 * 2 + Theme.spacingS + Theme.spacingM;
|
|
||||||
|
|
||||||
// Add search field height
|
|
||||||
baseHeight += 56;
|
|
||||||
// Add fixed results height for consistent size
|
|
||||||
let fixedResultsHeight = 400;
|
|
||||||
// Always same height regardless of content
|
|
||||||
baseHeight += fixedResultsHeight;
|
|
||||||
// Ensure reasonable bounds
|
|
||||||
return Math.min(Math.max(baseHeight, 500), parent.height - 40);
|
|
||||||
}
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadiusXLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
layer.enabled: true
|
|
||||||
opacity: spotlightOpen ? 1 : 0
|
|
||||||
scale: spotlightOpen ? 1 : 0.96
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingXL
|
anchors.margins: Theme.spacingXL
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
|
||||||
// Combined row for categories and view mode toggle
|
// Combined row for categories and view mode toggle
|
||||||
Column {
|
Column {
|
||||||
@@ -368,11 +378,8 @@ PanelWindow {
|
|||||||
updateFilteredApps();
|
updateFilteredApps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom row: Media, Office, Settings, System, Utilities (5 items)
|
// Bottom row: Media, Office, Settings, System, Utilities (5 items)
|
||||||
@@ -412,15 +419,10 @@ PanelWindow {
|
|||||||
updateFilteredApps();
|
updateFilteredApps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search field with view toggle buttons
|
// Search field with view toggle buttons
|
||||||
@@ -444,31 +446,51 @@ PanelWindow {
|
|||||||
showClearButton: true
|
showClearButton: true
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
focus: spotlightOpen
|
focus: false
|
||||||
enabled: spotlightOpen
|
enabled: spotlightOpen
|
||||||
placeholderText: "Search applications..."
|
placeholderText: "Search applications..."
|
||||||
|
text: searchQuery
|
||||||
onTextEdited: {
|
onTextEdited: {
|
||||||
|
searchQuery = text;
|
||||||
searchDebounceTimer.restart();
|
searchDebounceTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: spotlightLauncher
|
||||||
|
function onShouldFocusSearchChanged() {
|
||||||
|
if (shouldFocusSearch) {
|
||||||
|
Qt.callLater(function() {
|
||||||
|
searchField.forceActiveFocus();
|
||||||
|
searchField.selectAll();
|
||||||
|
shouldFocusSearch = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onActiveFocusChanged: {
|
||||||
|
if (!activeFocus && searchQuery.length === 0) {
|
||||||
|
// If search field loses focus and there's no search text, give focus back to main handler
|
||||||
|
parent.parent.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: (event) => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
hide();
|
hide();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && searchQuery.length > 0) {
|
||||||
launchSelected();
|
// Launch first app when typing in search field
|
||||||
event.accepted = true;
|
if (filteredApps.length > 0) {
|
||||||
} else if (event.key === Qt.Key_Down) {
|
launchApp(filteredApps[0]);
|
||||||
selectNext();
|
}
|
||||||
event.accepted = true;
|
|
||||||
} else if (event.key === Qt.Key_Up) {
|
|
||||||
selectPrevious();
|
|
||||||
event.accepted = true;
|
|
||||||
} else if (event.key === Qt.Key_Right && viewMode === "grid") {
|
|
||||||
selectNextInRow();
|
|
||||||
event.accepted = true;
|
|
||||||
} else if (event.key === Qt.Key_Left && viewMode === "grid") {
|
|
||||||
selectPreviousInRow();
|
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up ||
|
||||||
|
(event.key === Qt.Key_Left && viewMode === "grid") ||
|
||||||
|
(event.key === Qt.Key_Right && viewMode === "grid") ||
|
||||||
|
((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && searchQuery.length === 0)) {
|
||||||
|
// Pass navigation keys and enter (when not searching) to main handler
|
||||||
|
event.accepted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,7 +528,6 @@ PanelWindow {
|
|||||||
Prefs.setSpotlightLauncherViewMode("list");
|
Prefs.setSpotlightLauncherViewMode("list");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grid view button
|
// Grid view button
|
||||||
@@ -536,11 +557,8 @@ PanelWindow {
|
|||||||
Prefs.setSpotlightLauncherViewMode("grid");
|
Prefs.setSpotlightLauncherViewMode("grid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Results container
|
// Results container
|
||||||
@@ -562,6 +580,7 @@ PanelWindow {
|
|||||||
itemHeight: 60
|
itemHeight: 60
|
||||||
iconSize: 40
|
iconSize: 40
|
||||||
showDescription: true
|
showDescription: true
|
||||||
|
hoverUpdatesSelection: false
|
||||||
onItemClicked: function(index, modelData) {
|
onItemClicked: function(index, modelData) {
|
||||||
launchApp(modelData);
|
launchApp(modelData);
|
||||||
}
|
}
|
||||||
@@ -577,13 +596,14 @@ PanelWindow {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: viewMode === "grid"
|
visible: viewMode === "grid"
|
||||||
model: filteredModel
|
model: filteredModel
|
||||||
columns: 6
|
columns: 4
|
||||||
adaptiveColumns: true
|
adaptiveColumns: false
|
||||||
minCellWidth: 120
|
minCellWidth: 120
|
||||||
maxCellWidth: 160
|
maxCellWidth: 160
|
||||||
iconSizeRatio: 0.55
|
iconSizeRatio: 0.55
|
||||||
maxIconSize: 48
|
maxIconSize: 48
|
||||||
currentIndex: selectedIndex
|
currentIndex: selectedIndex
|
||||||
|
hoverUpdatesSelection: false
|
||||||
onItemClicked: function(index, modelData) {
|
onItemClicked: function(index, modelData) {
|
||||||
launchApp(modelData);
|
launchApp(modelData);
|
||||||
}
|
}
|
||||||
@@ -591,37 +611,9 @@ PanelWindow {
|
|||||||
selectedIndex = index;
|
selectedIndex = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowHorizontalOffset: 0
|
|
||||||
shadowVerticalOffset: 8
|
|
||||||
shadowBlur: 1 // radius/32
|
|
||||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
}
|
}
|
||||||
// Center-screen fade with subtle scale
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
@@ -645,5 +637,4 @@ PanelWindow {
|
|||||||
|
|
||||||
target: "spotlight"
|
target: "spotlight"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -10,25 +10,21 @@ Rectangle {
|
|||||||
|
|
||||||
property bool isActive: false
|
property bool isActive: false
|
||||||
|
|
||||||
readonly property bool nerdFontAvailable: Qt.fontFamilies()
|
|
||||||
.indexOf("Symbols Nerd Font") !== -1
|
|
||||||
|
|
||||||
width: 40
|
width: 40
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: launcherArea.containsMouse || isActive ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
color: launcherArea.containsMouse || isActive ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||||
|
|
||||||
Text {
|
SystemLogo {
|
||||||
visible: nerdFontAvailable && OSDetectorService.osLogo
|
visible: Prefs.useOSLogo
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: OSDetectorService.osLogo
|
width: Theme.iconSize - 6
|
||||||
font.family: "Symbols Nerd Font"
|
height: Theme.iconSize - 6
|
||||||
font.pixelSize: Theme.iconSize - 6
|
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
visible: !nerdFontAvailable || !OSDetectorService.osLogo
|
visible: !Prefs.useOSLogo
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "apps"
|
name: "apps"
|
||||||
size: Theme.iconSize - 6
|
size: Theme.iconSize - 6
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
DankModal {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool wifiPasswordDialogVisible: false
|
property bool wifiPasswordDialogVisible: false
|
||||||
@@ -15,75 +12,31 @@ PanelWindow {
|
|||||||
property string wifiPasswordInput: ""
|
property string wifiPasswordInput: ""
|
||||||
|
|
||||||
visible: wifiPasswordDialogVisible
|
visible: wifiPasswordDialogVisible
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
size: "medium"
|
||||||
WlrLayershell.exclusiveZone: -1
|
keyboardFocus: "exclusive"
|
||||||
WlrLayershell.keyboardFocus: wifiPasswordDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (!visible) {
|
||||||
passwordInput.enabled = true;
|
wifiPasswordInput = "";
|
||||||
Qt.callLater(function() {
|
|
||||||
passwordInput.forceActiveFocus();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
passwordInput.enabled = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors {
|
onBackgroundClicked: {
|
||||||
top: true
|
wifiPasswordDialogVisible = false;
|
||||||
left: true
|
wifiPasswordInput = "";
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
content: Component {
|
||||||
anchors.fill: parent
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.5)
|
|
||||||
opacity: wifiPasswordDialogVisible ? 1 : 0
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
passwordInput.enabled = false; // Disable before hiding to prevent Wayland warnings
|
|
||||||
wifiPasswordDialogVisible = false;
|
|
||||||
wifiPasswordInput = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.min(400, parent.width - Theme.spacingL * 2)
|
|
||||||
height: Math.min(250, parent.height - Theme.spacingL * 2)
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
radius: Theme.cornerRadiusLarge
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
|
||||||
border.width: 1
|
|
||||||
opacity: wifiPasswordDialogVisible ? 1 : 0
|
|
||||||
scale: wifiPasswordDialogVisible ? 1 : 0.9
|
|
||||||
|
|
||||||
// Prevent clicks inside dialog from closing it
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
// Do nothing - prevent propagation to background
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingL
|
anchors.margins: Theme.spacingL
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
Qt.callLater(function() {
|
||||||
|
passwordInput.forceActiveFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
Row {
|
Row {
|
||||||
@@ -107,7 +60,6 @@ PanelWindow {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
@@ -116,12 +68,10 @@ PanelWindow {
|
|||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
passwordInput.enabled = false; // Disable before hiding to prevent Wayland warnings
|
|
||||||
wifiPasswordDialogVisible = false;
|
wifiPasswordDialogVisible = false;
|
||||||
wifiPasswordInput = "";
|
wifiPasswordInput = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password input
|
// Password input
|
||||||
@@ -135,13 +85,11 @@ PanelWindow {
|
|||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: passwordInput
|
id: passwordInput
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
text: wifiPasswordInput
|
text: wifiPasswordInput
|
||||||
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
|
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
|
||||||
enabled: wifiPasswordDialogVisible
|
|
||||||
placeholderText: "Enter password"
|
placeholderText: "Enter password"
|
||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
normalBorderColor: "transparent"
|
normalBorderColor: "transparent"
|
||||||
@@ -150,20 +98,12 @@ PanelWindow {
|
|||||||
wifiPasswordInput = text;
|
wifiPasswordInput = text;
|
||||||
}
|
}
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
|
WifiService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text);
|
||||||
// Close dialog immediately after pressing Enter
|
|
||||||
passwordInput.enabled = false;
|
|
||||||
wifiPasswordDialogVisible = false;
|
wifiPasswordDialogVisible = false;
|
||||||
wifiPasswordInput = "";
|
wifiPasswordInput = "";
|
||||||
|
passwordInput.text = "";
|
||||||
}
|
}
|
||||||
Component.onCompleted: {
|
|
||||||
if (wifiPasswordDialogVisible)
|
|
||||||
forceActiveFocus();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show password checkbox
|
// Show password checkbox
|
||||||
@@ -172,9 +112,7 @@ PanelWindow {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: showPasswordCheckbox
|
id: showPasswordCheckbox
|
||||||
|
|
||||||
property bool checked: false
|
property bool checked: false
|
||||||
|
|
||||||
width: 20
|
width: 20
|
||||||
height: 20
|
height: 20
|
||||||
radius: 4
|
radius: 4
|
||||||
@@ -198,7 +136,6 @@ PanelWindow {
|
|||||||
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
|
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
@@ -207,7 +144,6 @@ PanelWindow {
|
|||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
@@ -230,7 +166,6 @@ PanelWindow {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: cancelText
|
id: cancelText
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Cancel"
|
text: "Cancel"
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
@@ -240,17 +175,14 @@ PanelWindow {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: cancelArea
|
id: cancelArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
passwordInput.enabled = false; // Disable before hiding to prevent Wayland warnings
|
|
||||||
wifiPasswordDialogVisible = false;
|
wifiPasswordDialogVisible = false;
|
||||||
wifiPasswordInput = "";
|
wifiPasswordInput = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -258,12 +190,11 @@ PanelWindow {
|
|||||||
height: 36
|
height: 36
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||||
enabled: wifiPasswordInput.length > 0
|
enabled: passwordInput.text.length > 0
|
||||||
opacity: enabled ? 1 : 0.5
|
opacity: enabled ? 1 : 0.5
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: connectText
|
id: connectText
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Connect"
|
text: "Connect"
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
@@ -273,17 +204,15 @@ PanelWindow {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: connectArea
|
id: connectArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
enabled: parent.enabled
|
enabled: parent.enabled
|
||||||
onClicked: {
|
onClicked: {
|
||||||
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
|
WifiService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text);
|
||||||
// Close dialog immediately after clicking connect
|
|
||||||
passwordInput.enabled = false;
|
|
||||||
wifiPasswordDialogVisible = false;
|
wifiPasswordDialogVisible = false;
|
||||||
wifiPasswordInput = "";
|
wifiPasswordInput = "";
|
||||||
|
passwordInput.text = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,33 +221,11 @@ PanelWindow {
|
|||||||
duration: Theme.shortDuration
|
duration: Theme.shortDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Theme.standardEasing
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-reopen dialog on invalid password
|
// Auto-reopen dialog on invalid password
|
||||||
@@ -333,5 +240,4 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -34,7 +34,10 @@ paru -S quickshell-git
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Arch
|
# Arch
|
||||||
paru -S nerd-fonts ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil swaybg
|
paru -S ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil swaybg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note on networking:**
|
||||||
|
|
||||||
3. Configure SwayBG (If Desired)
|
3. Configure SwayBG (If Desired)
|
||||||
|
|
||||||
|
|||||||
@@ -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 maxIconSize: 56
|
||||||
property int minIconSize: 32
|
property int minIconSize: 32
|
||||||
property real wheelStepSize: 60
|
property real wheelStepSize: 60
|
||||||
|
property bool hoverUpdatesSelection: true
|
||||||
|
|
||||||
signal itemClicked(int index, var modelData)
|
signal itemClicked(int index, var modelData)
|
||||||
signal itemHovered(int index)
|
signal itemHovered(int index)
|
||||||
@@ -136,7 +137,9 @@ ScrollView {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
z: 10
|
z: 10
|
||||||
onEntered: {
|
onEntered: {
|
||||||
currentIndex = index;
|
if (hoverUpdatesSelection) {
|
||||||
|
currentIndex = index;
|
||||||
|
}
|
||||||
itemHovered(index);
|
itemHovered(index);
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ ScrollView {
|
|||||||
property real wheelStepSize: 60
|
property real wheelStepSize: 60
|
||||||
property bool showDescription: true
|
property bool showDescription: true
|
||||||
property int itemSpacing: Theme.spacingS
|
property int itemSpacing: Theme.spacingS
|
||||||
|
property bool hoverUpdatesSelection: true
|
||||||
|
|
||||||
signal itemClicked(int index, var modelData)
|
signal itemClicked(int index, var modelData)
|
||||||
signal itemHovered(int index)
|
signal itemHovered(int index)
|
||||||
@@ -133,7 +134,9 @@ ScrollView {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
z: 10
|
z: 10
|
||||||
onEntered: {
|
onEntered: {
|
||||||
listView.currentIndex = index;
|
if (hoverUpdatesSelection) {
|
||||||
|
listView.currentIndex = index;
|
||||||
|
}
|
||||||
itemHovered(index);
|
itemHovered(index);
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveFocusChanged: (hasFocus) => {
|
onFocusStateChanged: (hasFocus) => {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
dropdownHideTimer.stop()
|
dropdownHideTimer.stop()
|
||||||
if (text.length <= 2) {
|
if (text.length <= 2) {
|
||||||
|
|||||||
235
Widgets/DankModal.qml
Normal file
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 textEdited()
|
||||||
signal editingFinished()
|
signal editingFinished()
|
||||||
signal accepted()
|
signal accepted()
|
||||||
signal activeFocusChanged(bool hasFocus)
|
signal focusStateChanged(bool hasFocus)
|
||||||
|
|
||||||
// Access to inner TextInput properties via functions
|
// Access to inner TextInput properties via functions
|
||||||
function getActiveFocus() {
|
function getActiveFocus() {
|
||||||
@@ -120,7 +120,7 @@ Rectangle {
|
|||||||
onTextChanged: root.textEdited()
|
onTextChanged: root.textEdited()
|
||||||
onEditingFinished: root.editingFinished()
|
onEditingFinished: root.editingFinished()
|
||||||
onAccepted: root.accepted()
|
onAccepted: root.accepted()
|
||||||
onActiveFocusChanged: root.activeFocusChanged(activeFocus)
|
onActiveFocusChanged: root.focusStateChanged(activeFocus)
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
39
Widgets/SystemLogo.qml
Normal file
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