diff --git a/button.go b/button.go index 56d1c0c..bfa615f 100644 --- a/button.go +++ b/button.go @@ -5,9 +5,9 @@ import "git.tebibyte.media/tomo/tomo/input" import "git.tebibyte.media/tomo/tomo/event" import "git.tebibyte.media/tomo/objects/layouts" -var buttonLayout = layouts.NewGrid([]bool { true }, []bool { true }) -var iconButtonLayout = layouts.NewGrid([]bool { true }, []bool { true }) -var bothButtonLayout = layouts.NewGrid([]bool { false, true }, []bool { true }) +var buttonLayout = layouts.Row { true } +var iconButtonLayout = layouts.Row { true } +var bothButtonLayout = layouts.Row { false, true } // Button is a clickable button. type Button struct { diff --git a/layouts/contract.go b/layouts/contract.go index 24d612e..2c147d6 100644 --- a/layouts/contract.go +++ b/layouts/contract.go @@ -16,93 +16,25 @@ const ContractVertical Contract = true const ContractHorizontal Contract = false func (contract Contract) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point { - if contract.v() { - dot := image.Point { } - for _, box := range boxes { - minimum := box.MinimumSize() - dot.Y += minimum.Y - if dot.X < minimum.X { - dot.X = minimum.X - } - } - dot.Y += hints.Gap.Y * (len(boxes) - 1) - return dot - } else { - dot := image.Point { } - for _, box := range boxes { - minimum := box.MinimumSize() - dot.X += minimum.X - if dot.Y < minimum.Y { - dot.Y = minimum.Y - } - } - dot.X += hints.Gap.X * (len(boxes) - 1) - return dot - } + return contract.fallback().MinimumSize(hints, boxes) } func (contract Contract) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) { - // TODO if we overflow in a direction, respect the reccomended size - if contract.v() { - dot := hints.Bounds.Min - for index, box := range boxes { - if index > 0 { dot.Y += hints.Gap.Y } - minimum := box.MinimumSize() - box.SetBounds(image.Rectangle { - Min: dot, - Max: dot.Add(image.Pt(hints.Bounds.Dx(), minimum.Y)), - }) - dot.Y += minimum.Y - } - - height := dot.Y - hints.Bounds.Min.Y - offset := 0 - - switch hints.AlignY { - case tomo.AlignMiddle: - offset = (hints.Bounds.Dy() - height) / 2 - case tomo.AlignEnd: - offset = hints.Bounds.Dy() - height - } - for _, box := range boxes { - box.SetBounds(box.Bounds().Add(image.Pt(0, offset))) - } - } else { - dot := hints.Bounds.Min - for index, box := range boxes { - if index > 0 { dot.X += hints.Gap.X } - minimum := box.MinimumSize() - box.SetBounds(image.Rectangle { - Min: dot, - Max: dot.Add(image.Pt(minimum.X, hints.Bounds.Dy())), - }) - dot.X += minimum.X - } - - width := dot.X - hints.Bounds.Min.X - offset := 0 - - switch hints.AlignX { - case tomo.AlignMiddle: - offset = (hints.Bounds.Dx() - width) / 2 - case tomo.AlignEnd: - offset = hints.Bounds.Dx() - width - } - for _, box := range boxes { - box.SetBounds(box.Bounds().Add(image.Pt(offset, 0))) - } - } + contract.fallback().Arrange(hints, boxes) } func (contract Contract) RecommendedHeight (hints tomo.LayoutHints, boxes []tomo.Box, width int) int { - // TODO - return 0 + return contract.fallback().RecommendedHeight(hints, boxes, width) } func (contract Contract) RecommendedWidth (hints tomo.LayoutHints, boxes []tomo.Box, height int) int { - // TODO - return 0 + return contract.fallback().RecommendedWidth(hints, boxes, height) } -func (contract Contract) v () bool { return contract == ContractVertical } -func (contract Contract) h () bool { return contract == ContractHorizontal } +func (contract Contract) fallback () tomo.Layout { + if contract == ContractVertical { + return Column { } + } else { + return Row { } + } +} diff --git a/layouts/rowcol.go b/layouts/rowcol.go new file mode 100644 index 0000000..b239a5b --- /dev/null +++ b/layouts/rowcol.go @@ -0,0 +1,200 @@ +package layouts + +import "image" +import "git.tebibyte.media/tomo/tomo" + +var _ tomo.Layout = ContractVertical + +// Row arranges boxes in a row. Boxes that share an index with a true value will +// expand, and others will contract. +type Row []bool + +// Column arranges boxes in a column. Boxes that share an index with a true +// value will expand, and others will contract. +type Column []bool + +func (column Column) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point { + dot := image.Point { } + for _, box := range boxes { + minimum := box.MinimumSize() + dot.Y += minimum.Y + if dot.X < minimum.X { + dot.X = minimum.X + } + } + dot.Y += hints.Gap.Y * (len(boxes) - 1) + return dot +} + +func (row Row) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point { + dot := image.Point { } + for _, box := range boxes { + minimum := box.MinimumSize() + dot.X += minimum.X + if dot.Y < minimum.Y { + dot.Y = minimum.Y + } + } + dot.X += hints.Gap.X * (len(boxes) - 1) + return dot +} + +func (column Column) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) { + expands := func (index int) bool { + if index < len(boxes) { return false } + return column[index] + } + + // determine expanding box size + expandingSize := 0 + if !hints.OverflowY { + gaps := len(boxes) - 1 + freeSpace := float64(hints.Bounds.Dy() - hints.Gap.Y * gaps) + nExpanding := 0; for index, box := range boxes { + if expands(index) { + nExpanding ++ + } else { + freeSpace -= float64(box.MinimumSize().X) + } + } + } + + // determine width + width := 0 + if hints.OverflowX { + for _, box := range boxes { + minimum := box.MinimumSize() + if width < minimum.X { width = minimum.X } + } + } else { + width = hints.Bounds.Dx() + } + + // arrange + dot := hints.Bounds.Min + for index, box := range boxes { + if index > 0 { dot.Y += hints.Gap.Y } + + // determine height + height := box.MinimumSize().Y + if hints.OverflowY { + if box, ok := box.(tomo.ContentBox); ok { + height = box.RecommendedHeight(width) + } + } else { + if expands(index) { + height = expandingSize + } + } + + // set bounds + box.SetBounds(image.Rectangle { + Min: dot, + Max: dot.Add(image.Pt(width, height)), + }) + dot.Y += height + } + + height := dot.Y - hints.Bounds.Min.Y + offset := 0 + + switch hints.AlignY { + case tomo.AlignMiddle: + offset = (hints.Bounds.Dy() - height) / 2 + case tomo.AlignEnd: + offset = hints.Bounds.Dy() - height + } + for _, box := range boxes { + box.SetBounds(box.Bounds().Add(image.Pt(0, offset))) + } +} + +func (row Row) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) { + expands := func (index int) bool { + if index < len(boxes) { return false } + return row[index] + } + + // determine expanding box size + expandingSize := 0 + if !hints.OverflowY { + gaps := len(boxes) - 1 + freeSpace := float64(hints.Bounds.Dx() - hints.Gap.X * gaps) + nExpanding := 0; for index, box := range boxes { + if expands(index) { + nExpanding ++ + } else { + freeSpace -= float64(box.MinimumSize().Y) + } + } + } + + // determine height + height := 0 + if hints.OverflowY { + for _, box := range boxes { + minimum := box.MinimumSize() + if height < minimum.Y { height = minimum.Y } + } + } else { + height = hints.Bounds.Dy() + } + + // arrange + dot := hints.Bounds.Min + for index, box := range boxes { + if index > 0 { dot.X += hints.Gap.X } + + // determine width + width := box.MinimumSize().X + if hints.OverflowY { + if box, ok := box.(tomo.ContentBox); ok { + width = box.RecommendedHeight(height) + } + } else { + if expands(index) { + width = expandingSize + } + } + + // set bounds + box.SetBounds(image.Rectangle { + Min: dot, + Max: dot.Add(image.Pt(width, height)), + }) + dot.X += width + } + + width := dot.X - hints.Bounds.Min.X + offset := 0 + + switch hints.AlignX { + case tomo.AlignMiddle: + offset = (hints.Bounds.Dx() - width) / 2 + case tomo.AlignEnd: + offset = hints.Bounds.Dx() - width + } + for _, box := range boxes { + box.SetBounds(box.Bounds().Add(image.Pt(offset, 0))) + } +} + +func (column Column) RecommendedHeight (hints tomo.LayoutHints, boxes []tomo.Box, width int) int { + // TODO + return 0 +} + +func (row Row) RecommendedHeight (hints tomo.LayoutHints, boxes []tomo.Box, width int) int { + // TODO + return 0 +} + +func (column Column) RecommendedWidth (hints tomo.LayoutHints, boxes []tomo.Box, height int) int { + // TODO + return 0 +} + +func (row Row) RecommendedWidth (hints tomo.LayoutHints, boxes []tomo.Box, height int) int { + // TODO + return 0 +}