rewrite qtquick size+position guide
This commit is contained in:
		
							parent
							
								
									bc5e480cd1
								
							
						
					
					
						commit
						e151d8c304
					
				
					 3 changed files with 195 additions and 82 deletions
				
			
		| 
						 | 
				
			
			@ -32,7 +32,7 @@ const remarkParseAtTypes: RemarkPlugin<[]> = () => {
 | 
			
		|||
        const node = rawNode as Md.Literal;
 | 
			
		||||
 | 
			
		||||
        node.value = node.value.replace(
 | 
			
		||||
          /@@((?<module>([A-Z]\w*\.)*)(?<type>([A-Z]\w*))\.?)?((?<member>[a-z]\w*)((?<function>\(\))|(?<signal>\(s\)))?)?(?=[$.,;:\s]|$)/g,
 | 
			
		||||
          /@@((?<module>([A-Z]\w*\.)*)(?<type>([A-Z]\w*))\.?)?((?<member>[a-z]\w*)((?<function>\(\))|(?<signal>\(s\)))?)?(?=[$.,;:)\s]|$)/g,
 | 
			
		||||
          (_full, ...args) => {
 | 
			
		||||
            type Capture = {
 | 
			
		||||
              module: string | undefined;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -145,7 +145,8 @@ more correct code should you chose to use it.
 | 
			
		|||
 | 
			
		||||
> [!NOTE]
 | 
			
		||||
> Nix users should note that qmlls will not be able to pick up qml modules
 | 
			
		||||
> that are not in `QML2_IMPORT_PATH`.
 | 
			
		||||
> that are not in `QML2_IMPORT_PATH`. The easiest way to ensure this is by setting
 | 
			
		||||
> `qt.enable` to `true` and installing the quickshell package globally.
 | 
			
		||||
 | 
			
		||||
# Next steps
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,107 +6,219 @@ import MD_Title from "@components/MD_Title.tsx"
 | 
			
		|||
 | 
			
		||||
# {frontmatter.title}
 | 
			
		||||
 | 
			
		||||
QtQuick has multiple ways to position components. This page has instructions for where and how
 | 
			
		||||
to use them.
 | 
			
		||||
> [!TIP]
 | 
			
		||||
> Read the entire page, understanding this is critical to building a well designed shell.
 | 
			
		||||
 | 
			
		||||
## <MD_Title titleVar={2}> Anchors </MD_Title>
 | 
			
		||||
@@QtQuick.Item has two sets of size properties, actual size (@@QtQuick.Item.width and @@QtQuick.Item.height)
 | 
			
		||||
and implicit / desired (@@QtQuick.Item.implicitWidth and @@QtQuick.Item.implicitHeight).
 | 
			
		||||
 | 
			
		||||
Anchors can be used to position components relative to another neighboring component.
 | 
			
		||||
It is faster than [manual positioning](#manual-positioning) and covers a lot of simple
 | 
			
		||||
use cases.
 | 
			
		||||
Container items, such as layouts and wrappers, use the implicit size of their children to determine
 | 
			
		||||
their own implicit size, and their actual size to detetermine the actual size of their children.
 | 
			
		||||
If managed by a container, an Item should not set its own size, and should instead allow
 | 
			
		||||
the container to determine it based on its implicit size.
 | 
			
		||||
 | 
			
		||||
The [Qt Documentation: Positioning with Anchors](https://doc.qt.io/qt-6/qtquick-positioning-anchors.html)
 | 
			
		||||
page has comprehensive documentation of anchors.
 | 
			
		||||
Put simply, implicit size should flow from children to parents, while actual size should flow from
 | 
			
		||||
parent to children.
 | 
			
		||||
 | 
			
		||||
## <MD_Title titleVar={2}> Layouts </MD_Title>
 | 
			
		||||
In addition to size, Items also have position properties (@@QtQuick.Item.x and @@QtQuick.Item.y).
 | 
			
		||||
Similarly to actual size, (actual) position should not be set directly if your item is managed
 | 
			
		||||
by a container, though there is no such thing as implicit position.
 | 
			
		||||
 | 
			
		||||
Layouts are useful when you have many components that need to be positioned relative to
 | 
			
		||||
eachother such as a list.
 | 
			
		||||
> [!WARNING]
 | 
			
		||||
> Many QtQuick Items have *zero size* by default (both implicit and actual).
 | 
			
		||||
>
 | 
			
		||||
> An invisible zero sized item (usually a custom container without implicit size set)
 | 
			
		||||
> is a common bug and often manifests as an item being laid out as if it took no space.
 | 
			
		||||
>
 | 
			
		||||
> Quickshell will attempt to detect zero sized items when a window is initially made visible
 | 
			
		||||
> and log a warning, but it cannot detect all cases. Please be aware these exist.
 | 
			
		||||
 | 
			
		||||
The [Qt Documentation: Layouts Overview](https://doc.qt.io/qt-6/qtquicklayouts-overview.html)
 | 
			
		||||
page has good documentation of the basic layout types and how to use them.
 | 
			
		||||
 | 
			
		||||
> [!note/Note:]
 | 
			
		||||
> Layouts by default have a nonzero spacing.
 | 
			
		||||
 | 
			
		||||
## <MD_Title titleVar={2}> Manual Positioning </MD_Title>
 | 
			
		||||
 | 
			
		||||
If layouts and anchors can't easily fulfill your usecase, you can also manually position and size
 | 
			
		||||
components by setting their @@QtQuick.Item.x, @@QtQuick.Item.y, @@QtQuick.Item.width and @@QtQuick.Item.height
 | 
			
		||||
properties, which are relative to the parent component.
 | 
			
		||||
 | 
			
		||||
This example puts a 100x100px blue rectangle at x=20,y=40 in the parent item. Ensure the size
 | 
			
		||||
of the parent is large enough for its content or positioning based on them will break.
 | 
			
		||||
## Container Items
 | 
			
		||||
Below is an example container which adds a margin to a rectangle, and interacts properly
 | 
			
		||||
with other container types.
 | 
			
		||||
 | 
			
		||||
```qml
 | 
			
		||||
@@QtQuick.Item {
 | 
			
		||||
  property real margin: 5
 | 
			
		||||
  
 | 
			
		||||
  // Set the implicit size of the containing item to the size of
 | 
			
		||||
  // the contained item, plus the margin on each side.
 | 
			
		||||
  implicitWidth: child.implicitWidth + margin * 2
 | 
			
		||||
  implicitHeight: child.implicitHeight + margin * 2
 | 
			
		||||
 | 
			
		||||
  @@QtQuick.Rectangle {
 | 
			
		||||
    id: child
 | 
			
		||||
 | 
			
		||||
    // Set the size of the child item relative to the actual size
 | 
			
		||||
    // of the parent item. If the parent item is constrained
 | 
			
		||||
    // or stretched the child's position and size will be similarly
 | 
			
		||||
    // constrained.
 | 
			
		||||
    x: parent.margin
 | 
			
		||||
    y: parent.margin
 | 
			
		||||
    width: parent.width - parent.margin * 2
 | 
			
		||||
    height: parent.height - parent.margin * 2
 | 
			
		||||
    
 | 
			
		||||
    // The child's implicit / desired size, which will be respected
 | 
			
		||||
    // by the container item as long as it is not constrained
 | 
			
		||||
    // or stretched.
 | 
			
		||||
    implicitWidth: 50
 | 
			
		||||
    implicitHeight: 50
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If we were to write this as a reusable component, we could use @@QtQml.Binding
 | 
			
		||||
to control the child item's actual size and position.
 | 
			
		||||
```qml
 | 
			
		||||
@@QtQuick.Item {
 | 
			
		||||
  id: wrapper
 | 
			
		||||
  property real margin: 5
 | 
			
		||||
  required default property Item child
 | 
			
		||||
 | 
			
		||||
  // Set the item's visual children list to just the passed item.
 | 
			
		||||
  children: [child]
 | 
			
		||||
 | 
			
		||||
  implicitWidth: child.implicitWidth + margin * 2
 | 
			
		||||
  implicitHeight: child.implicitHeight + margin * 2
 | 
			
		||||
 | 
			
		||||
  // Bind the child's position and size.
 | 
			
		||||
  // Note that this syntax is exclusive to the Binding type.
 | 
			
		||||
  @@QtQuick.Binding { wrapper.child.x: wrapper.margin }
 | 
			
		||||
  @@QtQuick.Binding { wrapper.child.y: wrapper.margin }
 | 
			
		||||
  @@QtQuick.Binding { wrapper.child.width: wrapper.width - wrapper.margin * 2 }
 | 
			
		||||
  @@QtQuick.Binding { wrapper.child.height: wrapper.height - wrapper.margin * 2 }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
Note: @@Quickshell.Widgets.WrapperItem is a builtin component that adds margins similarly to this.
 | 
			
		||||
 | 
			
		||||
### Reducing boilerplate with Anchors
 | 
			
		||||
We can reduce the amount of boilerplate we have to write using
 | 
			
		||||
[QtQuick Anchors](https://doc.qt.io/qt-6/qtquick-positioning-anchors.html).
 | 
			
		||||
 | 
			
		||||
Anchors exist as a shorthand way to achieve many common position and size bindings.
 | 
			
		||||
See the linked qt documentation for more details on how to use them.
 | 
			
		||||
 | 
			
		||||
The following example is equivalent to the one above, but uses anchors instead of setting
 | 
			
		||||
position and size directly. A similar change can be made to the `Binding` example.
 | 
			
		||||
```qml
 | 
			
		||||
@@QtQuick.Item {
 | 
			
		||||
  property real margin: 5
 | 
			
		||||
  
 | 
			
		||||
  implicitWidth: child.implicitWidth + margin * 2
 | 
			
		||||
  implicitHeight: child.implicitHeight + margin * 2
 | 
			
		||||
 | 
			
		||||
  @@QtQuick.Rectangle {
 | 
			
		||||
    id: child
 | 
			
		||||
 | 
			
		||||
    // "Fill" the space occupied by the parent, setting width
 | 
			
		||||
    anchors.fill: parent
 | 
			
		||||
    // Add a margin to all anchored sides.
 | 
			
		||||
    anchors.margins: parent.margin
 | 
			
		||||
    
 | 
			
		||||
    implicitWidth: 50
 | 
			
		||||
    implicitHeight: 50
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### childrenRect and binding loops
 | 
			
		||||
The most common mistake made when creating container items is trying to use @@QtQuick.Item.childrenRect
 | 
			
		||||
to determine the size of a child item, such as in the example below:
 | 
			
		||||
 | 
			
		||||
```qml
 | 
			
		||||
@@QtQuick.Item {
 | 
			
		||||
  // make sure the component is large enough to fit its children
 | 
			
		||||
  implicitWidth: childrenRect.width
 | 
			
		||||
  implicitHeight: childrenRect.height
 | 
			
		||||
  
 | 
			
		||||
  @@QtQuick.Rectangle {
 | 
			
		||||
    color: "blue"
 | 
			
		||||
    x: 20
 | 
			
		||||
    y: 40
 | 
			
		||||
    width: 100
 | 
			
		||||
    height: 100
 | 
			
		||||
    anchors.fill: parent
 | 
			
		||||
 | 
			
		||||
    implicitWidth: 50
 | 
			
		||||
    implicitHeight: 50
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## <MD_Title titleVar={2}> Notes </MD_Title>
 | 
			
		||||
While the snippet above might look like it should work, it is actually hiding a nasty bug.
 | 
			
		||||
 | 
			
		||||
### <MD_Title titleVar={3}> Component Size </MD_Title>
 | 
			
		||||
As stated at the top of the page, an item's implicit size should be used to determine
 | 
			
		||||
its parent's implicit size, and the parent's actual size should be used to determine
 | 
			
		||||
the child's actual size. **`childrenRect` breaks this pattern.**
 | 
			
		||||
 | 
			
		||||
The @@QtQuick.Item.implicitHeight and @@QtQuick.Item.implicitWidth properties control the
 | 
			
		||||
_base size_ of a component, before layouts are applied. These properties are _not_ the same as
 | 
			
		||||
@@QtQuick.Item.height and @@QtQuick.Item.width which are the final size of the component.
 | 
			
		||||
You should nearly always use the implicit size properties when creating a component,
 | 
			
		||||
however using the normal width and height properties is fine if you know an
 | 
			
		||||
item will never go in a layout.
 | 
			
		||||
`childrenRect` encompasses the geometry of all child items, meaning their *actual* geometry,
 | 
			
		||||
not their *implicit* geometry. This results in the container item's size having an indirect
 | 
			
		||||
dependency on itself, in what is known as a *binding loop*.
 | 
			
		||||
 | 
			
		||||
This example component puts a colored rectangle behind some text, and will act the same
 | 
			
		||||
way in a layout as the text by itself.
 | 
			
		||||
If we were to try to figure out what implicitWidth is by hand, it would look something like this:
 | 
			
		||||
 | 
			
		||||
```qml {filename="TextWithBkgColor.qml"}
 | 
			
		||||
@@QtQuick.Rectangle {
 | 
			
		||||
  implicitWidth: text.implicitWidth
 | 
			
		||||
  implicitHeight: text.implicitHeight
 | 
			
		||||
*container.implicitWidth = container.childrenRect.width = child.width = container.width (via anchor)
 | 
			
		||||
= container.implicitWidth = ... (repeats forever)*
 | 
			
		||||
 | 
			
		||||
  @@QtQuick.Text {
 | 
			
		||||
    id: text
 | 
			
		||||
    text: "hello!"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
which isn't a valid definition.
 | 
			
		||||
 | 
			
		||||
If you want to size your component based on multiple others or use any other math you can.
 | 
			
		||||
### MarginWrapper components
 | 
			
		||||
To solve the boilerplate problem that often leads users to `childrenRect`, Quickshell comes with
 | 
			
		||||
@@Quickshell.Widgets.MarginWrapperManager and a set of components based on it.
 | 
			
		||||
 | 
			
		||||
```qml {filename="PaddedTexts.qml"}
 | 
			
		||||
@@Quickshell.Widgets.MarginWrapperManager automatically handles the size and position relationship
 | 
			
		||||
between a container item and a single child item, skipping most of the boilerplate in the above
 | 
			
		||||
examples. See its linked documentation for more information on how to use it.
 | 
			
		||||
 | 
			
		||||
Rewriting the examples from the top of the page:
 | 
			
		||||
```qml
 | 
			
		||||
@@QtQuick.Item {
 | 
			
		||||
  // width of both texts plus 5
 | 
			
		||||
  implicitWidth: text1.implicitWidth + text2.implicitWidth + 5
 | 
			
		||||
  // max height of both texts plus 5
 | 
			
		||||
  implicitHeight: Math.min(text1.implicitHeight, text2.implicitHeight) + 5
 | 
			
		||||
 | 
			
		||||
  @@QtQuick.Text {
 | 
			
		||||
    id: text1
 | 
			
		||||
    text: "text1"
 | 
			
		||||
  @@Quickshell.Widgets.MarginWrapperManager {
 | 
			
		||||
    margin: 5
 | 
			
		||||
    // By default, MarginWrapperManager centers the child
 | 
			
		||||
    // instead of resizing it when encountering constraints.
 | 
			
		||||
    resizeChild: true
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @@QtQuick.Text {
 | 
			
		||||
    id: text2
 | 
			
		||||
    anchors.left: text1.left
 | 
			
		||||
    text: "text2"
 | 
			
		||||
  // Automatically detected by MarginWrapperManager as the
 | 
			
		||||
  // primary child of the container and sized accordingly.
 | 
			
		||||
  @@QtQuick.Rectangle {
 | 
			
		||||
    implicitWidth: 50
 | 
			
		||||
    implicitHeight: 50
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### <MD_Title titleVar={3}> Coordinate space </MD_Title>
 | 
			
		||||
Or as a reusable component:
 | 
			
		||||
```qml
 | 
			
		||||
@@QtQuick.Item {
 | 
			
		||||
  // A bidirectional binding to manager.margin,
 | 
			
		||||
  // where the default value is set.
 | 
			
		||||
  property alias margin: manager.margin
 | 
			
		||||
  
 | 
			
		||||
You should always position or size components relative to the closest possible
 | 
			
		||||
parent. Often this is just the @@QtQuick.Item.parent property.
 | 
			
		||||
  // MarginWrapperManager tries to automatically detect
 | 
			
		||||
  // the primary child of the container, but exposing the
 | 
			
		||||
  // child property allows us to both access the child
 | 
			
		||||
  // externally and override it if automatic detection fails.
 | 
			
		||||
  property alias child: manager.margin
 | 
			
		||||
 | 
			
		||||
Refrain from using things like the size of your screen to size a component,
 | 
			
		||||
as this will break as soon as anything up the component hierarchy changes, such
 | 
			
		||||
as adding padding to a bar.
 | 
			
		||||
  // MarginWrapperManager automatically manages the implicit size
 | 
			
		||||
  // of the container and actual size of the child.
 | 
			
		||||
  @@Quickshell.Widgets.MarginWrapperManager {
 | 
			
		||||
    id: manager
 | 
			
		||||
    resizeChild: true
 | 
			
		||||
    margin: 5 // the default value of margin
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Quickshell bundles three of the most commonly used wrappers, which are implemented similarly
 | 
			
		||||
to the example above:
 | 
			
		||||
- @@Quickshell.Widgets.WrapperItem
 | 
			
		||||
- @@Quickshell.Widgets.WrapperRectangle
 | 
			
		||||
- @@Quickshell.Widgets.WrapperMouseArea
 | 
			
		||||
 | 
			
		||||
## Layouts
 | 
			
		||||
 | 
			
		||||
QtQuick comes with a set of layout types in the
 | 
			
		||||
[QtQuick.Layouts](https://doc.qt.io/qt-6/qtquicklayouts-overview.html) module.
 | 
			
		||||
 | 
			
		||||
Layouts, such as the Row, Column and Grid layout, are extremely useful for positioning
 | 
			
		||||
items adjacent to eachother. See the linked qt documentation for more details.
 | 
			
		||||
 | 
			
		||||
> [!NOTE]
 | 
			
		||||
> Layouts have a default spacing of 5 pixels between items, not zero.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue