The menu, emoji picker, clipboard, OSD, reminder flow and Wi-Fi QR code mapped a fresh surface on every open. Qt drew its first frames before Hyprland sent the surface's fractional scale: the pixel ratio stepped 2, 1, then 1.6, so on a 1.6 display each overlay showed blurry for ~350ms before going sharp. A new OverlayWindow keeps the surface. Hidden, it parks as a 1x1, input-less layer below windows, off the overlay layer so it never blocks direct scanout. Showing only resizes and raises it, so the scale is already settled. Content stays hidden until the surface has grown, so no 1x1 frame is stretched across the screen, and the window follows the focused monitor each time it is shown. The image picker's own fullscreen parking moves onto it too. Each parked overlay keeps a Qt window alive: at rest the shell holds ~40 MiB more RSS and ~20 MiB more GPU memory. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
169 lines
4.9 KiB
QML
169 lines
4.9 KiB
QML
import Quickshell
|
|
import Quickshell.Wayland
|
|
import QtQuick
|
|
import qs.Commons
|
|
import qs.Ui
|
|
import "ReminderFlowModel.js" as ReminderFlowModel
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property string omarchyPath: Quickshell.env("OMARCHY_PATH")
|
|
property var shell: null
|
|
property var manifest: null
|
|
|
|
property bool opened: false
|
|
property string step: "minutes"
|
|
property string minutes: ""
|
|
property string filterText: ""
|
|
property string fontFamily: Style.font.menuFamily
|
|
|
|
property color background: Color.menu.background
|
|
property color foreground: Color.menu.text
|
|
property color border: Color.menu.border
|
|
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
|
|
property color scrim: Color.menu.scrim
|
|
readonly property int cornerRadius: Style.cornerRadius
|
|
property int contentMargin: Style.spacing.panelPadding
|
|
property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2)
|
|
property int cardWidth: Math.min(Style.space(300), panel.width - Style.gapsOut * 2)
|
|
property int cardHeight: Math.min(contentMargin * 2 + headerHeight, panel.height - Style.gapsOut * 2)
|
|
readonly property string promptText: root.step === "message" ? "Reminder message" : "Remind in minutes"
|
|
|
|
function open(payloadJson) {
|
|
var payload = ({})
|
|
try { payload = JSON.parse(payloadJson || "{}") } catch (e) { payload = ({}) }
|
|
if (payload.fontFamily) root.fontFamily = payload.fontFamily
|
|
|
|
root.opened = true
|
|
root.step = "minutes"
|
|
root.minutes = ""
|
|
root.filterText = ""
|
|
|
|
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
|
|
}
|
|
|
|
function close() {
|
|
root.opened = false
|
|
}
|
|
|
|
function dismiss() {
|
|
root.opened = false
|
|
if (root.shell && typeof root.shell.hide === "function")
|
|
root.shell.hide((root.manifest && root.manifest.id) || "omarchy.reminders")
|
|
}
|
|
|
|
function toggle() {
|
|
if (root.opened) root.dismiss()
|
|
else root.open("{}")
|
|
}
|
|
|
|
function setFilter(nextFilter) {
|
|
root.filterText = nextFilter
|
|
}
|
|
|
|
function submit() {
|
|
var selection = root.filterText
|
|
|
|
if (root.step === "minutes") {
|
|
var nextMinutes = ReminderFlowModel.validMinutes(selection)
|
|
|
|
if (!selection.trim()) {
|
|
root.dismiss()
|
|
return
|
|
}
|
|
|
|
if (!nextMinutes) {
|
|
Quickshell.execDetached([root.omarchyPath + "/bin/omarchy-notification-send", "Invalid reminder", "Enter the number of minutes"])
|
|
return
|
|
}
|
|
|
|
root.minutes = nextMinutes
|
|
root.step = "message"
|
|
root.filterText = ""
|
|
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
|
|
return
|
|
}
|
|
|
|
if (root.step === "message") {
|
|
var args = [root.omarchyPath + "/bin/omarchy-reminder"].concat(ReminderFlowModel.reminderArgs(root.minutes, selection))
|
|
root.dismiss()
|
|
Quickshell.execDetached(args)
|
|
}
|
|
}
|
|
|
|
OverlayWindow {
|
|
id: panel
|
|
shown: root.opened
|
|
WlrLayershell.namespace: "omarchy-reminders"
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: root.scrim
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: root.dismiss()
|
|
}
|
|
|
|
BorderSurface {
|
|
id: card
|
|
width: root.cardWidth
|
|
height: root.cardHeight
|
|
radius: root.cornerRadius
|
|
anchors.centerIn: parent
|
|
color: root.background
|
|
borderSpec: root.borderSpec
|
|
padding: root.contentMargin
|
|
|
|
MouseArea { anchors.fill: parent; onClicked: {} }
|
|
|
|
Item {
|
|
id: keyCatcher
|
|
anchors.fill: parent
|
|
focus: true
|
|
|
|
Keys.priority: Keys.BeforeItem
|
|
Keys.onPressed: function(event) {
|
|
if (event.key === Qt.Key_Escape) {
|
|
if (root.filterText) root.setFilter("")
|
|
else root.dismiss()
|
|
event.accepted = true
|
|
} else if (Util.editsFilter(event, root.filterText)) {
|
|
root.setFilter(Util.editedFilter(event, root.filterText))
|
|
event.accepted = true
|
|
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
root.submit()
|
|
event.accepted = true
|
|
} else if (event.text && event.text.length === 1 && event.text.charCodeAt(0) >= 32 && event.text.charCodeAt(0) !== 127) {
|
|
root.setFilter(root.filterText + event.text)
|
|
event.accepted = true
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
anchors.topMargin: card.contentTopInset
|
|
anchors.rightMargin: card.contentRightInset
|
|
anchors.bottomMargin: card.contentBottomInset
|
|
anchors.leftMargin: card.contentLeftInset
|
|
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: root.filterText || (root.promptText + "...")
|
|
color: root.foreground
|
|
opacity: root.filterText ? 1 : 0.58
|
|
font.family: root.fontFamily
|
|
font.pixelSize: Style.font.heading
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|