// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import QtQuick import QtQuick.Controls Item { id: page property string title: "" // The module's tile glyph, set by the drawer, shown before the title. property string icon: "" default property alias content: holder.data signal back // Closes the whole drawer, for a page whose action is finished once done // (the clipboard copies and gets out of the way). signal dismiss // Opens another module's page by name, for a page that links to one (the // user page opens the git page). signal go(string name) Item { id: header anchors { top: parent.top; left: parent.left; right: parent.right } height: 44 Rectangle { id: backBtn anchors { left: parent.left; verticalCenter: parent.verticalCenter } width: 32; height: 32; radius: 16 color: backArea.containsMouse ? Qt.alpha(Theme.accent, 0.22) : "transparent" Text { anchors.centerIn: parent text: "" font { family: Theme.iconFamily; pixelSize: 14 } color: Theme.text } MouseArea { id: backArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: page.back() } } Row { anchors { left: backBtn.right; leftMargin: 10; verticalCenter: parent.verticalCenter } spacing: 8 Text { visible: page.icon !== "" text: page.icon font { family: Theme.iconFamily; pixelSize: Theme.fontSize + 2 } color: Theme.text } Text { text: page.title font { family: Theme.fontFamily; pixelSize: Theme.fontSize + 2; bold: true } color: Theme.text } } } Rectangle { id: rule anchors { top: header.bottom; left: parent.left; right: parent.right } height: 1 color: Qt.alpha(Theme.text, 0.12) } // Scrolls to the content row whose objectName is key and flashes it, for // a settings search hit. Rows usually arrive after the page loads its // data, so it looks for up to 3s before giving up. function reveal(key) { finder.key = key; finder.tries = 0; finder.restart(); } function findNamed(item, key) { if (item.objectName === key) return item; for (let i = 0; i < item.children.length; i++) { const f = page.findNamed(item.children[i], key); if (f) return f; } return null; } Timer { id: finder property string key: "" property int tries: 0 interval: 100 repeat: true onTriggered: { const it = page.findNamed(holder, key); if (!it) { if (++tries > 30) stop(); return; } stop(); const p = it.mapToItem(holder, 0, 0); const flick = scroll.contentItem; flick.contentY = Math.max(0, Math.min(p.y - 40, flick.contentHeight - flick.height)); flash.x = p.x - 4; flash.y = p.y - 2; flash.width = it.width + 8; flash.height = it.height + 4; flashAnim.restart(); } } ScrollView { id: scroll anchors { top: rule.bottom; left: parent.left; right: parent.right; bottom: parent.bottom } anchors.topMargin: 12 clip: true contentWidth: availableWidth Item { id: holder width: parent.width implicitHeight: childrenRect.height } } // The reveal highlight, over the row and in holder's coordinates. // Declared out here, not in the ScrollView, which sizes from a single // child; parent puts it in holder all the same. Rectangle { id: flash parent: holder z: 10 radius: 8 opacity: 0 color: Qt.alpha(Theme.accent, 0.28) border.width: 1 border.color: Theme.accent SequentialAnimation { id: flashAnim NumberAnimation { target: flash; property: "opacity"; to: 1; duration: 120 } PauseAnimation { duration: 1400 } NumberAnimation { target: flash; property: "opacity"; to: 0; duration: 600 } } } }