From 1717fc0992f223018f7640987af53e598b2a76ee Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Sun, 21 Sep 2025 21:17:12 -0400 Subject: [PATCH] NTextInput: new approach to avoid all input leakage and dragging NPanel issues. --- Modules/SettingsPanel/Tabs/GeneralTab.qml | 2 +- Widgets/NInputButton.qml | 2 +- Widgets/NTextInput.qml | 164 +++++++++++++++++----- 3 files changed, 127 insertions(+), 41 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/SettingsPanel/Tabs/GeneralTab.qml index 1a13bb88..1036a4a3 100644 --- a/Modules/SettingsPanel/Tabs/GeneralTab.qml +++ b/Modules/SettingsPanel/Tabs/GeneralTab.qml @@ -45,7 +45,7 @@ ColumnLayout { "selectFiles": true, "scaling": scaling, "parent": root, - "onSelected": path => Settings.data.general.avatarImage = path + "onSelected": path => Settings.data.general.avatarImage = path }) } } diff --git a/Widgets/NInputButton.qml b/Widgets/NInputButton.qml index 8ff2d5f2..5e157c1e 100644 --- a/Widgets/NInputButton.qml +++ b/Widgets/NInputButton.qml @@ -36,7 +36,7 @@ ColumnLayout { Layout.fillWidth: true spacing: Style.marginM * scaling - NTextInput{ + NTextInput { id: input Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index ad5267ce..7f2ec9d7 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -35,62 +35,148 @@ ColumnLayout { Layout.fillWidth: true } - // Container - Rectangle { - id: frame + // An active control that blocks input, to avoid events leakage and dragging stuff in the background. + Control { + id: frameControl Layout.fillWidth: true Layout.minimumWidth: 80 * scaling implicitHeight: Style.baseWidgetSize * 1.1 * scaling - radius: Style.radiusM * scaling - color: Color.mSurface - border.color: input.activeFocus ? Color.mSecondary : Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) + // This is important - makes the control accept focus + focusPolicy: Qt.StrongFocus + hoverEnabled: true - Behavior on border.color { - ColorAnimation { - duration: Style.animationFast + background: Rectangle { + id: frame + + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: input.activeFocus ? Color.mSecondary : Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } } } - TextField { - id: input + contentItem: Item { + // Invisible background that captures ALL mouse events + MouseArea { + id: backgroundCapture + anchors.fill: parent + z: 0 + acceptedButtons: Qt.AllButtons + hoverEnabled: true + preventStealing: true + propagateComposedEvents: false - anchors.fill: parent - anchors.leftMargin: Style.marginM * scaling - anchors.rightMargin: Style.marginM * scaling + onPressed: mouse => { + mouse.accepted = true + // Focus the input and position cursor + input.forceActiveFocus() + var inputPos = mapToItem(inputContainer, mouse.x, mouse.y) + if (inputPos.x >= 0 && inputPos.x <= inputContainer.width) { + var textPos = inputPos.x - Style.marginM * scaling + if (textPos >= 0 && textPos <= input.width) { + input.cursorPosition = input.positionAt(textPos, input.height / 2) + } + } + } - verticalAlignment: TextInput.AlignVCenter + onReleased: mouse => { + mouse.accepted = true + } + onDoubleClicked: mouse => { + mouse.accepted = true + input.selectAll() + } + onPositionChanged: mouse => { + mouse.accepted = true + } + onWheel: wheel => { + wheel.accepted = true + } + } - echoMode: TextInput.Normal - readOnly: root.readOnly - enabled: root.enabled - color: Color.mOnSurface - placeholderTextColor: Qt.alpha(Color.mOnSurfaceVariant, 0.6) + // Container for the actual text field + Item { + id: inputContainer + anchors.fill: parent + anchors.leftMargin: Style.marginM * scaling + anchors.rightMargin: Style.marginM * scaling + z: 1 - selectByMouse: true + TextField { + id: input - topPadding: 0 - bottomPadding: 0 - leftPadding: 0 - rightPadding: 0 + anchors.fill: parent + verticalAlignment: TextInput.AlignVCenter - background: null + echoMode: TextInput.Normal + readOnly: root.readOnly + enabled: root.enabled + color: Color.mOnSurface + placeholderTextColor: Qt.alpha(Color.mOnSurfaceVariant, 0.6) - font.family: root.fontFamily - font.pointSize: root.fontSize - font.weight: root.fontWeight + selectByMouse: true - onEditingFinished: root.editingFinished() - } + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 - // Block mouse events from going through to components behind this one - MouseArea { - anchors.fill: parent - z: -1 // Place behind the TextField - acceptedButtons: Qt.AllButtons - propagateComposedEvents: false + background: null + + font.family: root.fontFamily + font.pointSize: root.fontSize + font.weight: root.fontWeight + + onEditingFinished: root.editingFinished() + + // Override mouse handling to prevent propagation + MouseArea { + id: textFieldMouse + anchors.fill: parent + acceptedButtons: Qt.AllButtons + preventStealing: true + propagateComposedEvents: false + cursorShape: Qt.IBeamCursor + + property int selectionStart: 0 + + onPressed: mouse => { + mouse.accepted = true + input.forceActiveFocus() + var pos = input.positionAt(mouse.x, mouse.y) + input.cursorPosition = pos + selectionStart = pos + } + + onPositionChanged: mouse => { + if (mouse.buttons & Qt.LeftButton) { + mouse.accepted = true + var pos = input.positionAt(mouse.x, mouse.y) + input.select(selectionStart, pos) + } + } + + onDoubleClicked: mouse => { + mouse.accepted = true + input.selectAll() + } + + onReleased: mouse => { + mouse.accepted = true + } + onWheel: wheel => { + wheel.accepted = true + } + } + } + } } } -} \ No newline at end of file +}