Warning, /plasma/discover/discover/qml/Rating.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 pragma ComponentBehavior: Bound 0008 0009 import QtQuick 0010 import QtQuick.Controls as QQC2 0011 import org.kde.kirigami as Kirigami 0012 0013 QQC2.Control { 0014 id: control 0015 0016 enum EditPolicy { 0017 None = 0, 0018 AllowTapToUnset = 1, 0019 AllowSetZero = 2 0020 } 0021 0022 enum Precision { 0023 FullStar, 0024 HalfStar 0025 } 0026 0027 property real starSize: Kirigami.Units.gridUnit 0028 property bool readOnly: true 0029 property /*EditPolicy*/ int editPolicy: Rating.EditPolicy.None 0030 property int /*Precision*/ precision: Rating.Precision.FullStar 0031 0032 // Allows gracefully handling edits in FullStar precision starting from a HalfStar value. 0033 // For example, starting from value 3 (one and a half star, displayed as 2 full stars), 0034 // with AllowTapToUnset policy clicking on second star will reset value to 0. 0035 readonly property int effectiveValue: precision === Rating.Precision.HalfStar ? value : Math.ceil(value / 2) * 2 0036 0037 readonly property int hoveredValue: mouseArea.hoveredValue 0038 readonly property bool pressed: mouseArea.pressed 0039 0040 // Accessible API requires Slider types to have properties named literally like this. 0041 property int value: 0 0042 readonly property int minimumValue: 0 0043 property int maximumValue: 10 0044 readonly property int stepSize: precision === Rating.Precision.HalfStar ? 1 : 2 0045 0046 signal edited() 0047 0048 function valueAt(position: real): int { 0049 position = Math.max(0, Math.min(1, position)); 0050 let val = 0; 0051 let step = 0; 0052 switch (precision) { 0053 case Rating.Precision.HalfStar: 0054 val = Math.ceil(position * maximumValue); 0055 step = 1; 0056 break; 0057 case Rating.Precision.FullStar: 0058 val = Math.ceil(position * maximumValue / 2) * 2; 0059 step = 2; 0060 break; 0061 } 0062 const min = (editPolicy & Rating.EditPolicy.AllowSetZero) ? 0 : step; 0063 val = Math.max(min, val); 0064 return val; 0065 } 0066 0067 function __edit(newValue: int) { 0068 if (newValue === value) { 0069 return; 0070 } 0071 value = newValue; 0072 edited(); 0073 } 0074 0075 function decrease() { 0076 if (!readOnly) { 0077 const step = precision === Rating.Precision.HalfStar ? 1 : 2; 0078 const min = (editPolicy & Rating.EditPolicy.AllowSetZero) ? 0 : step; 0079 const newValue = Math.max(min, effectiveValue - step); 0080 __edit(newValue); 0081 } 0082 } 0083 0084 function increase() { 0085 if (!readOnly) { 0086 const step = precision === Rating.Precision.HalfStar ? 1 : 2; 0087 const newValue = Math.min(maximumValue, effectiveValue + step); 0088 __edit(newValue); 0089 } 0090 } 0091 0092 Keys.onLeftPressed: event => { 0093 if (readOnly) { 0094 event.accepted = false; 0095 } else { 0096 event.accepted = true; 0097 if (control.mirrored) { 0098 increase(); 0099 } else { 0100 decrease(); 0101 } 0102 } 0103 } 0104 0105 Keys.onRightPressed: event => { 0106 if (readOnly) { 0107 event.accepted = false; 0108 } else { 0109 event.accepted = true; 0110 if (control.mirrored) { 0111 decrease(); 0112 } else { 0113 increase(); 0114 } 0115 } 0116 } 0117 0118 Accessible.role: Accessible.Slider 0119 Accessible.description: i18n("Rating") 0120 Accessible.onIncreaseAction: increase() 0121 Accessible.onDecreaseAction: decrease() 0122 0123 focusPolicy: readOnly ? Qt.NoFocus : Qt.StrongFocus 0124 0125 hoverEnabled: !readOnly 0126 0127 padding: Kirigami.Units.smallSpacing 0128 // Reset paddings after qqc2-desktop-style Control 0129 topPadding: undefined 0130 leftPadding: undefined 0131 rightPadding: undefined 0132 bottomPadding: undefined 0133 verticalPadding: undefined 0134 horizontalPadding: undefined 0135 0136 spacing: 0 0137 0138 contentItem: Item { 0139 implicitWidth: row.implicitWidth 0140 implicitHeight: row.implicitHeight 0141 0142 Row { 0143 id: row 0144 0145 spacing: 0 0146 0147 LayoutMirroring.enabled: control.mirrored 0148 0149 Repeater { 0150 model: Math.ceil(control.maximumValue / 2) 0151 0152 Kirigami.Icon { 0153 required property int index 0154 0155 width: control.starSize 0156 height: control.starSize 0157 0158 animated: false 0159 0160 source: { 0161 const base = index * 2; 0162 const rating = !control.readOnly && control.hovered ? control.hoveredValue : control.effectiveValue; 0163 if (rating <= base) { 0164 return "rating-unrated"; 0165 } else if (rating === base + 1 && control.precision === Rating.Precision.HalfStar) { 0166 return control.mirrored ? "rating-half-rtl" : "rating-half"; 0167 } else { 0168 // rating >= base + 2 0169 return "rating"; 0170 } 0171 } 0172 0173 opacity: { 0174 const base = index * 2; 0175 const rating = !control.readOnly && control.hovered ? control.hoveredValue : control.effectiveValue; 0176 if (rating <= base) { 0177 return 1; 0178 } else if (!control.readOnly && control.hovered && (control.pressed || control.hoveredValue !== control.effectiveValue)) { 0179 return 0.7; 0180 } else { 0181 return 1; 0182 } 0183 } 0184 } 0185 } 0186 } 0187 } 0188 0189 // Spans entire control, accounts for paddings 0190 MouseArea { 0191 id: mouseArea 0192 0193 anchors.fill: parent 0194 0195 enabled: !control.readOnly 0196 0197 acceptedButtons: Qt.LeftButton 0198 hoverEnabled: true 0199 // Event stealing prevention seem to be required for it to work in Kirigami.OverlaySheet dialog. 0200 preventStealing: true 0201 0202 property int hoveredValue: 0 0203 0204 // Need to differentiate between press+drag vs click 0205 property bool dragging: false 0206 property int dragStartValue: -1 0207 0208 function initDrag(x: real) { 0209 const value = valueAt(x); 0210 dragStartValue = value; 0211 dragging = false; 0212 } 0213 0214 function updateDrag(value: int) { 0215 if (value !== dragStartValue) { 0216 dragging = true; 0217 } 0218 } 0219 0220 function resetDrag() { 0221 dragStartValue = -1; 0222 dragging = false; 0223 } 0224 0225 function positionAt(x: real): real { 0226 const visualPosition = (x - control.leftPadding) / (control.width - control.leftPadding - control.rightPadding); 0227 const position = control.mirrored ? 1 - visualPosition : visualPosition; 0228 return position; 0229 } 0230 0231 function valueAt(x: real): int { 0232 const position = positionAt(x); 0233 const value = control.valueAt(position); 0234 return value; 0235 } 0236 0237 function setValueAt(x: real) { 0238 let value = valueAt(x); 0239 if (!dragging && (control.editPolicy & Rating.EditPolicy.AllowTapToUnset) && (value === control.effectiveValue)) { 0240 value = 0; 0241 } 0242 control.__edit(value); 0243 } 0244 0245 function handleMove(x: real) { 0246 const value = valueAt(mouseX); 0247 hoveredValue = value; 0248 if (pressed) { 0249 updateDrag(value); 0250 } 0251 } 0252 0253 onPressed: mouse => { 0254 initDrag(mouse.x); 0255 } 0256 0257 onReleased: mouse => { 0258 setValueAt(mouse.x); 0259 resetDrag(); 0260 } 0261 0262 onEntered: { 0263 // In some situations entered() signal may not be immediately 0264 // followed by positionChanged(), leading to desync with 0265 // control which does react to mouse enter appropriately. 0266 // Fix this by handling entered() and reading mouseX property. 0267 handleMove(mouseX); 0268 } 0269 0270 onPositionChanged: mouse => { 0271 handleMove(mouse.x); 0272 } 0273 0274 onExited: { 0275 hoveredValue = 0; 0276 resetDrag(); 0277 } 0278 0279 onCanceled: { 0280 hoveredValue = 0; 0281 resetDrag(); 0282 } 0283 } 0284 0285 background: Rectangle { 0286 color: "transparent" 0287 border.color: control.Kirigami.Theme.highlightColor 0288 border.width: 1 0289 radius: Kirigami.Units.smallSpacing 0290 0291 opacity: control.activeFocus && [Qt.TabFocusReason, Qt.BacktabFocusReason].includes(control.focusReason) 0292 ? 1 : 0 0293 0294 Behavior on opacity { 0295 OpacityAnimator { 0296 duration: Kirigami.Units.shortDuration 0297 easing.type: Easing.InOutCubic 0298 } 0299 } 0300 } 0301 }