Reputation: 2388
is there a way to get a textfield to display the placeholder text next to an icon like in the picture below.
I tried doing it by setting the padding, but that only works for the actual text not the placeholder text.
import QtQuick
import QtQuick.Controls
TextField {
property alias iconSource: image.source
selectByMouse: true
leftPadding: iconSource.toString() ? 32 : 16
Image {
id: image
anchors.verticalCenter: parent.verticalCenter
width: parent.height * 0.5
height: width
sourceSize.width: width
sourceSize.height: height
}
}
It looks ok if the textfield is focused. If it is out of focus, the text is overlapped by the icon (see password field):
But I want it to look like this:
Upvotes: 2
Views: 151
Reputation: 1004
The TextField
in Material style uses Material.textFieldHorizontalPadding
(source code). And since textFieldHorizontalPadding
is read-only and there is no alias defined for placeholder
, you cannot change the placeholder's x
position directly.
There are two options here:
You can either copy and paste the source code from material/TextField.qml
and modify it, as shown below:
T.TextField {
// ...
+ property real holderPadding: control.Material.textFieldHorizontalPadding
// ...
FloatingPlaceholderText {
// ...
+ x: control.holderPadding
- x: control.Material.textFieldHorizontalPadding
// ...
}
}
Or, you can access the placeholder
using the children
property (or visibleChildren
).
The current placeHolder
object is located at visibleChildren[0]
. By using Binding
, you can bind the placeholder's x
position to a custom property (e.g., holderPadding
). You can then change that property based on the TextField
's activeFocus
. The changes can be seen in the following code:
TextField {
id: input
property alias iconSource: image.source
property real spacing: 5
property real holderPadding: activeFocus ? Material.textFieldHorizontalPadding : leftPadding
Binding { target: input.visibleChildren[0]; property: 'x'; value: input.holderPadding }
Behavior on holderPadding { NumberAnimation {} }
selectByMouse: true
height: 40; width: 250
placeholderText: 'placeholder'
leftPadding: image.x + image.width + spacing
Image {
id: image
x: 10; y: (parent.height - height) / 2
height: width; width: parent.height * 0.5
source: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 483.1 482.7'%3E%3Cpath fill='%235f6469' d='M93 364.4c88.9-67.4 208-67 296.8 0 105-124.6 15.1-316.6-148.4-316.1C78.2 47.6-11 239.7 93 364.4zm148.4-99c-75.2 1.5-112.9-92.4-59.7-144.2 31.5-32.5 88.8-33.1 120.1 0 52.2 51.8 15.1 147-60.4 144.2zm0 217.2a239.3 239.3 0 0 1-222-147.2C-48.1 176.9 68.9.4 241.4 0A241.7 241.7 0 0 1 464 335.4c-36.9 87.7-126.3 148.9-222.6 147.2zm0-48.2a180 180 0 0 0 112.2-36.2A190.4 190.4 0 0 0 241.4 362c-41-.2-79.7 13-112.2 36.2 32.3 23.9 71 37.2 112.2 36.2zm0-217.2a35.1 35.1 0 0 0 36.2-36.2 35.1 35.1 0 0 0-36.2-36.2 35.3 35.3 0 0 0-36.2 36.2 35.1 35.1 0 0 0 36.2 36.2z'/%3E%3C/svg%3E"
sourceSize{ width: 50; height: 50 }
}
}
Note
There are also some issues if you want to use it with RTL, which you can fix by adding some conditions.
Upvotes: 2
Reputation: 26214
The pattern I follow is
Frame {
RowLayout {
/* IconButton */
TextField { }
/* IconButton */
}
}
To make this work, I make use of the fact that:
background: Item { }
background: Rectangle { radius: height / 2 }
Butting it altogether we can complete the component as follows:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
background: Rectangle { color: "#848895" }
Frame {
width: parent.width
background: Rectangle { radius: height / 2 }
RowLayout {
width: parent.width
IconButton { icon.source: "lock.svg" }
TextField {
Layout.fillWidth: true
background: Item { }
placeholderText: "Password"
}
IconButton { icon.source: "view.svg" }
}
}
}
// IconButton.qml
import QtQuick
import QtQuick.Controls
Item {
width: 32
height: 32
clip: true
property alias icon: btn.icon
property alias button: btn
Button {
id: btn
anchors.centerIn: parent
background: Item { }
icon.width: 32
icon.height: 32
icon.color: "grey"
}
}
// user.svg : https://raw.githubusercontent.com/Esri/calcite-ui-icons/master/icons/user-32.svg
// lock.svg : https://raw.githubusercontent.com/Esri/calcite-ui-icons/master/icons/lock-32.svg
// view.svg : https://raw.githubusercontent.com/Esri/calcite-ui-icons/master/icons/view-visible-32.svg
You can Try it Online!
Upvotes: 0
Reputation: 3967
What you are trying to achieve is documented on the Customizing Qt Quick Controls help page: you need to set the background property of your TextField
to some rectangle and bind that rectangle's radius
.
Below is a demonstration of that. I am going to assume you defined your applications colors in a qtquickcontrols2.conf file such as this one:
[Controls]
Style=Universal
[Universal]
Accent="#0267ff"
[Material]
Theme=Light
Accent=Blue
Primary=Gray
Your control becomes somethings in the lines of:
TextField {
id: textField
verticalAlignment: Text.AlignVCenter
leftPadding: image.x + image.width + 2
placeholderText: qsTr("User name")
background: Rectangle {
anchors.fill: parent
border.color: textField.activeFocus ? Material.accent : Material.primary
border.width: 1
radius: textField.height / 2
}
Image {
id: image
x: (textField.height - width) / 2 /* Set the center of the image to be the same as the center of the rounded border */
y: 5
width: textField.height - 10
height: textField.height - 10
source: "../account_circle.svg"
fillMode: Image.PreserveAspectFit
}
}
which renders like so (without and with focus respectively):
Since your screenshot seem to show the placeholder text moves when the field has active focus, you must have noticed the above code does not force the border to open up for it to fit.
Solving this issue requires a lot more work, because of the following items:
Shape
as there is not way to open a Rectangle
.RoundedBorderShape.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Shapes 1.8
Shape {
property int placeholderLeft: 0
property int placeholderLength: 0
property color fill: "transparent"
property real pathExtraLength: parent.activeFocus ? 0.0 : placeholderLength / 2
anchors.fill: parent
anchors.bottomMargin: -parent.topInset
/* Add layer properties for antialiasing */
layer {
enabled: true
samples: 4
}
HoverHandler { id: hoverHandler }
ShapePath {
id: shapePath
fillColor: fill
strokeColor: parent.activeFocus ? Material.accent : (hoverHandler.hovered ? "black" : Material.primary)
strokeWidth: parent.activeFocus ? 1.5 : 0.75
startX: placeholderLeft + placeholderLength - pathExtraLength
startY: parent.topInset + 1
Behavior on startX {
NumberAnimation {
duration: 250
}
}
PathLine {
x: width - (height - parent.topInset) / 2 + 1
y: parent.topInset + 1
}
PathArc {
x: width - (height - parent.topInset) / 2 + 1
y: height - parent.topInset - 2
radiusY: height / 2 - parent.topInset - 2
radiusX: radiusY
}
PathLine {
x: (height - parent.topInset) / 2 - 1
y: height - parent.topInset - 2
}
PathArc {
x: (height - parent.topInset) / 2 - 1
y: parent.topInset + 1
radiusY: height / 2 - parent.topInset - 2
radiusX: radiusY
}
PathLine {
x: placeholderLeft + pathExtraLength
y: parent.topInset + 1
Behavior on x {
NumberAnimation {
duration: 250
}
}
}
}
}
The text field becomes (you may have to force the size to something that matches the Material style):
TextField {
id: textField
topInset: 5
bottomInset: -5
verticalAlignment: Text.AlignVCenter
leftPadding: image.x + image.width + 2
placeholderText: qsTr("User name")
background: RoundedBorderShape {
placeholderLeft: 0.8 * textField.height
placeholderLength: 75 /* Hardcoded, to be replaced by actual text length in pixels */
}
Image {
id: image
x: (textField.height - width) / 2 /* Set the center of the image to be the same as the center of the rounded border */
y: 5 + textField.topInset
width: height
height: textField.height - textField.topInset - 10
source: "../account_circle.svg"
fillMode: Image.PreserveAspectFit
}
}
The rendering, next to another text field rendered natively by QML, becomes:
(Animations are in fact smoother, the above gif has limited FPS).
PS: I did not demonstrate it but I added a property called "fill" to let you turn the field background color to white, as per your screenshot.
Upvotes: 1