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 }