fedora
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
// Public properties
|
||||
property int value: 0
|
||||
property int from: 0
|
||||
property int to: 100
|
||||
property int stepSize: 1
|
||||
property string suffix: ""
|
||||
property string prefix: ""
|
||||
property string label: ""
|
||||
property string description: ""
|
||||
property bool hovering: false
|
||||
property int baseSize: Style.baseWidgetSize
|
||||
property var defaultValue: undefined
|
||||
property string settingsPath: ""
|
||||
|
||||
// Convenience properties for common naming
|
||||
property alias minimum: root.from
|
||||
property alias maximum: root.to
|
||||
|
||||
// Properties for repeating
|
||||
property int initialRepeatDelay: 400 // The "pause" after the first click (in ms)
|
||||
property int repeatInterval: 80 // How often to step up after fist pause (ms)
|
||||
property int rampFactor: 4 // How many ticks to wait before increasing the step multiplier
|
||||
property int maxStepMultiplier: 10 // The max step (e.g., 10 * stepSize)
|
||||
property int _holdTicks: 0 // Internal counter for hold duration
|
||||
property int _repeatDirection: 0 // -1 for decrease, 1 for increase
|
||||
|
||||
signal entered
|
||||
signal exited
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
readonly property bool isValueChanged: (defaultValue !== undefined) && (value !== defaultValue)
|
||||
readonly property string indicatorTooltip: defaultValue !== undefined ? I18n.tr("panels.indicator.default-value", {
|
||||
"value": String(defaultValue)
|
||||
}) : ""
|
||||
|
||||
Timer {
|
||||
id: repeatTimer
|
||||
repeat: true
|
||||
interval: root.initialRepeatDelay
|
||||
|
||||
onTriggered: {
|
||||
if (repeatTimer.interval === root.initialRepeatDelay) {
|
||||
repeatTimer.interval = root.repeatInterval;
|
||||
root._holdTicks = 0;
|
||||
}
|
||||
root._holdTicks++;
|
||||
var stepMultiplier = Math.min(root.maxStepMultiplier, 1 + Math.floor(root._holdTicks / root.rampFactor));
|
||||
changeValue(root._repeatDirection, root.stepSize * stepMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
function changeValue(direction, step) {
|
||||
var currentStep = step || root.stepSize;
|
||||
|
||||
if (direction === 1 && root.value < root.to) {
|
||||
root.value = Math.min(root.to, root.value + currentStep);
|
||||
} else if (direction === -1 && root.value > root.from) {
|
||||
root.value = Math.max(root.from, root.value - currentStep);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.value === root.to || root.value === root.from) {
|
||||
stopRepeat();
|
||||
}
|
||||
}
|
||||
|
||||
function stopRepeat() {
|
||||
root._repeatDirection = 0;
|
||||
repeatTimer.stop();
|
||||
repeatTimer.interval = root.initialRepeatDelay;
|
||||
}
|
||||
|
||||
NLabel {
|
||||
label: root.label
|
||||
description: root.description
|
||||
showIndicator: root.isValueChanged
|
||||
indicatorTooltip: root.indicatorTooltip
|
||||
}
|
||||
|
||||
// Main spinbox container
|
||||
Rectangle {
|
||||
id: spinBoxContainer
|
||||
Layout.margins: Style.borderS
|
||||
implicitWidth: 120
|
||||
implicitHeight: Math.round((root.baseSize - 4) / 2) * 2
|
||||
radius: Style.iRadiusS
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: (root.hovering || decreaseArea.containsMouse || increaseArea.containsMouse) ? Color.mHover : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse area for hover and scroll
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
if (!root.enabled)
|
||||
return;
|
||||
root.hovering = true;
|
||||
root.entered();
|
||||
}
|
||||
onExited: {
|
||||
root.hovering = false;
|
||||
root.exited();
|
||||
}
|
||||
onWheel: wheel => {
|
||||
if (wheel.angleDelta.y > 0 && root.value < root.to) {
|
||||
let newValue = Math.min(root.to, root.value + root.stepSize);
|
||||
root.value = newValue;
|
||||
} else if (wheel.angleDelta.y < 0 && root.value > root.from) {
|
||||
let newValue = Math.max(root.from, root.value - root.stepSize);
|
||||
root.value = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrease button (left)
|
||||
Item {
|
||||
id: decreaseButton
|
||||
height: parent.height
|
||||
width: height
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
opacity: (root.enabled && root.value > root.from) || decreaseArea.containsMouse ? 1.0 : 0.3
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.height
|
||||
height: width
|
||||
radius: spinBoxContainer.radius
|
||||
color: Color.mHover
|
||||
opacity: decreaseArea.containsMouse ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: "chevron-left"
|
||||
pointSize: Style.fontSizeS
|
||||
color: decreaseArea.containsMouse ? Color.mOnHover : Color.mPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: decreaseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: root.enabled && root.value > root.from
|
||||
onPressed: {
|
||||
root._repeatDirection = -1;
|
||||
changeValue(root._repeatDirection, root.stepSize);
|
||||
repeatTimer.start();
|
||||
}
|
||||
onReleased: stopRepeat()
|
||||
onExited: stopRepeat()
|
||||
}
|
||||
}
|
||||
|
||||
// Increase button (right)
|
||||
Item {
|
||||
id: increaseButton
|
||||
height: parent.height
|
||||
width: height
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
opacity: (root.enabled && root.value < root.to) || increaseArea.containsMouse ? 1.0 : 0.3
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.height
|
||||
height: width
|
||||
radius: spinBoxContainer.radius
|
||||
color: Color.mHover
|
||||
opacity: increaseArea.containsMouse ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: "chevron-right"
|
||||
pointSize: Style.fontSizeS
|
||||
color: increaseArea.containsMouse ? Color.mOnHover : Color.mPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: increaseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: root.enabled && root.value < root.to
|
||||
onPressed: {
|
||||
root._repeatDirection = 1;
|
||||
changeValue(root._repeatDirection, root.stepSize);
|
||||
repeatTimer.start();
|
||||
}
|
||||
onReleased: stopRepeat()
|
||||
onExited: stopRepeat()
|
||||
}
|
||||
}
|
||||
|
||||
// Center value display with separate prefix, value, and suffix
|
||||
Item {
|
||||
id: valueContainer
|
||||
anchors.left: decreaseButton.right
|
||||
anchors.right: increaseButton.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: 4
|
||||
height: parent.height
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
// Prefix text (non-editable)
|
||||
NText {
|
||||
text: root.prefix
|
||||
family: Settings.data.ui.fontFixed
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Qt.alpha(Color.mOnSurface, root.enabled ? 1.0 : 0.6)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: root.prefix !== ""
|
||||
}
|
||||
|
||||
// Editable number input
|
||||
TextInput {
|
||||
id: valueInput
|
||||
text: valueInput.focus ? valueInput.text : root.value.toString()
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Qt.alpha(Color.mOnSurface, root.enabled ? 1.0 : 0.6)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
selectByMouse: true
|
||||
enabled: root.enabled
|
||||
|
||||
// Only allow numeric input within range
|
||||
validator: IntValidator {
|
||||
bottom: root.from
|
||||
top: root.to
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
applyValue();
|
||||
focus = false;
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
text = root.value.toString();
|
||||
focus = false;
|
||||
}
|
||||
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
selectAll();
|
||||
} else {
|
||||
applyValue();
|
||||
}
|
||||
}
|
||||
|
||||
function applyValue() {
|
||||
let newValue = parseInt(text);
|
||||
if (!isNaN(newValue)) {
|
||||
// Don't manually set text here - let the binding handle it
|
||||
newValue = Math.max(root.from, Math.min(root.to, newValue));
|
||||
root.value = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Suffix text (non-editable)
|
||||
NText {
|
||||
text: root.suffix
|
||||
family: Settings.data.ui.fontFixed
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Qt.alpha(Color.mOnSurface, root.enabled ? 1.0 : 0.6)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: root.suffix !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user