mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 15:30:37 +08:00

* 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>
223 lines
5.0 KiB
Go
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)
|
|
}
|
|
}
|