Files
omarchy/shell/plugins/reminders/ReminderFlow.qml
T
David Heinemeier HanssonandClaude Opus 5.5 48de7823da Keep overlay surfaces mapped so they open sharp (#13419)
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>
2026-09-27 18:03:01 +02:00

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