Documentation
¶
Overview ¶
Package layout divides a region among rectangular items.
It is geometry and nothing else: it allocates rectangles without knowing how callers will use them. That keeps the same rules useful in any coordinate model and makes sizing testable as arithmetic.
Measuring ¶
A slot whose size follows from its item says so with Measured and supplies a Measurer. The measurer is asked about the axis being divided, given how much room there is across the other one: an item asked for one dimension given the available other dimension. One question, either axis, which is why Measured means the same thing for Down and Across.
The other axis, and the room between ¶
Dividing an axis leaves two questions it cannot ask, and both were being answered by hand above this package before they were answered here. Flow is an axis with a gap between the things it divides. Slot.Cross says where an item sits when it takes less than the available cross-axis extent.
This is intentionally a small one-dimensional allocator, not the beginning of a flexbox implementation. It does not own a tree, wrapping or reflow: callers compose its rectangles when they need nesting, and callers needing a layout engine should use one above this package rather than making this allocator know their item lifecycle.
Each Slot carries one Sizing policy in Size. Fixed reserves an exact extent, Flex shares what remains, Part takes a fraction of the whole, and Measured asks Slot.Of. Down makes rows and Across makes columns; both use the same operation because only the divided axis changes:
slots := []layout.Slot{
{Size: layout.Fixed(1)},
{Size: layout.Flex(1)},
}
rows := (layout.Flow{Axis: layout.Down}).Rects(space, slots)
columns := (layout.Flow{Axis: layout.Across}).Rects(space, slots)
Index ¶
- func Relative(at, origin int) int
- func Remaining(total int, used ...int) int
- func Scale(total, part, whole int) int
- func Sum(extents ...int) int
- func Translate(at, delta int) int
- type Align
- type Anchor
- type Axis
- type Cross
- type Flow
- type Inset
- type MeasureFunc
- type Measurer
- type Placement
- type Sizing
- type Slot
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Relative ¶ added in v0.4.0
Relative projects an absolute coordinate into a space whose origin is origin, saturating when the mathematical difference is outside the integer range.
func Remaining ¶ added in v0.4.0
Remaining subtracts non-negative extents from total without underflow. Negative inputs describe no room or no use. It is the subtraction counterpart to Sum and the one rule for asking how much of a measured axis is left.
func Scale ¶ added in v0.3.0
Scale returns total*part/whole, capped to [0, total], without overflowing the intermediate product.
It is the one proportional-coordinate operation shared by layout allocation and controls that map a bounded value onto an extent. Keeping it here gives both the same endpoint, saturation, and architecture-width semantics instead of two subtly different overflow workarounds.
Types ¶
type Axis ¶ added in v0.0.2
type Axis uint8
Axis is which way a region is divided.
It exists so that something arranging its contents can be told which way round it goes instead of being written twice. Down and Across are its two values.
type Cross ¶ added in v0.0.2
type Cross struct {
// Size is how many units across the other axis the item takes. Zero, and
// anything larger than the region, is all of it.
Size int
// Align is where it sits when it is less than all of it.
Align Align
}
Cross is how much of the other axis a slot's item takes, and where in the slot it sits when that is less than all of it.
It is the answer to the one question dividing an axis cannot ask: how a smaller cross-axis extent is aligned within the rectangle allocated to its slot.
The size is a number rather than a Measurer on purpose. An item answers about one axis — Down asks how tall at a width, Across how wide at a height — and a slot that asked the other way round would be asking most values a question they cannot answer.
type Flow ¶ added in v0.0.2
type Flow struct {
Axis Axis
// Gap is how many units go between one slot and the next.
//
// It is reserved for every join, including the ones beside a slot that ended up
// with no room. A gap that appeared and disappeared with its neighbour's contents
// would move every following slot whenever a value happened to be empty.
Gap int
}
Flow is an axis with room between the things it divides.
The gap is here rather than in Slot because it is one answer for the whole division: a caller specifies the spacing once, instead of padding every slot but the last and getting the last one wrong. A zero gap is the ordinary contiguous arrangement; it uses this same value and the same operations.
Example ¶
package main
import (
"fmt"
"image"
"github.com/Tangerg/oolong/core/layout"
)
func main() {
// A gap belongs to the division rather than to every region but the last. Cross
// placement independently constrains and aligns a region on the other axis.
rows := (layout.Flow{Axis: layout.Down}).Rects(image.Pt(24, 4), []layout.Slot{
{Size: layout.Flex(1)},
{Size: layout.Fixed(1), Cross: layout.Cross{Size: 8, Align: layout.Center}},
})
regions := (layout.Flow{Axis: layout.Across, Gap: 2}).Rects(rows[0].Size(), []layout.Slot{
{Size: layout.Part(1, 2)},
{Size: layout.Flex(1)},
{Size: layout.Flex(1)},
})
for _, region := range regions {
fmt.Printf("region %dx%d\n", region.Dx(), region.Dy())
}
fmt.Printf("aligned %d wide\n", rows[1].Dx())
}
Output: region 10x3 region 5x3 region 5x3 aligned 8 wide
func (Flow) Divide ¶ added in v0.0.2
Divide splits total among the slots, holding back the gaps between them first.
func (Flow) Rects ¶ added in v0.0.2
Rects is where each slot goes when a space is divided, in the space's own coordinates.
Example ¶
package main
import (
"fmt"
"image"
"github.com/Tangerg/oolong/core/layout"
)
func main() {
// The order of business is measure, then arrange. The caller projects the
// rectangles into whatever coordinate model it uses.
rects := (layout.Flow{Axis: layout.Down}).Rects(image.Pt(20, 10), []layout.Slot{
{Size: layout.Fixed(1)},
{Size: layout.Flex(1)},
{Size: layout.Fixed(2)},
})
for i, r := range rects {
fmt.Printf("slot %d: %d units\n", i, r.Dy())
}
}
Output: slot 0: 1 units slot 1: 7 units slot 2: 2 units
type Inset ¶
type Inset struct{ Top, Right, Bottom, Left int }
Inset is space held clear on each side.
func Symmetric ¶
Symmetric is one inset above and below, and another to the left and right. The two axes are separate because equal coordinate extents need not occupy equal physical space; naming both pairs also avoids repeating four values at every call site.
func (Inset) Apply ¶
Apply is what is left of r after the inset is held clear, and nothing at all when the inset is larger than the region.
The rectangle is built by hand rather than with image.Rect, which puts a backwards rectangle the right way round: an inset that overran its region would come back as a real region somewhere else instead of as no region at all.
type MeasureFunc ¶
MeasureFunc adapts a function to Measurer.
type Measurer ¶
Measurer reports how much of one axis something wants, given how much room it has across the other.
Which axis is which is decided by whoever is asking: Down divides height and asks for a height at a width, Across divides width and asks for a width at a height. A type that can only answer for one axis is a type that belongs in only one of them, and saying so is the caller's business rather than this package's.
type Placement ¶
type Placement struct {
Anchor Anchor
// Width and Height are the rectangle's size. Zero means as large as the
// space allows, less Margin.
Width, Height int
// Margin is kept clear between the rectangle and the edges of the space, so one
// anchored to a corner remains separated from it.
Margin int
}
Placement is where a rectangle goes inside a larger space.
It is geometry and has no knowledge of what occupies the returned rectangle. The result is clamped to the space rather than allowed to hang off an edge.
type Sizing ¶
type Sizing struct {
// contains filtered or unexported fields
}
Sizing says how much of an axis a slot wants.
Its representation is private because fixed, fractional, flexible and measured are alternatives, not fields a caller should combine by priority. Construct one with Fixed, Part, Flex or Measured; use Sizing.AtLeast when a fractional, flexible or measured slot also has a floor. The zero value asks for no space.
func Flex ¶
Flex is a slot taking a share of what is left.
A weight is a ratio and nothing else: doubling every weight in a division changes no result. Each one is therefore capped at an equal part of the largest representable total, which is what lets the weights be added in ordinary arithmetic with no case for the sum running past it. Weights above the cap saturate to it and become indistinguishable — which costs a caller nothing that a smaller pair of weights could not have said.
func Measured ¶
Measured is a slot as big as its Measurer asks to be, within bounds. A zero maximum means no cap. Negative bounds are normalized to zero.
A non-zero maximum below the minimum is a programmer error and panics here. The two bounds are a contradiction the caller wrote, and clamping either of them would hand back a slot sized by neither, in a division whose other slots then absorb the difference somewhere the caller never looks.
Example ¶
package main
import (
"fmt"
"image"
"github.com/Tangerg/oolong/core/layout"
)
func main() {
// A measured slot is asked about the axis being divided, given the room across
// the other one — so Measured means the same thing in a row and in a column.
wide := layout.MeasureFunc(func(across int) int { return across / 4 })
rows := (layout.Flow{Axis: layout.Down}).Rects(image.Pt(20, 10), []layout.Slot{
{Size: layout.Measured(0, 0), Of: wide},
{Size: layout.Flex(1)},
})
cols := (layout.Flow{Axis: layout.Across}).Rects(image.Pt(20, 8), []layout.Slot{
{Size: layout.Measured(0, 0), Of: wide},
{Size: layout.Flex(1)},
})
fmt.Printf("measured against a width of 20: %d rows\n", rows[0].Dy())
fmt.Printf("measured against a height of 8: %d columns\n", cols[0].Dx())
}
Output: measured against a width of 20: 5 rows measured against a height of 8: 2 columns
func Part ¶ added in v0.0.2
Part is a slot taking a fraction of the whole division: Part(1, 2) is half of it, whatever else is there. A whole of zero asks for nothing, which is what makes the zero Sizing mean what it always did.
func (Sizing) AtLeast ¶ added in v0.4.0
AtLeast returns s with a non-negative floor. Floors compose with fractional, flexible and measured sizing. Applying one to a fixed or zero sizing is a programmer error: the former is already exact, and the latter names no sizing policy to constrain.
That error panics, as does a floor above a measured slot's non-zero maximum. Both are contradictions in one slot's own description, and a Sizing is passed to Flow.Rects far from where it was built — a silently corrected one would surface as a neighbouring slot being the wrong size, which is the wrong place to look.
type Slot ¶
type Slot struct {
Size Sizing
// Of is asked how much of the divided axis this slot wants, and is only
// consulted when Size says the slot is measured. A measured slot with nothing to
// ask gets its floor, which is zero unless one was set.
Of Measurer
// Cross is where the slot's item sits across the other axis. The zero value
// fills it.
Cross Cross
}
Slot is one division of a region: how much room it gets, and what to ask when that follows from its item.