diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 31319011..f55b3310 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -171,14 +171,14 @@ Singleton { // Detect user's favorite locale - languages for (var i = 0; i < Qt.locale().uiLanguages.length; i++) { const fullUserLang = Qt.locale().uiLanguages[i] - + // Try full code match (such as zh CN, en US) if (availableLanguages.includes(fullUserLang)) { Logger.log("I18n", `Exact match found: "${fullUserLang}"`) setLanguage(fullUserLang) return } - + // If full code match fails, try short code matching (such as zh, en) const shortUserLang = fullUserLang.substring(0, 2) if (availableLanguages.includes(shortUserLang)) { @@ -186,7 +186,7 @@ Singleton { setLanguage(shortUserLang) return } - + Logger.log("I18n", `No match for system language: "${fullUserLang}"`) } diff --git a/Widgets/NSpinBox.qml b/Widgets/NSpinBox.qml index 0f5feb0f..5202ecfc 100644 --- a/Widgets/NSpinBox.qml +++ b/Widgets/NSpinBox.qml @@ -9,10 +9,10 @@ RowLayout { id: root // Public properties - property alias value: spinBox.value - property alias from: spinBox.from - property alias to: spinBox.to - property alias stepSize: spinBox.stepSize + 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: "" @@ -22,8 +22,8 @@ RowLayout { property int baseSize: Style.baseWidgetSize // Convenience properties for common naming - property alias minimum: spinBox.from - property alias maximum: spinBox.to + property alias minimum: root.from + property alias maximum: root.to signal entered signal exited @@ -35,13 +35,12 @@ RowLayout { description: root.description } - // Value + // Main spinbox container Rectangle { id: spinBoxContainer - - implicitWidth: 100 * scaling // Wider for better proportions - implicitHeight: (root.baseSize - 4) * scaling // Slightly shorter than toggle - radius: height * 0.5 // Fully rounded like toggle + implicitWidth: 120 * scaling + implicitHeight: (root.baseSize - 4) * scaling + radius: height * 0.5 color: Color.mSurfaceVariant border.color: root.hovering ? Color.mPrimary : Color.mOutline border.width: 1 @@ -52,7 +51,7 @@ RowLayout { } } - // Mouse area for scroll wheel and hover + // Mouse area for hover and scroll MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton @@ -66,10 +65,12 @@ RowLayout { root.exited() } onWheel: wheel => { - if (wheel.angleDelta.y > 0 && spinBox.value < spinBox.to) { - spinBox.increase() - } else if (wheel.angleDelta.y < 0 && spinBox.value > spinBox.from) { - spinBox.decrease() + 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 } } } @@ -77,14 +78,14 @@ RowLayout { // Decrease button (left) Rectangle { id: decreaseButton - width: parent.height * 0.8 // Make it circular + width: parent.height * 0.8 height: parent.height * 0.8 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: parent.height * 0.1 - radius: width * 0.5 // Perfect circle + radius: width * 0.5 color: decreaseArea.containsMouse ? Color.mPrimary : "transparent" - opacity: root.enabled && spinBox.value > spinBox.from ? 1.0 : 0.3 + opacity: root.enabled && root.value > root.from ? 1.0 : 0.3 Behavior on color { ColorAnimation { @@ -105,22 +106,25 @@ RowLayout { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor - enabled: root.enabled && spinBox.value > spinBox.from - onClicked: spinBox.decrease() + enabled: root.enabled && root.value > root.from + onClicked: { + let newValue = Math.max(root.from, root.value - root.stepSize) + root.value = newValue + } } } // Increase button (right) Rectangle { id: increaseButton - width: parent.height * 0.8 // Make it circular + width: parent.height * 0.8 height: parent.height * 0.8 anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: parent.height * 0.1 - radius: width * 0.5 // Perfect circle + radius: width * 0.5 color: increaseArea.containsMouse ? Color.mPrimary : "transparent" - opacity: root.enabled && spinBox.value < spinBox.to ? 1.0 : 0.3 + opacity: root.enabled && root.value < root.to ? 1.0 : 0.3 Behavior on color { ColorAnimation { @@ -140,44 +144,96 @@ RowLayout { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor - enabled: root.enabled && spinBox.value < spinBox.to - onClicked: spinBox.increase() + enabled: root.enabled && root.value < root.to + onClicked: { + let newValue = Math.min(root.to, root.value + root.stepSize) + root.value = newValue + } } } - // Center value display - SpinBox { - id: spinBox + // Center value display with separate prefix, value, and suffix + Rectangle { + id: valueContainer anchors.left: decreaseButton.right anchors.right: increaseButton.left anchors.verticalCenter: parent.verticalCenter anchors.margins: 4 * scaling height: parent.height + color: "transparent" - background: Item {} - up.indicator: Item {} - down.indicator: Item {} + Row { + anchors.centerIn: parent + spacing: 0 - font.pointSize: Style.fontSizeM * scaling - font.family: Settings.data.ui.fontDefault - - from: 0 - to: 100 - stepSize: 1 - editable: false // Only use buttons/scroll - enabled: root.enabled - - contentItem: Item { - anchors.fill: parent - - NText { - anchors.centerIn: parent - text: root.prefix + spinBox.value + root.suffix + // Prefix text (non-editable) + Text { + text: root.prefix font.family: Settings.data.ui.fontFixed font.pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightMedium color: Color.mOnSurface - horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: root.prefix !== "" + } + + // Editable number input + TextInput { + id: valueInput + text: root.value.toString() + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightMedium + color: Color.mOnSurface + verticalAlignment: Text.AlignVCenter + selectByMouse: true + enabled: root.enabled + + // Only allow numeric input within range + validator: IntValidator { + bottom: root.from + top: root.to + } + + Keys.onReturnPressed: { + 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)) { + newValue = Math.max(root.from, Math.min(root.to, newValue)) + root.value = newValue + text = root.value.toString() + } else { + text = root.value.toString() + } + } + } + + // Suffix text (non-editable) + Text { + text: root.suffix + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightMedium + color: Color.mOnSurface + verticalAlignment: Text.AlignVCenter + visible: root.suffix !== "" } } }