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
|
||||
}
|
||||
|
||||
// Replace the delegate in Launcher.qml's ListView with this enhanced version:
|
||||
delegate: Rectangle {
|
||||
id: entry
|
||||
|
||||
property bool isSelected: mouseArea.containsMouse || (index === selectedIndex)
|
||||
property int badgeSize: Style.baseWidgetSize * 1.75 * scaling
|
||||
|
||||
width: resultsList.width - Style.marginS * scaling
|
||||
height: 65 * scaling
|
||||
height: badgeSize + Style.marginM * 2 *scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: entry.isSelected ? Color.mTertiary : Color.mSurface
|
||||
|
||||
|
|
@ -267,33 +269,107 @@ NPanel {
|
|||
anchors.margins: Style.marginM * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Icon badge
|
||||
// Icon badge or Image preview
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 1.25 * scaling
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 1.25 * scaling
|
||||
radius: Style.radiusS * scaling
|
||||
Layout.preferredWidth: badgeSize
|
||||
Layout.preferredHeight: badgeSize
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.mSurfaceVariant
|
||||
clip: true
|
||||
|
||||
IconImage {
|
||||
// Image preview for clipboard images
|
||||
Image {
|
||||
id: imagePreview
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS * scaling
|
||||
source: modelData.icon ? Icons.iconFromName(modelData.icon, "application-x-executable") : ""
|
||||
visible: modelData.icon && source !== ""
|
||||
anchors.margins: 2 * scaling
|
||||
visible: modelData.isImage && modelData.imageSource
|
||||
source: modelData.imageSource || ""
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
mipmap: 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 {
|
||||
anchors.centerIn: parent
|
||||
visible: !modelData.icon || parent.children[0].source === ""
|
||||
visible: !imagePreview.visible && !iconLoader.visible
|
||||
text: modelData.name ? modelData.name.charAt(0).toUpperCase() : "?"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
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 {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0 * scaling
|
||||
|
|
@ -315,6 +391,23 @@ NPanel {
|
|||
Layout.fillWidth: true
|
||||
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",
|
||||
"description": app.genericName || app.comment || "",
|
||||
"icon": app.icon || "application-x-executable",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
Logger.log("ApplicationsPlugin", `Launching: ${app.name}`)
|
||||
if (app.execute) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ QtObject {
|
|||
"name": ">calc",
|
||||
"description": "Calculator - evaluate mathematical expressions",
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
launcher.setSearchText(">calc ")
|
||||
}
|
||||
|
|
@ -39,6 +40,7 @@ QtObject {
|
|||
"name": "Calculator",
|
||||
"description": "Enter a mathematical expression",
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}]
|
||||
}
|
||||
|
|
@ -50,6 +52,7 @@ QtObject {
|
|||
"name": AdvancedMath.formatResult(result),
|
||||
"description": `${expression} = ${result}`,
|
||||
"icon": "accessories-calculator",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
// Copy result to clipboard if service available
|
||||
// if (typeof ClipboardService !== 'undefined') {
|
||||
|
|
@ -63,6 +66,7 @@ QtObject {
|
|||
"name": "Error",
|
||||
"description": error.message || "Invalid expression",
|
||||
"icon": "dialog-error",
|
||||
"isImage": false,
|
||||
"onActivate": function () {}
|
||||
}]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ QtObject {
|
|||
"name": ">clip",
|
||||
"description": "Search clipboard history",
|
||||
"icon": "content_paste",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
launcher.setSearchText(">clip ")
|
||||
}
|
||||
|
|
@ -42,6 +43,7 @@ QtObject {
|
|||
"name": ">clip clear",
|
||||
"description": "Clear all clipboard history",
|
||||
"icon": "delete_sweep",
|
||||
"isImage": false,
|
||||
"onActivate": function () {
|
||||
CliphistService.wipeAll()
|
||||
launcher.close()
|
||||
|
|
@ -110,6 +112,7 @@ QtObject {
|
|||
"name": searchTerm ? "No matching clipboard items" : "Clipboard is empty",
|
||||
"description": searchTerm ? `No items containing "${query}"` : "Copy something to see it here",
|
||||
"icon": "content_paste_off",
|
||||
"isImage": false,
|
||||
"onActivate": function () {// Do nothing
|
||||
}
|
||||
})
|
||||
|
|
@ -118,18 +121,44 @@ QtObject {
|
|||
return results
|
||||
}
|
||||
|
||||
// Helper: Format image clipboard entry
|
||||
// Helper: Format image clipboard entry with actual image data
|
||||
function formatImageEntry(item) {
|
||||
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 {
|
||||
"name": meta ? `Image ${meta.w}×${meta.h}` : "Image",
|
||||
"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) {
|
||||
const preview = (item.preview || "").trim()
|
||||
const lines = preview.split('\n').filter(l => l.trim())
|
||||
|
|
@ -156,7 +185,8 @@ QtObject {
|
|||
return {
|
||||
"name": title,
|
||||
"description": description,
|
||||
"icon": "description"
|
||||
"icon": "description",
|
||||
"isImage": false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,4 +206,10 @@ QtObject {
|
|||
"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() {
|
||||
if (root._b64Queue.length === 0 || !root.cliphistAvailable)
|
||||
return
|
||||
|
|
@ -316,7 +321,11 @@ Singleton {
|
|||
if (!root.cliphistAvailable) {
|
||||
return
|
||||
}
|
||||
|
||||
Quickshell.execDetached(["cliphist", "wipe"])
|
||||
|
||||
revision++
|
||||
|
||||
Qt.callLater(() => list())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue