clipboard history using cliphist
This commit is contained in:
@@ -0,0 +1,82 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
enum ButtonType {
|
||||||
|
Filled,
|
||||||
|
Tonal,
|
||||||
|
Text
|
||||||
|
}
|
||||||
|
|
||||||
|
property color activeColor
|
||||||
|
property color activeOnColor
|
||||||
|
property bool checked
|
||||||
|
property real checkedRadius: Appearance.rounding.medium
|
||||||
|
property real defaultRadius: Appearance.rounding.large
|
||||||
|
property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
|
||||||
|
property color disabledOnColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
|
||||||
|
property bool fillWidth
|
||||||
|
property real horizontalPadding: padding
|
||||||
|
readonly property alias hovered: stateLayer.containsMouse
|
||||||
|
required implicitHeight
|
||||||
|
required implicitWidth
|
||||||
|
property color inactiveColor
|
||||||
|
property color inactiveOnColor
|
||||||
|
property bool internalChecked
|
||||||
|
property bool isRound
|
||||||
|
property bool isToggle
|
||||||
|
readonly property color onColor: !enabled ? disabledOnColor : internalChecked ? activeOnColor : inactiveOnColor
|
||||||
|
property real padding
|
||||||
|
readonly property alias pressed: stateLayer.pressed
|
||||||
|
property real pressedRadius: Appearance.rounding.small
|
||||||
|
readonly property alias radiusAnim: radiusAnim
|
||||||
|
property bool radiusMorph: true
|
||||||
|
property alias shapeMorph: stateLayer.shapeMorph
|
||||||
|
property real shapeMorphExpansion: shapeMorph && pressed ? 24 : 0
|
||||||
|
readonly property alias stateLayer: stateLayer
|
||||||
|
property int type: ButtonBase.Filled
|
||||||
|
property real verticalPadding: padding
|
||||||
|
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
color: type === ButtonBase.Text ? "transparent" : !enabled ? disabledColor : internalChecked ? activeColor : inactiveColor
|
||||||
|
radius: {
|
||||||
|
if (radiusMorph && pressed)
|
||||||
|
return pressedRadius;
|
||||||
|
if (internalChecked)
|
||||||
|
return checkedRadius;
|
||||||
|
if (isRound)
|
||||||
|
return (height || implicitHeight) / 2 * Math.min(1, Appearance.rounding.scale);
|
||||||
|
return defaultRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
Anim {
|
||||||
|
id: radiusAnim
|
||||||
|
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on shapeMorphExpansion {
|
||||||
|
Anim {
|
||||||
|
type: Anim.FastSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCheckedChanged: internalChecked = checked
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
id: stateLayer
|
||||||
|
|
||||||
|
color: root.internalChecked ? root.activeOnColor : root.inactiveOnColor
|
||||||
|
enabled: enabled
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (root.isToggle)
|
||||||
|
root.internalChecked = !root.internalChecked;
|
||||||
|
root.clicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-53
@@ -1,76 +1,45 @@
|
|||||||
import qs.Config
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
CustomRect {
|
ButtonBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
enum Type {
|
|
||||||
Filled,
|
|
||||||
Tonal,
|
|
||||||
Text
|
|
||||||
}
|
|
||||||
|
|
||||||
property color activeColour: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
|
|
||||||
property color activeOnColour: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary
|
|
||||||
property bool checked
|
|
||||||
property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
|
|
||||||
property color disabledOnColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
|
|
||||||
property alias font: label.font
|
property alias font: label.font
|
||||||
property alias icon: label.text
|
property alias icon: label.text
|
||||||
property color inactiveColour: {
|
readonly property alias label: label
|
||||||
if (!toggle && type === IconButton.Filled)
|
|
||||||
|
activeColor: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
|
||||||
|
activeOnColor: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary
|
||||||
|
implicitHeight: {
|
||||||
|
const h = label.implicitHeight + padding * 2;
|
||||||
|
if (h % 2 !== 0)
|
||||||
|
return h + 1;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
inactiveColor: {
|
||||||
|
if (!isToggle && type === IconButton.Filled)
|
||||||
return DynamicColors.palette.m3primary;
|
return DynamicColors.palette.m3primary;
|
||||||
return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
|
return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
|
||||||
}
|
}
|
||||||
property color inactiveOnColour: {
|
inactiveOnColor: {
|
||||||
if (!toggle && type === IconButton.Filled)
|
if (!isToggle && type === IconButton.Filled)
|
||||||
return DynamicColors.palette.m3onPrimary;
|
return DynamicColors.palette.m3onPrimary;
|
||||||
return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant;
|
return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant;
|
||||||
}
|
}
|
||||||
property bool internalChecked
|
padding: type === IconButton.Text ? Appearance.padding.extraSmall / 2 : Appearance.padding.small
|
||||||
property alias label: label
|
|
||||||
property real padding: type === IconButton.Text ? 10 / 2 : 7
|
|
||||||
property alias radiusAnim: radiusAnim
|
|
||||||
property alias stateLayer: stateLayer
|
|
||||||
property bool toggle
|
|
||||||
property int type: IconButton.Filled
|
|
||||||
|
|
||||||
signal clicked
|
|
||||||
|
|
||||||
color: type === IconButton.Text ? "transparent" : !enabled ? disabledColour : internalChecked ? activeColour : inactiveColour
|
|
||||||
implicitHeight: label.implicitHeight + padding * 2
|
|
||||||
implicitWidth: implicitHeight
|
|
||||||
radius: internalChecked ? 6 : (implicitHeight / 2 * Math.min(1, 1)) * Appearance.rounding.scale
|
|
||||||
|
|
||||||
Behavior on radius {
|
|
||||||
Anim {
|
|
||||||
id: radiusAnim
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onCheckedChanged: internalChecked = checked
|
|
||||||
|
|
||||||
StateLayer {
|
|
||||||
id: stateLayer
|
|
||||||
|
|
||||||
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (root.toggle)
|
|
||||||
root.internalChecked = !root.internalChecked;
|
|
||||||
root.clicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
id: label
|
id: label
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: !root.enabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour
|
anchors.verticalCenterOffset: 1
|
||||||
fill: !root.toggle || root.internalChecked ? 1 : 0
|
color: root.onColor
|
||||||
|
fill: !root.isToggle || root.internalChecked ? 1 : 0
|
||||||
|
|
||||||
Behavior on fill {
|
Behavior on fill {
|
||||||
Anim {
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ Item {
|
|||||||
const dragX = x - centroid.pressPosition.x;
|
const dragX = x - centroid.pressPosition.x;
|
||||||
const dragY = y - centroid.pressPosition.y;
|
const dragY = y - centroid.pressPosition.y;
|
||||||
|
|
||||||
if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && dragY < -200)
|
if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -200)
|
||||||
root.visibilities.launcher = true;
|
root.visibilities.launcher = true;
|
||||||
|
|
||||||
if (root.singleGestureTriggered)
|
if (root.singleGestureTriggered)
|
||||||
@@ -90,7 +90,10 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight)
|
if (centroid.pressPosition.y > root.screen.height - Config.barConfig.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50)
|
||||||
|
root.visibilities.clipboard = true;
|
||||||
|
|
||||||
|
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight && centroid.pressPosition.x > root.screen.width / 5)
|
||||||
if (dragY < -10) {
|
if (dragY < -10) {
|
||||||
root.visibilities.dock = true;
|
root.visibilities.dock = true;
|
||||||
root.singleGestureTriggered = true;
|
root.singleGestureTriggered = true;
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ import qs.Modules.Resources as Resources
|
|||||||
import qs.Modules.Settings as Settings
|
import qs.Modules.Settings as Settings
|
||||||
import qs.Modules.Drawing as Drawing
|
import qs.Modules.Drawing as Drawing
|
||||||
import qs.Modules.Dock as Dock
|
import qs.Modules.Dock as Dock
|
||||||
|
import qs.Modules.Clipboard as Clipboard
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Item bar
|
required property Item bar
|
||||||
|
readonly property alias clipboard: clipboard
|
||||||
readonly property alias dashboard: dashboard
|
readonly property alias dashboard: dashboard
|
||||||
readonly property alias dashboardWrapper: dashboardWrapper
|
readonly property alias dashboardWrapper: dashboardWrapper
|
||||||
readonly property alias dock: dock
|
readonly property alias dock: dock
|
||||||
@@ -207,4 +209,13 @@ Item {
|
|||||||
screen: root.screen
|
screen: root.screen
|
||||||
visibilities: root.visibilities
|
visibilities: root.visibilities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Clipboard.Wrapper {
|
||||||
|
id: clipboard
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
screen: root.screen
|
||||||
|
visibilities: root.visibilities
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-1
@@ -40,6 +40,7 @@ CustomWindow {
|
|||||||
visibilities.settings = false;
|
visibilities.settings = false;
|
||||||
visibilities.resources = false;
|
visibilities.resources = false;
|
||||||
visibilities.dock = false;
|
visibilities.dock = false;
|
||||||
|
visibilities.clipboard = false;
|
||||||
panels.popouts.hasCurrent = false;
|
panels.popouts.hasCurrent = false;
|
||||||
}
|
}
|
||||||
onHasFullscreenChanged: {
|
onHasFullscreenChanged: {
|
||||||
@@ -49,6 +50,7 @@ CustomWindow {
|
|||||||
visibilities.osd = false;
|
visibilities.osd = false;
|
||||||
visibilities.settings = false;
|
visibilities.settings = false;
|
||||||
visibilities.resources = false;
|
visibilities.resources = false;
|
||||||
|
visibilities.clipboard = false;
|
||||||
visibilities.dock = false;
|
visibilities.dock = false;
|
||||||
panels.popouts.hasCurrent = false;
|
panels.popouts.hasCurrent = false;
|
||||||
}
|
}
|
||||||
@@ -96,7 +98,7 @@ CustomWindow {
|
|||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
id: focusGrab
|
||||||
|
|
||||||
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
|
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
|
||||||
windows: [root]
|
windows: [root]
|
||||||
|
|
||||||
onCleared: {
|
onCleared: {
|
||||||
@@ -106,6 +108,7 @@ CustomWindow {
|
|||||||
visibilities.osd = false;
|
visibilities.osd = false;
|
||||||
visibilities.settings = false;
|
visibilities.settings = false;
|
||||||
visibilities.resources = false;
|
visibilities.resources = false;
|
||||||
|
visibilities.clipboard = false;
|
||||||
visibilities.dock = false;
|
visibilities.dock = false;
|
||||||
panels.popouts.hasCurrent = false;
|
panels.popouts.hasCurrent = false;
|
||||||
}
|
}
|
||||||
@@ -115,6 +118,7 @@ CustomWindow {
|
|||||||
id: visibilities
|
id: visibilities
|
||||||
|
|
||||||
property bool bar
|
property bool bar
|
||||||
|
property bool clipboard
|
||||||
property bool dashboard
|
property bool dashboard
|
||||||
property bool dock
|
property bool dock
|
||||||
property bool isDrawing
|
property bool isDrawing
|
||||||
@@ -303,6 +307,14 @@ CustomWindow {
|
|||||||
panel: panels.drawing
|
panel: panels.drawing
|
||||||
radius: Appearance.rounding.normal
|
radius: Appearance.rounding.normal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PanelBg {
|
||||||
|
id: clipboardBg
|
||||||
|
|
||||||
|
deformAmount: 0.03
|
||||||
|
panel: panels.clipboard
|
||||||
|
radius: 29
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -355,6 +367,9 @@ CustomWindow {
|
|||||||
screen: root.screen
|
screen: root.screen
|
||||||
visibilities: visibilities
|
visibilities: visibilities
|
||||||
|
|
||||||
|
clipboard.transform: Matrix4x4 {
|
||||||
|
matrix: clipboardBg.deformMatrix
|
||||||
|
}
|
||||||
dashboard.transform: Matrix4x4 {
|
dashboard.transform: Matrix4x4 {
|
||||||
matrix: dashBg.deformMatrix
|
matrix: dashBg.deformMatrix
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Config
|
||||||
|
import "../scripts/fuzzysort.js" as Fuzzy
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string cliphistBinary: "cliphist"
|
||||||
|
property list<string> entries: []
|
||||||
|
property real pasteDelay: 0.05
|
||||||
|
readonly property var preparedEntries: entries.map(a => ({
|
||||||
|
name: Fuzzy.prepare(`${a.replace(/^\s*\S+\s+/, "")}`),
|
||||||
|
entry: a
|
||||||
|
}))
|
||||||
|
property string pressPasteCommand: "ydotool key -d 1 29:1 47:1 47:0 29:0"
|
||||||
|
property real scoreThreshold: 0.2
|
||||||
|
|
||||||
|
function copy(entry) {
|
||||||
|
if (root.cliphistBinary.includes("cliphist"))
|
||||||
|
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) {
|
||||||
|
deleteProc.deleteEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
function entryIsImage(entry) {
|
||||||
|
return !!(/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fuzzyQuery(search: string): var {
|
||||||
|
if (search.trim() === "") {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
return Fuzzy.go(search, preparedEntries, {
|
||||||
|
all: true,
|
||||||
|
key: "name"
|
||||||
|
}).map(r => {
|
||||||
|
return r.obj.entry;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function paste(entry) {
|
||||||
|
if (root.cliphistBinary.includes("cliphist"))
|
||||||
|
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() {
|
||||||
|
readProc.buffer = [];
|
||||||
|
readProc.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shellSingleQuoteEscape(str) {
|
||||||
|
return String(str).replace(/'/g, "'\\''");
|
||||||
|
}
|
||||||
|
|
||||||
|
function wipe() {
|
||||||
|
wipeProc.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: deleteProc
|
||||||
|
|
||||||
|
property string entry: ""
|
||||||
|
|
||||||
|
function deleteEntry(entry) {
|
||||||
|
deleteProc.entry = entry;
|
||||||
|
deleteProc.running = true;
|
||||||
|
deleteProc.entry = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
command: ["bash", "-c", `echo '${root.shellSingleQuoteEscape(deleteProc.entry)}' | ${root.cliphistBinary} delete`]
|
||||||
|
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
root.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: wipeProc
|
||||||
|
|
||||||
|
command: [root.cliphistBinary, "wipe"]
|
||||||
|
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
root.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onClipboardTextChanged() {
|
||||||
|
delayedUpdateTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: Quickshell
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: delayedUpdateTimer
|
||||||
|
|
||||||
|
interval: 50
|
||||||
|
repeat: false
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
root.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: readProc
|
||||||
|
|
||||||
|
property list<string> buffer: []
|
||||||
|
|
||||||
|
command: [root.cliphistBinary, "list"]
|
||||||
|
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: line => {
|
||||||
|
readProc.buffer.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
root.entries = readProc.buffer;
|
||||||
|
} else {
|
||||||
|
console.error("[Cliphist] Failed to refresh with code", exitCode, "and status", exitStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
function update(): void {
|
||||||
|
root.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: "cliphistService"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,21 +17,15 @@ Item {
|
|||||||
implicitWidth: content.implicitWidth
|
implicitWidth: content.implicitWidth
|
||||||
visible: width > 0 && height > 0
|
visible: width > 0 && height > 0
|
||||||
x: {
|
x: {
|
||||||
if (content.isDetached)
|
|
||||||
return (parent.width - content.nonAnimWidth) / 2;
|
|
||||||
|
|
||||||
const off = content.currentCenter - Config.barConfig.border - content.nonAnimWidth / 2;
|
const off = content.currentCenter - Config.barConfig.border - content.nonAnimWidth / 2;
|
||||||
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
|
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
|
||||||
if (diff < 0)
|
if (diff < 0)
|
||||||
return off + diff;
|
return off + diff;
|
||||||
return Math.floor(Math.max(off, 0));
|
return Math.floor(Math.max(off, 0));
|
||||||
}
|
}
|
||||||
y: content.isDetached ? (parent.height - content.nonAnimHeight) / 2 : 0
|
|
||||||
|
|
||||||
Behavior on offsetScale {
|
Behavior on offsetScale {
|
||||||
Anim {
|
Anim {
|
||||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
|
||||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property ShellScreen screen
|
||||||
|
required property PersistentProperties visibilities
|
||||||
|
|
||||||
|
implicitHeight: screen.height / 2
|
||||||
|
implicitWidth: 500
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (!ClipHistory.entries.length > 0)
|
||||||
|
ClipHistory.refresh();
|
||||||
|
searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
id: search
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: 50
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: searchIcon
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "search"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTextField {
|
||||||
|
id: searchField
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: searchIcon.right
|
||||||
|
anchors.leftMargin: Appearance.spacing.small
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
color: DynamicColors.palette.m3onSurface
|
||||||
|
placeholderText: "Search clipboard history..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
id: entries
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: search.bottom
|
||||||
|
anchors.topMargin: Appearance.spacing.normal
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: view
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
delegate: RowLayout {
|
||||||
|
id: clipItem
|
||||||
|
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
height: 50
|
||||||
|
width: view.width
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
id: textRect
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
// Layout.preferredWidth: implicitWidth + (textLayer.pressed ? 18 : 0)
|
||||||
|
// implicitWidth: 250
|
||||||
|
radius: textLayer.pressed ? (Appearance.rounding.small / 2) : Appearance.rounding.small
|
||||||
|
|
||||||
|
Behavior on Layout.preferredWidth {
|
||||||
|
Anim {
|
||||||
|
type: Anim.FastEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on radius {
|
||||||
|
Anim {
|
||||||
|
type: Anim.FastEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: text
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: clipItem.modelData
|
||||||
|
}
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
id: textLayer
|
||||||
|
|
||||||
|
onClicked: ClipHistory.copy(clipItem.modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.margins: Appearance.padding.smallest
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
icon: "content_copy"
|
||||||
|
isToggle: false
|
||||||
|
// implicitWidth: 30
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.margins: Appearance.padding.smallest
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
icon: "delete"
|
||||||
|
inactiveColor: Qt.alpha(DynamicColors.palette.m3error, 0.8)
|
||||||
|
inactiveOnColor: DynamicColors.palette.m3onError
|
||||||
|
isToggle: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model: ScriptModel {
|
||||||
|
values: {
|
||||||
|
const entries = ClipHistory.entries;
|
||||||
|
const search = searchField.text;
|
||||||
|
var regex = new RegExp(search, "i");
|
||||||
|
|
||||||
|
return entries.filter(n => regex.test(n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int contentHeight
|
||||||
|
property real offsetScale: shouldBeActive ? 0 : 1
|
||||||
|
required property ShellScreen screen
|
||||||
|
readonly property bool shouldBeActive: visibilities.clipboard
|
||||||
|
required property PersistentProperties visibilities
|
||||||
|
|
||||||
|
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
||||||
|
implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
|
||||||
|
implicitWidth: content.implicitWidth + Appearance.padding.normal * 2 || 400
|
||||||
|
opacity: 1 - offsetScale
|
||||||
|
visible: offsetScale < 1
|
||||||
|
|
||||||
|
Behavior on offsetScale {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: content
|
||||||
|
|
||||||
|
active: root.shouldBeActive || root.visible
|
||||||
|
anchors.centerIn: parent
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
sourceComponent: Content {
|
||||||
|
screen: root.screen
|
||||||
|
visibilities: root.visibilities
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -267,8 +267,8 @@ CustomRect {
|
|||||||
checked: Recorder.paused
|
checked: Recorder.paused
|
||||||
font.pointSize: Appearance.font.size.large
|
font.pointSize: Appearance.font.size.large
|
||||||
icon: Recorder.paused ? "play_arrow" : "pause"
|
icon: Recorder.paused ? "play_arrow" : "pause"
|
||||||
|
isToggle: true
|
||||||
label.animate: true
|
label.animate: true
|
||||||
toggle: true
|
|
||||||
type: IconButton.Tonal
|
type: IconButton.Tonal
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -280,8 +280,8 @@ CustomRect {
|
|||||||
IconButton {
|
IconButton {
|
||||||
font.pointSize: Appearance.font.size.large
|
font.pointSize: Appearance.font.size.large
|
||||||
icon: "stop"
|
icon: "stop"
|
||||||
inactiveColour: DynamicColors.palette.m3error
|
inactiveColor: DynamicColors.palette.m3error
|
||||||
inactiveOnColour: DynamicColors.palette.m3onError
|
inactiveOnColor: DynamicColors.palette.m3onError
|
||||||
|
|
||||||
onClicked: Recorder.stop()
|
onClicked: Recorder.stop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,11 +101,11 @@ CustomRect {
|
|||||||
component Toggle: IconButton {
|
component Toggle: IconButton {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
|
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
|
||||||
inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
||||||
|
isToggle: true
|
||||||
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
|
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
|
||||||
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
|
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
|
||||||
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
|
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||||
toggle: true
|
|
||||||
|
|
||||||
Behavior on Layout.preferredWidth {
|
Behavior on Layout.preferredWidth {
|
||||||
Anim {
|
Anim {
|
||||||
|
|||||||
@@ -221,11 +221,11 @@ Item {
|
|||||||
font: Appearance.font.family.sans
|
font: Appearance.font.family.sans
|
||||||
// icon: root.iconForId(modelData.entry.id)
|
// icon: root.iconForId(modelData.entry.id)
|
||||||
icon: root.labelForId(modelData.entry.id)
|
icon: root.labelForId(modelData.entry.id)
|
||||||
inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
||||||
|
isToggle: true
|
||||||
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
|
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
|
||||||
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
|
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
|
||||||
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
|
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||||
toggle: true
|
|
||||||
visible: !["spacer", "upower", "dash", "audio"].some(prefix => modelData.entry.id.startsWith(prefix))
|
visible: !["spacer", "upower", "dash", "audio"].some(prefix => modelData.entry.id.startsWith(prefix))
|
||||||
|
|
||||||
Behavior on Layout.preferredWidth {
|
Behavior on Layout.preferredWidth {
|
||||||
|
|||||||
@@ -59,4 +59,13 @@ Scope {
|
|||||||
visibilities.settings = !visibilities.settings;
|
visibilities.settings = !visibilities.settings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CustomShortcut {
|
||||||
|
name: "toggle-clipboard"
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
const visibilities = Visibilities.getForActive();
|
||||||
|
visibilities.clipboard = !visibilities.clipboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ Item {
|
|||||||
detachedMode = "";
|
detachedMode = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
focus: hasCurrent
|
|
||||||
implicitHeight: nonAnimHeight
|
implicitHeight: nonAnimHeight
|
||||||
implicitWidth: nonAnimWidth
|
implicitWidth: nonAnimWidth
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user