229 lines
7.2 KiB
QML
229 lines
7.2 KiB
QML
// ==================================================
|
||
// =============== 可缩放的图片预览组件 ===============
|
||
// ==================================================
|
||
|
||
import QtQuick 2.15
|
||
import QtQuick.Controls 2.15
|
||
|
||
import ".."
|
||
|
||
Rectangle {
|
||
id: iRoot
|
||
// ========================= 【接口】 =========================
|
||
|
||
// 可设置
|
||
property real scaleMax: 2.0 // 比例上下限
|
||
property real scaleMin: 0.1 // 比例上下限
|
||
property QtObject overlayLayer // 图片叠加层
|
||
property var border: bRect.border // 边框
|
||
// 只读
|
||
property alias showImage: showImage // 图片组件
|
||
property real scale: 1.0 // 图片缩放比例
|
||
property int imageSW: 0 // 图片原始宽高
|
||
property int imageSH: 0
|
||
// 子类重写
|
||
property var beforeShow: undefined // 展示图片之前执行的操作
|
||
|
||
// 设置图片源,展示一张图片
|
||
function setSource(source) {
|
||
if(source) {
|
||
// 特殊字符#替换为%23
|
||
if(source.startsWith("file:///") && source.includes("#"))
|
||
source = source.replace(new RegExp("#", "g"), "%23");
|
||
showImage.source = source // 设置源
|
||
}
|
||
else
|
||
showImage.source = ""
|
||
}
|
||
|
||
// 传入路径,展示图片
|
||
function showPath(path) {
|
||
if(beforeShow) beforeShow()
|
||
showImage.showPath(path)
|
||
}
|
||
|
||
// 传入imgID,展示图片
|
||
function showImgID(imgID) {
|
||
if(beforeShow) beforeShow()
|
||
showImage.showImgID(imgID)
|
||
}
|
||
|
||
// 清空展示
|
||
function clear() {
|
||
if(beforeShow) beforeShow()
|
||
// showImage.clear()
|
||
showImage.source = ""
|
||
imageSW = imageSH = 0
|
||
}
|
||
|
||
// 复制当前图片
|
||
function copyImage() {
|
||
if(showImage.source == "") return
|
||
const res = qmlapp.imageManager.copyImage(showImage.source)
|
||
if(res === "[Success]")
|
||
qmlapp.popup.simple(qsTr("复制图片"), "")
|
||
else
|
||
qmlapp.popup.simple(qsTr("复制图片失败"), res)
|
||
}
|
||
|
||
// 用系统默认应用打开图片
|
||
function openImage() {
|
||
if(showImage.source == "") return
|
||
const res = qmlapp.imageManager.openImage(showImage.source)
|
||
if(res === "[Success]")
|
||
qmlapp.popup.simple(qsTr("打开图片"), "")
|
||
else
|
||
qmlapp.popup.simple(qsTr("打开图片失败"), res)
|
||
}
|
||
|
||
// 保存当前图片
|
||
function saveImage() {
|
||
if(showImage.source == "") return
|
||
saveDialog.open()
|
||
}
|
||
|
||
FileDialog_ {
|
||
id: saveDialog
|
||
title: qsTr("保存图片")
|
||
selectExisting: false
|
||
selectFolder: false
|
||
folder: shortcuts.desktop // 默认放桌面
|
||
nameFilters: ["*.png", "*.jpg"]
|
||
onAccepted: {
|
||
if(!fileUrl) {
|
||
console.log("文件对话框:未选择任何文件")
|
||
return
|
||
}
|
||
let filePath = fileUrl
|
||
const res = qmlapp.imageManager.saveImage(showImage.source, filePath)
|
||
if(res.startsWith("[Success]"))
|
||
qmlapp.popup.simple(qsTr("保存图片"), res)
|
||
else
|
||
qmlapp.popup.simple(qsTr("保存图片失败"), res)
|
||
}
|
||
}
|
||
|
||
// ========================= 【处理】 =========================
|
||
|
||
Component.onCompleted: {
|
||
// 叠加层挂父级
|
||
if(overlayLayer && overlayLayer.hasOwnProperty("parent"))
|
||
overlayLayer.parent = showImage
|
||
}
|
||
|
||
|
||
// 图片组件的状态改变
|
||
function imageStatusChanged(s) {
|
||
// 已就绪
|
||
if(s == Image.Ready) {
|
||
imageSW = showImage.sourceSize.width // 记录图片原始宽高
|
||
imageSH = showImage.sourceSize.height
|
||
imageFullFit() // 初始大小
|
||
}
|
||
else {
|
||
imageSW = imageSH = 0
|
||
iRoot.scale = 1
|
||
}
|
||
}
|
||
|
||
// 缩放,传入 flag>0 放大, <0 缩小 ,0回归100%。以相框中心为锚点。
|
||
function imageScaleAddSub(flag=0, step=0.1) {
|
||
if(showImage.status != Image.Ready) return
|
||
// 计算缩放比例
|
||
let s = 1.0 // flag==0 时复原
|
||
if (flag > 0) { // 放大
|
||
s = (iRoot.scale + step).toFixed(1)
|
||
// 禁止大于上限 或 图片填满大小(裁切)
|
||
const max = Math.max(flickable.width/imageSW, flickable.height/imageSH, scaleMax)
|
||
if(s > max) s = max
|
||
}
|
||
else if(flag < 0) { // 缩小
|
||
s = (iRoot.scale - step).toFixed(1)
|
||
// 禁止小于下限 或 图片填满大小(不裁切)
|
||
const min = Math.min(flickable.width/imageSW, flickable.height/imageSH, scaleMin)
|
||
if(s < min) s = min
|
||
}
|
||
|
||
// 目标锚点
|
||
let gx = -flickable.width/2
|
||
let gy = -flickable.height/2
|
||
// 目标锚点在图片中的原比例
|
||
let s1x = (flickable.contentX-gx)/showImageContainer.width
|
||
let s1y = (flickable.contentY-gy)/showImageContainer.height
|
||
// 目标锚点在图片中的新比例,及差值
|
||
iRoot.scale = s // 更新缩放
|
||
let s2x = (flickable.contentX-gx)/showImageContainer.width
|
||
let s2y = (flickable.contentY-gy)/showImageContainer.height
|
||
let sx = s2x-s1x
|
||
let sy = s2y-s1y
|
||
// 实际长度差值
|
||
let lx = sx*showImageContainer.width
|
||
let ly = sy*showImageContainer.height
|
||
// 偏移
|
||
flickable.contentX -= lx
|
||
flickable.contentY -= ly
|
||
}
|
||
|
||
// 图片填满组件,不裁切
|
||
function imageFullFit() {
|
||
if(showImage.source == "" || imageSW <= 0) return
|
||
iRoot.scale = Math.min(flickable.width/imageSW, flickable.height/imageSH)
|
||
// 图片中心对齐相框
|
||
flickable.contentY = - (flickable.height - showImageContainer.height)/2
|
||
flickable.contentX = - (flickable.width - showImageContainer.width)/2
|
||
}
|
||
|
||
// ======================== 【布局】 =========================
|
||
color: theme.bgColor
|
||
|
||
|
||
// 滑动区域,显示图片,监听左键拖拽
|
||
Flickable {
|
||
id: flickable
|
||
anchors.fill: parent
|
||
contentWidth: showImageContainer.width
|
||
contentHeight: showImageContainer.height
|
||
clip: true
|
||
|
||
// 图片容器,大小不小于滑动区域
|
||
Item {
|
||
id: showImageContainer
|
||
width: Math.max( imageSW * iRoot.scale , flickable.width )
|
||
height: Math.max( imageSH * iRoot.scale , flickable.height )
|
||
Image_ {
|
||
id: showImage
|
||
anchors.centerIn: parent
|
||
scale: iRoot.scale
|
||
onStatusChanged: imageStatusChanged(status)
|
||
}
|
||
}
|
||
|
||
// 滚动条
|
||
ScrollBar.vertical: ScrollBar { }
|
||
ScrollBar.horizontal: ScrollBar { }
|
||
}
|
||
|
||
// 监听滚轮缩放
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
acceptedButtons: Qt.NoButton
|
||
// 滚轮缩放
|
||
onWheel: {
|
||
if (wheel.angleDelta.y > 0) {
|
||
imageScaleAddSub(1) // 放大
|
||
}
|
||
else {
|
||
imageScaleAddSub(-1) // 缩小
|
||
}
|
||
}
|
||
}
|
||
|
||
// 边框
|
||
Rectangle {
|
||
id: bRect
|
||
anchors.fill: parent
|
||
color: "#00000000"
|
||
border.width: 1
|
||
border.color: theme.coverColor4
|
||
}
|
||
} |