mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
362 lines
11 KiB
QML
362 lines
11 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Hyprland
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
visible: false
|
|
|
|
property bool spotlightOpen: false
|
|
property bool keyboardActive: false
|
|
property bool contentVisible: false
|
|
property alias spotlightContent: launcherContent
|
|
property bool openedFromOverview: false
|
|
property bool isClosing: false
|
|
property bool _windowEnabled: true
|
|
|
|
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
|
readonly property var effectiveScreen: launcherWindow.screen
|
|
readonly property real screenWidth: effectiveScreen?.width ?? 1920
|
|
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
|
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
|
|
|
readonly property int baseWidth: SettingsData.dankLauncherV2Size === "medium" ? 720 : SettingsData.dankLauncherV2Size === "large" ? 860 : 620
|
|
readonly property int baseHeight: SettingsData.dankLauncherV2Size === "medium" ? 720 : SettingsData.dankLauncherV2Size === "large" ? 860 : 600
|
|
readonly property int modalWidth: Math.min(baseWidth, screenWidth - 100)
|
|
readonly property int modalHeight: Math.min(baseHeight, screenHeight - 100)
|
|
readonly property real modalX: (screenWidth - modalWidth) / 2
|
|
readonly property real modalY: (screenHeight - modalHeight) / 2
|
|
|
|
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
|
readonly property real cornerRadius: Theme.cornerRadius
|
|
readonly property color borderColor: {
|
|
if (!SettingsData.dankLauncherV2BorderEnabled)
|
|
return Theme.outlineMedium;
|
|
switch (SettingsData.dankLauncherV2BorderColor) {
|
|
case "primary":
|
|
return Theme.primary;
|
|
case "secondary":
|
|
return Theme.secondary;
|
|
case "outline":
|
|
return Theme.outline;
|
|
case "surfaceText":
|
|
return Theme.surfaceText;
|
|
default:
|
|
return Theme.primary;
|
|
}
|
|
}
|
|
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 1
|
|
|
|
signal dialogClosed
|
|
|
|
function _initializeAndShow(query, mode) {
|
|
contentVisible = true;
|
|
spotlightContent.searchField.forceActiveFocus();
|
|
|
|
if (spotlightContent.searchField) {
|
|
spotlightContent.searchField.text = query;
|
|
}
|
|
if (spotlightContent.controller) {
|
|
var targetMode = mode || "all";
|
|
spotlightContent.controller.searchMode = targetMode;
|
|
spotlightContent.controller.activePluginId = "";
|
|
spotlightContent.controller.activePluginName = "";
|
|
spotlightContent.controller.pluginFilter = "";
|
|
spotlightContent.controller.collapsedSections = {};
|
|
if (query) {
|
|
spotlightContent.controller.setSearchQuery(query);
|
|
} else {
|
|
spotlightContent.controller.searchQuery = "";
|
|
spotlightContent.controller.performSearch();
|
|
}
|
|
}
|
|
if (spotlightContent.resetScroll) {
|
|
spotlightContent.resetScroll();
|
|
}
|
|
if (spotlightContent.actionPanel) {
|
|
spotlightContent.actionPanel.hide();
|
|
}
|
|
}
|
|
|
|
function show() {
|
|
closeCleanupTimer.stop();
|
|
isClosing = false;
|
|
openedFromOverview = false;
|
|
|
|
var focusedScreen = CompositorService.getFocusedScreen();
|
|
if (focusedScreen)
|
|
launcherWindow.screen = focusedScreen;
|
|
|
|
spotlightOpen = true;
|
|
keyboardActive = true;
|
|
ModalManager.openModal(root);
|
|
if (useHyprlandFocusGrab)
|
|
focusGrab.active = true;
|
|
|
|
_initializeAndShow("");
|
|
}
|
|
|
|
function showWithQuery(query) {
|
|
closeCleanupTimer.stop();
|
|
isClosing = false;
|
|
openedFromOverview = false;
|
|
|
|
var focusedScreen = CompositorService.getFocusedScreen();
|
|
if (focusedScreen)
|
|
launcherWindow.screen = focusedScreen;
|
|
|
|
spotlightOpen = true;
|
|
keyboardActive = true;
|
|
ModalManager.openModal(root);
|
|
if (useHyprlandFocusGrab)
|
|
focusGrab.active = true;
|
|
|
|
_initializeAndShow(query);
|
|
}
|
|
|
|
function hide() {
|
|
if (!spotlightOpen)
|
|
return;
|
|
openedFromOverview = false;
|
|
isClosing = true;
|
|
contentVisible = false;
|
|
|
|
keyboardActive = false;
|
|
spotlightOpen = false;
|
|
focusGrab.active = false;
|
|
ModalManager.closeModal(root);
|
|
|
|
closeCleanupTimer.start();
|
|
}
|
|
|
|
function toggle() {
|
|
spotlightOpen ? hide() : show();
|
|
}
|
|
|
|
function showWithMode(mode) {
|
|
closeCleanupTimer.stop();
|
|
isClosing = false;
|
|
openedFromOverview = false;
|
|
|
|
var focusedScreen = CompositorService.getFocusedScreen();
|
|
if (focusedScreen)
|
|
launcherWindow.screen = focusedScreen;
|
|
|
|
spotlightOpen = true;
|
|
keyboardActive = true;
|
|
ModalManager.openModal(root);
|
|
if (useHyprlandFocusGrab)
|
|
focusGrab.active = true;
|
|
|
|
_initializeAndShow("", mode);
|
|
}
|
|
|
|
function toggleWithMode(mode) {
|
|
if (spotlightOpen) {
|
|
hide();
|
|
} else {
|
|
showWithMode(mode);
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: closeCleanupTimer
|
|
interval: Theme.expressiveDurations.expressiveFastSpatial + 50
|
|
repeat: false
|
|
onTriggered: {
|
|
isClosing = false;
|
|
dialogClosed();
|
|
}
|
|
}
|
|
|
|
HyprlandFocusGrab {
|
|
id: focusGrab
|
|
windows: [launcherWindow]
|
|
active: false
|
|
|
|
onCleared: {
|
|
if (spotlightOpen) {
|
|
hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: ModalManager
|
|
function onCloseAllModalsExcept(excludedModal) {
|
|
if (excludedModal !== root && spotlightOpen) {
|
|
hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: Quickshell
|
|
function onScreensChanged() {
|
|
if (Quickshell.screens.length === 0)
|
|
return;
|
|
|
|
const screen = launcherWindow.screen;
|
|
const screenName = screen?.name;
|
|
|
|
let needsReset = !screen || !screenName;
|
|
if (!needsReset) {
|
|
needsReset = true;
|
|
for (let i = 0; i < Quickshell.screens.length; i++) {
|
|
if (Quickshell.screens[i].name === screenName) {
|
|
needsReset = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!needsReset)
|
|
return;
|
|
|
|
const newScreen = CompositorService.getFocusedScreen() ?? Quickshell.screens[0];
|
|
if (!newScreen)
|
|
return;
|
|
|
|
root._windowEnabled = false;
|
|
launcherWindow.screen = newScreen;
|
|
Qt.callLater(() => {
|
|
root._windowEnabled = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
PanelWindow {
|
|
id: launcherWindow
|
|
visible: root._windowEnabled
|
|
color: "transparent"
|
|
exclusionMode: ExclusionMode.Ignore
|
|
|
|
WlrLayershell.namespace: "dms:spotlight"
|
|
WlrLayershell.layer: {
|
|
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
|
case "bottom":
|
|
console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
|
return WlrLayershell.Top;
|
|
case "background":
|
|
console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
|
return WlrLayershell.Top;
|
|
case "overlay":
|
|
return WlrLayershell.Overlay;
|
|
default:
|
|
return WlrLayershell.Top;
|
|
}
|
|
}
|
|
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
|
|
|
anchors {
|
|
top: true
|
|
bottom: true
|
|
left: true
|
|
right: true
|
|
}
|
|
|
|
mask: Region {
|
|
item: spotlightOpen ? fullScreenMask : null
|
|
}
|
|
|
|
Item {
|
|
id: fullScreenMask
|
|
anchors.fill: parent
|
|
}
|
|
|
|
Rectangle {
|
|
id: backgroundDarken
|
|
anchors.fill: parent
|
|
color: "black"
|
|
opacity: contentVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
|
visible: contentVisible || opacity > 0
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.expressiveFastSpatial
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.emphasized
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: spotlightOpen
|
|
onClicked: mouse => {
|
|
var contentX = modalContainer.x;
|
|
var contentY = modalContainer.y;
|
|
var contentW = modalContainer.width;
|
|
var contentH = modalContainer.height;
|
|
|
|
if (mouse.x < contentX || mouse.x > contentX + contentW || mouse.y < contentY || mouse.y > contentY + contentH) {
|
|
root.hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: modalContainer
|
|
x: root.modalX
|
|
y: root.modalY
|
|
width: root.modalWidth
|
|
height: root.modalHeight
|
|
visible: contentVisible || opacity > 0
|
|
|
|
opacity: contentVisible ? 1 : 0
|
|
scale: contentVisible ? 1 : 0.96
|
|
transformOrigin: Item.Center
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.fast
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
|
}
|
|
}
|
|
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.fast
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
|
}
|
|
}
|
|
|
|
DankRectangle {
|
|
anchors.fill: parent
|
|
color: root.backgroundColor
|
|
borderColor: root.borderColor
|
|
borderWidth: root.borderWidth
|
|
radius: root.cornerRadius
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onPressed: mouse => mouse.accepted = true
|
|
}
|
|
|
|
FocusScope {
|
|
anchors.fill: parent
|
|
focus: keyboardActive
|
|
|
|
LauncherContent {
|
|
id: launcherContent
|
|
anchors.fill: parent
|
|
parentModal: root
|
|
}
|
|
|
|
Keys.onEscapePressed: event => {
|
|
root.hide();
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|