summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorAlexander Neumann <alexander.neumann@picos-software.com>2018-12-05 16:46:35 +0100
committerAlexander Neumann <alexander.neumann@picos-software.com>2018-12-05 16:46:35 +0100
commitd855149da0e0c79a9bd2fe3add4ac406efbca952 (patch)
tree2fa42b16ab23380529003d7435ecfadb034164f7
parentWindowBase: Add persistence implementations that recursively call descendant's (diff)
downloadwireguard-windows-d855149da0e0c79a9bd2fe3add4ac406efbca952.tar.xz
wireguard-windows-d855149da0e0c79a9bd2fe3add4ac406efbca952.zip
Add support for widgets that trade height for width to layouts and do some refactoring
-rw-r--r--boxlayout.go205
-rw-r--r--container.go144
-rw-r--r--flowlayout.go117
-rw-r--r--form.go106
-rw-r--r--gridlayout.go178
-rw-r--r--groupbox.go17
-rw-r--r--lineedit.go4
-rw-r--r--scrollview.go2
-rw-r--r--splitter.go23
-rw-r--r--splitterlayout.go75
-rw-r--r--window.go84
11 files changed, 665 insertions, 290 deletions
diff --git a/boxlayout.go b/boxlayout.go
index e00f429c..cc5203ce 100644
--- a/boxlayout.go
+++ b/boxlayout.go
@@ -25,6 +25,7 @@ type BoxLayout struct {
spacing int
orientation Orientation
hwnd2StretchFactor map[win.HWND]int
+ size2MinSize map[Size]Size
resetNeeded bool
}
@@ -32,6 +33,7 @@ func newBoxLayout(orientation Orientation) *BoxLayout {
return &BoxLayout{
orientation: orientation,
hwnd2StretchFactor: make(map[win.HWND]int),
+ size2MinSize: make(map[Size]Size),
}
}
@@ -204,78 +206,68 @@ func (l *BoxLayout) LayoutFlags() LayoutFlags {
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
- }
+ return boxLayoutFlags(l.orientation, l.container.Children())
+}
- f := widget.LayoutFlags()
- flags |= f
- if f&ShrinkableHorz == 0 {
- hasNonShrinkableHorz = true
- }
- if f&ShrinkableVert == 0 {
- hasNonShrinkableVert = true
- }
- }
+func (l *BoxLayout) MinSize() Size {
+ if l.container == nil {
+ return Size{}
}
- if l.orientation == Horizontal {
- flags |= GrowableHorz
+ return l.MinSizeForSize(l.container.ClientBounds().Size())
+}
- if hasNonShrinkableVert {
- flags &^= ShrinkableVert
- }
- } else {
- flags |= GrowableVert
+func (l *BoxLayout) MinSizeForSize(size Size) Size {
+ if l.container == nil {
+ return Size{}
+ }
- if hasNonShrinkableHorz {
- flags &^= ShrinkableHorz
- }
+ if min, ok := l.size2MinSize[size]; ok {
+ return min
}
- return flags
-}
+ bounds := Rectangle{Width: size.Width, Height: size.Height}
-func (l *BoxLayout) MinSize() Size {
- if l.container == nil {
+ items, err := boxLayoutItems(widgetsToLayout(l.Container().Children()), l.orientation, bounds, l.margins, l.spacing, l.hwnd2StretchFactor)
+ if err != nil {
return Size{}
}
- widgets := widgetsToLayout(l.container.Children())
- var s Size
+ s := Size{l.margins.HNear + l.margins.HFar, l.margins.VNear + l.margins.VFar}
- for _, widget := range widgets {
- min := minSizeEffective(widget)
+ var maxSecondary int
+
+ for _, item := range items {
+ min := minSizeEffective(item.widget)
+
+ if hfw, ok := item.widget.(HeightForWidther); ok {
+ item.bounds.Height = hfw.HeightForWidth(item.bounds.Width)
+ } else {
+ item.bounds.Height = min.Height
+ }
+ item.bounds.Width = min.Width
if l.orientation == Horizontal {
- s.Width += min.Width
- s.Height = maxi(s.Height, min.Height)
+ maxSecondary = maxi(maxSecondary, item.bounds.Height)
+
+ s.Width += item.bounds.Width
} else {
- s.Height += min.Height
- s.Width = maxi(s.Width, min.Width)
+ maxSecondary = maxi(maxSecondary, item.bounds.Width)
+
+ s.Height += item.bounds.Height
}
}
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
+ s.Width += (len(items) - 1) * l.spacing
+ s.Height += maxSecondary
} else {
- s.Height += l.spacing * (len(widgets) - 1)
- s.Height += l.margins.VNear + l.margins.VFar
- s.Width += l.margins.HNear + l.margins.HFar
+ s.Height += (len(items) - 1) * l.spacing
+ s.Width += maxSecondary
+ }
+
+ if s.Width > 0 && s.Height > 0 {
+ l.size2MinSize[size] = s
}
return s
@@ -286,6 +278,8 @@ func (l *BoxLayout) Update(reset bool) error {
return nil
}
+ l.size2MinSize = make(map[Size]Size)
+
if reset {
l.resetNeeded = true
}
@@ -306,10 +300,59 @@ func (l *BoxLayout) Update(reset bool) error {
ifContainerIsScrollViewDoCoolSpecialLayoutStuff(l)
- return performBoxLayout(widgetsToLayout(l.Container().Children()), l.orientation, l.container.ClientBounds(), l.margins, l.spacing, l.hwnd2StretchFactor)
+ items, err := boxLayoutItems(widgetsToLayout(l.Container().Children()), l.orientation, l.container.ClientBounds(), l.margins, l.spacing, l.hwnd2StretchFactor)
+ if err != nil {
+ return err
+ }
+
+ return applyLayoutResults(l.container, items)
}
-func performBoxLayout(widgets []Widget, orientation Orientation, bounds Rectangle, margins Margins, spacing int, hwnd2StretchFactor map[win.HWND]int) error {
+func boxLayoutFlags(orientation Orientation, children *WidgetList) LayoutFlags {
+ var flags LayoutFlags
+ var hasNonShrinkableHorz bool
+ var hasNonShrinkableVert bool
+
+ count := children.Len()
+ if count == 0 {
+ return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert
+ } else {
+ for i := 0; i < count; i++ {
+ widget := children.At(i)
+
+ if _, ok := widget.(*splitterHandle); ok || !shouldLayoutWidget(widget) {
+ continue
+ }
+
+ f := widget.LayoutFlags()
+ flags |= f
+ if f&ShrinkableHorz == 0 {
+ hasNonShrinkableHorz = true
+ }
+ if f&ShrinkableVert == 0 {
+ hasNonShrinkableVert = true
+ }
+ }
+ }
+
+ if orientation == Horizontal {
+ flags |= GrowableHorz
+
+ if hasNonShrinkableVert {
+ flags &^= ShrinkableVert
+ }
+ } else {
+ flags |= GrowableVert
+
+ if hasNonShrinkableHorz {
+ flags &^= ShrinkableHorz
+ }
+ }
+
+ return flags
+}
+
+func boxLayoutItems(widgets []Widget, orientation Orientation, bounds Rectangle, margins Margins, spacing int, hwnd2StretchFactor map[win.HWND]int) ([]layoutResultItem, error) {
var greedyNonSpacerCount int
var greedySpacerCount int
var stretchFactorsTotal [3]int
@@ -353,7 +396,11 @@ func performBoxLayout(widgets []Widget, orientation Orientation, bounds Rectangl
} else {
growable2[i] = flags&GrowableHorz > 0
- minSizes[i] = minSizeEffective(widget).Height
+ if hfw, ok := widget.(HeightForWidther); ok {
+ minSizes[i] = hfw.HeightForWidth(bounds.Width - margins.HNear - margins.HFar)
+ } else {
+ minSizes[i] = minSizeEffective(widget).Height
+ }
if max.Height > 0 {
maxSizes[i] = max.Height
@@ -404,7 +451,6 @@ func performBoxLayout(widgets []Widget, orientation Orientation, bounds Rectangl
space2 = bounds.Width - margins.HNear - margins.HFar
}
- // Now calculate widget primary axis sizes.
spacingRemaining := spacing * (len(widgets) - 1)
offsets := [3]int{0, greedyNonSpacerCount, greedyNonSpacerCount + greedySpacerCount}
@@ -441,11 +487,7 @@ func performBoxLayout(widgets []Widget, orientation Orientation, bounds Rectangl
}
}
- // Finally position widgets.
- hdwp := win.BeginDeferWindowPos(int32(len(widgets)))
- if hdwp == 0 {
- return lastError("BeginDeferWindowPos")
- }
+ results := make([]layoutResultItem, 0, len(widgets))
excessTotal := space1 - minSizesRemaining - spacingRemaining
excessShare := excessTotal / (len(widgets) + 1)
@@ -472,45 +514,8 @@ func performBoxLayout(widgets []Widget, orientation Orientation, bounds Rectangl
p1 += s1 + spacing
- if b := widget.Bounds(); b.X == x && b.Y == y && b.Width == w {
- if _, ok := widget.(*ComboBox); ok {
- if b.Height+1 == h {
- continue
- }
- } else if b.Height == h {
- continue
- }
- }
-
- if widget.GraphicsEffects().Len() > 0 {
- widget.AsWidgetBase().invalidateBorderInParent()
- }
-
- 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")
- }
- }
-
- if !win.EndDeferWindowPos(hdwp) {
- return lastError("EndDeferWindowPos")
+ results = append(results, layoutResultItem{widget: widget, bounds: Rectangle{X: x, Y: y, Width: w, Height: h}})
}
- for _, widget := range widgets {
- if widget.GraphicsEffects().Len() == 0 {
- continue
- }
-
- widget.AsWidgetBase().invalidateBorderInParent()
- }
-
- return nil
+ return results, nil
}
diff --git a/container.go b/container.go
index a38aae90..ebf2062a 100644
--- a/container.go
+++ b/container.go
@@ -7,10 +7,9 @@
package walk
import (
+ "errors"
"unsafe"
-)
-import (
"github.com/lxn/win"
)
@@ -98,9 +97,143 @@ type Layout interface {
SetSpacing(value int) error
LayoutFlags() LayoutFlags
MinSize() Size
+ MinSizeForSize(size Size) Size
Update(reset bool) error
}
+type HeightForWidther interface {
+ HeightForWidth(width int) int
+}
+
+type minSizeForSize struct {
+ size Size
+ minSize Size
+}
+
+type layoutResultItem struct {
+ widget Widget
+ bounds Rectangle
+}
+
+func applyLayoutResults(container Container, items []layoutResultItem) error {
+ if applyLayoutResultsProc != 0 {
+ return applyLayoutResultsRun(container, items)
+ }
+
+ return applyLayoutResultsWalk(container, items)
+}
+
+func applyLayoutResultsWalk(container Container, items []layoutResultItem) error {
+ hdwp := win.BeginDeferWindowPos(int32(len(items)))
+ if hdwp == 0 {
+ return lastError("BeginDeferWindowPos")
+ }
+
+ maybeInvalidate := container.AsContainerBase().hasComplexBackground()
+
+ for _, item := range items {
+ widget := item.widget
+ x, y, w, h := item.bounds.X, item.bounds.Y, item.bounds.Width, item.bounds.Height
+
+ b := widget.Bounds()
+
+ if b.X == x && b.Y == y && b.Width == w {
+ if _, ok := widget.(*ComboBox); ok {
+ if b.Height+1 == h {
+ continue
+ }
+ } else if b.Height == h {
+ continue
+ }
+ }
+
+ if maybeInvalidate {
+ if w == b.Width && h == b.Height && (x != b.X || y != b.Y) {
+ widget.Invalidate()
+ }
+ }
+
+ 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")
+ }
+
+ // FIXME: Is this really necessary?
+ for _, item := range items {
+ if !shouldLayoutWidget(item.widget) || item.widget.GraphicsEffects().Len() == 0 {
+ continue
+ }
+
+ item.widget.AsWidgetBase().invalidateBorderInParent()
+ }
+ }
+
+ if !win.EndDeferWindowPos(hdwp) {
+ return lastError("EndDeferWindowPos")
+ }
+
+ return nil
+}
+
+func applyLayoutResultsRun(container Container, items []layoutResultItem) error {
+ resultItems := make([]applyLayoutResultsItem, 0, len(items))
+
+ for _, item := range items {
+ widget := item.widget
+ x, y, w, h := item.bounds.X, item.bounds.Y, item.bounds.Width, item.bounds.Height
+
+ b := widget.Bounds()
+
+ if b.X == x && b.Y == y && b.Width == w {
+ if _, ok := widget.(*ComboBox); ok {
+ if b.Height+1 == h {
+ continue
+ }
+ } else if b.Height == h {
+ continue
+ }
+ }
+
+ resultItems = append(resultItems, applyLayoutResultsItem{
+ hwnd: widget.Handle(),
+ x: int32(x),
+ y: int32(y),
+ w: int32(w),
+ h: int32(h),
+ oldBounds: b.toRECT(),
+ shouldInvalidateBorderInParent: win.BoolToBOOL(shouldLayoutWidget(item.widget) && item.widget.GraphicsEffects().Len() > 0),
+ })
+ }
+
+ if len(resultItems) == 0 {
+ return nil
+ }
+
+ if !applyLayoutResultsImpl(container.Handle(), win.BoolToBOOL(container.AsContainerBase().hasComplexBackground()), &resultItems[0], int32(len(resultItems))) {
+ return errors.New("ApplyLayoutResults failed")
+ }
+
+ return nil
+}
+
+type applyLayoutResultsItem struct {
+ hwnd win.HWND
+ x int32
+ y int32
+ w int32
+ h int32
+ oldBounds win.RECT
+ shouldInvalidateBorderInParent win.BOOL
+}
+
func widgetsToLayout(allWidgets *WidgetList) []Widget {
filteredWidgets := make([]Widget, 0, allWidgets.Len())
@@ -452,6 +585,13 @@ func (cb *ContainerBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintp
}
}
+ case win.WM_DRAWITEM:
+ dis := (*win.DRAWITEMSTRUCT)(unsafe.Pointer(lParam))
+ if window := windowFromHandle(dis.HwndItem); window != nil {
+ // The window that sent the notification shall handle it itself.
+ return window.WndProc(hwnd, msg, wParam, lParam)
+ }
+
case win.WM_NOTIFY:
nmh := (*win.NMHDR)(unsafe.Pointer(lParam))
if window := windowFromHandle(nmh.HwndFrom); window != nil {
diff --git a/flowlayout.go b/flowlayout.go
index 6488cbfa..e85ef820 100644
--- a/flowlayout.go
+++ b/flowlayout.go
@@ -14,6 +14,7 @@ type FlowLayout struct {
container Container
hwnd2StretchFactor map[win.HWND]int
margins Margins
+ size2MinSize map[Size]Size
spacing int
resetNeeded bool
}
@@ -30,7 +31,7 @@ type flowLayoutSectionItem struct {
}
func NewFlowLayout() *FlowLayout {
- return new(FlowLayout)
+ return &FlowLayout{size2MinSize: make(map[Size]Size)}
}
func (l *FlowLayout) Container() Container {
@@ -135,42 +136,80 @@ func (l *FlowLayout) MinSize() Size {
return Size{}
}
- children := l.container.Children()
-
- var width, height int
- for i := children.Len() - 1; i >= 0; i-- {
- widget := children.At(i)
- if !shouldLayoutWidget(widget) {
- continue
- }
-
- minSize := minSizeEffective(widget)
-
- width = maxi(minSize.Width, width)
- height = maxi(minSize.Height, height)
- }
-
- return Size{width + l.margins.HNear + l.margins.HFar, height}
+ return l.MinSizeForSize(l.container.ClientBounds().Size())
}
-func (l *FlowLayout) minSizeForWidth(w int) Size {
+func (l *FlowLayout) MinSizeForSize(size Size) Size {
if l.container == nil {
return Size{}
}
- sections := l.sectionsForPrimarySize(w)
+ if min, ok := l.size2MinSize[size]; ok {
+ return min
+ }
+
+ bounds := Rectangle{Width: size.Width}
+
+ sections := l.sectionsForPrimarySize(size.Width)
+
+ var s Size
+ var maxPrimary int
- var width, height int
for i, section := range sections {
- width = maxi(width, w-section.primarySpaceLeft)
+ var widgets []Widget
+ var sectionMinWidth int
+ for _, item := range section.items {
+ widgets = append(widgets, item.widget)
+
+ sectionMinWidth += item.minSize.Width
+ }
+ sectionMinWidth += (len(section.items) - 1) * l.spacing
+ maxPrimary = maxi(maxPrimary, sectionMinWidth)
+ bounds.Height = section.secondaryMinSize
+
+ margins := l.margins
if i > 0 {
- height += l.spacing
+ margins.VNear = 0
+ }
+ if i < len(sections)-1 {
+ margins.VFar = 0
+ }
+
+ layoutItems, err := boxLayoutItems(widgets, Horizontal, bounds, margins, l.spacing, l.hwnd2StretchFactor)
+ if err != nil {
+ return Size{}
}
- height += section.secondaryMinSize
+
+ var maxSecondary int
+
+ for _, item := range layoutItems {
+ min := minSizeEffective(item.widget)
+
+ if hfw, ok := item.widget.(HeightForWidther); ok {
+ item.bounds.Height = hfw.HeightForWidth(item.bounds.Width)
+ } else {
+ item.bounds.Height = min.Height
+ }
+
+ maxSecondary = maxi(maxSecondary, item.bounds.Height)
+ }
+
+ s.Height += maxSecondary
+
+ bounds.Y += maxSecondary + l.spacing
+ }
+
+ s.Width = maxPrimary
+
+ s.Width += l.margins.HNear + l.margins.HFar
+ s.Height += l.margins.VNear + l.margins.VFar + (len(sections)-1)*l.spacing
+
+ if s.Width > 0 && s.Height > 0 {
+ l.size2MinSize[size] = s
}
- return Size{width + l.margins.HNear + l.margins.HFar, height}
+ return s
}
func (l *FlowLayout) Update(reset bool) error {
@@ -178,6 +217,8 @@ func (l *FlowLayout) Update(reset bool) error {
return nil
}
+ l.size2MinSize = make(map[Size]Size)
+
if reset {
l.resetNeeded = true
}
@@ -217,7 +258,30 @@ func (l *FlowLayout) Update(reset bool) error {
margins.VFar = 0
}
- if err := performBoxLayout(widgets, Horizontal, bounds, margins, l.spacing, l.hwnd2StretchFactor); err != nil {
+ layoutItems, err := boxLayoutItems(widgets, Horizontal, bounds, margins, l.spacing, l.hwnd2StretchFactor)
+ if err != nil {
+ return err
+ }
+
+ var maxSecondary int
+
+ for _, li := range layoutItems {
+ if hfw, ok := li.widget.(HeightForWidther); ok {
+ li.bounds.Height = hfw.HeightForWidth(li.bounds.Width)
+ } else {
+ li.bounds.Height = minSizeEffective(li.widget).Height
+ }
+
+ maxSecondary = maxi(maxSecondary, li.bounds.Height)
+ }
+
+ bounds.Height = maxSecondary + margins.VNear + margins.VFar
+
+ if layoutItems, err = boxLayoutItems(widgets, Horizontal, bounds, margins, l.spacing, l.hwnd2StretchFactor); err != nil {
+ return err
+ }
+
+ if err := applyLayoutResults(l.container, layoutItems); err != nil {
return err
}
@@ -261,13 +325,14 @@ func (l *FlowLayout) sectionsForPrimarySize(primarySize int) []flowLayoutSection
section.primarySpaceLeft -= l.spacing
}
section.primarySpaceLeft -= item.minSize.Width
+
section.secondaryMinSize = maxi(section.secondaryMinSize, item.minSize.Height)
}
if section.primarySpaceLeft < item.minSize.Width && len(section.items) == 0 {
addItem()
addSection()
- } else if section.primarySpaceLeft < l.spacing+item.minSize.Width {
+ } else if section.primarySpaceLeft < l.spacing+item.minSize.Width && len(section.items) > 0 {
addSection()
addItem()
} else {
diff --git a/form.go b/form.go
index ac4f1f2c..54996bc4 100644
--- a/form.go
+++ b/form.go
@@ -8,13 +8,10 @@ package walk
import (
"fmt"
+ "strconv"
"sync"
"syscall"
"unsafe"
-)
-
-import (
- "strconv"
"github.com/lxn/win"
)
@@ -26,19 +23,39 @@ const (
CloseReasonUser
)
-var syncFuncs struct {
- m sync.Mutex
- funcs []func()
-}
+var (
+ processMessageProc uintptr
+ applyLayoutResultsProc uintptr
+
+ syncFuncs struct {
+ m sync.Mutex
+ funcs []func()
+ }
-var syncMsgId uint32
-var taskbarButtonCreatedMsgId uint32
+ syncMsgId uint32
+ taskbarButtonCreatedMsgId uint32
+)
func init() {
+ if dll, err := syscall.LoadLibrary("run.dll"); err == nil {
+ applyLayoutResultsProc, err = syscall.GetProcAddress(dll, "ApplyLayoutResults")
+ processMessageProc, err = syscall.GetProcAddress(dll, "ProcessMessage")
+ }
+
syncMsgId = win.RegisterWindowMessage(syscall.StringToUTF16Ptr("WalkSync"))
taskbarButtonCreatedMsgId = win.RegisterWindowMessage(syscall.StringToUTF16Ptr("TaskbarButtonCreated"))
}
+func applyLayoutResultsImpl(hwndParent win.HWND, maybeInvalidate win.BOOL, items *applyLayoutResultsItem, itemsLen int32) bool {
+ ret, _, _ := syscall.Syscall6(applyLayoutResultsProc, 4, uintptr(hwndParent), uintptr(maybeInvalidate), uintptr(unsafe.Pointer(items)), uintptr(itemsLen), 0, 0)
+ return ret != 0
+}
+
+func processMessage(hwnd win.HWND, msg *win.MSG) bool {
+ ret, _, _ := syscall.Syscall(processMessageProc, 2, uintptr(hwnd), uintptr(unsafe.Pointer(msg)), 0)
+ return ret != 0
+}
+
func synchronize(f func()) {
syncFuncs.m.Lock()
defer syncFuncs.m.Unlock()
@@ -100,6 +117,7 @@ type FormBase struct {
progressIndicator *ProgressIndicator
icon *Icon
prevFocusHWnd win.HWND
+ proposedSize Size
isInRestoreState bool
started bool
closeReason CloseReason
@@ -376,30 +394,48 @@ func (fb *FormBase) Run() int {
fb.started = true
fb.startingPublisher.Publish()
+ fb.SetBounds(fb.Bounds())
+
var msg win.MSG
- for fb.hWnd != 0 {
- switch win.GetMessage(&msg, 0, 0, 0) {
- case 0:
- return int(msg.WParam)
+ if processMessageProc != 0 {
+ for fb.hWnd != 0 {
+ if !processMessage(fb.hWnd, &msg) {
+ return int(msg.WParam)
+ }
+
+ if msg.Message == win.WM_KEYDOWN {
+ if fb.webViewTranslateAccelerator(&msg) {
+ // handled accelerator key of webview and its childen (ie IE)
+ }
+ }
- case -1:
- return -1
+ runSynchronized()
}
+ } else {
+ for fb.hWnd != 0 {
+ switch win.GetMessage(&msg, 0, 0, 0) {
+ case 0:
+ return int(msg.WParam)
+
+ case -1:
+ return -1
+ }
- switch msg.Message {
- case win.WM_KEYDOWN:
- if fb.webViewTranslateAccelerator(&msg) {
- // handled accelerator key of webview and its childen (ie IE)
+ switch msg.Message {
+ case win.WM_KEYDOWN:
+ if fb.webViewTranslateAccelerator(&msg) {
+ // handled accelerator key of webview and its childen (ie IE)
+ }
}
- }
- if !win.IsDialogMessage(fb.hWnd, &msg) {
- win.TranslateMessage(&msg)
- win.DispatchMessage(&msg)
- }
+ if !win.IsDialogMessage(fb.hWnd, &msg) {
+ win.TranslateMessage(&msg)
+ win.DispatchMessage(&msg)
+ }
- runSynchronized()
+ runSynchronized()
+ }
}
return 0
@@ -646,17 +682,16 @@ func (fb *FormBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) u
return fb.clientComposite.WndProc(hwnd, msg, wParam, lParam)
case win.WM_GETMINMAXINFO:
- if fb.Suspended() {
+ if fb.Suspended() || fb.proposedSize == (Size{}) {
break
}
mmi := (*win.MINMAXINFO)(unsafe.Pointer(lParam))
- layout := fb.clientComposite.Layout()
-
var min Size
- if layout != nil {
- min = fb.sizeFromClientSize(layout.MinSize())
+ if layout := fb.clientComposite.layout; layout != nil {
+ size := fb.clientSizeFromSize(fb.proposedSize)
+ min = fb.sizeFromClientSize(layout.MinSizeForSize(size))
}
mmi.PtMinTrackSize = win.POINT{
@@ -671,7 +706,14 @@ func (fb *FormBase) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) u
case win.WM_SETTEXT:
fb.titleChangedPublisher.Publish()
- case win.WM_SIZE, win.WM_SIZING:
+ case win.WM_SIZING:
+ rc := (*win.RECT)(unsafe.Pointer(lParam))
+
+ fb.proposedSize = rectangleFromRECT(*rc).Size()
+
+ fb.clientComposite.SetBounds(fb.window.ClientBounds())
+
+ case win.WM_SIZE:
fb.clientComposite.SetBounds(fb.window.ClientBounds())
case win.WM_SYSCOMMAND:
diff --git a/gridlayout.go b/gridlayout.go
index 4e985047..ce67eca7 100644
--- a/gridlayout.go
+++ b/gridlayout.go
@@ -10,10 +10,6 @@ import (
"sort"
)
-import (
- "github.com/lxn/win"
-)
-
type gridLayoutCell struct {
row int
column int
@@ -42,11 +38,13 @@ type GridLayout struct {
columnStretchFactors []int
widget2Info map[Widget]*gridLayoutWidgetInfo
cells [][]gridLayoutCell
+ size2MinSize map[Size]Size
}
func NewGridLayout() *GridLayout {
l := &GridLayout{
- widget2Info: make(map[Widget]*gridLayoutWidgetInfo),
+ widget2Info: make(map[Widget]*gridLayoutWidgetInfo),
+ size2MinSize: make(map[Size]Size),
}
return l
@@ -363,8 +361,19 @@ func (l *GridLayout) MinSize() Size {
return Size{}
}
- widths := make([]int, len(l.cells[0]))
- heights := make([]int, len(l.cells))
+ return l.MinSizeForSize(l.container.ClientBounds().Size())
+}
+
+func (l *GridLayout) MinSizeForSize(size Size) Size {
+ if l.container == nil || len(l.cells) == 0 {
+ return Size{}
+ }
+
+ if min, ok := l.size2MinSize[size]; ok {
+ return min
+ }
+
+ ws := make([]int, len(l.cells[0]))
widget2MinSize := make(map[Widget]Size)
@@ -376,8 +385,8 @@ func (l *GridLayout) MinSize() Size {
widget2MinSize[widget] = minSizeEffective(widget)
}
- for row := 0; row < len(heights); row++ {
- for col := 0; col < len(widths); col++ {
+ for row := 0; row < len(l.cells); row++ {
+ for col := 0; col < len(ws); col++ {
widget := l.cells[row][col].widget
if !shouldLayoutWidget(widget) {
@@ -388,18 +397,38 @@ func (l *GridLayout) MinSize() Size {
info := l.widget2Info[widget]
if info.spanHorz == 1 {
- widths[col] = maxi(widths[col], min.Width)
+ ws[col] = maxi(ws[col], min.Width)
}
- if info.spanVert == 1 {
- heights[row] = maxi(heights[row], min.Height)
+ }
+ }
+
+ widths := l.sectionSizesForSpace(Horizontal, size.Width, nil)
+ heights := l.sectionSizesForSpace(Vertical, size.Height, widths)
+
+ for row := range heights {
+ var maxHeight int
+ for col := range widths {
+ widget := l.cells[row][col].widget
+
+ if !shouldLayoutWidget(widget) {
+ continue
+ }
+
+ if info := l.widget2Info[widget]; info.spanVert == 1 {
+ if hfw, ok := widget.(HeightForWidther); ok {
+ maxHeight = maxi(maxHeight, hfw.HeightForWidth(l.spannedWidth(l.widget2Info[widget], widths)))
+ } else {
+ maxHeight = maxi(maxHeight, widget2MinSize[widget].Height)
+ }
}
}
+ heights[row] = maxHeight
}
width := l.margins.HNear + l.margins.HFar
height := l.margins.VNear + l.margins.VFar
- for i, w := range widths {
+ for i, w := range ws {
if w > 0 {
if i > 0 {
width += l.spacing
@@ -416,9 +445,43 @@ func (l *GridLayout) MinSize() Size {
}
}
+ if width > 0 && height > 0 {
+ l.size2MinSize[size] = Size{width, height}
+ }
+
return Size{width, height}
}
+func (l *GridLayout) spannedWidth(info *gridLayoutWidgetInfo, widths []int) int {
+ width := 0
+
+ for i := info.cell.column; i < info.cell.column+info.spanHorz; i++ {
+ if w := widths[i]; w > 0 {
+ width += w
+ if i > info.cell.column {
+ width += l.spacing
+ }
+ }
+ }
+
+ return width
+}
+
+func (l *GridLayout) spannedHeight(info *gridLayoutWidgetInfo, heights []int) int {
+ height := 0
+
+ for i := info.cell.row; i < info.cell.row+info.spanVert; i++ {
+ if h := heights[i]; h > 0 {
+ height += h
+ if i > info.cell.row {
+ height += l.spacing
+ }
+ }
+ }
+
+ return height
+}
+
type gridLayoutSectionInfo struct {
index int
minSize int
@@ -461,6 +524,8 @@ func (l *GridLayout) Update(reset bool) error {
return nil
}
+ l.size2MinSize = make(map[Size]Size)
+
if reset {
l.resetNeeded = true
}
@@ -481,13 +546,12 @@ func (l *GridLayout) Update(reset bool) error {
ifContainerIsScrollViewDoCoolSpecialLayoutStuff(l)
- widths := l.sectionSizes(Horizontal)
- heights := l.sectionSizes(Vertical)
+ cb := l.container.ClientBounds()
- hdwp := win.BeginDeferWindowPos(int32(l.container.Children().Len()))
- if hdwp == 0 {
- return lastError("BeginDeferWindowPos")
- }
+ widths := l.sectionSizesForSpace(Horizontal, cb.Width, nil)
+ heights := l.sectionSizesForSpace(Vertical, cb.Height, widths)
+
+ items := make([]layoutResultItem, 0, len(l.widget2Info))
for widget, info := range l.widget2Info {
if !shouldLayoutWidget(widget) {
@@ -508,25 +572,8 @@ func (l *GridLayout) Update(reset bool) error {
}
}
- w := 0
- for i := info.cell.column; i < info.cell.column+info.spanHorz; i++ {
- if width := widths[i]; width > 0 {
- w += width
- if i > info.cell.column {
- w += l.spacing
- }
- }
- }
-
- h := 0
- for i := info.cell.row; i < info.cell.row+info.spanVert; i++ {
- if height := heights[i]; height > 0 {
- h += height
- if i > info.cell.row {
- h += l.spacing
- }
- }
- }
+ w := l.spannedWidth(info, widths)
+ h := l.spannedHeight(info, heights)
if lf := widget.LayoutFlags(); lf&GrowableHorz == 0 || lf&GrowableVert == 0 {
s := widget.SizeHint()
@@ -546,50 +593,13 @@ func (l *GridLayout) Update(reset bool) error {
}
}
- if b := widget.Bounds(); b.X == x && b.Y == y && b.Width == w {
- if _, ok := widget.(*ComboBox); ok {
- if b.Height+1 == h {
- continue
- }
- } else if b.Height == h {
- continue
- }
- }
-
- if widget.GraphicsEffects().Len() > 0 {
- widget.AsWidgetBase().invalidateBorderInParent()
- }
-
- 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")
- }
-
- for widget := range l.widget2Info {
- if !shouldLayoutWidget(widget) || widget.GraphicsEffects().Len() == 0 {
- continue
- }
-
- widget.AsWidgetBase().invalidateBorderInParent()
- }
- }
-
- if !win.EndDeferWindowPos(hdwp) {
- return lastError("EndDeferWindowPos")
+ items = append(items, layoutResultItem{widget: widget, bounds: Rectangle{X: x, Y: y, Width: w, Height: h}})
}
- return nil
+ return applyLayoutResults(l.container, items)
}
-func (l *GridLayout) sectionSizes(orientation Orientation) []int {
+func (l *GridLayout) sectionSizesForSpace(orientation Orientation, space int, widths []int) []int {
var stretchFactors []int
if orientation == Horizontal {
stretchFactors = l.columnStretchFactors
@@ -654,7 +664,11 @@ func (l *GridLayout) sectionSizes(orientation Orientation) []int {
}
} else {
if info.spanVert == 1 {
- minSizes[i] = maxi(minSizes[i], minSizeEffective(widget).Height)
+ if hfw, ok := widget.(HeightForWidther); ok {
+ minSizes[i] = maxi(minSizes[i], hfw.HeightForWidth(l.spannedWidth(info, widths)))
+ } else {
+ minSizes[i] = maxi(minSizes[i], minSizeEffective(widget).Height)
+ }
}
if max.Height > 0 {
@@ -695,12 +709,10 @@ func (l *GridLayout) sectionSizes(orientation Orientation) []int {
sort.Stable(sortedSections)
- cb := l.container.ClientBounds()
- var space int
if orientation == Horizontal {
- space = cb.Width - l.margins.HNear - l.margins.HFar
+ space -= l.margins.HNear + l.margins.HFar
} else {
- space = cb.Height - l.margins.VNear - l.margins.VFar
+ space -= l.margins.VNear + l.margins.VFar
}
var spacingRemaining int
diff --git a/groupbox.go b/groupbox.go
index 600dd11a..4135e0fe 100644
--- a/groupbox.go
+++ b/groupbox.go
@@ -140,6 +140,23 @@ func (gb *GroupBox) SizeHint() Size {
return gb.MinSizeHint()
}
+func (gb *GroupBox) HeightForWidth(width int) int {
+ if gb.composite == nil || gb.composite.layout == nil {
+ return 100
+ }
+
+ cmsh := gb.composite.layout.MinSizeForSize(Size{Width: width})
+
+ if gb.Checkable() {
+ s := gb.checkBox.SizeHint()
+
+ cmsh.Width = maxi(cmsh.Width, s.Width)
+ cmsh.Height += s.Height
+ }
+
+ return cmsh.Height + 14
+}
+
func (gb *GroupBox) ClientBounds() Rectangle {
cb := windowClientBounds(gb.hWndGroupBox)
diff --git a/lineedit.go b/lineedit.go
index d32bd3d4..e8c9cd9e 100644
--- a/lineedit.go
+++ b/lineedit.go
@@ -157,6 +157,10 @@ func (le *LineEdit) Alignment() Alignment1D {
}
func (le *LineEdit) SetAlignment(alignment Alignment1D) error {
+ if alignment == AlignDefault {
+ alignment = AlignNear
+ }
+
var bit uint32
switch alignment {
diff --git a/scrollview.go b/scrollview.go
index 6f3fd56e..50312164 100644
--- a/scrollview.go
+++ b/scrollview.go
@@ -247,7 +247,7 @@ func (sv *ScrollView) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr)
case win.WM_SIZE, win.WM_SIZING:
var minSize Size
if fl, ok := sv.composite.layout.(*FlowLayout); ok {
- minSize = fl.minSizeForWidth(sv.ClientBounds().Width)
+ minSize = fl.MinSizeForSize(sv.ClientBounds().Size())
} else {
minSize = sv.composite.layout.MinSize()
}
diff --git a/splitter.go b/splitter.go
index c32bae80..153b7969 100644
--- a/splitter.go
+++ b/splitter.go
@@ -85,7 +85,7 @@ func NewVSplitter(parent Container) (*Splitter, error) {
}
func (s *Splitter) LayoutFlags() LayoutFlags {
- return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert | GreedyHorz | GreedyVert
+ return s.layout.LayoutFlags()
}
func (s *Splitter) SizeHint() Size {
@@ -202,7 +202,12 @@ func (s *Splitter) SaveState() error {
buf.WriteString(" ")
}
- buf.WriteString(strconv.FormatInt(int64(layout.hwnd2Item[s.children.At(i).Handle()].size), 10))
+ item := layout.hwnd2Item[s.children.At(i).Handle()]
+ size := item.oldExplicitSize
+ if size == 0 {
+ size = item.size
+ }
+ buf.WriteString(strconv.FormatInt(int64(size), 10))
}
s.WriteState(buf.String())
@@ -263,7 +268,9 @@ func (s *Splitter) RestoreState() error {
size = int(float64(regularSpace) * fraction)
}
- layout.hwnd2Item[widget.Handle()].size = size
+ item := layout.hwnd2Item[widget.Handle()]
+ item.size = size
+ item.oldExplicitSize = size
}
if persistable, ok := widget.(Persistable); ok {
@@ -472,8 +479,14 @@ func (s *Splitter) onInsertedWidget(index int, widget Widget) (err error) {
}
layout := s.Layout().(*splitterLayout)
- layout.hwnd2Item[prev.Handle()].size = sizePrev
- layout.hwnd2Item[next.Handle()].size = sizeNext
+
+ prevItem := layout.hwnd2Item[prev.Handle()]
+ prevItem.size = sizePrev
+ prevItem.oldExplicitSize = sizePrev
+
+ nextItem := layout.hwnd2Item[next.Handle()]
+ nextItem.size = sizeNext
+ nextItem.oldExplicitSize = sizeNext
})
}
}()
diff --git a/splitterlayout.go b/splitterlayout.go
index 15221aff..d6580500 100644
--- a/splitterlayout.go
+++ b/splitterlayout.go
@@ -21,10 +21,12 @@ type splitterLayout struct {
}
type splitterLayoutItem struct {
- size int
- stretchFactor int
- growth int
- fixed bool
+ size int
+ oldExplicitSize int
+ stretchFactor int
+ growth int
+ fixed bool
+ keepSize bool
}
func newSplitterLayout(orientation Orientation) *splitterLayout {
@@ -142,10 +144,22 @@ func (l *splitterLayout) cleanupItems() {
}
func (l *splitterLayout) LayoutFlags() LayoutFlags {
- return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert | GreedyHorz | GreedyVert
+ if l.container == nil {
+ return 0
+ }
+
+ return boxLayoutFlags(l.orientation, l.container.Children())
}
func (l *splitterLayout) MinSize() Size {
+ if l.container == nil {
+ return Size{}
+ }
+
+ return l.MinSizeForSize(l.container.ClientBounds().Size())
+}
+
+func (l *splitterLayout) MinSizeForSize(size Size) Size {
margins := Size{l.margins.HNear + l.margins.HFar, l.margins.VNear + l.margins.VFar}
s := margins
@@ -206,12 +220,19 @@ func (l *splitterLayout) reset() {
l.cleanupItems()
children := l.container.Children()
+ minSizes := make([]int, children.Len())
var minSizesTotal int
- for _, w := range children.items {
+ for i, w := range children.items {
+ if i%2 == 1 {
+ continue
+ }
+
min := minSizeEffective(w)
if l.orientation == Horizontal {
+ minSizes[i] = min.Width
minSizesTotal += min.Width
} else {
+ minSizes[i] = min.Height
minSizesTotal += min.Height
}
}
@@ -241,19 +262,28 @@ func (l *splitterLayout) reset() {
item := l.hwnd2Item[child.Handle()]
item.growth = 0
- item.size = int(float64(l.StretchFactor(child)) / float64(stretchTotal) * float64(regularSpace))
+ item.keepSize = false
+ if item.oldExplicitSize > 0 {
+ item.size = item.oldExplicitSize
+ } else {
+ item.size = int(float64(l.StretchFactor(child)) / float64(stretchTotal) * float64(regularSpace))
+ }
+ min := minSizes[i]
if minSizesTotal <= regularSpace {
- var min int
- if minSize := minSizeEffective(child); l.orientation == Horizontal {
- min = minSize.Width
- } else {
- min = minSize.Height
- }
if item.size < min {
item.size = min
}
}
+
+ if item.size >= min {
+ flags := child.LayoutFlags()
+
+ if l.orientation == Horizontal && flags&GrowableHorz == 0 || l.orientation == Vertical && flags&GrowableVert == 0 {
+ item.size = min
+ item.keepSize = true
+ }
+ }
}
}
@@ -355,7 +385,16 @@ func (l *splitterLayout) Update(reset bool) error {
}
})
- wi := wis[0]
+ var wi *WidgetItem
+ for _, wItem := range wis {
+ if !wItem.item.keepSize {
+ wi = &wItem
+ break
+ }
+ }
+ if wi == nil {
+ break
+ }
if diff > 0 {
if wi.max > 0 && wi.item.size >= wi.max {
@@ -379,6 +418,8 @@ func (l *splitterLayout) Update(reset bool) error {
}
}
+ maybeInvalidate := l.container.AsContainerBase().hasComplexBackground()
+
hdwp := win.BeginDeferWindowPos(int32(len(widgets)))
if hdwp == 0 {
return lastError("BeginDeferWindowPos")
@@ -400,6 +441,12 @@ func (l *splitterLayout) Update(reset bool) error {
x, y, w, h = l.margins.HNear, p1, space2, s1
}
+ if maybeInvalidate {
+ if b := widget.Bounds(); w == b.Width && h == b.Height && (x != b.X || y != b.Y) {
+ widget.Invalidate()
+ }
+ }
+
if hdwp = win.DeferWindowPos(
hdwp,
widget.Handle(),
diff --git a/window.go b/window.go
index 0fe0e58b..13a5b1c5 100644
--- a/window.go
+++ b/window.go
@@ -280,9 +280,10 @@ type Window interface {
}
type calcTextSizeInfo struct {
- font fontInfo
- text string
- size Size
+ width int
+ font fontInfo
+ text string
+ size Size
}
// WindowBase implements many operations common to all Windows.
@@ -1153,9 +1154,14 @@ func (wb *WindowBase) dialogBaseUnitsToPixels(dlus Size) (pixels Size) {
}
func (wb *WindowBase) calculateTextSizeImpl(text string) Size {
+ return wb.calculateTextSizeImplForWidth(text, 0)
+}
+
+func (wb *WindowBase) calculateTextSizeImplForWidth(text string, width int) Size {
font := wb.window.Font()
if wb.calcTextSizeInfoPrev != nil &&
+ width == wb.calcTextSizeInfoPrev.width &&
font.family == wb.calcTextSizeInfoPrev.font.family &&
font.pointSize == wb.calcTextSizeInfoPrev.font.pointSize &&
font.style == wb.calcTextSizeInfoPrev.font.style &&
@@ -1163,36 +1169,52 @@ func (wb *WindowBase) calculateTextSizeImpl(text string) Size {
return wb.calcTextSizeInfoPrev.size
}
- hdc := win.GetDC(wb.hWnd)
- if hdc == 0 {
- newError("GetDC failed")
- return Size{}
- }
- defer win.ReleaseDC(wb.hWnd, hdc)
-
- hFontOld := win.SelectObject(hdc, win.HGDIOBJ(font.handleForDPI(0)))
- defer win.SelectObject(hdc, hFontOld)
-
var size Size
- lines := strings.Split(text, "\n")
+ if width > 0 {
+ canvas, err := wb.CreateCanvas()
+ if err != nil {
+ return size
+ }
+ defer canvas.Dispose()
- for _, line := range lines {
- var s win.SIZE
- str := syscall.StringToUTF16(strings.TrimRight(line, "\r "))
+ bounds, _, err := canvas.MeasureText(text, font, Rectangle{Width: width, Height: 9999999}, 0)
+ if err != nil {
+ return size
+ }
- if !win.GetTextExtentPoint32(hdc, &str[0], int32(len(str)-1), &s) {
- newError("GetTextExtentPoint32 failed")
+ size = bounds.Size()
+ } else {
+ hdc := win.GetDC(wb.hWnd)
+ if hdc == 0 {
+ newError("GetDC failed")
return Size{}
}
+ defer win.ReleaseDC(wb.hWnd, hdc)
+
+ hFontOld := win.SelectObject(hdc, win.HGDIOBJ(font.handleForDPI(0)))
+ defer win.SelectObject(hdc, hFontOld)
+
+ lines := strings.Split(text, "\n")
- size.Width = maxi(size.Width, int(s.CX))
- size.Height += int(s.CY)
+ for _, line := range lines {
+ var s win.SIZE
+ str := syscall.StringToUTF16(strings.TrimRight(line, "\r "))
+
+ if !win.GetTextExtentPoint32(hdc, &str[0], int32(len(str)-1), &s) {
+ newError("GetTextExtentPoint32 failed")
+ return Size{}
+ }
+
+ size.Width = maxi(size.Width, int(s.CX))
+ size.Height += int(s.CY)
+ }
}
if wb.calcTextSizeInfoPrev == nil {
wb.calcTextSizeInfoPrev = new(calcTextSizeInfo)
}
+ wb.calcTextSizeInfoPrev.width = width
wb.calcTextSizeInfoPrev.font.family = font.family
wb.calcTextSizeInfoPrev.font.pointSize = font.pointSize
wb.calcTextSizeInfoPrev.font.style = font.style
@@ -1203,6 +1225,10 @@ func (wb *WindowBase) calculateTextSizeImpl(text string) Size {
}
func (wb *WindowBase) calculateTextSize() Size {
+ return wb.calculateTextSizeForWidth(0)
+}
+
+func (wb *WindowBase) calculateTextSizeForWidth(width int) Size {
var text string
if wb.calcTextSizeInfoPrev != nil {
// setText copied the new text here for us.
@@ -1213,7 +1239,7 @@ func (wb *WindowBase) calculateTextSize() Size {
text = wb.text()
}
- return wb.calculateTextSizeImpl(text)
+ return wb.calculateTextSizeImplForWidth(text, width)
}
// Size returns the outer Size of the *WindowBase, including decorations.
@@ -1319,6 +1345,15 @@ func (wb *WindowBase) sizeFromClientSize(clientSize Size) Size {
return Size{clientSize.Width + ncs.Width, clientSize.Height + ncs.Height}
}
+func (wb *WindowBase) clientSizeFromSize(size Size) Size {
+ window := wb.window
+ s := window.Size()
+ cs := window.ClientBounds().Size()
+ ncs := Size{s.Width - cs.Width, s.Height - cs.Height}
+
+ return Size{size.Width - ncs.Width, size.Height - ncs.Height}
+}
+
// SetClientSize sets the Size of the inner bounding box of the *WindowBase,
// excluding decorations.
func (wb *WindowBase) SetClientSize(value Size) error {
@@ -1683,11 +1718,6 @@ func (wb *WindowBase) handleWMCTLCOLOR(wParam, lParam uintptr) uintptr {
}
switch wnd.(type) {
- case *Label:
- win.SetBkMode(hdc, win.TRANSPARENT)
-
- return win.COLOR_BTNSHADOW
-
case *LineEdit, *numberLineEdit, *TextEdit:
type ReadOnlyer interface {
ReadOnly() bool