github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/winc/layout.go (about)

     1  //go:build windows
     2  
     3  /*
     4   * Copyright (C) 2019 The Winc Authors. All Rights Reserved.
     5   */
     6  
     7  package winc
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"sort"
    15  	"unsafe"
    16  
    17  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32"
    18  )
    19  
    20  // Dockable component must satisfy interface to be docked.
    21  type Dockable interface {
    22  	Handle() w32.HWND
    23  
    24  	Pos() (x, y int)
    25  	Width() int
    26  	Height() int
    27  	Visible() bool
    28  
    29  	SetPos(x, y int)
    30  	SetSize(width, height int)
    31  
    32  	OnMouseMove() *EventManager
    33  	OnLBUp() *EventManager
    34  }
    35  
    36  // DockAllow is window, panel or other component that satisfies interface.
    37  type DockAllow interface {
    38  	Handle() w32.HWND
    39  	ClientWidth() int
    40  	ClientHeight() int
    41  	SetLayout(mng LayoutManager)
    42  }
    43  
    44  // Various layout managers
    45  type Direction int
    46  
    47  const (
    48  	Top Direction = iota
    49  	Bottom
    50  	Left
    51  	Right
    52  	Fill
    53  )
    54  
    55  type LayoutControl struct {
    56  	child Dockable
    57  	dir   Direction
    58  }
    59  
    60  type LayoutControls []*LayoutControl
    61  
    62  type SimpleDock struct {
    63  	parent      DockAllow
    64  	layoutCtl   LayoutControls
    65  	loadedState bool
    66  }
    67  
    68  // DockState gets saved and loaded from json
    69  type CtlState struct {
    70  	X, Y, Width, Height int
    71  }
    72  
    73  type LayoutState struct {
    74  	WindowState string
    75  	Controls    []*CtlState
    76  }
    77  
    78  func (lc LayoutControls) Len() int           { return len(lc) }
    79  func (lc LayoutControls) Swap(i, j int)      { lc[i], lc[j] = lc[j], lc[i] }
    80  func (lc LayoutControls) Less(i, j int) bool { return lc[i].dir < lc[j].dir }
    81  
    82  func NewSimpleDock(parent DockAllow) *SimpleDock {
    83  	d := &SimpleDock{parent: parent}
    84  	parent.SetLayout(d)
    85  	return d
    86  }
    87  
    88  // Layout management for the child controls.
    89  func (sd *SimpleDock) Dock(child Dockable, dir Direction) {
    90  	sd.layoutCtl = append(sd.layoutCtl, &LayoutControl{child, dir})
    91  }
    92  
    93  // SaveState of the layout. Only works for Docks with parent set to main form.
    94  func (sd *SimpleDock) SaveState(w io.Writer) error {
    95  	var ls LayoutState
    96  
    97  	var wp w32.WINDOWPLACEMENT
    98  	wp.Length = uint32(unsafe.Sizeof(wp))
    99  	if !w32.GetWindowPlacement(sd.parent.Handle(), &wp) {
   100  		return fmt.Errorf("GetWindowPlacement failed")
   101  	}
   102  
   103  	ls.WindowState = fmt.Sprint(
   104  		wp.Flags, wp.ShowCmd,
   105  		wp.PtMinPosition.X, wp.PtMinPosition.Y,
   106  		wp.PtMaxPosition.X, wp.PtMaxPosition.Y,
   107  		wp.RcNormalPosition.Left, wp.RcNormalPosition.Top,
   108  		wp.RcNormalPosition.Right, wp.RcNormalPosition.Bottom)
   109  
   110  	for _, c := range sd.layoutCtl {
   111  		x, y := c.child.Pos()
   112  		w, h := c.child.Width(), c.child.Height()
   113  
   114  		ctl := &CtlState{X: x, Y: y, Width: w, Height: h}
   115  		ls.Controls = append(ls.Controls, ctl)
   116  	}
   117  
   118  	if err := json.NewEncoder(w).Encode(ls); err != nil {
   119  		return err
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // LoadState of the layout. Only works for Docks with parent set to main form.
   126  func (sd *SimpleDock) LoadState(r io.Reader) error {
   127  	var ls LayoutState
   128  
   129  	if err := json.NewDecoder(r).Decode(&ls); err != nil {
   130  		return err
   131  	}
   132  
   133  	var wp w32.WINDOWPLACEMENT
   134  	if _, err := fmt.Sscan(ls.WindowState,
   135  		&wp.Flags, &wp.ShowCmd,
   136  		&wp.PtMinPosition.X, &wp.PtMinPosition.Y,
   137  		&wp.PtMaxPosition.X, &wp.PtMaxPosition.Y,
   138  		&wp.RcNormalPosition.Left, &wp.RcNormalPosition.Top,
   139  		&wp.RcNormalPosition.Right, &wp.RcNormalPosition.Bottom); err != nil {
   140  		return err
   141  	}
   142  	wp.Length = uint32(unsafe.Sizeof(wp))
   143  
   144  	if !w32.SetWindowPlacement(sd.parent.Handle(), &wp) {
   145  		return fmt.Errorf("SetWindowPlacement failed")
   146  	}
   147  
   148  	// if number of controls in the saved layout does not match
   149  	// current number on screen - something changed and we do not reload
   150  	// rest of control sizes from json
   151  	if len(sd.layoutCtl) != len(ls.Controls) {
   152  		return nil
   153  	}
   154  
   155  	for i, c := range sd.layoutCtl {
   156  		c.child.SetPos(ls.Controls[i].X, ls.Controls[i].Y)
   157  		c.child.SetSize(ls.Controls[i].Width, ls.Controls[i].Height)
   158  	}
   159  	return nil
   160  }
   161  
   162  // SaveStateFile convenience function.
   163  func (sd *SimpleDock) SaveStateFile(file string) error {
   164  	f, err := os.Create(file)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	return sd.SaveState(f)
   169  }
   170  
   171  // LoadStateFile loads state ignores error if file is not found.
   172  func (sd *SimpleDock) LoadStateFile(file string) error {
   173  	f, err := os.Open(file)
   174  	if err != nil {
   175  		return nil // if file is not found or not accessible ignore it
   176  	}
   177  	return sd.LoadState(f)
   178  }
   179  
   180  // Update is called to resize child items based on layout directions.
   181  func (sd *SimpleDock) Update() {
   182  	sort.Stable(sd.layoutCtl)
   183  
   184  	x, y := 0, 0
   185  	w, h := sd.parent.ClientWidth(), sd.parent.ClientHeight()
   186  	winw, winh := w, h
   187  
   188  	for _, c := range sd.layoutCtl {
   189  		// Non visible controls do not preserve space.
   190  		if !c.child.Visible() {
   191  			continue
   192  		}
   193  
   194  		switch c.dir {
   195  		case Top:
   196  			c.child.SetPos(x, y)
   197  			c.child.SetSize(w, c.child.Height())
   198  			h -= c.child.Height()
   199  			y += c.child.Height()
   200  		case Bottom:
   201  			c.child.SetPos(x, winh-c.child.Height())
   202  			c.child.SetSize(w, c.child.Height())
   203  			h -= c.child.Height()
   204  			winh -= c.child.Height()
   205  		case Left:
   206  			c.child.SetPos(x, y)
   207  			c.child.SetSize(c.child.Width(), h)
   208  			w -= c.child.Width()
   209  			x += c.child.Width()
   210  		case Right:
   211  			c.child.SetPos(winw-c.child.Width(), y)
   212  			c.child.SetSize(c.child.Width(), h)
   213  			w -= c.child.Width()
   214  			winw -= c.child.Width()
   215  		case Fill:
   216  			// fill available space
   217  			c.child.SetPos(x, y)
   218  			c.child.SetSize(w, h)
   219  		}
   220  		//c.child.Invalidate(true)
   221  	}
   222  }