Is there a way to style the cursorDelegate on a TextArea/TextInput so that it ends up taller and thicker than the default one?
With the following code, I can currently get one that has the following properties:
Thicker than normal, at 4px wide
Taller than the rest of the text, with bits sticking out above/below the line as wanted. BUT, this only works until the user moves the cursor. Then, the vertical size gets clipped again, and can't be reset.
TextArea {
id: editor
cursorDelegate: Rectangle {
width: 4
property int vpad: 4
y: editor.cursorRectangle.y - (vpad / 2)
height: editor.cursorRectangle.height + vpad
}
}
It looks like the y and height bindings are getting overwritten by whatever sets those automatically internally.
Trying to overwrite these again myself using a onCursorPositionChanged handler on the TextArea fails, as you cannot write to cursorDelegate.
Managed to find a solution. Instead of setting these values in the normal way, you need to set these on the change handlers for those properties.
TextArea {
id: editor
cursorDelegate: Rectangle {
width: 4
property int vpad: 4
onYChanged: editor.cursorRectangle.y - (vpad / 2)
onHeightChanged: editor.cursorRectangle.height + vpad
}
}
This seems to resist the widgets auto behaviour that was overwriting our values.
Related
I want to specify anchors for all sides using anchors.margins, but set a side anchor, like anchors.leftMargin to a different value.
For example:
Rectangle {
id: rect
width: 100; height: 100
color: "black"
Rectangle {
anchors {
fill: parent
leftMargin: 0
margins: 30
}
color: "lime"
}
}
Which shows:
It seems to work, but is this a feature or a bug waiting to happen? Isn't margins a shortcut that sets all side anchor margins and the fact that it works just due to the order in which bindings are evaluated? If someone modifies the code might anchors.margins overwrite the leftMargin property?
Anchors.margins provides the default value for the more specific margin properties. So, what you are doing is safe and supported.
It is safe to set side anchors and anchors.margins together.
anchors.margins, anchors.leftMargin, anchors.topMargin, anchors.rightMargin, and anchors.bottomMargin are all separate properties. The default value for all side anchors is anchors.margins; assigning undefined to a side anchor reverts it to the anchors.margins value.
Does setting an component's width or height to zero has the same effect as setting its visible property to false?
An example use-case:
I have an item, which slides into the window. The sliding happens by animating its height from 0 to x and when I dismiss this item from x to 0. Don't want to go in depth why I am not animating the position of the item instead. When the item has 0 height, should I set its visible property to false or it doesn't make any difference?
Not really, unless you clip. And it is better to avoid clipping as much as possible.
An Item with zero size will still have its children visible.
Whereas setting visible to false will hide the entire object tree.
In your particular case it seems like it doesn't matter as long as it doesn't cause you to have unwanted visible leftovers. You certainly do not want to have a binding such as visible: height as that would needlessly execute on every step of the animation.
Just to be on the safe side, you can install handlers on the animation to toggle visibility:
// in the animation
onStarted: if (!item.height) item.visible = true // show if start at 0
onStopped: if (!item.height) item.visible = false // hide if end at 0
This will avoid the continuous reevaluations you'd get if you bind visibility to height directly, but will still ensure visibility on before your object begins expanding and off after it has finished contracting.
As dtech already pointed out, the dimensions of the root node of a component do not automatically represent the dimensions of the underlying object tree. As an example take this:
Item {
id: root
Text {
id: txt
text: 'some text produces implicit width'
}
}
In this example the text of txt will be shown, though the dimensions of root are width: 0; height: 0.
As dtech already mentioned, you might set clip to true, but this is not advisable, as then it would be passed to the renderer, which renders the Item and its tree and finally applies clipping to it - in a seperate batch.
If you have something like that:
Item {
Rectangle {
anchors.fill: parent
color: 'red'
}
}
The renderer would do nothing extra when rendering, as it could be processed in the same batch as the rest. However as a developer it is hard to tell, whether something is visible when the size is set to 0 or not. Therefore it is adivsable to always set visible properly.
We might simply set
visible: width > 0 && height > 0 && opacity > 0
which works fine, as long as we don't animate on any of those properties or change them frequently. At least for animations we might have good knowledge, when the any of those properties might become 0 and use this information to reduce the amount of evaluations.
The nice thing about QML is, that the logical expression is evaluated from the left to the right, which means in our last example:
If width === 0 and height changes, it wont trigger reevaluation
If height === 0 and width changes, each change triggers reevaluation.
This means, we need to put the most stable condition first. This might be our information about when any of those values might change. I propose, using the animation.running property, to prevent reevaluation of the binding, while the animation is running.
Let's take a more complete example: Upon click, this Rectangle will shrink from width: 800 to width: 0 - which shall set it invisible.
Or three additional properties binding1, binding2, binding3 are bound to expressions, that we might use to set visible. When ever a particular part of the binding is reeavluated, we log this.
Rectangle {
id: rect
color: 'red'
width: 800
height: 600
NumberAnimation {
id: ani1
target: rect
property: 'width'
from: 800
to: 0
duration: 3000
}
}
MouseArea {
anchors.fill: parent
onClicked: ani1.running = true
}
property bool binding1: {console.log("1", !rect.width); return !rect.width}
property bool binding2: {!ani1.running && (function() { console.log("2", !rect.width); return !rect.width })()}
property bool binding3: {(function() { console.log("3", !rect.width); return !rect.width })() && !ani1.running}
// equivalent, stripped of the logging:
// property bool binding1: !rect.width
// property bool binding2: !ani1.running && !rect.width
// property bool binding3: !rect.width && !ani1.running
As we can see, binding1 is constantly reevaluated, when ever the width changes. This is not desirable.
We can see, that binding2 is only evaluated once at creation, and whenever ani stops running.
In binding3 we have it the other way around and we first evaluate the width, and then whether the ani is running. This means, we have a reevaluation whenever the width is changing.
We could also use the signal handlers ani.onStarted and ani.onStopped and explicitly set the visiblity then, but that would not be declarative and QML encourages you to always strive to stay declarativ.
What is the difference between width/height and implicitWidth/Height in QML? When should one set the implicit dimensions instead of the regular? When should one ask the implicit dimensions instead of the regular from a component/item?
Generally, usage of implicitHeight/Width only makes sense within reusable components.
It gives a hint, of the natural size of the Item without enforcing this size.
Let's take a Image as an example. The natural size of the image would map one pixel from the image file to one pixel on the screen. But it allows us to stretch it, so the size is not enforced and can be overridden.
Let's say now, we want to have a gallery with pictures of unknown dimension, and we don't want to grow but only shrink them if necessary. So we need to store the natural size of the image. That is, where the implicit height comes into play.
Image {
width: Math.max(150, implicitWidth)
height: Math.max(150, implicitHeight)
}
In custom components, you have a choice on how to define the sizes.
The one choice is, to have all dimensions relative to the components root-node, maybe like this:
Item {
id: root
Rectangle {
width: root.width * 0.2
height: root.height * 0.2
color: 'red'
}
Rectangle {
x: 0.2 * root.width
y: 0.2 * root.height
width: root.width * 0.8
height: root.height * 0.8
color: 'green'
}
}
In this case, there is no natural size of the object. Everything works out perfectly for each size you set for the component.
On the other hand, you might have an object, that has a natural size - that happens, e.g. if you have absolute values in it
Item {
id: root
property alias model: repeater.model
Repeater {
id: repeater
delegate: Rectangle {
width: 100
height: 100
x: 102 * index
y: 102 * index
}
}
}
In this example you should provide the user with information about the natural size, where the content does not protude the item. The user might still decide to set a smaller size and deal with the protrusion, e.g. by clipping it, but he needs the information about the natural size to make his decision.
In many cases, childrenRect.height/width is a good measure for the implcitHeight/Width, but there are examples, where this is not a good idea. - e.g. when the content of the item has x: -500.
A real life example is the Flickable that is specifically designed to contain larger objects than its own size. Having the size of the Flickable to be equal to the content would not be natural.
Also be careful, when using scale in custom components, as the childrenRect will not know about the scaling.
Item {
id: root
implicitWidth: child.width * child.scale
implicitHeight: child.height * child.scale
Rectangle {
id: child
width: 100
height: 100
scale: 3
color: 'red'
}
}
And to your comment: I just don't understand why it is better to set implicitWidth/Height instead of setting width/height of a component's root dimension.
implicitWidht/Height are not a necessety - QtQuick could do without them. They exist for convenience and shall be convention.
Rule of Thumb
When you want to set dimension of a root node of a reusable component, set implicitWidth/Height.
In some cases, set it for non-root-nodes, if the nodes are exposed as a property.
Do so only, if you have a reason for it (many official components come without any).
When you use a component, set width/height.
I don't have the definitive answer but I can tell you what I found out. First, from the documentation:
implicitWidth : real
Defines the natural width or height of the Item if no width or height
is specified.
The default implicit size for most items is 0x0, however some items
have an inherent implicit size which cannot be overridden, for
example, Image and Text.
but less informative for width:
width
Defines the item's position and size.
The width and height reflect the actual size of the item in the scene. The implicit size is some kind of inherent property of the item itself.1
I use them as follows: When I create a new item and it can be resized, I set an implicit size inside the object2. When I'm using the object, I often set the real size explicitly from the outside.
The implicit size of an object can be overridden by setting height and width.
an example: TextWithBackground.qml
Item {
implicitWidth: text.implicitWidth
implicitHeight: text.implicitHeight
// the rectangle will expand to the real size of the item
Rectangle { anchors.fill: parent; color: "yellow" }
Text { id: text; text: "Lorem ipsum dolor..." }
}
an example: MyWindow.qml
Item {
width: 400
height: 300
TextWithBackground {
// half of the scene, but never smaller than its implicitWidth
width: Math.max(parent.width / 2, implicitWidth)
// the height of the element is equal to implicitHeight
// because height is not explicitly set
}
}
1) For some elements, like Text, the implicit height depends on the (not-implicit) width.
2) The implicit size usually depends on the implicit size of its children.
Implicit size is supposed to be used when calculating size of an item based on its contents. Whereas setting width or height on a parent item may affect the size of its children it should never be a case, when you set implicit size.
Rule of thumb
Implicit size should only "bubble up", i.e. children should never
lookup for implicit size of their parent to calculate their own
implicit size, neither parent should try to force implicit size of its
children.
If you would try to set width on a component similar to layout, that initially calculates its width from the width (rather than implicitWidth) of its child item and that child is affected by the size of a parent, you would end up with a binding loop.
This is why the property exists - to break cyclic dependencies when calculating size of an item based on its contents.
I am creating a simple 'tooltip' subclass that is a rounded rectangle and a small triangle that will be 'anchored' to another view.
I created a UILabel subclass and am overriding 'drawRect' to shrink the main label area and draw a triangle.
The 'roundRect' represents the rounded rectangle portion that should contain the full text.
This all works great, except that I am having trouble getting the full text to be shown within the 'roundRect'. It appears that the text just doesn't show up for the last line (i.e. where the triangle now occupies).
One other thing to note, I am using auto layout and have setup the constraints for the 'tooltipLabel' to fill the screen as needed. This works as expected, as reducing/adding more text shows the appropriate sized tooltip. But it seems like I need to somehow inform the 'tooltip' or 'auto layout' that the text should be fitted into the 'roundRect' portion of the 'tooltipLabel'.
Screenshot #1 uses this 'drawTextInRect' method and you can see that the full text is being shown, but overlaps into the 'triangle' area (plus it has no insets, which is not the desired look):
override public func drawTextInRect(rect: CGRect) {
super.drawTextInRect(rect)
// super.drawTextInRect(UIEdgeInsetsInsetRect(rect, UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)))
}
Screenshot #2 uses this 'drawTextInRect' method with the desired insets (also adds the triangleHeight so that space is not written into with text) and you can see that it cuts off the 2nd and 3rd lines of text, as they don't 'fit' within the 'roundRect' bounds:
override public func drawTextInRect(rect: CGRect) {
// super.drawTextInRect(rect)
super.drawTextInRect(UIEdgeInsetsInsetRect(self.roundRect, UIEdgeInsets(top: 10, left: 10, bottom: 10+self.triangleHeight, right: 10)))
}
Here is the 'drawRect' override:
override public func drawRect(rect: CGRect) {
self.roundRect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height-self.triangleHeight)
self.triangleBezier.moveToPoint(CGPoint(x: self.roundRect.midX-self.triangleWidth/2, y: self.roundRect.maxY))
self.triangleBezier.addLineToPoint(CGPoint(x: rect.midX, y: rect.maxY))
self.triangleBezier.addLineToPoint(CGPoint(x: self.roundRect.midX+self.triangleWidth/2, y: self.roundRect.maxY))
self.triangleBezier.closePath()
self.roundRectBezier = UIBezierPath(roundedRect: self.roundRect, cornerRadius: 5.0)
self.roundRectBezier.appendPath(self.triangleBezier)
self.tooltipColor.setFill()
self.roundRectBezier.fill()
super.drawRect(rect)
}
I would actually not be subclassing UILabel for this, and make your own tooltip class composed of the outer view and the internal label with auto layout constraints. The internal label determines the whole height of the view.
Something like this with appropriately rounded off corners/triangle:
Alternatively, use UITextView instead if you want to assign padding: Adding space/padding to a UILabel
I'm trying to make a smaller version of the ComboBox but the gap between the text and the arrow button is constant no matter what I do.
If I use the css:
.combo-box-base > *.arrow-button {
-fx-padding: 0 0 0 0;
-fx-background-color: pink, pink, pink, pink;
}
the arrow button gets smaller but the ComboBox itself still have the same size, only increasing the gap between the arrow and text to compensate.
If I do
.combo-box > .list-cell {
-fx-padding: 0 0 0 0;
-fx-border-insets: 0 0 0 0;
}
The combo get a smaller height but the width remain fixed.
Is there any way to make the preferred size of the combo smaller by reducing the size between the text and arrow?
I know this question is quite old but i came across the same problem and wanted to solve it. So i started debugging the JavaFX code step by step to get an idea where the additional space comes from.
It took me longer than it should because i overlooked a small piece of code with a huge impact.
The class ComboBoxListViewSkin is by default responsible for the computation of the ComboBox width. Its compute method delegates the computation to the super class and the prefWidth method of the field listView and chooses the greater result.
/** {#inheritDoc} */
#Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
double superPrefWidth = super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
double listViewWidth = listView.prefWidth(height);
double pw = Math.max(superPrefWidth, listViewWidth);
reconfigurePopup();
return pw;
}
The listView field is actually an instance of an Anonymous Classes extending ListView which does some ComboBox specific things and overrides some methods. One of these methods is protected double computePrefWidth(double height). It contains the line which causes the problem with extra whitespace we cannot get rid of.
pw = Math.max(comboBox.getWidth(), skin.getMaxCellWidth(rowsToMeasure) + 30);
Adding 30 to the widest cell's width is exactly what we don't want. Because it's hard coded we cannot easily change it. I overlooked it at first glance causing some WTF moments when debugging the code.
Of course you can extend ComboBoxListViewSkin and override its computePrefWidth method but i think the default ComboBoxListViewSkin class should provide the possibility to change this extra whitespace easily. So I filed an issue at GitHub.
Have you tried setting the padding of .combox-box > .text-input ?
According to Oracles ComboBox CSS documentation, this could be another source of padding.
If you plan on developing more CSS-rules, maybe have a look at the CSS inspector of the SceneBuilder2 or use ScenicView. ScenicView also allows live-modification of the CSS, which significantly improves debugging speed.
Did you check the Hgrow property ? you should set it in the layout manager for the combo box
ComboBox field = new ComboBox();
HBox.setHgrow(field, Priority.NEVER);
Also check the preferred and maximum width you should set them to USE_COMPUTED_SIZE (in SceneBuilder, not sure how to do it by code).