1、概述
源码放在文章末尾
该项目实现了仿 Windows10 画图3D 的颜色选择器,功能更加丰富更加强大。
项目部分代码如下所示:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15Item {id: rootwidth: 460height: 500scale: 0opacity: 0enabled: falseproperty bool movable: trueproperty alias title: contentText.textproperty color initColor: "white"readonly property color currentColor: pickerRect.currentColoronInitColorChanged: pickerRect.setColor(initColor);signal accepted();signal rejected();function open() {focus = true;enabled = true;}function hide() {focus = false;enabled = false;}Keys.onEscapePressed: cancelButton.clicked();NumberAnimation on scale {running: root.enabledduration: 350easing.type: Easing.OutBackeasing.overshoot: 1.0to: 1.0}NumberAnimation on opacity {running: root.enabledduration: 300easing.type: Easing.OutQuadto: 1.0}NumberAnimation on scale {running: !root.enabledduration: 300easing.type: Easing.InBackeasing.overshoot: 1.0to: 0.0}NumberAnimation on opacity {running: !root.enabledduration: 250easing.type: Easing.OutQuadto: 0.0}RectangularGlow {width: parent.width + 4height: parent.height + 4anchors.centerIn: parentglowRadius: 4spread: 0.2color: "#206856E6"cornerRadius: 4}Rectangle {anchors.fill: parentcolor: "#f6f6f6"border.color: "#aea4ee"}MouseArea {anchors.fill: parentenabled: root.movableproperty point startPos: Qt.point(0, 0)property point offsetPos: Qt.point(0, 0)onClicked: (mouse) => mouse.accepted = false;onPressed: (mouse) => {startPos = Qt.point(mouse.x, mouse.y);cursorShape = Qt.SizeAllCursor;}onReleased: (mouse) => {startPos = Qt.point(mouse.x, mouse.y);cursorShape = Qt.ArrowCursor;}onPositionChanged: (mouse) => {if (pressed) {offsetPos = Qt.point(mouse.x - startPos.x, mouse.y - startPos.y);root.x = root.x + offsetPos.x;root.y = root.y + offsetPos.y;}}Text {id: contentTextheight: 20anchors.top: parent.topanchors.topMargin: 15anchors.left: parent.leftanchors.leftMargin: 25anchors.right: parent.rightfont.family: "微软雅黑"color: "#222255"text: qsTr("选择新颜色")antialiasing: trueverticalAlignment: Text.AlignVCenter}Item {id: pickerRectwidth: 330height: 290anchors.top: contentText.bottomanchors.left: contentText.leftanchors.leftMargin: -cursorWidth * 0.5property real cursorWidth: 30property color hueColor: {let v = 1.0 - hueSlider.value;if (0.0 <= v && v < 0.16) {return Qt.rgba(1.0, 0.0, v / 0.16, 1.0);} else if (0.16 <= v && v < 0.33) {return Qt.rgba(1.0 - (v - 0.16) / 0.17, 0.0, 1.0, 1.0);} else if (0.33 <= v && v < 0.5) {return Qt.rgba(0.0, ((v - 0.33) / 0.17), 1.0, 1.0);} else if (0.5 <= v && v < 0.76) {return Qt.rgba(0.0, 1.0, 1.0 - (v - 0.5) / 0.26, 1.0);} else if (0.76 <= v && v < 0.85) {return Qt.rgba((v - 0.76) / 0.09, 1.0, 0.0, 1.0);} else if (0.85 <= v && v <= 1.0) {return Qt.rgba(1.0, 1.0 - (v - 0.85) / 0.15, 0.0, 1.0);} else {return "red";}}property real saturation: colorPickerCursor.x / (width - cursorWidth)property real brightness: 1 - colorPickerCursor.y / (height - cursorWidth)property color currentColor: Qt.hsva(hueSlider.value, saturation, brightness, alphaSlider.value)property color __color: Qt.rgba(0, 0, 0, 0)function setColor(color) {alphaSlider.x = alphaPicker.width == 0 ? 0 : (alphaPicker.width - alphaSlider.width) * color.a;hueSlider.x = (huePicker.width - hueSlider.width) * (Math.max(color.hsvHue, 0));colorPickerCursor.x = color.hsvSaturation * (width - cursorWidth);colorPickerCursor.y = (1.0 - color.hsvValue) * (height - cursorWidth);}function fromColor() {pickerRect.setColor(Qt.rgba(parseInt(redEditor.text) / 255., parseInt(greenEditor.text) / 255., parseInt(blueEditor.text) / 255., parseInt(alphaEditor.text) / 255.));}function fromArgbColor() {__color = '#' + argbEditor.text;pickerRect.setColor(__color);}onCurrentColorChanged: {redEditor.text = (currentColor.r * 255).toFixed(0);greenEditor.text = (currentColor.g * 255).toFixed(0);blueEditor.text = (currentColor.b * 255).toFixed(0);alphaEditor.text = (currentColor.a * 255).toFixed(0);argbEditor.text = currentColor.toString().replace("#", "");}Rectangle {x: pickerRect.cursorWidth * 0.5y: pickerRect.height - pickerRect.cursorWidth * 0.5width: pickerRect.height - pickerRect.cursorWidthheight: pickerRect.width - pickerRect.cursorWidthrotation: -90transformOrigin: Item.TopLeftgradient: Gradient {GradientStop { position: 0.0; color: "white" }GradientStop { position: 1.0; color: pickerRect.hueColor }}}Rectangle {x: pickerRect.cursorWidth * 0.5y: pickerRect.cursorWidth * 0.5width: pickerRect.width - pickerRect.cursorWidthheight: pickerRect.height - pickerRect.cursorWidthgradient: Gradient {GradientStop { position: 1.0; color: "#ff000000" }GradientStop { position: 0.0; color: "#00000000" }}}Rectangle {id: colorPickerCursorwidth: pickerRect.cursorWidthheight: pickerRect.cursorWidthborder.color: "#e6e6e6"border.width: 1color: pickerRect.currentColorBehavior on scale { NumberAnimation { easing.type: Easing.OutBack; duration: 300 } }Rectangle {anchors.fill: parentanchors.margins: 1color: "transparent"border.color: "white"border.width: 1}}MouseArea {x: pickerRect.cursorWidthy: pickerRect.cursorWidthanchors.fill: parentfunction handleCursorPos(x, y) {let halfWidth = pickerRect.cursorWidth * 0.5;colorPickerCursor.x = Math.max(0, Math.min(width , x + halfWidth) - pickerRect.cursorWidth);colorPickerCursor.y = Math.max(0, Math.min(height, y + halfWidth) - pickerRect.cursorWidth);}onPositionChanged: (mouse) => handleCursorPos(mouse.x, mouse.y);onPressed: (mouse) => {colorPickerCursor.scale = 0.7;handleCursorPos(mouse.x, mouse.y);}onReleased: colorPickerCursor.scale = 1.0;}}Item {id: previewItemwidth: 90height: 90anchors.left: pickerRect.rightanchors.leftMargin: 10anchors.top: contentText.bottomanchors.topMargin: 15Grid {id: previwBackgroundanchors.fill: parentrows: 11columns: 11clip: trueproperty real cellWidth: width / columnsproperty real cellHeight: height / rowsRepeater {model: parent.columns * parent.rowsRectangle {width: previwBackground.cellWidthheight: widthcolor: (index % 2 == 0) ? "gray" : "transparent"}}}Rectangle {anchors.fill: parentanchors.margins: -2color: pickerRect.currentColorborder.color: "#e6e6e6"border.width: 2}}component ColorEditor: ColumnLayout {id: __layoutwidth: previewItem.widthheight: 50property alias label: label.textproperty alias text: input.textproperty alias validator: input.validatorsignal textEdited();signal accepted();Text {id: labelfont.family: "微软雅黑"color: "#222255"verticalAlignment: Text.AlignVCenterLayout.fillWidth: true}Rectangle {clip: truecolor: "transparent"border.color: "#e6e6e6"border.width: 2Layout.fillHeight: trueLayout.fillWidth: trueTextInput {id: inputleftPadding: 10rightPadding: 10selectionColor: "#398ed4"selectByMouse: trueanchors.fill: parenthorizontalAlignment: TextInput.AlignRightverticalAlignment: TextInput.AlignVCenteronTextEdited: __layout.textEdited();onAccepted: __layout.accepted();}}}Column {anchors.top: previewItem.bottomanchors.topMargin: 10anchors.left: previewItem.leftspacing: 6ColorEditor {id: redEditorlabel: "红色"validator: IntValidator { top: 255; bottom: 0 }onAccepted: pickerRect.fromColor();}ColorEditor {id: greenEditorlabel: "绿色"validator: IntValidator { top: 255; bottom: 0 }onAccepted: pickerRect.fromColor();}ColorEditor {id: blueEditorlabel: "蓝色"validator: IntValidator { top: 255; bottom: 0 }onAccepted: pickerRect.fromColor();}ColorEditor {id: alphaEditorlabel: "透明度"validator: IntValidator { top: 255; bottom: 0 }onAccepted: pickerRect.fromColor();}ColorEditor {id: argbEditorlabel: "十六进制 (ARGB)"validator: RegularExpressionValidator { regularExpression: /[0-9a-fA-F]{0,8}/ }onAccepted: pickerRect.fromArgbColor();}}Rectangle {id: huePickerwidth: pickerRect.width - pickerRect.cursorWidthheight: 32anchors.top: pickerRect.bottomanchors.topMargin: 10anchors.left: contentText.leftgradient: Gradient {orientation: Gradient.HorizontalGradientStop { position: 0.0; color: "#ff0000" }GradientStop { position: 0.16; color: "#ffff00" }GradientStop { position: 0.33; color: "#00ff00" }GradientStop { position: 0.5; color: "#00ffff" }GradientStop { position: 0.76; color: "#0000ff" }GradientStop { position: 0.85; color: "#ff00ff" }GradientStop { position: 1.0; color: "#ff0000" }}Rectangle {id: hueSliderwidth: heightheight: parent.heightanchors.verticalCenter: parent.verticalCenterborder.color: "#e6e6e6"border.width: 2scale: 0.9color: pickerRect.hueColorproperty real value: x / (parent.width - width)Behavior on scale { NumberAnimation { easing.type: Easing.OutBack; duration: 300 } }Rectangle {anchors.fill: parentanchors.margins: 1color: "transparent"border.color: "white"border.width: 2}}MouseArea {anchors.fill: parentfunction handleCursorPos(x) {let halfWidth = hueSlider.width * 0.5;hueSlider.x = Math.max(0, Math.min(width, x + halfWidth) - hueSlider.width);}onPressed: (mouse) => {hueSlider.scale = 0.6;handleCursorPos(mouse.x);}onReleased: hueSlider.scale = 0.9;onPositionChanged: (mouse) => handleCursorPos(mouse.x);}}Item {id: alphaPickerItemwidth: huePicker.widthheight: huePicker.heightanchors.top: huePicker.bottomanchors.topMargin: 25anchors.left: huePicker.leftGrid {id: alphaPickeranchors.fill: parentrows: 4columns: 29clip: trueproperty real cellWidth: width / columnsproperty real cellHeight: height / rowsRepeater {model: parent.columns * parent.rowsRectangle {width: alphaPicker.cellWidthheight: widthcolor: (index % 2 == 0) ? "gray" : "transparent"}}}Rectangle {anchors.fill: parentgradient: Gradient {orientation: Gradient.HorizontalGradientStop { position: 1.0; color: "#ff000000" }GradientStop { position: 0.0; color: "#00ffffff" }}}Rectangle {id: alphaSliderx: parent.width - widthwidth: heightheight: parent.heightanchors.verticalCenter: parent.verticalCentercolor: Qt.rgba(0.1, 0.1, 0.1, (value + 1.0) / 2.0)border.color: "#e6e6e6"border.width: 2scale: 0.9property real value: x / (parent.width - width)Behavior on scale { NumberAnimation { easing.type: Easing.OutBack; duration: 300 } }Rectangle {anchors.fill: parentanchors.margins: 1color: "transparent"border.color: "white"border.width: 1}}MouseArea {anchors.fill: parentfunction handleCursorPos(x) {let halfWidth = alphaSlider.width * 0.5;alphaSlider.x = Math.max(0, Math.min(width, x + halfWidth) - alphaSlider.width);}onPressed: (mouse) => {alphaSlider.scale = 0.6;handleCursorPos(mouse.x);}onReleased: alphaSlider.scale = 0.9;onPositionChanged: (mouse) => handleCursorPos(mouse.x);}}Button {id: confirmButtonwidth: 200height: alphaPickerItem.heightanchors.top: alphaPickerItem.bottomanchors.topMargin: 25anchors.left: alphaPickerItem.lefttext: qsTr("确定")hoverEnabled: truetopInset: down ? 1 : 0bottomInset: topInsetleftInset: topInsetrightInset: topInsetfont.family: "微软雅黑"onClicked: {root.initColor = root.currentColor;root.hide();root.accepted();}}Button {id: cancelButtonwidth: 200height: alphaPickerItem.heightanchors.top: alphaPickerItem.bottomanchors.topMargin: 25anchors.right: parent.rightanchors.rightMargin: 25text: qsTr("取消")hoverEnabled: truetopInset: down ? 1 : 0bottomInset: topInsetleftInset: topInsetrightInset: topInsetfont.family: "微软雅黑"onClicked: {pickerRect.setColor(root.initColor);root.hide();root.rejected();}}}
}