Custom Layouts
Some osu!framework containers, such as FillFlowContainer, perform automatic layout of their children, overriding their position. This chapter describes how this works and how to do it yourself.
FillFlowContainer derives from FlowContainer. While the former provides a concrete container you can use, the latter is an abstract class that can be used to make your own custom layouts. However, this is not required.
FillFlowContainer
As an example, FillFlowContainer allows laying out drawables next to each other, similar to a FlowLayoutPanel in WPF or how inline-block and block work in CSS. The direction can be configured to be "full", which fills horizontally but wraps, or horizontal/vertical without wrapping. It also offers other features such as spacing between elements and those it inherits from FlowContainer, such as easing on position after new layouts are calculated.
FlowContainer
FlowContainer<T> is an abstract class that provides functionality to dynamically calculate the positions of your children.
To implement it, the following are required:
IEnumerable<Vector2> ComputeLayoutPositions(): This method should return a list of positions for allFlowingChildren, in order.
You can override the following:
IEnumerable<Drawable> FlowingChildren { get; }: Returns which children to draw, in what order. By default, it takes all live and present children, ordered by their layout position.void InvalidateLayout()
The order of your children can be changed with the void SetLayoutPosition(Drawable, float) method. This will in turn decide the order of the drawables from the default FlowingChildren implementation. To insert an element and immediately set its order, use the void Insert(int, T) method.
Creating your own custom layout class from scratch
Container
A Container provides several methods that may come in useful when overridden:
void UpdateAfterChildren(): This method fromCompositeDrawablegets called every frame after thevoid Drawable.Update()method is executed for allInternalChildren, and before the auto-size calculations are performed. This is a good point to calculate the new layout after an invalidation.AddInternal/RemoveInternal/ClearInternal: Allows for invalidating the layout when new children are added.bool UpdateChildrenLife(): Called to update the life status ofInternalChildrenbased onDrawable.ShouldBeAlive. Can be used to invalidate the layout if base call returnstrue.void AddLayout(LayoutMember): Registers aLayoutMember(or in practice,LayoutValue) that will be invalidated when the correctInvalidationis triggered.
LayoutValue / LayoutMember
LayoutMember is an abstract class that represents part of the layout of a Drawable. It is used to track the "invalidation" of certain properties to determine when parts of the layout must be recomputed. Its public properties are Invalidation, IsValid, Conditions and Source.
Invalidation contains the flags the LayoutMember listens for. For example, if a drawable's DrawSize is changed, it will invalidate LayoutMembers with Invalidation.DrawSize.
TODO: What is Source used for?
The LayoutValue class seems to be an implementation of LayoutMember. LayoutValue<T> can contain a value.
Layout Calculation
- Create 1 or more
LayoutValueobjects with the Invalidation values you want and add useAddLayoutin your constructor. - Invalidate the
LayoutValues when required, e.g. during Add or AddInternal, property changes, etc. - In
void UpdateAfterChildren(), check if your layout is valid and if not, compute your new layout and validate it.
Last verified against v2021.611.0.