Launcher: wip image preview
This commit is contained in:
parent
7548ffc191
commit
ded133d164
5 changed files with 159 additions and 16 deletions
|
|
@ -245,13 +245,15 @@ NPanel {
|
||||||
policy: ScrollBar.AsNeeded
|
policy: ScrollBar.AsNeeded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the delegate in Launcher.qml's ListView with this enhanced version:
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
id: entry
|
id: entry
|
||||||
|
|
||||||
property bool isSelected: mouseArea.containsMouse || (index === selectedIndex)
|
property bool isSelected: mouseArea.containsMouse || (index === selectedIndex)
|
||||||
|
property int badgeSize: Style.baseWidgetSize * 1.75 * scaling
|
||||||
|
|
||||||
width: resultsList.width - Style.marginS * scaling
|
width: resultsList.width - Style.marginS * scaling
|
||||||
height: 65 * scaling
|
height: badgeSize + Style.marginM * 2 *scaling
|
||||||
radius: Style.radiusM * scaling
|
radius: Style.radiusM * scaling
|
||||||
color: entry.isSelected ? Color.mTertiary : Color.mSurface
|
color: entry.isSelected ? Color.mTertiary : Color.mSurface
|
||||||
|
|
||||||
|
|
@ -267,33 +269,107 @@ NPanel {
|
||||||
anchors.margins: Style.marginM * scaling
|
anchors.margins: Style.marginM * scaling
|
||||||
spacing: Style.marginM * scaling
|
spacing: Style.marginM * scaling
|
||||||
|
|
||||||
// Icon badge
|
// Icon badge or Image preview
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.preferredWidth: Style.baseWidgetSize * 1.25 * scaling
|
Layout.preferredWidth: badgeSize
|
||||||
Layout.preferredHeight: Style.baseWidgetSize * 1.25 * scaling
|
Layout.preferredHeight: badgeSize
|
||||||
radius: Style.radiusS * scaling
|
radius: Style.radiusM * scaling
|
||||||
color: Color.mSurfaceVariant
|
color: Color.mSurfaceVariant
|
||||||
|
clip: true
|
||||||
|
|
||||||
IconImage {
|
// Image preview for clipboard images
|
||||||
|
Image {
|
||||||
|
id: imagePreview
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Style.marginXS * scaling
|
anchors.margins: 2 * scaling
|
||||||
source: modelData.icon ? Icons.iconFromName(modelData.icon, "application-x-executable") : ""
|
visible: modelData.isImage && modelData.imageSource
|
||||||
visible: modelData.icon && source !== ""
|
source: modelData.imageSource || ""
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
mipmap: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
// Loading indicator
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: parent.status === Image.Loading
|
||||||
|
color: Color.mSurfaceVariant
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
running: true
|
||||||
|
width: Style.baseWidgetSize * 0.5 * scaling
|
||||||
|
height: width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error fallback
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Error) {
|
||||||
|
// Fall back to icon
|
||||||
|
iconLoader.visible = true
|
||||||
|
imagePreview.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback if no icon
|
// Icon fallback
|
||||||
|
Loader {
|
||||||
|
id: iconLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginXS * scaling
|
||||||
|
visible: !modelData.isImage || !modelData.imageSource || imagePreview.status === Image.Error
|
||||||
|
active: visible
|
||||||
|
|
||||||
|
sourceComponent: Component {
|
||||||
|
IconImage {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: modelData.icon ? Icons.iconFromName(modelData.icon, "application-x-executable") : ""
|
||||||
|
visible: modelData.icon && source !== ""
|
||||||
|
asynchronous: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback text if no icon and no image
|
||||||
NText {
|
NText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: !modelData.icon || parent.children[0].source === ""
|
visible: !imagePreview.visible && !iconLoader.visible
|
||||||
text: modelData.name ? modelData.name.charAt(0).toUpperCase() : "?"
|
text: modelData.name ? modelData.name.charAt(0).toUpperCase() : "?"
|
||||||
font.pointSize: Style.fontSizeXXL * scaling
|
font.pointSize: Style.fontSizeXXL * scaling
|
||||||
font.weight: Style.fontWeightBold
|
font.weight: Style.fontWeightBold
|
||||||
color: Color.mOnPrimary
|
color: Color.mOnPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Image type indicator overlay
|
||||||
|
Rectangle {
|
||||||
|
visible: modelData.isImage && imagePreview.visible
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: 2 * scaling
|
||||||
|
width: formatLabel.width + 6 * scaling
|
||||||
|
height: formatLabel.height + 2 * scaling
|
||||||
|
radius: 2 * scaling
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.7)
|
||||||
|
|
||||||
|
NText {
|
||||||
|
id: formatLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: {
|
||||||
|
if (!modelData.isImage)
|
||||||
|
return ""
|
||||||
|
const desc = modelData.description || ""
|
||||||
|
const parts = desc.split(" • ")
|
||||||
|
return parts[0] || "IMG"
|
||||||
|
}
|
||||||
|
font.pointSize: Style.fontSizeXXS * scaling
|
||||||
|
color: Color.mPrimary
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text
|
// Text content
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 0 * scaling
|
spacing: 0 * scaling
|
||||||
|
|
@ -315,6 +391,23 @@ NPanel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: text !== ""
|
visible: text !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // Show text preview for text items if space allows
|
||||||
|
// NText {
|
||||||
|
// visible: !modelData.isImage && modelData.fullText && modelData.fullText.length > 100
|
||||||
|
// text: {
|
||||||
|
// if (!modelData.fullText) return ""
|
||||||
|
// const preview = modelData.fullText.substring(0, 150).replace(/\n/g, " ")
|
||||||
|
// return preview + (modelData.fullText.length > 150 ? "..." : "")
|
||||||
|
// }
|
||||||
|
// font.pointSize: Style.fontSizeXS * scaling
|
||||||
|
// color: entry.isSelected ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||||
|
// opacity: 0.7
|
||||||
|
// elide: Text.ElideRight
|
||||||
|
// maximumLineCount: 2
|
||||||
|
// wrapMode: Text.WordWrap
|
||||||
|
// Layout.fillWidth: true
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ QtObject {
|
||||||
"name": app.name || "Unknown",
|
"name": app.name || "Unknown",
|
||||||
"description": app.genericName || app.comment || "",
|
"description": app.genericName || app.comment || "",
|
||||||
"icon": app.icon || "application-x-executable",
|
"icon": app.icon || "application-x-executable",
|
||||||
|
"isImage": false,
|
||||||
"onActivate": function () {
|
"onActivate": function () {
|
||||||
Logger.log("ApplicationsPlugin", `Launching: ${app.name}`)
|
Logger.log("ApplicationsPlugin", `Launching: ${app.name}`)
|
||||||
if (app.execute) {
|
if (app.execute) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ QtObject {
|
||||||
"name": ">calc",
|
"name": ">calc",
|
||||||
"description": "Calculator - evaluate mathematical expressions",
|
"description": "Calculator - evaluate mathematical expressions",
|
||||||
"icon": "accessories-calculator",
|
"icon": "accessories-calculator",
|
||||||
|
"isImage": false,
|
||||||
"onActivate": function () {
|
"onActivate": function () {
|
||||||
launcher.setSearchText(">calc ")
|
launcher.setSearchText(">calc ")
|
||||||
}
|
}
|
||||||
|
|
@ -39,6 +40,7 @@ QtObject {
|
||||||
"name": "Calculator",
|
"name": "Calculator",
|
||||||
"description": "Enter a mathematical expression",
|
"description": "Enter a mathematical expression",
|
||||||
"icon": "accessories-calculator",
|
"icon": "accessories-calculator",
|
||||||
|
"isImage": false,
|
||||||
"onActivate": function () {}
|
"onActivate": function () {}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +52,7 @@ QtObject {
|
||||||
"name": AdvancedMath.formatResult(result),
|
"name": AdvancedMath.formatResult(result),
|
||||||
"description": `${expression} = ${result}`,
|
"description": `${expression} = ${result}`,
|
||||||
"icon": "accessories-calculator",
|
"icon": "accessories-calculator",
|
||||||
|
"isImage": false,
|
||||||
"onActivate": function () {
|
"onActivate": function () {
|
||||||
// Copy result to clipboard if service available
|
// Copy result to clipboard if service available
|
||||||
// if (typeof ClipboardService !== 'undefined') {
|
// if (typeof ClipboardService !== 'undefined') {
|
||||||
|
|
@ -63,6 +66,7 @@ QtObject {
|
||||||
"name": "Error",
|
"name": "Error",
|
||||||
"description": error.message || "Invalid expression",
|
"description": error.message || "Invalid expression",
|
||||||
"icon": "dialog-error",
|
"icon": "dialog-error",
|
||||||
|
"isImage": false,
|
||||||
"onActivate": function () {}
|
"onActivate": function () {}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ QtObject {
|
||||||
"name": ">clip",
|
"name": ">clip",
|
||||||
"description": "Search clipboard history",
|
"description": "Search clipboard history",
|
||||||
"icon": "content_paste",
|
"icon": "content_paste",
|
||||||
|
"isImage": false,
|
||||||
"onActivate": function () {
|
"onActivate": function () {
|
||||||
launcher.setSearchText(">clip ")
|
launcher.setSearchText(">clip ")
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +43,7 @@ QtObject {
|
||||||
"name": ">clip clear",
|
"name": ">clip clear",
|
||||||
"description": "Clear all clipboard history",
|
"description": "Clear all clipboard history",
|
||||||
"icon": "delete_sweep",
|
"icon": "delete_sweep",
|
||||||
|
"isImage": false,
|
||||||
"onActivate": function () {
|
"onActivate": function () {
|
||||||
CliphistService.wipeAll()
|
CliphistService.wipeAll()
|
||||||
launcher.close()
|
launcher.close()
|
||||||
|
|
@ -110,6 +112,7 @@ QtObject {
|
||||||
"name": searchTerm ? "No matching clipboard items" : "Clipboard is empty",
|
"name": searchTerm ? "No matching clipboard items" : "Clipboard is empty",
|
||||||
"description": searchTerm ? `No items containing "${query}"` : "Copy something to see it here",
|
"description": searchTerm ? `No items containing "${query}"` : "Copy something to see it here",
|
||||||
"icon": "content_paste_off",
|
"icon": "content_paste_off",
|
||||||
|
"isImage": false,
|
||||||
"onActivate": function () {// Do nothing
|
"onActivate": function () {// Do nothing
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -118,18 +121,44 @@ QtObject {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Format image clipboard entry
|
// Helper: Format image clipboard entry with actual image data
|
||||||
function formatImageEntry(item) {
|
function formatImageEntry(item) {
|
||||||
const meta = parseImageMeta(item.preview)
|
const meta = parseImageMeta(item.preview)
|
||||||
|
|
||||||
|
// Get the actual image data/path from the clipboard service
|
||||||
|
// This assumes CliphistService provides either a path or base64 data
|
||||||
|
let imageData = null
|
||||||
|
|
||||||
|
// Try to get image data from the service
|
||||||
|
// Method 1: If the service provides a file path
|
||||||
|
if (item.imagePath) {
|
||||||
|
imageData = "file://" + item.imagePath
|
||||||
|
} // Method 2: If the service provides base64 data
|
||||||
|
else if (item.imageData) {
|
||||||
|
imageData = ClipHistService.getImageData(item.id)
|
||||||
|
|
||||||
|
// "data:" + (item.mime || "image/png") + ";base64," + item.imageData
|
||||||
|
} // Method 3: If we need to fetch it from the service
|
||||||
|
|
||||||
|
// else if (item.id) {
|
||||||
|
// // Some clipboard services might require fetching the image separately
|
||||||
|
// // This would depend on your CliphistService implementation
|
||||||
|
// imageData = CliphistService.getImageData ? CliphistService.getImageData(item.id) : null
|
||||||
|
// }
|
||||||
return {
|
return {
|
||||||
"name": meta ? `Image ${meta.w}×${meta.h}` : "Image",
|
"name": meta ? `Image ${meta.w}×${meta.h}` : "Image",
|
||||||
"description": meta ? `${meta.fmt} • ${meta.size}` : item.mime || "Image data",
|
"description": meta ? `${meta.fmt} • ${meta.size}` : item.mime || "Image data",
|
||||||
"icon": "image"
|
"icon": "image",
|
||||||
|
"isImage": true,
|
||||||
|
"imageSource": imageData,
|
||||||
|
"imageWidth": meta ? meta.w : 0,
|
||||||
|
"imageHeight": meta ? meta.h : 0,
|
||||||
|
"clipboardId"// Add clipboard item ID for potential async loading
|
||||||
|
: item.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Format text clipboard entry
|
// Helper: Format text clipboard entry with preview
|
||||||
function formatTextEntry(item) {
|
function formatTextEntry(item) {
|
||||||
const preview = (item.preview || "").trim()
|
const preview = (item.preview || "").trim()
|
||||||
const lines = preview.split('\n').filter(l => l.trim())
|
const lines = preview.split('\n').filter(l => l.trim())
|
||||||
|
|
@ -156,7 +185,8 @@ QtObject {
|
||||||
return {
|
return {
|
||||||
"name": title,
|
"name": title,
|
||||||
"description": description,
|
"description": description,
|
||||||
"icon": "description"
|
"icon": "description",
|
||||||
|
"isImage": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,4 +206,10 @@ QtObject {
|
||||||
"h": Number(match[4])
|
"h": Number(match[4])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Public method to get image data for a clipboard item
|
||||||
|
// This can be called by the launcher when rendering
|
||||||
|
function getImageForItem(clipboardId) {
|
||||||
|
return CliphistService.getImageData ? CliphistService.getImageData(clipboardId) : null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,11 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getImageData(id) {
|
||||||
|
return root.imageDataById[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function _startNextB64() {
|
function _startNextB64() {
|
||||||
if (root._b64Queue.length === 0 || !root.cliphistAvailable)
|
if (root._b64Queue.length === 0 || !root.cliphistAvailable)
|
||||||
return
|
return
|
||||||
|
|
@ -316,7 +321,11 @@ Singleton {
|
||||||
if (!root.cliphistAvailable) {
|
if (!root.cliphistAvailable) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Quickshell.execDetached(["cliphist", "wipe"])
|
Quickshell.execDetached(["cliphist", "wipe"])
|
||||||
|
|
||||||
|
revision++
|
||||||
|
|
||||||
Qt.callLater(() => list())
|
Qt.callLater(() => list())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue