From 74a88a70b3ad61964a43f3c7e35a30d13e6fb8ce Mon Sep 17 00:00:00 2001 From: Pascal Precht Date: Tue, 7 Sep 2021 12:52:13 +0200 Subject: [PATCH] feat(StatusBaseInput): introduce `component` property This property enables users to load any component into the input field. This is useful for rendering a "clearable" icon button, simple icons or even more complex buttons. Usage: ```qml StatusBaseInput { ... component: StatusIcon { name: "cancel" color: Theme.palette.dangerColor1 width: 16 } } ``` The `clearable` property of `StatusBaseInput` also renders and icon button on the right hand side. With this new feature, `clearable` is just a short-hand for: ```qml StatusBaseInput { ... component: StatusFlatRoundButton { visible: edit.text.length != 0 && statusBaseInput.clearable && !statusBaseInput.multiline && edit.activeFocus type: StatusFlatRoundButton.Type.Secondary width: 24 height: 24 icon.name: "clear" icon.width: 16 icon.height: 16 icon.color: Theme.palette.baseColor1 onClicked: { edit.clear() } } } ``` Closes #380 --- ui/StatusQ/sandbox/StatusInputPage.qml | 11 + .../src/StatusQ/Controls/StatusBaseInput.qml | 300 ++++++++++-------- 2 files changed, 179 insertions(+), 132 deletions(-) diff --git a/ui/StatusQ/sandbox/StatusInputPage.qml b/ui/StatusQ/sandbox/StatusInputPage.qml index 617ffc1cd9..2be5b7d31f 100644 --- a/ui/StatusQ/sandbox/StatusInputPage.qml +++ b/ui/StatusQ/sandbox/StatusInputPage.qml @@ -102,6 +102,17 @@ Column { ] } + StatusInput { + label: "Label" + input.placeholderText: "Input width component (right side)" + input.component: StatusIcon { + icon: "cancel" + height: 16 + color: Theme.palette.dangerColor1 + } + } + + StatusInput { input.multiline: true input.placeholderText: "Multiline" diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml index f1c3ea679d..dae323378d 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml @@ -56,6 +56,23 @@ Item { color: Theme.palette.baseColor1 } + property Item component + + onClearableChanged: { + if (clearable && !component) { + clearButtonLoader.active = true + clearButtonLoader.parent = statusBaseInputComponentSlot + } else { + clearButtonLoader.active = false + } + } + + onComponentChanged: { + if (!!component) { + component.parent = statusBaseInputComponentSlot + } + } + implicitWidth: 448 implicitHeight: multiline ? Math.max((edit.implicitHeight + topPadding + bottomPadding), 44) : 44 @@ -80,149 +97,168 @@ Item { return sensor.containsMouse ? Theme.palette.primaryColor2 : "transparent" } - StatusIcon { - id: statusIcon - anchors.topMargin: 10 - anchors.left: statusBaseInput.leftIcon ? parent.left : undefined - anchors.right: !statusBaseInput.leftIcon ? parent.right : undefined - anchors.leftMargin: 10 - anchors.rightMargin: 10 - anchors.verticalCenter: parent.verticalCenter - icon: statusBaseInput.icon.name - width: statusBaseInput.icon.width - height: statusBaseInput.icon.height - color: statusBaseInput.icon.color - visible: !!statusBaseInput.icon.name - } - Flickable { - id: flick - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: (statusIcon.visible && statusBaseInput.leftIcon) ? - statusIcon.right : parent.left - anchors.right: (statusIcon.visible && !statusBaseInput.leftIcon) ? - statusIcon.left : parent.right - anchors.leftMargin: statusIcon.visible && statusBaseInput.leftIcon ? 8 - : statusBaseInput.leftPadding - anchors.rightMargin: statusBaseInput.rightPadding + clearable ? clearButton.width - : statusIcon.visible && !leftIcon ? 8: 0 - anchors.topMargin: statusBaseInput.topPadding - anchors.bottomMargin: statusBaseInput.bottomPadding - contentWidth: edit.paintedWidth - contentHeight: edit.paintedHeight - boundsBehavior: Flickable.StopAtBounds - QC.ScrollBar.vertical: QC.ScrollBar { interactive: multiline; enabled: multiline } - function ensureVisible(r) { - if (contentX >= r.x) - contentX = r.x; - else if (contentX+width <= r.x+r.width) - contentX = r.x+r.width-width; - if (contentY >= r.y) - contentY = r.y; - else if (contentY+height <= r.y+r.height) - contentY = r.y+r.height-height; - } - TextEdit { - id: edit - property string previousText: text - width: flick.width - height: flick.height - verticalAlignment: Text.AlignVCenter - selectByMouse: true - selectionColor: Theme.palette.primaryColor2 - selectedTextColor: color - focus: true - font.pixelSize: 15 - font.family: Theme.palette.baseFont.name - color: Theme.palette.directColor1 - onCursorRectangleChanged: { flick.ensureVisible(cursorRectangle); } - wrapMode: statusBaseInput.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap - onActiveFocusChanged: { - if (statusBaseInput.pristine) { - statusBaseInput.pristine = false - } - } - - Keys.onReturnPressed: { - if (multiline) { - event.accepted = false - } else { - event.accepted = true - } - } - - Keys.onEnterPressed: { - if (multiline) { - event.accepted = false - } else { - event.accepted = true - } - } - - Keys.forwardTo: [statusBaseInput] - - onTextChanged: { - statusBaseInput.dirty = true - if (statusBaseInput.maximumLength > 0) { - if (text.length > statusBaseInput.maximumLength) { - var cursor = cursorPosition; - text = previousText; - if (cursor > text.length) { - cursorPosition = text.length; - } else { - cursorPosition = cursor-1; - } - } - previousText = text - } - } - StatusBaseText { - id: placeholder - visible: (edit.text.length === 0) - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: statusBaseInput.rightPadding - anchors.verticalCenter: parent.verticalCenter - font.pixelSize: 15 - elide: StatusBaseText.ElideRight - font.family: Theme.palette.baseFont.name - color: statusBaseInput.enabled ? Theme.palette.baseColor1 : - Theme.palette.directColor6 - } - } - - } // Flickable MouseArea { id: sensor enabled: !edit.activeFocus hoverEnabled: true anchors.fill: parent cursorShape: Qt.IBeamCursor - onClicked: edit.forceActiveFocus() - } + onClicked: { + edit.forceActiveFocus() + } + StatusIcon { + id: statusIcon + anchors.topMargin: 10 + anchors.left: statusBaseInput.leftIcon ? parent.left : undefined + anchors.right: !statusBaseInput.leftIcon ? parent.right : undefined + anchors.leftMargin: 10 + anchors.rightMargin: 10 + anchors.verticalCenter: parent.verticalCenter + icon: statusBaseInput.icon.name + width: statusBaseInput.icon.width + height: statusBaseInput.icon.height + color: statusBaseInput.icon.color + visible: !!statusBaseInput.icon.name + } + + Flickable { + id: flick + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: (statusIcon.visible && statusBaseInput.leftIcon) ? + statusIcon.right : parent.left + anchors.right: { + if (!!statusBaseInput.component) { + return statusBaseInputComponentSlot.left + } + return statusIcon.visible && !statusBaseInput.leftIcon ? statusIcon.left : parent.right + } + anchors.leftMargin: statusIcon.visible && statusBaseInput.leftIcon ? 8 + : statusBaseInput.leftPadding + anchors.rightMargin: { + return clearable ? clearButtonLoader.width + 12 : + (statusIcon.visible && !leftIcon) || !!statusBaseInput.component ? 8 : 0 + } + anchors.topMargin: statusBaseInput.topPadding + anchors.bottomMargin: statusBaseInput.bottomPadding + contentWidth: edit.paintedWidth + contentHeight: edit.paintedHeight + boundsBehavior: Flickable.StopAtBounds + QC.ScrollBar.vertical: QC.ScrollBar { interactive: multiline; enabled: multiline } + function ensureVisible(r) { + if (contentX >= r.x) + contentX = r.x; + else if (contentX+width <= r.x+r.width) + contentX = r.x+r.width-width; + if (contentY >= r.y) + contentY = r.y; + else if (contentY+height <= r.y+r.height) + contentY = r.y+r.height-height; + } + TextEdit { + id: edit + property string previousText: text + width: flick.width + height: flick.height + verticalAlignment: Text.AlignVCenter + selectByMouse: true + selectionColor: Theme.palette.primaryColor2 + selectedTextColor: color + focus: true + font.pixelSize: 15 + font.family: Theme.palette.baseFont.name + color: Theme.palette.directColor1 + onCursorRectangleChanged: { flick.ensureVisible(cursorRectangle); } + wrapMode: statusBaseInput.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap + onActiveFocusChanged: { + if (statusBaseInput.pristine) { + statusBaseInput.pristine = false + } + } + + Keys.onReturnPressed: { + if (multiline) { + event.accepted = false + } else { + event.accepted = true + } + } + + Keys.onEnterPressed: { + if (multiline) { + event.accepted = false + } else { + event.accepted = true + } + } + + Keys.forwardTo: [statusBaseInput] + + onTextChanged: { + statusBaseInput.dirty = true + if (statusBaseInput.maximumLength > 0) { + if (text.length > statusBaseInput.maximumLength) { + var cursor = cursorPosition; + text = previousText; + if (cursor > text.length) { + cursorPosition = text.length; + } else { + cursorPosition = cursor-1; + } + } + previousText = text + } + } + StatusBaseText { + id: placeholder + visible: (edit.text.length === 0) + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: statusBaseInput.rightPadding + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: 15 + elide: StatusBaseText.ElideRight + font.family: Theme.palette.baseFont.name + color: statusBaseInput.enabled ? Theme.palette.baseColor1 : + Theme.palette.directColor6 + } + } + + } // Flickable + + Item { + id: statusBaseInputComponentSlot + anchors.right: parent.right + anchors.rightMargin: 12 + width: childrenRect.width + height: childrenRect.height + anchors.verticalCenter: parent.verticalCenter + } + } } // Rectangle - StatusFlatRoundButton { - id: clearButton - visible: edit.text.length != 0 && - statusBaseInput.clearable && - !statusBaseInput.multiline && - edit.activeFocus - anchors.right: parent.right - anchors.rightMargin: 8 - anchors.verticalCenter: parent.verticalCenter - type: StatusFlatRoundButton.Type.Secondary - width: 24 - height: 24 - icon.name: "clear" - icon.width: 16 - icon.height: 16 - icon.color: Theme.palette.baseColor1 - onClicked: { - edit.clear() + Loader { + id: clearButtonLoader + sourceComponent: StatusFlatRoundButton { + id: clearButton + visible: edit.text.length != 0 && + statusBaseInput.clearable && + !statusBaseInput.multiline && + edit.activeFocus + type: StatusFlatRoundButton.Type.Secondary + width: 24 + height: 24 + icon.name: "clear" + icon.width: 16 + icon.height: 16 + icon.color: Theme.palette.baseColor1 + onClicked: { + edit.clear() + } } } + }