123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- // Copyright 2010 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 (
- "sort"
- )
- import (
- "github.com/lxn/win"
- )
- type Orientation byte
- const (
- Horizontal Orientation = iota
- Vertical
- )
- type BoxLayout struct {
- container Container
- margins Margins
- spacing int
- orientation Orientation
- hwnd2StretchFactor map[win.HWND]int
- resetNeeded bool
- }
- func newBoxLayout(orientation Orientation) *BoxLayout {
- return &BoxLayout{
- orientation: orientation,
- hwnd2StretchFactor: make(map[win.HWND]int),
- }
- }
- func NewHBoxLayout() *BoxLayout {
- return newBoxLayout(Horizontal)
- }
- func NewVBoxLayout() *BoxLayout {
- return newBoxLayout(Vertical)
- }
- func (l *BoxLayout) Container() Container {
- return l.container
- }
- func (l *BoxLayout) SetContainer(value Container) {
- if value != l.container {
- if l.container != nil {
- l.container.SetLayout(nil)
- }
- l.container = value
- if value != nil && value.Layout() != Layout(l) {
- value.SetLayout(l)
- l.Update(true)
- }
- }
- }
- func (l *BoxLayout) Margins() Margins {
- return l.margins
- }
- func (l *BoxLayout) SetMargins(value Margins) error {
- if value.HNear < 0 || value.VNear < 0 || value.HFar < 0 || value.VFar < 0 {
- return newError("margins must be positive")
- }
- l.margins = value
- l.Update(false)
- return nil
- }
- func (l *BoxLayout) Orientation() Orientation {
- return l.orientation
- }
- func (l *BoxLayout) SetOrientation(value Orientation) error {
- if value != l.orientation {
- switch value {
- case Horizontal, Vertical:
- default:
- return newError("invalid Orientation value")
- }
- l.orientation = value
- l.Update(false)
- }
- return nil
- }
- func (l *BoxLayout) Spacing() int {
- return l.spacing
- }
- func (l *BoxLayout) SetSpacing(value int) error {
- if value != l.spacing {
- if value < 0 {
- return newError("spacing cannot be negative")
- }
- l.spacing = value
- l.Update(false)
- }
- return nil
- }
- func (l *BoxLayout) StretchFactor(widget Widget) int {
- if factor, ok := l.hwnd2StretchFactor[widget.Handle()]; ok {
- return factor
- }
- return 1
- }
- func (l *BoxLayout) 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.Update(false)
- }
- return nil
- }
- func (l *BoxLayout) cleanupStretchFactors() {
- widgets := l.container.Children()
- for handle, _ := range l.hwnd2StretchFactor {
- if !widgets.containsHandle(handle) {
- delete(l.hwnd2StretchFactor, handle)
- }
- }
- }
- type widgetInfo struct {
- index int
- minSize int
- maxSize int
- stretch int
- greedy bool
- widget Widget
- }
- type widgetInfoList []widgetInfo
- func (l widgetInfoList) Len() int {
- return len(l)
- }
- func (l widgetInfoList) Less(i, j int) bool {
- _, iIsSpacer := l[i].widget.(*Spacer)
- _, jIsSpacer := l[j].widget.(*Spacer)
- if l[i].greedy == l[j].greedy {
- if iIsSpacer == jIsSpacer {
- minDiff := l[i].minSize - l[j].minSize
- if minDiff == 0 {
- return l[i].maxSize/l[i].stretch < l[j].maxSize/l[j].stretch
- }
- return minDiff > 0
- }
- return jIsSpacer
- }
- return l[i].greedy
- }
- func (l widgetInfoList) Swap(i, j int) {
- l[i], l[j] = l[j], l[i]
- }
- func (l *BoxLayout) widgets() []Widget {
- children := l.container.Children()
- widgets := make([]Widget, 0, children.Len())
- for i := 0; i < cap(widgets); i++ {
- widget := children.At(i)
- if !shouldLayoutWidget(widget) {
- continue
- }
- ps := widget.SizeHint()
- if ps.Width == 0 && ps.Height == 0 && widget.LayoutFlags() == 0 {
- continue
- }
- widgets = append(widgets, widget)
- }
- return widgets
- }
- func (l *BoxLayout) LayoutFlags() LayoutFlags {
- if l.container == nil {
- return 0
- }
- var flags LayoutFlags
- var hasNonShrinkableHorz bool
- var hasNonShrinkableVert bool
- children := l.container.Children()
- count := children.Len()
- if count == 0 {
- return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert
- } else {
- for i := 0; i < count; i++ {
- widget := children.At(i)
- if !shouldLayoutWidget(widget) {
- continue
- }
- f := widget.LayoutFlags()
- flags |= f
- if f&ShrinkableHorz == 0 {
- hasNonShrinkableHorz = true
- }
- if f&ShrinkableVert == 0 {
- hasNonShrinkableVert = true
- }
- }
- }
- if l.orientation == Horizontal {
- flags |= GrowableHorz
- if hasNonShrinkableVert {
- flags &^= ShrinkableVert
- }
- } else {
- flags |= GrowableVert
- if hasNonShrinkableHorz {
- flags &^= ShrinkableHorz
- }
- }
- return flags
- }
- func (l *BoxLayout) MinSize() Size {
- if l.container == nil {
- return Size{}
- }
- widgets := l.widgets()
- var s Size
- for _, widget := range widgets {
- min := minSizeEffective(widget)
- if l.orientation == Horizontal {
- s.Width += min.Width
- s.Height = maxi(s.Height, min.Height)
- } else {
- s.Height += min.Height
- s.Width = maxi(s.Width, min.Width)
- }
- }
- if l.orientation == Horizontal {
- s.Width += l.spacing * (len(widgets) - 1)
- s.Width += l.margins.HNear + l.margins.HFar
- s.Height += l.margins.VNear + l.margins.VFar
- } else {
- s.Height += l.spacing * (len(widgets) - 1)
- s.Height += l.margins.VNear + l.margins.VFar
- s.Width += l.margins.HNear + l.margins.HFar
- }
- return s
- }
- func (l *BoxLayout) Update(reset bool) error {
- if l.container == nil {
- return nil
- }
- if reset {
- l.resetNeeded = true
- }
- if l.container.Suspended() {
- return nil
- }
- if l.resetNeeded {
- l.resetNeeded = false
- // Make GC happy.
- l.cleanupStretchFactors()
- }
- // Begin by finding out which widgets we care about.
- widgets := l.widgets()
- // Prepare some useful data.
- var greedyNonSpacerCount int
- var greedySpacerCount int
- var stretchFactorsTotal [3]int
- stretchFactors := make([]int, len(widgets))
- var minSizesRemaining int
- minSizes := make([]int, len(widgets))
- maxSizes := make([]int, len(widgets))
- sizes := make([]int, len(widgets))
- prefSizes2 := make([]int, len(widgets))
- growable2 := make([]bool, len(widgets))
- sortedWidgetInfo := widgetInfoList(make([]widgetInfo, len(widgets)))
- for i, widget := range widgets {
- sf := l.hwnd2StretchFactor[widget.Handle()]
- if sf == 0 {
- sf = 1
- }
- stretchFactors[i] = sf
- flags := widget.LayoutFlags()
- min := widget.MinSize()
- max := widget.MaxSize()
- minHint := widget.MinSizeHint()
- pref := widget.SizeHint()
- if l.orientation == Horizontal {
- growable2[i] = flags&GrowableVert > 0
- minSizes[i] = maxi(min.Width, minHint.Width)
- if max.Width > 0 {
- maxSizes[i] = max.Width
- } else if pref.Width > 0 && flags&GrowableHorz == 0 {
- maxSizes[i] = pref.Width
- } else {
- maxSizes[i] = 32768
- }
- prefSizes2[i] = pref.Height
- sortedWidgetInfo[i].greedy = flags&GreedyHorz > 0
- } else {
- growable2[i] = flags&GrowableHorz > 0
- minSizes[i] = maxi(min.Height, minHint.Height)
- if max.Height > 0 {
- maxSizes[i] = max.Height
- } else if pref.Height > 0 && flags&GrowableVert == 0 {
- maxSizes[i] = pref.Height
- } else {
- maxSizes[i] = 32768
- }
- prefSizes2[i] = pref.Width
- sortedWidgetInfo[i].greedy = flags&GreedyVert > 0
- }
- sortedWidgetInfo[i].index = i
- sortedWidgetInfo[i].minSize = minSizes[i]
- sortedWidgetInfo[i].maxSize = maxSizes[i]
- sortedWidgetInfo[i].stretch = sf
- sortedWidgetInfo[i].widget = widget
- minSizesRemaining += minSizes[i]
- if sortedWidgetInfo[i].greedy {
- if _, isSpacer := widget.(*Spacer); !isSpacer {
- greedyNonSpacerCount++
- stretchFactorsTotal[0] += sf
- } else {
- greedySpacerCount++
- stretchFactorsTotal[1] += sf
- }
- } else {
- stretchFactorsTotal[2] += sf
- }
- }
- sort.Stable(sortedWidgetInfo)
- cb := l.container.ClientBounds()
- var start1, start2, space1, space2 int
- if l.orientation == Horizontal {
- start1 = cb.X + l.margins.HNear
- start2 = cb.Y + l.margins.VNear
- space1 = cb.Width - l.margins.HNear - l.margins.HFar
- space2 = cb.Height - l.margins.VNear - l.margins.VFar
- } else {
- start1 = cb.Y + l.margins.VNear
- start2 = cb.X + l.margins.HNear
- space1 = cb.Height - l.margins.VNear - l.margins.VFar
- space2 = cb.Width - l.margins.HNear - l.margins.HFar
- }
- // Now calculate widget primary axis sizes.
- spacingRemaining := l.spacing * (len(widgets) - 1)
- offsets := [3]int{0, greedyNonSpacerCount, greedyNonSpacerCount + greedySpacerCount}
- counts := [3]int{greedyNonSpacerCount, greedySpacerCount, len(widgets) - greedyNonSpacerCount - greedySpacerCount}
- for i := 0; i < 3; i++ {
- stretchFactorsRemaining := stretchFactorsTotal[i]
- for j := 0; j < counts[i]; j++ {
- info := sortedWidgetInfo[offsets[i]+j]
- k := info.index
- stretch := stretchFactors[k]
- min := info.minSize
- max := info.maxSize
- size := min
- if min < max {
- excessSpace := float64(space1 - minSizesRemaining - spacingRemaining)
- size += int(excessSpace * float64(stretch) / float64(stretchFactorsRemaining))
- if size < min {
- size = min
- } else if size > max {
- size = max
- }
- }
- sizes[k] = size
- minSizesRemaining -= min
- stretchFactorsRemaining -= stretch
- space1 -= (size + l.spacing)
- spacingRemaining -= l.spacing
- }
- }
- // Finally position widgets.
- hdwp := win.BeginDeferWindowPos(int32(len(widgets)))
- if hdwp == 0 {
- return lastError("BeginDeferWindowPos")
- }
- excessTotal := space1 - minSizesRemaining - spacingRemaining
- excessShare := excessTotal / (len(widgets) + 1)
- p1 := start1
- for i, widget := range widgets {
- p1 += excessShare
- s1 := sizes[i]
- var s2 int
- if growable2[i] {
- s2 = space2
- } else {
- s2 = prefSizes2[i]
- }
- p2 := start2 + (space2-s2)/2
- var x, y, w, h int
- if l.orientation == Horizontal {
- x, y, w, h = p1, p2, s1, s2
- } else {
- x, y, w, h = p2, p1, s2, s1
- }
- if hdwp = win.DeferWindowPos(
- hdwp,
- widget.Handle(),
- 0,
- int32(x),
- int32(y),
- int32(w),
- int32(h),
- win.SWP_NOACTIVATE|win.SWP_NOOWNERZORDER|win.SWP_NOZORDER); hdwp == 0 {
- return lastError("DeferWindowPos")
- }
- p1 += s1 + l.spacing
- }
- if !win.EndDeferWindowPos(hdwp) {
- return lastError("EndDeferWindowPos")
- }
- return nil
- }
|