pragma ComponentBehavior: Bound import Quickshell import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Components import qs.Helpers import ZShell.Config import qs.Services Item { id: root readonly property int itemHeight: Config.clipboard.sizes.itemHeight required property ShellScreen screen required property PersistentProperties visibilities implicitHeight: search.implicitHeight + entries.anchors.topMargin + ((view.spacing + itemHeight) * Config.clipboard.maxEntriesShown) - view.spacing implicitWidth: Config.clipboard.sizes.width + preview.width + preview.anchors.leftMargin Component.onCompleted: { if (ClipHistory.entries.length === 0) ClipHistory.refresh(); searchField.forceActiveFocus(); } CustomClippingRect { id: search anchors.left: parent.left anchors.right: entries.right anchors.top: parent.top color: Colors.tPalette.m3surfaceContainer implicitHeight: 50 radius: Tokens.rounding.full SearchBar { id: searchField anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top color: Colors.palette.m3onSurface placeholderText: "Search clipboard history..." Keys.onDownPressed: view.incrementCurrentIndex() Keys.onUpPressed: view.decrementCurrentIndex() onAccepted: { ClipHistory.copy(view.currentItem.modelData); root.visibilities.clipboard = false; } } } CustomClippingRect { id: preview anchors.bottom: parent.bottom anchors.left: entries.right anchors.leftMargin: Tokens.spacing.medium anchors.top: parent.top color: Colors.tPalette.m3surfaceContainer implicitWidth: ClipHistory.previewIsImage ? Math.max(Math.min(imagePreview.sourceSize.width + Tokens.padding.large * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) : Math.max(Math.min(textPreviewColumn.width + textPreviewColumn.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) radius: 25 Behavior on implicitWidth { Anim { } } Column { id: textPreviewColumn anchors.left: parent.left anchors.leftMargin: 0 anchors.margins: Tokens.padding.small anchors.top: parent.top visible: !ClipHistory.previewIsImage Repeater { id: processedLines model: ClipHistory.previewText Row { id: lineRow required property int index required property var modelData spacing: Tokens.spacing.medium CustomRect { color: lineRow.index % 2 ? Colors.tPalette.m3surfaceContainer : "transparent" implicitHeight: lineText.paintedHeight + Tokens.padding.extraSmall * 2 implicitWidth: 50 CustomText { id: number anchors.margins: Tokens.padding.large anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter color: Colors.palette.m3onSurfaceVariant font.pointSize: Tokens.font.size.small text: lineRow.modelData.line } } Repeater { model: lineRow.modelData.text.split("\t").slice(1) RowLayout { height: lineText.height width: lineText.height + Tokens.spacing.extraSmall Item { Layout.fillHeight: true Layout.fillWidth: true CustomRect { anchors.centerIn: parent color: Colors.tPalette.m3surfaceContainer implicitHeight: parent.height / 8 implicitWidth: parent.height / 8 radius: Tokens.rounding.full } } Item { Layout.fillHeight: true Layout.fillWidth: true CustomRect { anchors.centerIn: parent color: Colors.tPalette.m3surfaceContainer implicitHeight: parent.height / 8 implicitWidth: parent.height / 8 radius: Tokens.rounding.full } } Item { Layout.fillHeight: true Layout.fillWidth: true CustomRect { anchors.centerIn: parent color: Colors.tPalette.m3surfaceContainer implicitHeight: parent.height / 8 implicitWidth: parent.height / 8 radius: Tokens.rounding.full } } } } CustomText { id: lineText color: Colors.palette.m3onSurface height: lineText.paintedHeight + Tokens.padding.extraSmall * 2 text: lineRow.modelData.text.trim() verticalAlignment: Text.AlignVCenter wrapMode: Text.Wrap } } } } Image { id: imagePreview anchors.centerIn: parent asynchronous: true cache: false fillMode: Image.PreserveAspectFit mipmap: true retainWhileLoading: true smooth: true source: ClipHistory.previewImageSource visible: ClipHistory.previewIsImage && ClipHistory.previewImageSource !== "" width: Math.min(sourceSize.width, Config.clipboard.sizes.previewWidth) } } Item { id: entries anchors.bottom: parent.bottom anchors.left: parent.left anchors.top: search.bottom anchors.topMargin: Tokens.spacing.medium implicitWidth: Config.clipboard.sizes.width CustomListView { id: view anchors.fill: parent cacheBuffer: (root.itemHeight + spacing) * 2 highlightFollowsCurrentItem: false highlightRangeMode: ListView.ApplyRange preferredHighlightBegin: 0 preferredHighlightEnd: height spacing: Tokens.spacing.medium CustomScrollBar.vertical: CustomScrollBar { flickable: view minimumSize: 0.1 } delegate: CustomRect { id: clipItem readonly property bool isImage: ClipHistory.entryIsImage(modelData) required property string modelData implicitHeight: root.itemHeight implicitWidth: view.width radius: textLayer.pressed ? (Tokens.rounding.small / 2) : Tokens.rounding.small Behavior on radius { Anim { type: Anim.FastEffects } } RowLayout { anchors.fill: parent spacing: Tokens.spacing.small CustomClippingRect { id: textRect Layout.fillHeight: true Layout.fillWidth: true Item { id: textWrapper anchors.fill: parent layer.enabled: true layer.effect: OpacityMask { maskSource: fadeMask } MaterialIcon { id: icon anchors.left: parent.left anchors.margins: Tokens.padding.small anchors.verticalCenter: parent.verticalCenter font.pointSize: Tokens.font.size.large text: clipItem.isImage ? "image" : "text_fields" } CustomText { id: text anchors.left: icon.right anchors.margins: Tokens.spacing.medium anchors.verticalCenter: parent.verticalCenter elide: Text.ElideRight text: clipItem.isImage ? qsTr("Image") : ClipHistory.displayText(clipItem.modelData) } } CustomRect { id: fadeMask anchors.fill: parent layer.enabled: true visible: false gradient: Gradient { orientation: Gradient.Horizontal GradientStop { color: Qt.rgba(1, 1, 1, 1.0) position: 0.85 } GradientStop { color: Qt.rgba(1, 1, 1, 0) position: 1.0 } } } } IconButton { Layout.fillHeight: true Layout.margins: Tokens.padding.small Layout.preferredWidth: height icon: "delete" inactiveColor: Qt.alpha(Colors.palette.m3error, 0.8) inactiveOnColor: Colors.palette.m3onError isToggle: false onClicked: ClipHistory.deleteEntry(clipItem.modelData) } } StateLayer { id: textLayer onClicked: ClipHistory.copy(clipItem.modelData) } } highlight: CustomRect { color: Colors.palette.m3onSurface implicitHeight: view.currentItem?.height ?? 0 implicitWidth: view.width opacity: 0.08 radius: Tokens.rounding.small y: view.currentItem?.y ?? 0 Behavior on y { Anim { duration: Tokens.anim.durations.small easing: Tokens.anim.expressiveDefaultEffects } } } model: ScriptModel { values: ClipHistory.fuzzyQuery(searchField.text) onValuesChanged: view.currentIndex = 0 } onCurrentItemChanged: { if (!currentItem) return; ClipHistory.currentEntry = currentItem.modelData; ClipHistory.refreshPreview(); } onVisibleChanged: currentIndex = 0 CustomClippingWrapperRect { anchors.fill: parent child: view.contentItem radius: Tokens.rounding.small } } } }