clipboard config options + fade text and keyboard input handling
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 36s
Python / lint-format (pull_request) Successful in 35s
Python / test (pull_request) Successful in 58s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m46s

This commit is contained in:
2026-06-11 20:02:38 +02:00
parent 130e613eb5
commit e29763134e
4 changed files with 110 additions and 37 deletions
+13
View File
@@ -0,0 +1,13 @@
import Quickshell.Io
JsonObject {
property bool enabled: true
property int maxEntriesShown: 10
property Sizes sizes: Sizes {
}
component Sizes: JsonObject {
property int itemHeight: 60
property int width: 500
}
}
+16 -1
View File
@@ -13,6 +13,7 @@ Singleton {
property alias appearance: adapter.appearance property alias appearance: adapter.appearance
property alias background: adapter.background property alias background: adapter.background
property alias barConfig: adapter.barConfig property alias barConfig: adapter.barConfig
property alias clipboard: adapter.clipboard
property alias colors: adapter.colors property alias colors: adapter.colors
property alias dashboard: adapter.dashboard property alias dashboard: adapter.dashboard
property alias dock: adapter.dock property alias dock: adapter.dock
@@ -116,6 +117,17 @@ Singleton {
}; };
} }
function serializeClipboard(): var {
return {
enabled: clipboard.enabled,
maxEntriesShown: clipboard.maxEntriesShown,
sizes: {
width: clipboard.sizes.width,
itemHeight: clipboard.sizes.itemHeight
}
};
}
function serializeColors(): var { function serializeColors(): var {
return { return {
schemeType: colors.schemeType, schemeType: colors.schemeType,
@@ -143,7 +155,8 @@ Singleton {
launcher: serializeLauncher(), launcher: serializeLauncher(),
colors: serializeColors(), colors: serializeColors(),
dock: serializeDock(), dock: serializeDock(),
screenshot: serializeScreenshot() screenshot: serializeScreenshot(),
clipboard: serializeClipboard()
}; };
} }
@@ -447,6 +460,8 @@ Singleton {
} }
property BarConfig barConfig: BarConfig { property BarConfig barConfig: BarConfig {
} }
property ClipboardConfig clipboard: ClipboardConfig {
}
property Colors colors: Colors { property Colors colors: Colors {
} }
property DashboardConfig dashboard: DashboardConfig { property DashboardConfig dashboard: DashboardConfig {
-11
View File
@@ -17,16 +17,10 @@ Singleton {
name: Fuzzy.prepare(`${a.replace(/^\s*\S+\s+/, "")}`), name: Fuzzy.prepare(`${a.replace(/^\s*\S+\s+/, "")}`),
entry: a entry: a
})) }))
property string pressPasteCommand: "ydotool key -d 1 29:1 47:1 47:0 29:0"
property real scoreThreshold: 0.2 property real scoreThreshold: 0.2
function copy(entry) { function copy(entry) {
if (root.cliphistBinary.includes("cliphist"))
Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy`]); Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy`]);
else {
const entryNumber = entry.split("\t")[0];
Quickshell.execDetached(["bash", "-c", `${root.cliphistBinary} decode ${entryNumber} | wl-copy`]);
}
} }
function deleteEntry(entry) { function deleteEntry(entry) {
@@ -50,12 +44,7 @@ Singleton {
} }
function paste(entry) { function paste(entry) {
if (root.cliphistBinary.includes("cliphist"))
Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]); Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]);
else {
const entryNumber = entry.split("\t")[0];
Quickshell.execDetached(["bash", "-c", `${root.cliphistBinary} decode ${entryNumber} | wl-copy; ${root.pressPasteCommand}`]);
}
} }
function refresh() { function refresh() {
+72 -16
View File
@@ -10,11 +10,12 @@ import qs.Config
Item { Item {
id: root id: root
readonly property int itemHeight: Config.clipboard.sizes.itemHeight
required property ShellScreen screen required property ShellScreen screen
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: screen.height / 2 implicitHeight: search.implicitHeight + entries.anchors.topMargin + ((view.spacing + itemHeight) * Config.clipboard.maxEntriesShown) - view.spacing
implicitWidth: 500 implicitWidth: Config.clipboard.sizes.width
Component.onCompleted: { Component.onCompleted: {
if (!ClipHistory.entries.length > 0) if (!ClipHistory.entries.length > 0)
@@ -51,6 +52,13 @@ Item {
anchors.top: parent.top anchors.top: parent.top
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
placeholderText: "Search clipboard history..." placeholderText: "Search clipboard history..."
Keys.onDownPressed: view.incrementCurrentIndex()
Keys.onUpPressed: view.decrementCurrentIndex()
onAccepted: {
ClipHistory.copy(view.currentItem.modelData);
root.visibilities.clipboard = false;
}
} }
} }
@@ -62,20 +70,28 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: search.bottom anchors.top: search.bottom
anchors.topMargin: Appearance.spacing.normal anchors.topMargin: Appearance.spacing.normal
radius: Appearance.rounding.normal radius: Appearance.rounding.small
ListView { CustomListView {
id: view id: view
anchors.fill: parent anchors.fill: parent
highlightFollowsCurrentItem: false
highlightRangeMode: ListView.ApplyRange
preferredHighlightBegin: 0
preferredHighlightEnd: height
spacing: Appearance.spacing.normal spacing: Appearance.spacing.normal
CustomScrollBar.vertical: CustomScrollBar {
flickable: view
}
delegate: RowLayout { delegate: RowLayout {
id: clipItem id: clipItem
required property string modelData required property string modelData
height: 50 height: root.itemHeight
spacing: Appearance.spacing.small
width: view.width width: view.width
CustomClippingRect { CustomClippingRect {
@@ -83,8 +99,6 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
// Layout.preferredWidth: implicitWidth + (textLayer.pressed ? 18 : 0)
// implicitWidth: 250
radius: textLayer.pressed ? (Appearance.rounding.small / 2) : Appearance.rounding.small radius: textLayer.pressed ? (Appearance.rounding.small / 2) : Appearance.rounding.small
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
@@ -98,6 +112,16 @@ Item {
} }
} }
Item {
id: textWrapper
anchors.fill: parent
layer.enabled: true
layer.effect: OpacityMask {
maskSource: fadeMask
}
CustomText { CustomText {
id: text id: text
@@ -107,6 +131,29 @@ Item {
elide: Text.ElideRight elide: Text.ElideRight
text: clipItem.modelData text: 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
}
}
}
StateLayer { StateLayer {
id: textLayer id: textLayer
@@ -116,17 +163,17 @@ Item {
} }
IconButton { IconButton {
Layout.bottomMargin: Appearance.padding.normal
Layout.fillHeight: true Layout.fillHeight: true
Layout.margins: Appearance.padding.smallest
Layout.preferredWidth: height Layout.preferredWidth: height
Layout.topMargin: Appearance.padding.normal
icon: "content_copy" icon: "content_copy"
isToggle: false isToggle: false
// implicitWidth: 30
} }
IconButton { IconButton {
Layout.fillHeight: true Layout.fillHeight: true
Layout.margins: Appearance.padding.smallest Layout.margins: Appearance.padding.normal
Layout.preferredWidth: height Layout.preferredWidth: height
icon: "delete" icon: "delete"
inactiveColor: Qt.alpha(DynamicColors.palette.m3error, 0.8) inactiveColor: Qt.alpha(DynamicColors.palette.m3error, 0.8)
@@ -134,15 +181,24 @@ Item {
isToggle: false isToggle: false
} }
} }
model: ScriptModel { highlight: CustomRect {
values: { color: DynamicColors.palette.m3onSurface
const entries = ClipHistory.entries; implicitHeight: view.currentItem?.height ?? 0
const search = searchField.text; implicitWidth: view.width
var regex = new RegExp(search, "i"); opacity: 0.08
radius: Appearance.rounding.small
y: view.currentItem?.y ?? 0
return entries.filter(n => regex.test(n)); Behavior on y {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
} }
} }
} }
model: ScriptModel {
values: ClipHistory.fuzzyQuery(searchField.text)
}
}
} }
} }