diff options
author | Alexander Neumann <alexander.neumann@picos-software.com> | 2018-12-05 16:46:35 +0100 |
---|---|---|
committer | Alexander Neumann <alexander.neumann@picos-software.com> | 2018-12-05 16:46:35 +0100 |
commit | d855149da0e0c79a9bd2fe3add4ac406efbca952 (patch) | |
tree | 2fa42b16ab23380529003d7435ecfadb034164f7 | |
parent | WindowBase: Add persistence implementations that recursively call descendant's (diff) | |
download | wireguard-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.go | 205 | ||||
-rw-r--r-- | container.go | 144 | ||||
-rw-r--r-- | flowlayout.go | 117 | ||||
-rw-r--r-- | form.go | 106 | ||||
-rw-r--r-- | gridlayout.go | 178 | ||||
-rw-r--r-- | groupbox.go | 17 | ||||
-rw-r--r-- | lineedit.go | 4 | ||||
-rw-r--r-- | scrollview.go | 2 | ||||
-rw-r--r-- | splitter.go | 23 | ||||
-rw-r--r-- | splitterlayout.go | 75 | ||||
-rw-r--r-- | window.go | 84 |
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 { @@ -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(), @@ -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 |