erm/vendor/github.com/lxn/walk/splitterlayout.go

562 lines
12 KiB
Go

// 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"
"github.com/lxn/win"
)
type splitterLayout struct {
container Container
orientation Orientation
margins96dpi Margins
hwnd2Item map[win.HWND]*splitterLayoutItem
resetNeeded bool
suspended bool
}
type splitterLayoutItem struct {
size int // in native pixels
oldExplicitSize int // in native pixels
stretchFactor int
growth int
visibleChangedHandle int
fixed bool
keepSize bool
wasVisible bool
}
func newSplitterLayout(orientation Orientation) *splitterLayout {
return &splitterLayout{
orientation: orientation,
hwnd2Item: make(map[win.HWND]*splitterLayoutItem),
}
}
func (l *splitterLayout) asLayoutBase() *LayoutBase {
return nil
}
func (l *splitterLayout) Container() Container {
return l.container
}
func (l *splitterLayout) 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.container.RequestLayout()
}
}
}
func (l *splitterLayout) Margins() Margins {
return l.margins96dpi
}
func (l *splitterLayout) SetMargins(value Margins) error {
l.margins96dpi = value
l.container.RequestLayout()
return nil
}
func (l *splitterLayout) Spacing() int {
return l.container.(*Splitter).handleWidth
}
func (l *splitterLayout) SetSpacing(value int) error {
return newError("not supported")
}
func (l *splitterLayout) Orientation() Orientation {
return l.orientation
}
func (l *splitterLayout) SetOrientation(value Orientation) error {
if value != l.orientation {
switch value {
case Horizontal, Vertical:
default:
return newError("invalid Orientation value")
}
l.orientation = value
l.container.RequestLayout()
}
return nil
}
func (l *splitterLayout) Fixed(widget Widget) bool {
item := l.hwnd2Item[widget.Handle()]
return item != nil && item.fixed
}
func (l *splitterLayout) StretchFactor(widget Widget) int {
item := l.hwnd2Item[widget.Handle()]
if item == nil || item.stretchFactor == 0 {
return 1
}
return item.stretchFactor
}
func (l *splitterLayout) SetStretchFactor(widget Widget, factor int) error {
if factor != l.StretchFactor(widget) {
if factor < 1 {
return newError("factor must be >= 1")
}
if l.container == nil {
return newError("container required")
}
item := l.hwnd2Item[widget.Handle()]
if item == nil {
item = new(splitterLayoutItem)
l.hwnd2Item[widget.Handle()] = item
}
item.stretchFactor = factor
l.container.RequestLayout()
}
return nil
}
func (l *splitterLayout) anyNonFixed() bool {
for i, widget := range l.container.Children().items {
if i%2 == 0 && widget.visible && !l.Fixed(widget.window.(Widget)) {
return true
}
}
return false
}
// spaceUnavailableToRegularWidgets returns amount of space unavailable to regular widgets in native pixels.
func (l *splitterLayout) spaceUnavailableToRegularWidgets() int {
splitter := l.container.(*Splitter)
var space int
for _, widget := range l.container.Children().items {
if _, isHandle := widget.window.(*splitterHandle); isHandle && widget.visible {
space += splitter.handleWidth
}
}
return IntFrom96DPI(space, splitter.DPI())
}
func (l *splitterLayout) CreateLayoutItem(ctx *LayoutContext) ContainerLayoutItem {
splitter := l.container.(*Splitter)
hwnd2Item := make(map[win.HWND]*splitterLayoutItem, len(l.hwnd2Item))
for hwnd, sli := range l.hwnd2Item {
hwnd2Item[hwnd] = sli
}
li := &splitterContainerLayoutItem{
orientation: l.orientation,
hwnd2Item: hwnd2Item,
spaceUnavailableToRegularItems: l.spaceUnavailableToRegularWidgets(),
handleWidth96dpi: splitter.HandleWidth(),
anyNonFixed: l.anyNonFixed(),
resetNeeded: l.resetNeeded,
}
li.margins96dpi = l.margins96dpi
return li
}
type splitterContainerLayoutItem struct {
ContainerLayoutItemBase
orientation Orientation
hwnd2Item map[win.HWND]*splitterLayoutItem
spaceUnavailableToRegularItems int // in native pixels
handleWidth96dpi int
anyNonFixed bool
resetNeeded bool
}
func (li *splitterContainerLayoutItem) StretchFactor(item LayoutItem) int {
sli := li.hwnd2Item[item.Handle()]
if sli == nil || sli.stretchFactor == 0 {
return 1
}
return sli.stretchFactor
}
func (li *splitterContainerLayoutItem) LayoutFlags() LayoutFlags {
return boxLayoutFlags(li.orientation, li.children)
}
func (li *splitterContainerLayoutItem) MinSize() Size {
return li.MinSizeForSize(li.geometry.ClientSize)
}
func (li *splitterContainerLayoutItem) HeightForWidth(width int) int {
return li.MinSizeForSize(Size{width, li.geometry.ClientSize.Height}).Height
}
func (li *splitterContainerLayoutItem) MinSizeForSize(size Size) Size {
marginsPixels := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi)
margins := Size{marginsPixels.HNear + marginsPixels.HFar, marginsPixels.VNear + marginsPixels.VFar}
s := margins
for _, item := range li.children {
if !anyVisibleItemInHierarchy(item) {
continue
}
var cur Size
if sli, ok := li.hwnd2Item[item.Handle()]; ok && li.anyNonFixed && sli.fixed {
cur = item.Geometry().Size
if li.orientation == Horizontal {
cur.Height = 0
} else {
cur.Width = 0
}
} else {
cur = li.MinSizeEffectiveForChild(item)
}
if li.orientation == Horizontal {
s.Width += cur.Width
s.Height = maxi(s.Height, margins.Height+cur.Height)
} else {
s.Height += cur.Height
s.Width = maxi(s.Width, margins.Width+cur.Width)
}
}
return s
}
func (li *splitterContainerLayoutItem) PerformLayout() []LayoutResultItem {
if li.resetNeeded {
li.reset()
}
margins := MarginsFrom96DPI(li.margins96dpi, li.ctx.dpi)
handleWidthPixels := IntFrom96DPI(li.handleWidth96dpi, li.ctx.dpi)
sizes := make([]int, len(li.children))
cb := Rectangle{Width: li.geometry.ClientSize.Width, Height: li.geometry.ClientSize.Height}
cb.X += margins.HNear
cb.Y += margins.HFar
cb.Width -= margins.HNear + margins.HFar
cb.Height -= margins.VNear + margins.VFar
var space1, space2 int
if li.orientation == Horizontal {
space1 = cb.Width - li.spaceUnavailableToRegularItems
space2 = cb.Height
} else {
space1 = cb.Height - li.spaceUnavailableToRegularItems
space2 = cb.Width
}
type WidgetItem struct {
item *splitterLayoutItem
index int
min int // in native pixels
max int // in native pixels
shrinkable bool
growable bool
}
var wis []WidgetItem
anyNonFixed := li.anyNonFixed
var totalRegularSize int
for i, item := range li.children {
if !anyVisibleItemInHierarchy(item) {
continue
}
if i%2 == 0 {
slItem := li.hwnd2Item[item.Handle()]
var wi *WidgetItem
if !anyNonFixed || !slItem.fixed {
var min, max int
minSize := li.MinSizeEffectiveForChild(item)
maxSize := item.Geometry().MaxSize
if li.orientation == Horizontal {
min = minSize.Width
max = maxSize.Width
} else {
min = minSize.Height
max = maxSize.Height
}
wis = append(wis, WidgetItem{item: slItem, index: i, min: min, max: max})
wi = &wis[len(wis)-1]
}
size := slItem.size
var idealSize Size
if hfw, ok := item.(HeightForWidther); ok && li.orientation == Vertical && hfw.HasHeightForWidth() {
idealSize.Height = hfw.HeightForWidth(space2)
} else {
switch sizer := item.(type) {
case IdealSizer:
idealSize = sizer.IdealSize()
case MinSizer:
idealSize = sizer.MinSize()
}
}
if flags := item.LayoutFlags(); li.orientation == Horizontal {
if flags&ShrinkableHorz == 0 {
size = maxi(size, idealSize.Width)
if wi != nil {
wi.min = maxi(wi.min, size)
}
} else if wi != nil {
wi.shrinkable = true
}
if flags&GrowableHorz == 0 {
size = mini(size, idealSize.Width)
if wi != nil {
wi.max = mini(wi.max, size)
}
} else if wi != nil {
wi.growable = true
}
} else {
if flags&ShrinkableVert == 0 {
size = maxi(size, idealSize.Height)
if wi != nil {
wi.min = maxi(wi.min, size)
}
} else if wi != nil {
wi.shrinkable = true
}
if flags&GrowableVert == 0 {
size = mini(size, idealSize.Height)
if wi != nil {
wi.max = mini(wi.max, size)
}
} else if wi != nil {
wi.growable = true
}
}
totalRegularSize += size
sizes[i] = size
} else {
sizes[i] = handleWidthPixels
}
}
var resultItems []LayoutResultItem
diff := space1 - totalRegularSize
if diff != 0 && len(sizes) > 1 {
for diff != 0 {
sort.SliceStable(wis, func(i, j int) bool {
a := wis[i]
b := wis[j]
x := float64(a.item.growth) / float64(a.item.stretchFactor)
y := float64(b.item.growth) / float64(b.item.stretchFactor)
if diff > 0 {
return x < y && (a.max == 0 || a.max > a.item.size)
} else {
return x > y && a.min < a.item.size
}
})
var wi *WidgetItem
for _, wItem := range wis {
if !wItem.item.keepSize && (diff < 0 && wItem.item.size > wItem.min || diff > 0 && (wItem.item.size < wItem.max || wItem.max == 0)) {
wi = &wItem
break
}
}
if wi == nil {
break
}
if diff > 0 {
sizes[wi.index]++
wi.item.size++
wi.item.growth++
diff--
} else {
sizes[wi.index]--
wi.item.size--
wi.item.growth--
diff++
}
}
}
var p1 int
if li.orientation == Horizontal {
p1 = margins.HNear
} else {
p1 = margins.VNear
}
for i, item := range li.children {
if !anyVisibleItemInHierarchy(item) {
continue
}
s1 := sizes[i]
var x, y, w, h int
if li.orientation == Horizontal {
x, y, w, h = p1, margins.VNear, s1, space2
} else {
x, y, w, h = margins.HNear, p1, space2, s1
}
resultItems = append(resultItems, LayoutResultItem{Item: item, Bounds: Rectangle{x, y, w, h}})
p1 += s1
}
return resultItems
}
func (li *splitterContainerLayoutItem) reset() {
var anyVisible bool
for i, item := range li.children {
sli := li.hwnd2Item[item.Handle()]
visible := anyVisibleItemInHierarchy(item)
if !anyVisible && visible {
anyVisible = true
}
if sli == nil || visible == sli.wasVisible {
continue
}
sli.wasVisible = visible
if _, isHandle := item.(*splitterHandleLayoutItem); !isHandle {
var handleIndex int
if i == 0 {
if len(li.children) > 1 {
handleIndex = 1
} else {
handleIndex = -1
}
} else {
handleIndex = i - 1
}
if handleIndex > -1 {
li.children[handleIndex].AsLayoutItemBase().visible = visible
}
}
}
if li.Visible() != anyVisible {
li.AsLayoutItemBase().visible = anyVisible
}
minSizes := make([]int, len(li.children))
var minSizesTotal int
for i, item := range li.children {
if i%2 == 1 || !anyVisibleItemInHierarchy(item) {
continue
}
min := li.MinSizeEffectiveForChild(item)
if li.orientation == Horizontal {
minSizes[i] = min.Width
minSizesTotal += min.Width
} else {
minSizes[i] = min.Height
minSizesTotal += min.Height
}
}
var regularSpace int
if li.orientation == Horizontal {
regularSpace = li.Geometry().ClientSize.Width - li.spaceUnavailableToRegularItems
} else {
regularSpace = li.Geometry().ClientSize.Height - li.spaceUnavailableToRegularItems
}
stretchTotal := 0
for i, item := range li.children {
if i%2 == 1 || !anyVisibleItemInHierarchy(item) {
continue
}
if sli := li.hwnd2Item[item.Handle()]; sli == nil {
li.hwnd2Item[item.Handle()] = &splitterLayoutItem{stretchFactor: 1}
}
stretchTotal += li.StretchFactor(item)
}
for i, item := range li.children {
if i%2 == 1 || !anyVisibleItemInHierarchy(item) {
continue
}
sli := li.hwnd2Item[item.Handle()]
sli.growth = 0
sli.keepSize = false
if sli.oldExplicitSize > 0 {
sli.size = sli.oldExplicitSize
} else {
sli.size = int(float64(li.StretchFactor(item)) / float64(stretchTotal) * float64(regularSpace))
}
min := minSizes[i]
if minSizesTotal <= regularSpace {
if sli.size < min {
sli.size = min
}
}
if sli.size >= min {
flags := item.LayoutFlags()
if li.orientation == Horizontal && flags&GrowableHorz == 0 || li.orientation == Vertical && flags&GrowableVert == 0 {
sli.size = min
sli.keepSize = true
}
}
}
}