MRadioButton.qml 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import QtQuick 2.12
  2. import QtQuick.Layouts 1.12
  3. import QtQuick.Controls 2.12
  4. Rectangle {
  5. id: control
  6. // === 接口属性 ===
  7. property string controlId
  8. property int modelIndex
  9. property var model: [] // [{ text: string }]
  10. property var selectedIndices: [] // 单选时数组最多一个元素
  11. signal selectionChanged(var selectedIndices, var selectedData)
  12. signal signalSelectionChanged(int index, string id, var selectedIndices, var selectedData)
  13. property bool required: false
  14. property string requiredMsg
  15. property bool showRequiredMsg: false
  16. // === 状态属性 ===
  17. property bool enabled: true
  18. // === 样式属性 ===
  19. property bool backgroundVisible: true
  20. property real radius: 12
  21. property int fontSize: 15
  22. property color buttonColor: "transparent"
  23. property color hoverColor: Qt.darker(buttonColor, 1.2)
  24. property color textColor: "white"
  25. property color checkmarkColor: "#5a8fc4"
  26. property color borderColor: "#355a80"
  27. property color selectedBgColor: "#1a3d2e" // 选中项背景(偏绿与绿色勾一致)
  28. property color selectedBorderColor: "#4CAF50"
  29. property color checkmarkGreen: "#4CAF50" // 选中时的绿色勾
  30. property real pressedScale: 0.98
  31. property bool shadowEnabled: true
  32. property color shadowColor: theme.shadowColor
  33. // 布局尺寸(按钮形式:大点击区,左侧留勾图标位)
  34. property int verticalPadding: 12
  35. property int iconSize: 24
  36. property int spacingBetweenIconAndText: 10
  37. property int verticalSpacingBetweenButtons: 10
  38. property int buttonHeight: 48
  39. // === 隐藏文本用于测量最大宽度 ===
  40. Text {
  41. id: measureText
  42. visible: false
  43. font.pixelSize: control.fontSize
  44. }
  45. property real maxTextWidth: 0
  46. function updateMaxTextWidth() {
  47. var maxWidth = 0
  48. for (var i = 0; i < model.length; i++) {
  49. measureText.text = model[i].text
  50. if (measureText.width > maxWidth)
  51. maxWidth = measureText.width
  52. }
  53. maxTextWidth = maxWidth
  54. }
  55. Component.onCompleted: updateMaxTextWidth()
  56. onModelChanged: updateMaxTextWidth()
  57. // === 尺寸计算(大点击区) ===
  58. implicitWidth: model.length > 0 ? model.length * (buttonHeight + verticalSpacingBetweenButtons) - verticalSpacingBetweenButtons + 32 : 0
  59. implicitHeight: buttonHeight + 24
  60. width: implicitWidth
  61. height: implicitHeight
  62. color: "transparent"
  63. // === 背景(仅填充,不画外框;选项各自保留边框) ===
  64. Rectangle {
  65. id: background
  66. anchors.fill: parent
  67. clip: true
  68. radius: control.radius
  69. color: control.buttonColor
  70. visible: control.backgroundVisible
  71. border.width: 0
  72. layer.enabled: control.shadowEnabled && control.backgroundVisible
  73. // layer.effect: MultiEffect {
  74. // shadowEnabled: control.shadowEnabled
  75. // shadowColor: control.shadowColor
  76. // shadowBlur: theme.shadowBlur
  77. // shadowHorizontalOffset: theme.shadowXOffset
  78. // shadowVerticalOffset: theme.shadowYOffset
  79. // }
  80. }
  81. // === 按钮行 ===
  82. RowLayout {
  83. id: buttonsRow
  84. // anchors.top: parent.top
  85. // anchors.topMargin: 10
  86. // anchors.horizontalCenter: parent.horizontalCenter
  87. anchors.verticalCenter: parent.verticalCenter
  88. spacing: verticalSpacingBetweenButtons
  89. width: implicitWidth
  90. }
  91. // === 按钮 Repeater ===
  92. Repeater {
  93. model: control.model
  94. parent: buttonsRow
  95. delegate: Rectangle {
  96. id: btn
  97. implicitWidth: verticalPadding * 2 + iconSize + spacingBetweenIconAndText + label.implicitWidth + 24
  98. height: buttonHeight
  99. radius: 8
  100. border.width: checked ? 2.5 : 1.5
  101. border.color: checked ? (control.selectedBorderColor || theme.focusColor) : (hovered ? control.checkmarkColor : control.borderColor)
  102. // === 状态属性 ===
  103. property bool hovered: false
  104. property bool checked: control.selectedIndices.indexOf(index) !== -1
  105. // 工业感:选中整行有背景+边框,未选中时 hover 有轻微背景
  106. color: control.backgroundVisible
  107. ? (checked ? control.selectedBgColor : (hovered ? control.hoverColor : control.buttonColor))
  108. : "transparent"
  109. opacity: mouseArea.pressed ? 0.9 : 1.0
  110. Behavior on color { ColorAnimation { duration: 150 } }
  111. Behavior on border.color { ColorAnimation { duration: 150 } }
  112. Behavior on border.width { NumberAnimation { duration: 120 } }
  113. Behavior on opacity { NumberAnimation { duration: 100 } }
  114. // === 缩放动画 ===
  115. transform: Scale {
  116. id: scale
  117. origin.x: btn.width / 2
  118. origin.y: btn.height / 2
  119. }
  120. ParallelAnimation {
  121. id: restoreAnimation
  122. SpringAnimation { target: scale; property: "xScale"; to: 1.0; spring: 2.5; damping: 0.25 }
  123. SpringAnimation { target: scale; property: "yScale"; to: 1.0; spring: 2.5; damping: 0.25 }
  124. }
  125. // === 按钮内容布局:选中=变色+绿色勾,无圆形单选图标 ===
  126. RowLayout {
  127. anchors.fill: parent
  128. anchors.leftMargin: verticalPadding
  129. anchors.rightMargin: verticalPadding
  130. spacing: spacingBetweenIconAndText
  131. Layout.alignment: Qt.AlignVCenter
  132. // === 方框 + 勾在方框内(选中时显示绿色勾) ===
  133. Rectangle {
  134. id: checkBox
  135. width: iconSize
  136. height: iconSize
  137. radius: 4
  138. border.width: checked ? 2 : 1
  139. border.color: checked ? control.checkmarkGreen : control.checkmarkColor
  140. color: checked ? "#404CAF50" : "transparent"
  141. Layout.alignment: Qt.AlignVCenter
  142. Behavior on border.color { ColorAnimation { duration: 120 } }
  143. Behavior on color { ColorAnimation { duration: 120 } }
  144. Text {
  145. anchors.centerIn: parent
  146. text: "\uf00c"
  147. color: control.checkmarkGreen
  148. font.pixelSize: iconSize - 6
  149. font.family: iconFont.name
  150. visible: checked
  151. opacity: checked ? 1 : 0
  152. Behavior on opacity { NumberAnimation { duration: 120 } }
  153. }
  154. }
  155. // === 标签文本 ===
  156. Text {
  157. id: label
  158. text: modelData.text
  159. color: control.textColor
  160. font.pixelSize: control.fontSize
  161. font.bold: checked
  162. elide: Text.ElideRight
  163. Layout.preferredWidth: label.implicitWidth
  164. Layout.alignment: Qt.AlignVCenter
  165. }
  166. }
  167. // === 交互逻辑 ===
  168. MouseArea {
  169. id: mouseArea
  170. anchors.fill: parent
  171. hoverEnabled: true
  172. cursorShape: control.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
  173. enabled: control.enabled
  174. onEntered: btn.hovered = true
  175. onExited: btn.hovered = false
  176. onPressed: {
  177. scale.xScale = control.pressedScale
  178. scale.yScale = control.pressedScale
  179. btn.opacity = 0.85
  180. }
  181. onReleased: {
  182. restoreAnimation.restart()
  183. btn.opacity = 1.0
  184. // 单选逻辑
  185. var newSelection = []
  186. if (control.selectedIndices.length === 0 || control.selectedIndices[0] !== index) {
  187. newSelection.push(index)
  188. }
  189. control.selectedIndices = newSelection
  190. // 获取选中数据
  191. var selectedDataItems = []
  192. var selectedDataValue = []
  193. for (var i = 0; i < control.selectedIndices.length; i++) {
  194. selectedDataValue.push(control.model[control.selectedIndices[i]].text)
  195. selectedDataItems.push(control.model[control.selectedIndices[i]])
  196. }
  197. control.selectionChanged(control.selectedIndices, selectedDataItems)
  198. control.signalSelectionChanged(control.modelIndex, control.controlId, control.selectedIndices, selectedDataValue);
  199. }
  200. onCanceled: {
  201. restoreAnimation.restart()
  202. btn.opacity = 1.0
  203. }
  204. }
  205. }
  206. }
  207. // 禁用时降低透明度
  208. opacity: control.enabled ? 1.0 : 0.6
  209. Text {
  210. id: textRequiredMsg
  211. anchors.top: buttonsRow.bottom
  212. anchors.topMargin: 15
  213. anchors.left: buttonsRow.left
  214. visible: control.required && control.showRequiredMsg
  215. text: qsTr(requiredMsg)
  216. color: "red"
  217. font.pixelSize: control.fontSize
  218. // 普通文字不使用图标字体
  219. font.bold: true
  220. verticalAlignment: Text.AlignVCenter
  221. }
  222. function slotShowRequiredMsg(submit) {
  223. if (submit && control.selectedIndices.length === 0) {
  224. control.showRequiredMsg = true;
  225. }
  226. }
  227. }