5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 15:30:37 +08:00
wails/v2/internal/frontend/desktop/windows/winc/layout.go
Misite Bao f70d9de366
fix: fix go test errors (#2169)
* fix: fix go test errors

* Add flags to mac test

* Run on all branches

* Update PR workflow

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
2022-12-06 06:45:06 +11:00

223 lines
5.0 KiB
Go

//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
*/
package winc
import (
"encoding/json"
"fmt"
"io"
"os"
"sort"
"unsafe"
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
)
// Dockable component must satisfy interface to be docked.
type Dockable interface {
Handle() w32.HWND
Pos() (x, y int)
Width() int
Height() int
Visible() bool
SetPos(x, y int)
SetSize(width, height int)
OnMouseMove() *EventManager
OnLBUp() *EventManager
}
// DockAllow is window, panel or other component that satisfies interface.
type DockAllow interface {
Handle() w32.HWND
ClientWidth() int
ClientHeight() int
SetLayout(mng LayoutManager)
}
// Various layout managers
type Direction int
const (
Top Direction = iota
Bottom
Left
Right
Fill
)
type LayoutControl struct {
child Dockable
dir Direction
}
type LayoutControls []*LayoutControl
type SimpleDock struct {
parent DockAllow
layoutCtl LayoutControls
loadedState bool
}
// DockState gets saved and loaded from json
type CtlState struct {
X, Y, Width, Height int
}
type LayoutState struct {
WindowState string
Controls []*CtlState
}
func (lc LayoutControls) Len() int { return len(lc) }
func (lc LayoutControls) Swap(i, j int) { lc[i], lc[j] = lc[j], lc[i] }
func (lc LayoutControls) Less(i, j int) bool { return lc[i].dir < lc[j].dir }
func NewSimpleDock(parent DockAllow) *SimpleDock {
d := &SimpleDock{parent: parent}
parent.SetLayout(d)
return d
}
// Layout management for the child controls.
func (sd *SimpleDock) Dock(child Dockable, dir Direction) {
sd.layoutCtl = append(sd.layoutCtl, &LayoutControl{child, dir})
}
// SaveState of the layout. Only works for Docks with parent set to main form.
func (sd *SimpleDock) SaveState(w io.Writer) error {
var ls LayoutState
var wp w32.WINDOWPLACEMENT
wp.Length = uint32(unsafe.Sizeof(wp))
if !w32.GetWindowPlacement(sd.parent.Handle(), &wp) {
return fmt.Errorf("GetWindowPlacement failed")
}
ls.WindowState = fmt.Sprint(
wp.Flags, wp.ShowCmd,
wp.PtMinPosition.X, wp.PtMinPosition.Y,
wp.PtMaxPosition.X, wp.PtMaxPosition.Y,
wp.RcNormalPosition.Left, wp.RcNormalPosition.Top,
wp.RcNormalPosition.Right, wp.RcNormalPosition.Bottom)
for _, c := range sd.layoutCtl {
x, y := c.child.Pos()
w, h := c.child.Width(), c.child.Height()
ctl := &CtlState{X: x, Y: y, Width: w, Height: h}
ls.Controls = append(ls.Controls, ctl)
}
if err := json.NewEncoder(w).Encode(ls); err != nil {
return err
}
return nil
}
// LoadState of the layout. Only works for Docks with parent set to main form.
func (sd *SimpleDock) LoadState(r io.Reader) error {
var ls LayoutState
if err := json.NewDecoder(r).Decode(&ls); err != nil {
return err
}
var wp w32.WINDOWPLACEMENT
if _, err := fmt.Sscan(ls.WindowState,
&wp.Flags, &wp.ShowCmd,
&wp.PtMinPosition.X, &wp.PtMinPosition.Y,
&wp.PtMaxPosition.X, &wp.PtMaxPosition.Y,
&wp.RcNormalPosition.Left, &wp.RcNormalPosition.Top,
&wp.RcNormalPosition.Right, &wp.RcNormalPosition.Bottom); err != nil {
return err
}
wp.Length = uint32(unsafe.Sizeof(wp))
if !w32.SetWindowPlacement(sd.parent.Handle(), &wp) {
return fmt.Errorf("SetWindowPlacement failed")
}
// if number of controls in the saved layout does not match
// current number on screen - something changed and we do not reload
// rest of control sizes from json
if len(sd.layoutCtl) != len(ls.Controls) {
return nil
}
for i, c := range sd.layoutCtl {
c.child.SetPos(ls.Controls[i].X, ls.Controls[i].Y)
c.child.SetSize(ls.Controls[i].Width, ls.Controls[i].Height)
}
return nil
}
// SaveStateFile convenience function.
func (sd *SimpleDock) SaveStateFile(file string) error {
f, err := os.Create(file)
if err != nil {
return err
}
return sd.SaveState(f)
}
// LoadStateFile loads state ignores error if file is not found.
func (sd *SimpleDock) LoadStateFile(file string) error {
f, err := os.Open(file)
if err != nil {
return nil // if file is not found or not accessible ignore it
}
return sd.LoadState(f)
}
// Update is called to resize child items based on layout directions.
func (sd *SimpleDock) Update() {
sort.Stable(sd.layoutCtl)
x, y := 0, 0
w, h := sd.parent.ClientWidth(), sd.parent.ClientHeight()
winw, winh := w, h
for _, c := range sd.layoutCtl {
// Non visible controls do not preserve space.
if !c.child.Visible() {
continue
}
switch c.dir {
case Top:
c.child.SetPos(x, y)
c.child.SetSize(w, c.child.Height())
h -= c.child.Height()
y += c.child.Height()
case Bottom:
c.child.SetPos(x, winh-c.child.Height())
c.child.SetSize(w, c.child.Height())
h -= c.child.Height()
winh -= c.child.Height()
case Left:
c.child.SetPos(x, y)
c.child.SetSize(c.child.Width(), h)
w -= c.child.Width()
x += c.child.Width()
case Right:
c.child.SetPos(winw-c.child.Width(), y)
c.child.SetSize(c.child.Width(), h)
w -= c.child.Width()
winw -= c.child.Width()
case Fill:
// fill available space
c.child.SetPos(x, y)
c.child.SetSize(w, h)
}
//c.child.Invalidate(true)
}
}