283 lines
6.8 KiB
Go
283 lines
6.8 KiB
Go
|
// Copyright 2018 The Walk Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
// +build windows
|
||
|
|
||
|
package walk
|
||
|
|
||
|
import (
|
||
|
"github.com/lxn/win"
|
||
|
)
|
||
|
|
||
|
type FlowLayout struct {
|
||
|
LayoutBase
|
||
|
hwnd2StretchFactor map[win.HWND]int
|
||
|
}
|
||
|
|
||
|
func NewFlowLayout() *FlowLayout {
|
||
|
l := &FlowLayout{
|
||
|
LayoutBase: LayoutBase{
|
||
|
margins96dpi: Margins{9, 9, 9, 9},
|
||
|
spacing96dpi: 6,
|
||
|
},
|
||
|
hwnd2StretchFactor: make(map[win.HWND]int),
|
||
|
}
|
||
|
l.layout = l
|
||
|
|
||
|
return l
|
||
|
}
|
||
|
|
||
|
func (l *FlowLayout) StretchFactor(widget Widget) int {
|
||
|
if factor, ok := l.hwnd2StretchFactor[widget.Handle()]; ok {
|
||
|
return factor
|
||
|
}
|
||
|
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
func (l *FlowLayout) SetStretchFactor(widget Widget, factor int) error {
|
||
|
if factor != l.StretchFactor(widget) {
|
||
|
if l.container == nil {
|
||
|
return newError("container required")
|
||
|
}
|
||
|
|
||
|
handle := widget.Handle()
|
||
|
|
||
|
if !l.container.Children().containsHandle(handle) {
|
||
|
return newError("unknown widget")
|
||
|
}
|
||
|
if factor < 1 {
|
||
|
return newError("factor must be >= 1")
|
||
|
}
|
||
|
|
||
|
l.hwnd2StretchFactor[handle] = factor
|
||
|
|
||
|
l.container.RequestLayout()
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (l *FlowLayout) CreateLayoutItem(ctx *LayoutContext) ContainerLayoutItem {
|
||
|
li := &flowLayoutItem{
|
||
|
size2MinSize: make(map[Size]Size),
|
||
|
hwnd2StretchFactor: make(map[win.HWND]int),
|
||
|
}
|
||
|
|
||
|
for hwnd, sf := range l.hwnd2StretchFactor {
|
||
|
li.hwnd2StretchFactor[hwnd] = sf
|
||
|
}
|
||
|
|
||
|
return li
|
||
|
}
|
||
|
|
||
|
type flowLayoutItem struct {
|
||
|
ContainerLayoutItemBase
|
||
|
size2MinSize map[Size]Size // in native pixels
|
||
|
hwnd2StretchFactor map[win.HWND]int
|
||
|
}
|
||
|
|
||
|
type flowLayoutSection struct {
|
||
|
items []flowLayoutSectionItem
|
||
|
primarySpaceLeft int // in native pixels
|
||
|
secondaryMinSize int // in native pixels
|
||
|
}
|
||
|
|
||
|
type flowLayoutSectionItem struct {
|
||
|
item LayoutItem
|
||
|
minSize Size // in native pixels
|
||
|
}
|
||
|
|
||
|
func (*flowLayoutItem) LayoutFlags() LayoutFlags {
|
||
|
return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert | GreedyHorz | GreedyVert
|
||
|
}
|
||
|
|
||
|
func (li *flowLayoutItem) MinSize() Size {
|
||
|
return li.MinSizeForSize(li.geometry.ClientSize)
|
||
|
}
|
||
|
|
||
|
func (li *flowLayoutItem) HeightForWidth(width int) int {
|
||
|
return li.MinSizeForSize(Size{width, li.geometry.ClientSize.Height}).Height
|
||
|
}
|
||
|
|
||
|
func (li *flowLayoutItem) MinSizeForSize(size Size) Size {
|
||
|
if min, ok := li.size2MinSize[size]; ok {
|
||
|
return min
|
||
|
}
|
||
|
|
||
|
spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi)
|
||
|
margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi)
|
||
|
|
||
|
bounds := Rectangle{Width: size.Width}
|
||
|
|
||
|
sections := li.sectionsForPrimarySize(size.Width)
|
||
|
|
||
|
var s Size
|
||
|
var maxPrimary int
|
||
|
|
||
|
for i, section := range sections {
|
||
|
var items []LayoutItem
|
||
|
var sectionMinWidth int
|
||
|
for _, sectionItem := range section.items {
|
||
|
items = append(items, sectionItem.item)
|
||
|
|
||
|
sectionMinWidth += sectionItem.minSize.Width
|
||
|
}
|
||
|
sectionMinWidth += (len(section.items) - 1) * spacing
|
||
|
maxPrimary = maxi(maxPrimary, sectionMinWidth)
|
||
|
|
||
|
bounds.Height = section.secondaryMinSize
|
||
|
|
||
|
margins96dpi := li.margins96dpi
|
||
|
if i > 0 {
|
||
|
margins96dpi.VNear = 0
|
||
|
}
|
||
|
if i < len(sections)-1 {
|
||
|
margins96dpi.VFar = 0
|
||
|
}
|
||
|
|
||
|
layoutItems := boxLayoutItems(li, items, Horizontal, li.alignment, bounds, margins96dpi, li.spacing96dpi, li.hwnd2StretchFactor)
|
||
|
|
||
|
var maxSecondary int
|
||
|
|
||
|
for _, item := range layoutItems {
|
||
|
if hfw, ok := item.Item.(HeightForWidther); ok && hfw.HasHeightForWidth() {
|
||
|
item.Bounds.Height = hfw.HeightForWidth(item.Bounds.Width)
|
||
|
} else {
|
||
|
min := li.MinSizeEffectiveForChild(item.Item)
|
||
|
item.Bounds.Height = min.Height
|
||
|
}
|
||
|
|
||
|
maxSecondary = maxi(maxSecondary, item.Bounds.Height)
|
||
|
}
|
||
|
|
||
|
s.Height += maxSecondary
|
||
|
|
||
|
bounds.Y += maxSecondary + spacing
|
||
|
}
|
||
|
|
||
|
s.Width = maxPrimary
|
||
|
|
||
|
s.Width += margins.HNear + margins.HFar
|
||
|
s.Height += margins.VNear + margins.VFar + (len(sections)-1)*spacing
|
||
|
|
||
|
if s.Width > 0 && s.Height > 0 {
|
||
|
li.size2MinSize[size] = s
|
||
|
}
|
||
|
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func (li *flowLayoutItem) PerformLayout() []LayoutResultItem {
|
||
|
spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi)
|
||
|
bounds := Rectangle{Width: li.geometry.ClientSize.Width, Height: li.geometry.ClientSize.Height}
|
||
|
|
||
|
sections := li.sectionsForPrimarySize(bounds.Width)
|
||
|
|
||
|
var resultItems []LayoutResultItem
|
||
|
|
||
|
for i, section := range sections {
|
||
|
var items []LayoutItem
|
||
|
for _, sectionItem := range section.items {
|
||
|
items = append(items, sectionItem.item)
|
||
|
}
|
||
|
|
||
|
bounds.Height = section.secondaryMinSize
|
||
|
|
||
|
margins96dpi := li.margins96dpi
|
||
|
if i > 0 {
|
||
|
margins96dpi.VNear = 0
|
||
|
}
|
||
|
if i < len(sections)-1 {
|
||
|
margins96dpi.VFar = 0
|
||
|
}
|
||
|
|
||
|
layoutItems := boxLayoutItems(li, items, Horizontal, li.alignment, bounds, margins96dpi, li.spacing96dpi, li.hwnd2StretchFactor)
|
||
|
|
||
|
margins := MarginsFrom96DPI(margins96dpi, li.ctx.dpi)
|
||
|
|
||
|
var maxSecondary int
|
||
|
|
||
|
for _, item := range layoutItems {
|
||
|
if hfw, ok := item.Item.(HeightForWidther); ok && hfw.HasHeightForWidth() {
|
||
|
item.Bounds.Height = hfw.HeightForWidth(item.Bounds.Width)
|
||
|
} else {
|
||
|
item.Bounds.Height = li.MinSizeEffectiveForChild(item.Item).Height
|
||
|
}
|
||
|
|
||
|
maxSecondary = maxi(maxSecondary, item.Bounds.Height)
|
||
|
}
|
||
|
|
||
|
bounds.Height = maxSecondary + margins.VNear + margins.VFar
|
||
|
|
||
|
resultItems = append(resultItems, boxLayoutItems(li, items, Horizontal, li.alignment, bounds, margins96dpi, li.spacing96dpi, li.hwnd2StretchFactor)...)
|
||
|
|
||
|
bounds.Y += bounds.Height + spacing
|
||
|
}
|
||
|
|
||
|
return resultItems
|
||
|
}
|
||
|
|
||
|
// sectionsForPrimarySize calculates sections for primary width in native pixels.
|
||
|
func (li *flowLayoutItem) sectionsForPrimarySize(primarySize int) []flowLayoutSection {
|
||
|
margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi)
|
||
|
spacing := IntFrom96DPI(li.spacing96dpi, li.ctx.dpi)
|
||
|
|
||
|
var sections []flowLayoutSection
|
||
|
|
||
|
section := flowLayoutSection{
|
||
|
primarySpaceLeft: primarySize - margins.HNear - margins.HFar,
|
||
|
}
|
||
|
|
||
|
addSection := func() {
|
||
|
sections = append(sections, section)
|
||
|
section.items = nil
|
||
|
section.primarySpaceLeft = primarySize - margins.HNear - margins.HFar
|
||
|
section.secondaryMinSize = 0
|
||
|
}
|
||
|
|
||
|
for _, item := range li.children {
|
||
|
var sectionItem flowLayoutSectionItem
|
||
|
|
||
|
sectionItem.item = item
|
||
|
|
||
|
if !shouldLayoutItem(item) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
sectionItem.minSize = li.MinSizeEffectiveForChild(item)
|
||
|
|
||
|
addItem := func() {
|
||
|
section.items = append(section.items, sectionItem)
|
||
|
if len(section.items) > 1 {
|
||
|
section.primarySpaceLeft -= spacing
|
||
|
}
|
||
|
section.primarySpaceLeft -= sectionItem.minSize.Width
|
||
|
|
||
|
section.secondaryMinSize = maxi(section.secondaryMinSize, sectionItem.minSize.Height)
|
||
|
}
|
||
|
|
||
|
if section.primarySpaceLeft < sectionItem.minSize.Width && len(section.items) == 0 {
|
||
|
addItem()
|
||
|
addSection()
|
||
|
} else if section.primarySpaceLeft < spacing+sectionItem.minSize.Width && len(section.items) > 0 {
|
||
|
addSection()
|
||
|
addItem()
|
||
|
} else {
|
||
|
addItem()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(section.items) > 0 {
|
||
|
addSection()
|
||
|
}
|
||
|
|
||
|
if len(sections) > 0 {
|
||
|
sections[0].secondaryMinSize += margins.VNear
|
||
|
sections[len(sections)-1].secondaryMinSize += margins.VFar
|
||
|
}
|
||
|
|
||
|
return sections
|
||
|
}
|