点阵完成,加入opencv
This commit is contained in:
616
qml/content/ColorPickerDialog.qml
Normal file
616
qml/content/ColorPickerDialog.qml
Normal file
@@ -0,0 +1,616 @@
|
||||
// ColorPickerWindow.qml
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls.Material
|
||||
import TactileIPC 1.0
|
||||
|
||||
Window {
|
||||
id: root
|
||||
width: 360
|
||||
height: 650
|
||||
visible: false
|
||||
modality: Qt.ApplicationModal
|
||||
flags: Qt.Dialog
|
||||
title: qsTr("颜色选择")
|
||||
Material.theme: Backend.lightMode ? Material.Light : Material.Dark
|
||||
Material.accent: Material.Green
|
||||
Material.primary: Material.Green
|
||||
|
||||
readonly property bool isDark: !Backend.lightMode
|
||||
readonly property color windowBg: isDark ? "#1F1F1F" : "#F7F7F7"
|
||||
readonly property color panelBorder: isDark ? "#2E2E2E" : "#D9D9D9"
|
||||
readonly property color surfaceBorder: isDark ? "#343434" : "#CFCFCF"
|
||||
readonly property color textPrimary: isDark ? "#EDEDED" : "#1F1F1F"
|
||||
readonly property color textSecondary: isDark ? "#D8D8D8" : "#616161"
|
||||
readonly property color textMuted: isDark ? "#BEBEBE" : "#6E6E6E"
|
||||
readonly property color controlBg: isDark ? "#2A2A2A" : "#FFFFFF"
|
||||
readonly property color controlBorder: isDark ? "#3A3A3A" : "#CFCFCF"
|
||||
readonly property color highlightBg: isDark ? "#55FFFFFF" : "#33000000"
|
||||
readonly property color highlightText: isDark ? "#111111" : "#FFFFFF"
|
||||
readonly property color indicatorBorder: isDark ? "#6A6A6A" : "#9E9E9E"
|
||||
readonly property color accentColor: Material.color(Material.Green)
|
||||
|
||||
// ===== API =====
|
||||
property color color: "#FF7032D2" // AARRGGBB
|
||||
signal accepted(color c)
|
||||
signal rejected()
|
||||
|
||||
function openWith(c) {
|
||||
syncFromColor(c ?? root.color)
|
||||
visible = true
|
||||
requestActivate()
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
visible = false
|
||||
rejected()
|
||||
}
|
||||
|
||||
// ===== internal HSV(A) =====
|
||||
property real h: 0.75 // 0..1
|
||||
property real s: 0.45
|
||||
property real v: 0.82
|
||||
property real a: 1.0
|
||||
|
||||
property bool _lock: false
|
||||
|
||||
function clamp01(x) { return Math.max(0, Math.min(1, x)) }
|
||||
function clampInt(x, lo, hi) { return Math.max(lo, Math.min(hi, Math.round(x))) }
|
||||
|
||||
function hsvToRgb(hh, ss, vv) {
|
||||
const h6 = (hh % 1) * 6
|
||||
const c = vv * ss
|
||||
const x = c * (1 - Math.abs((h6 % 2) - 1))
|
||||
const m = vv - c
|
||||
let r1=0, g1=0, b1=0
|
||||
if (0 <= h6 && h6 < 1) { r1=c; g1=x; b1=0 }
|
||||
else if (1 <= h6 && h6 < 2) { r1=x; g1=c; b1=0 }
|
||||
else if (2 <= h6 && h6 < 3) { r1=0; g1=c; b1=x }
|
||||
else if (3 <= h6 && h6 < 4) { r1=0; g1=x; b1=c }
|
||||
else if (4 <= h6 && h6 < 5) { r1=x; g1=0; b1=c }
|
||||
else { r1=c; g1=0; b1=x }
|
||||
return { r: r1 + m, g: g1 + m, b: b1 + m }
|
||||
}
|
||||
|
||||
function rgbToHsv(r, g, b) {
|
||||
const max = Math.max(r, g, b)
|
||||
const min = Math.min(r, g, b)
|
||||
const d = max - min
|
||||
let hh = 0
|
||||
if (d !== 0) {
|
||||
if (max === r) hh = ((g - b) / d) % 6
|
||||
else if (max === g) hh = (b - r) / d + 2
|
||||
else hh = (r - g) / d + 4
|
||||
hh /= 6
|
||||
if (hh < 0) hh += 1
|
||||
}
|
||||
const ss = (max === 0) ? 0 : d / max
|
||||
const vv = max
|
||||
return { h: hh, s: ss, v: vv }
|
||||
}
|
||||
|
||||
function toHex2(v01) {
|
||||
const n = clampInt(clamp01(v01) * 255, 0, 255)
|
||||
const s = n.toString(16).toUpperCase()
|
||||
return (s.length === 1) ? ("0" + s) : s
|
||||
}
|
||||
|
||||
function colorToHexAARRGGBB(c) {
|
||||
return "#" + toHex2(c.a) + toHex2(c.r) + toHex2(c.g) + toHex2(c.b)
|
||||
}
|
||||
|
||||
function parseHex(str) {
|
||||
let t = ("" + str).trim()
|
||||
if (t.startsWith("0x") || t.startsWith("0X")) t = t.slice(2)
|
||||
if (t.startsWith("#")) t = t.slice(1)
|
||||
if (t.length === 6) {
|
||||
const rr = parseInt(t.slice(0,2), 16)
|
||||
const gg = parseInt(t.slice(2,4), 16)
|
||||
const bb = parseInt(t.slice(4,6), 16)
|
||||
if ([rr,gg,bb].some(x => isNaN(x))) return null
|
||||
return Qt.rgba(rr/255, gg/255, bb/255, 1)
|
||||
}
|
||||
if (t.length === 8) {
|
||||
const aa = parseInt(t.slice(0,2), 16)
|
||||
const rr = parseInt(t.slice(2,4), 16)
|
||||
const gg = parseInt(t.slice(4,6), 16)
|
||||
const bb = parseInt(t.slice(6,8), 16)
|
||||
if ([aa,rr,gg,bb].some(x => isNaN(x))) return null
|
||||
return Qt.rgba(rr/255, gg/255, bb/255, aa/255)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function syncFromHSV() {
|
||||
if (_lock) return
|
||||
_lock = true
|
||||
const rgb = hsvToRgb(h, s, v)
|
||||
color = Qt.rgba(rgb.r, rgb.g, rgb.b, a)
|
||||
|
||||
// 更新 UI
|
||||
hexField.text = colorToHexAARRGGBB(color)
|
||||
alphaPercent.text = clampInt(a*100, 0, 100) + "%"
|
||||
|
||||
rField.value = clampInt(color.r * 255, 0, 255)
|
||||
gField.value = clampInt(color.g * 255, 0, 255)
|
||||
bField.value = clampInt(color.b * 255, 0, 255)
|
||||
|
||||
// HSV 显示用度/百分比
|
||||
hField.value = clampInt(h * 360, 0, 360)
|
||||
sField.value = clampInt(s * 100, 0, 100)
|
||||
vField.value = clampInt(v * 100, 0, 100)
|
||||
|
||||
svCanvas.requestPaint()
|
||||
hueCanvas.requestPaint()
|
||||
alphaCanvas.requestPaint()
|
||||
_lock = false
|
||||
}
|
||||
|
||||
function syncFromColor(c) {
|
||||
if (_lock) return
|
||||
_lock = true
|
||||
const hsv = rgbToHsv(c.r, c.g, c.b)
|
||||
h = hsv.h; s = hsv.s; v = hsv.v; a = c.a
|
||||
color = Qt.rgba(c.r, c.g, c.b, a)
|
||||
|
||||
hexField.text = colorToHexAARRGGBB(color)
|
||||
alphaPercent.text = clampInt(a*100, 0, 100) + "%"
|
||||
|
||||
rField.value = clampInt(c.r * 255, 0, 255)
|
||||
gField.value = clampInt(c.g * 255, 0, 255)
|
||||
bField.value = clampInt(c.b * 255, 0, 255)
|
||||
|
||||
hField.value = clampInt(h * 360, 0, 360)
|
||||
sField.value = clampInt(s * 100, 0, 100)
|
||||
vField.value = clampInt(v * 100, 0, 100)
|
||||
|
||||
svCanvas.requestPaint()
|
||||
hueCanvas.requestPaint()
|
||||
alphaCanvas.requestPaint()
|
||||
_lock = false
|
||||
}
|
||||
|
||||
function applyRGB(r255, g255, b255) {
|
||||
if (_lock) return
|
||||
const rr = clampInt(r255, 0, 255) / 255
|
||||
const gg = clampInt(g255, 0, 255) / 255
|
||||
const bb = clampInt(b255, 0, 255) / 255
|
||||
const hsv = rgbToHsv(rr, gg, bb)
|
||||
h = hsv.h; s = hsv.s; v = hsv.v
|
||||
syncFromHSV()
|
||||
}
|
||||
|
||||
function applyHSV(hDeg, sPct, vPct) {
|
||||
if (_lock) return
|
||||
h = clamp01(hDeg / 360)
|
||||
s = clamp01(sPct / 100)
|
||||
v = clamp01(vPct / 100)
|
||||
syncFromHSV()
|
||||
}
|
||||
|
||||
// 初始同步
|
||||
Component.onCompleted: syncFromColor(root.color)
|
||||
|
||||
// ====== UI: 深色窗口面板(不是卡片)======
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 0
|
||||
color: root.windowBg
|
||||
border.width: 1
|
||||
border.color: root.panelBorder
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 10
|
||||
|
||||
// 顶部:标题 + 勾选(参考图右上角)
|
||||
// RowLayout {
|
||||
// Layout.fillWidth: true
|
||||
// spacing: 8
|
||||
|
||||
// Label {
|
||||
// text: root.title
|
||||
// color: root.textPrimary
|
||||
// font.pixelSize: 14
|
||||
// font.weight: Font.DemiBold
|
||||
// Layout.fillWidth: true
|
||||
// }
|
||||
|
||||
// CheckBox {
|
||||
// id: alphaToggle
|
||||
// checked: true
|
||||
|
||||
// contentItem: Label {
|
||||
// text: qsTr("实时预览")
|
||||
// color: root.textSecondary
|
||||
// verticalAlignment: Text.AlignVCenter
|
||||
// elide: Text.ElideRight
|
||||
// }
|
||||
|
||||
// indicator: Rectangle {
|
||||
// implicitWidth: 16
|
||||
// implicitHeight: 16
|
||||
// radius: 3
|
||||
// border.width: 1
|
||||
// border.color: root.indicatorBorder
|
||||
// color: alphaToggle.checked ? root.accentColor : "transparent"
|
||||
|
||||
// // 选中勾
|
||||
// Canvas {
|
||||
// anchors.fill: parent
|
||||
// onPaint: {
|
||||
// const ctx = getContext("2d")
|
||||
// ctx.clearRect(0,0,width,height)
|
||||
// if (!alphaToggle.checked) return
|
||||
// ctx.strokeStyle = "white"
|
||||
// ctx.lineWidth = 2
|
||||
// ctx.lineCap = "round"
|
||||
// ctx.beginPath()
|
||||
// ctx.moveTo(width*0.25, height*0.55)
|
||||
// ctx.lineTo(width*0.45, height*0.72)
|
||||
// ctx.lineTo(width*0.78, height*0.30)
|
||||
// ctx.stroke()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// ToolButton {
|
||||
// text: "✕"
|
||||
// onClicked: { root.visible = false; root.rejected() }
|
||||
// }
|
||||
// }
|
||||
|
||||
// HSV 面板
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 185
|
||||
radius: 8
|
||||
border.width: 1
|
||||
border.color: root.surfaceBorder
|
||||
clip: true
|
||||
|
||||
Canvas {
|
||||
id: svCanvas
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const w = width, hh = height
|
||||
ctx.clearRect(0,0,w,hh)
|
||||
|
||||
const rgbHue = root.hsvToRgb(root.h, 1, 1)
|
||||
|
||||
// X: white -> hue
|
||||
const gx = ctx.createLinearGradient(0,0,w,0)
|
||||
gx.addColorStop(0, "rgb(255,255,255)")
|
||||
gx.addColorStop(1, "rgb(" + Math.round(rgbHue.r*255) + "," + Math.round(rgbHue.g*255) + "," + Math.round(rgbHue.b*255) + ")")
|
||||
ctx.fillStyle = gx
|
||||
ctx.fillRect(0,0,w,hh)
|
||||
|
||||
// Y: transparent -> black
|
||||
const gy = ctx.createLinearGradient(0,0,0,hh)
|
||||
gy.addColorStop(0, "rgba(0,0,0,0)")
|
||||
gy.addColorStop(1, "rgba(0,0,0,1)")
|
||||
ctx.fillStyle = gy
|
||||
ctx.fillRect(0,0,w,hh)
|
||||
|
||||
// handle
|
||||
const x = root.s * w
|
||||
const y = (1 - root.v) * hh
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, y, 7, 0, Math.PI*2)
|
||||
ctx.lineWidth = 2
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.95)"
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, y, 6, 0, Math.PI*2)
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeStyle = "rgba(0,0,0,0.55)"
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
function apply(mx, my) {
|
||||
root.s = root.clamp01(mx / width)
|
||||
root.v = root.clamp01(1 - (my / height))
|
||||
root.syncFromHSV()
|
||||
}
|
||||
onPressed: (e) => apply(e.x, e.y)
|
||||
onPositionChanged: (e) => { if (pressed) apply(e.x, e.y) }
|
||||
}
|
||||
}
|
||||
|
||||
// Hue 彩虹条(参考图那种)
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 16
|
||||
radius: 8
|
||||
border.width: 1
|
||||
border.color: root.surfaceBorder
|
||||
clip: true
|
||||
|
||||
Canvas {
|
||||
id: hueCanvas
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const w = width, hh = height
|
||||
ctx.clearRect(0,0,w,hh)
|
||||
const grad = ctx.createLinearGradient(0,0,w,0)
|
||||
for (let i=0; i<=6; i++) {
|
||||
const t = i/6
|
||||
const rgb = root.hsvToRgb(t, 1, 1)
|
||||
grad.addColorStop(t, "rgb(" + Math.round(rgb.r*255) + "," + Math.round(rgb.g*255) + "," + Math.round(rgb.b*255) + ")")
|
||||
}
|
||||
ctx.fillStyle = grad
|
||||
ctx.fillRect(0,0,w,hh)
|
||||
|
||||
const x = root.h * w
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, hh/2, 7, 0, Math.PI*2)
|
||||
ctx.lineWidth = 2
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.95)"
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, hh/2, 6, 0, Math.PI*2)
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeStyle = "rgba(0,0,0,0.55)"
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
function apply(mx) {
|
||||
root.h = root.clamp01(mx / width)
|
||||
root.syncFromHSV()
|
||||
}
|
||||
onPressed: (e) => apply(e.x)
|
||||
onPositionChanged: (e) => { if (pressed) apply(e.x) }
|
||||
}
|
||||
}
|
||||
|
||||
// Alpha 条(棋盘 + 渐变 + 右侧圆点)
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 16
|
||||
radius: 8
|
||||
border.width: 1
|
||||
border.color: root.surfaceBorder
|
||||
clip: true
|
||||
|
||||
// checker
|
||||
Canvas {
|
||||
id: checkerCanvas
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const w = width, hh = height
|
||||
ctx.clearRect(0,0,w,hh)
|
||||
const s = 8
|
||||
for (let y=0; y<hh; y+=s) {
|
||||
for (let x=0; x<w; x+=s) {
|
||||
const on = ((x/s + y/s) % 2) === 0
|
||||
ctx.fillStyle = on ? "rgba(255,255,255,0.16)" : "rgba(0,0,0,0.0)"
|
||||
ctx.fillRect(x,y,s,s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: alphaCanvas
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const w = width, hh = height
|
||||
ctx.clearRect(0,0,w,hh)
|
||||
|
||||
const rgb = root.hsvToRgb(root.h, root.s, root.v)
|
||||
const grad = ctx.createLinearGradient(0,0,w,0)
|
||||
grad.addColorStop(0, "rgba(" + Math.round(rgb.r*255) + "," + Math.round(rgb.g*255) + "," + Math.round(rgb.b*255) + ",0)")
|
||||
grad.addColorStop(1, "rgba(" + Math.round(rgb.r*255) + "," + Math.round(rgb.g*255) + "," + Math.round(rgb.b*255) + ",1)")
|
||||
ctx.fillStyle = grad
|
||||
ctx.fillRect(0,0,w,hh)
|
||||
|
||||
const x = root.a * w
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, hh/2, 7, 0, Math.PI*2)
|
||||
ctx.lineWidth = 2
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.95)"
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, hh/2, 6, 0, Math.PI*2)
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeStyle = "rgba(0,0,0,0.55)"
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
function apply(mx) {
|
||||
root.a = root.clamp01(mx / width)
|
||||
root.syncFromHSV()
|
||||
}
|
||||
onPressed: (e) => apply(e.x)
|
||||
onPositionChanged: (e) => { if (pressed) apply(e.x) }
|
||||
}
|
||||
}
|
||||
|
||||
// 下方:色块 + Hex + 透明度百分比
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
// 预览色块(带棋盘底)
|
||||
Rectangle {
|
||||
width: 44
|
||||
height: 44
|
||||
radius: 8
|
||||
border.width: 1
|
||||
border.color: root.surfaceBorder
|
||||
clip: true
|
||||
|
||||
Canvas {
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const w = width, hh = height
|
||||
ctx.clearRect(0,0,w,hh)
|
||||
const s = 10
|
||||
for (let y=0; y<hh; y+=s) {
|
||||
for (let x=0; x<w; x+=s) {
|
||||
const on = ((x/s + y/s) % 2) === 0
|
||||
ctx.fillStyle = on ? "rgba(255,255,255,0.14)" : "rgba(0,0,0,0.0)"
|
||||
ctx.fillRect(x,y,s,s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: root.color
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: hexField
|
||||
Layout.fillWidth: true
|
||||
text: "#FF7032D2"
|
||||
placeholderText: "#AARRGGBB"
|
||||
inputMethodHints: Qt.ImhPreferUppercase | Qt.ImhNoPredictiveText
|
||||
|
||||
// 用 palette,而不是 color/selectionColor/selectedTextColor
|
||||
palette.text: root.textPrimary
|
||||
palette.placeholderText: root.textMuted
|
||||
palette.highlight: root.highlightBg
|
||||
palette.highlightedText: root.highlightText
|
||||
palette.base: root.controlBg // 输入框底色(有的 style 会用它)
|
||||
palette.buttonText: root.textPrimary
|
||||
|
||||
background: Rectangle {
|
||||
radius: 8
|
||||
color: root.controlBg
|
||||
border.width: 1
|
||||
border.color: root.controlBorder
|
||||
}
|
||||
|
||||
onEditingFinished: {
|
||||
const c = root.parseHex(text)
|
||||
if (c) root.syncFromColor(c)
|
||||
else text = root.colorToHexAARRGGBB(root.color)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
width: 54
|
||||
height: 44
|
||||
radius: 8
|
||||
color: root.controlBg
|
||||
border.width: 1
|
||||
border.color: root.controlBorder
|
||||
Label {
|
||||
id: alphaPercent
|
||||
anchors.centerIn: parent
|
||||
text: "100%"
|
||||
color: root.textPrimary
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RGB / HSV 数值区(像参考图那样两行块)
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: 3
|
||||
columnSpacing: 8
|
||||
rowSpacing: 8
|
||||
|
||||
// ---- Row1: RGB ----
|
||||
Label { text: "RGB"; color: root.textMuted; Layout.alignment: Qt.AlignVCenter }
|
||||
SpinBox {
|
||||
id: rField
|
||||
from: 0; to: 255; editable: true
|
||||
value: 112
|
||||
Layout.fillWidth: true
|
||||
onValueModified: root.applyRGB(value, gField.value, bField.value)
|
||||
}
|
||||
SpinBox {
|
||||
id: gField
|
||||
from: 0; to: 255; editable: true
|
||||
value: 50
|
||||
Layout.fillWidth: true
|
||||
onValueModified: root.applyRGB(rField.value, value, bField.value)
|
||||
}
|
||||
Item { width: 1; height: 1 } // 占位,让下一行对齐
|
||||
SpinBox {
|
||||
id: bField
|
||||
from: 0; to: 255; editable: true
|
||||
value: 210
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
onValueModified: root.applyRGB(rField.value, gField.value, value)
|
||||
}
|
||||
|
||||
// ---- Row2: HSV ----
|
||||
Label { text: "HSV"; color: root.textMuted; Layout.alignment: Qt.AlignVCenter }
|
||||
SpinBox {
|
||||
id: hField
|
||||
from: 0; to: 360; editable: true
|
||||
value: 263
|
||||
Layout.fillWidth: true
|
||||
onValueModified: root.applyHSV(value, sField.value, vField.value)
|
||||
}
|
||||
SpinBox {
|
||||
id: sField
|
||||
from: 0; to: 100; editable: true
|
||||
value: 64
|
||||
Layout.fillWidth: true
|
||||
onValueModified: root.applyHSV(hField.value, value, vField.value)
|
||||
}
|
||||
Item { width: 1; height: 1 }
|
||||
SpinBox {
|
||||
id: vField
|
||||
from: 0; to: 100; editable: true
|
||||
value: 51
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
onValueModified: root.applyHSV(hField.value, sField.value, value)
|
||||
}
|
||||
}
|
||||
|
||||
// 底部按钮(像窗口)
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
onClicked: { root.visible = false; root.rejected() }
|
||||
}
|
||||
Button {
|
||||
text: qsTr("OK")
|
||||
highlighted: true
|
||||
onClicked: { root.visible = false; root.accepted(root.color) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 每次 HSV 改变,UI 同步
|
||||
onHChanged: if (!_lock) syncFromHSV()
|
||||
onSChanged: if (!_lock) syncFromHSV()
|
||||
onVChanged: if (!_lock) syncFromHSV()
|
||||
onAChanged: if (!_lock) syncFromHSV()
|
||||
}
|
||||
Reference in New Issue
Block a user