1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-30 08:22:51 -05:00

Modularlize the shell

This commit is contained in:
bbedward
2025-07-10 16:40:04 -04:00
parent 7cdeba1625
commit 40b2a3af1e
28 changed files with 5260 additions and 4906 deletions

View File

@@ -0,0 +1,626 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Io
import "../Common"
PanelWindow {
id: clipboardHistory
property var theme
property bool isVisible: false
// Default theme fallback
property var defaultTheme: QtObject {
property color primary: "#D0BCFF"
property color background: "#10121E"
property color surfaceContainer: "#1D1B20"
property color surfaceText: "#E6E0E9"
property color surfaceVariant: "#49454F"
property color surfaceVariantText: "#CAC4D0"
property color outline: "#938F99"
property color error: "#F2B8B5"
property real cornerRadius: 12
property real cornerRadiusLarge: 16
property real cornerRadiusXLarge: 24
property real cornerRadiusSmall: 8
property real spacingXS: 4
property real spacingS: 8
property real spacingM: 12
property real spacingL: 16
property real spacingXL: 24
property real fontSizeLarge: 16
property real fontSizeMedium: 14
property real fontSizeSmall: 12
property real iconSize: 24
property real iconSizeLarge: 32
property string iconFont: "Material Symbols Rounded"
property int iconFontWeight: Font.Normal
property int shortDuration: 150
property int mediumDuration: 300
property int standardEasing: Easing.OutCubic
property int emphasizedEasing: Easing.OutQuart
}
property var activeTheme: theme || defaultTheme
// Window properties
color: "transparent"
visible: isVisible
anchors {
top: true
left: true
right: true
bottom: true
}
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: isVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
// Clipboard entries model
property var clipboardEntries: []
ListModel {
id: clipboardModel
}
ListModel {
id: filteredClipboardModel
}
function updateFilteredModel() {
filteredClipboardModel.clear()
for (let i = 0; i < clipboardModel.count; i++) {
const entry = clipboardModel.get(i).entry
if (searchField.text.trim().length === 0) {
filteredClipboardModel.append({"entry": entry})
} else {
const content = getEntryPreview(entry).toLowerCase()
if (content.includes(searchField.text.toLowerCase())) {
filteredClipboardModel.append({"entry": entry})
}
}
}
}
function toggle() {
if (isVisible) {
hide()
} else {
show()
}
}
function show() {
clipboardHistory.isVisible = true
searchField.focus = true
refreshClipboard()
console.log("ClipboardHistory: Opening and refreshing")
}
function hide() {
clipboardHistory.isVisible = false
searchField.focus = false
searchField.text = ""
}
function refreshClipboard() {
clipboardProcess.running = true
}
function copyEntry(entry) {
const entryId = entry.split('\t')[0]
copyProcess.command = ["sh", "-c", `cliphist decode ${entryId} | wl-copy`]
copyProcess.running = true
hide()
}
function deleteEntry(entry) {
const entryId = entry.split('\t')[0]
deleteProcess.command = ["cliphist", "delete-query", entryId]
deleteProcess.running = true
}
function clearAll() {
clearProcess.running = true
}
function getEntryPreview(entry) {
// Remove cliphist ID prefix and clean up content
let content = entry.replace(/^\s*\d+\s+/, "")
// Handle different content types
if (content.includes("image/")) {
const match = content.match(/(\d+)x(\d+)/)
return match ? `Image ${match[1]}×${match[2]}` : "Image"
}
// Truncate long text
if (content.length > 100) {
return content.substring(0, 100) + "..."
}
return content
}
function getEntryType(entry) {
if (entry.includes("image/")) return "image"
if (entry.length > 200) return "long_text"
return "text"
}
// Background overlay
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
opacity: clipboardHistory.isVisible ? 1.0 : 0.0
visible: clipboardHistory.isVisible
Behavior on opacity {
NumberAnimation {
duration: activeTheme.mediumDuration
easing.type: activeTheme.emphasizedEasing
}
}
MouseArea {
anchors.fill: parent
enabled: clipboardHistory.isVisible
onClicked: clipboardHistory.hide()
}
}
// Main clipboard container
Rectangle {
id: clipboardContainer
width: Math.min(600, parent.width - 200)
height: Math.min(500, parent.height - 100)
anchors.centerIn: parent
color: activeTheme.surfaceContainer
radius: activeTheme.cornerRadiusXLarge
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.2)
border.width: 1
opacity: clipboardHistory.isVisible ? 1.0 : 0.0
scale: clipboardHistory.isVisible ? 1.0 : 0.9
Behavior on opacity {
NumberAnimation {
duration: activeTheme.mediumDuration
easing.type: activeTheme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: activeTheme.mediumDuration
easing.type: activeTheme.emphasizedEasing
}
}
// Header section
Column {
id: headerSection
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: activeTheme.spacingXL
spacing: activeTheme.spacingL
// Title and actions
Row {
width: parent.width
height: 40
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Clipboard History"
font.pixelSize: activeTheme.fontSizeLarge + 4
font.weight: Font.Bold
color: activeTheme.surfaceText
}
Item {
width: parent.width - 180 - (clearAllButton.visible ? 48 : 0)
height: 1
}
// Clear all button
Rectangle {
id: clearAllButton
width: 40
height: 32
radius: activeTheme.cornerRadius
color: clearArea.containsMouse ? Qt.rgba(activeTheme.error.r, activeTheme.error.g, activeTheme.error.b, 0.12) : "transparent"
anchors.verticalCenter: parent.verticalCenter
visible: clipboardModel.count > 0
Text {
anchors.centerIn: parent
text: "delete_sweep"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize
color: clearArea.containsMouse ? activeTheme.error : activeTheme.surfaceText
}
MouseArea {
id: clearArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: clearAll()
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
}
}
// Close button
Rectangle {
width: 40
height: 32
radius: activeTheme.cornerRadius
color: closeArea.containsMouse ? Qt.rgba(activeTheme.error.r, activeTheme.error.g, activeTheme.error.b, 0.12) : "transparent"
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 4
Text {
anchors.centerIn: parent
text: "close"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize
color: closeArea.containsMouse ? activeTheme.error : activeTheme.surfaceText
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: clipboardHistory.hide()
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
}
}
}
// Search field
Rectangle {
width: parent.width
height: 48
radius: activeTheme.cornerRadiusLarge
color: Qt.rgba(activeTheme.surfaceVariant.r, activeTheme.surfaceVariant.g, activeTheme.surfaceVariant.b, 0.3)
border.color: searchField.focus ? activeTheme.primary : Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.2)
border.width: searchField.focus ? 2 : 1
Row {
anchors.left: parent.left
anchors.leftMargin: activeTheme.spacingL
anchors.verticalCenter: parent.verticalCenter
spacing: activeTheme.spacingM
Text {
text: "search"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize
color: searchField.focus ? activeTheme.primary : Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.6)
anchors.verticalCenter: parent.verticalCenter
}
TextInput {
id: searchField
width: parent.parent.width - 80
height: parent.parent.height
font.pixelSize: activeTheme.fontSizeLarge
color: activeTheme.surfaceText
verticalAlignment: TextInput.AlignVCenter
onTextChanged: updateFilteredModel()
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
clipboardHistory.hide()
}
}
// Placeholder text
Text {
text: "Search clipboard entries..."
font: searchField.font
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.6)
anchors.verticalCenter: parent.verticalCenter
visible: searchField.text.length === 0 && !searchField.focus
}
}
}
Behavior on border.color {
ColorAnimation { duration: activeTheme.shortDuration }
}
}
}
// Clipboard entries
Rectangle {
anchors.top: headerSection.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: activeTheme.spacingXL
anchors.topMargin: activeTheme.spacingL
color: "transparent"
ScrollView {
anchors.fill: parent
clip: true
ListView {
id: clipboardList
model: filteredClipboardModel
spacing: activeTheme.spacingS
delegate: Rectangle {
width: clipboardList.width
height: Math.max(60, contentColumn.implicitHeight + activeTheme.spacingM * 2)
radius: activeTheme.cornerRadius
color: entryArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.08) :
Qt.rgba(activeTheme.surfaceVariant.r, activeTheme.surfaceVariant.g, activeTheme.surfaceVariant.b, 0.05)
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.1)
border.width: 1
property string entryType: getEntryType(model.entry)
property string entryPreview: getEntryPreview(model.entry)
Row {
anchors.fill: parent
anchors.margins: activeTheme.spacingM
spacing: activeTheme.spacingL
// Entry type icon
Rectangle {
width: 36
height: 36
radius: activeTheme.cornerRadius
color: Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12)
anchors.verticalCenter: parent.verticalCenter
Text {
anchors.centerIn: parent
text: {
switch (entryType) {
case "image": return "image"
case "long_text": return "subject"
default: return "content_paste"
}
}
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize - 4
color: activeTheme.primary
}
}
// Entry content
Column {
id: contentColumn
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 100
spacing: activeTheme.spacingXS
Text {
text: {
switch (entryType) {
case "image": return "Image • " + entryPreview
case "long_text": return "Long Text"
default: return "Text"
}
}
font.pixelSize: activeTheme.fontSizeSmall
color: activeTheme.primary
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
}
Text {
text: entryPreview
font.pixelSize: activeTheme.fontSizeMedium
color: activeTheme.surfaceText
width: parent.width
wrapMode: Text.WordWrap
maximumLineCount: entryType === "long_text" ? 3 : 2
elide: Text.ElideRight
visible: entryType !== "image"
}
}
// Actions
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: activeTheme.spacingXS
// Copy button
Rectangle {
width: 28
height: 28
radius: activeTheme.cornerRadiusSmall
color: copyArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "content_copy"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize - 8
color: copyArea.containsMouse ? activeTheme.primary : activeTheme.surfaceText
}
MouseArea {
id: copyArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: copyEntry(model.entry)
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
}
}
// Delete button
Rectangle {
width: 28
height: 28
radius: activeTheme.cornerRadiusSmall
color: deleteArea.containsMouse ? Qt.rgba(activeTheme.error.r, activeTheme.error.g, activeTheme.error.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "delete"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize - 8
color: deleteArea.containsMouse ? activeTheme.error : activeTheme.surfaceText
}
MouseArea {
id: deleteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: deleteEntry(model.entry)
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
}
}
}
}
MouseArea {
id: entryArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: copyEntry(model.entry)
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
}
}
}
// Empty state
Column {
anchors.centerIn: parent
spacing: activeTheme.spacingL
visible: filteredClipboardModel.count === 0
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "content_paste_off"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSizeLarge + 16
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.3)
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "No clipboard history"
font.pixelSize: activeTheme.fontSizeLarge
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.6)
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "Copy something to see it here"
font.pixelSize: activeTheme.fontSizeMedium
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.4)
}
}
}
}
}
// Clipboard processes
Process {
id: clipboardProcess
command: ["cliphist", "list"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: (line) => {
if (line.trim()) {
clipboardHistory.clipboardEntries.push(line)
clipboardModel.append({"entry": line})
}
}
}
onStarted: {
clipboardHistory.clipboardEntries = []
clipboardModel.clear()
console.log("ClipboardHistory: Starting cliphist process...")
}
onExited: (exitCode) => {
if (exitCode === 0) {
updateFilteredModel()
} else {
console.warn("ClipboardHistory: Failed to load clipboard history")
}
}
}
Process {
id: copyProcess
running: false
onExited: (exitCode) => {
if (exitCode !== 0) {
console.warn("ClipboardHistory: Failed to copy entry")
}
}
}
Process {
id: deleteProcess
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
refreshClipboard()
}
}
}
Process {
id: clearProcess
command: ["cliphist", "wipe"]
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
clipboardHistory.clipboardEntries = []
clipboardModel.clear()
}
}
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
hide()
}
}
}