├── AUTHORS ├── CONTRIBUTORS ├── LICENSE ├── Makefile ├── README.md ├── all_test.go ├── app.go ├── dbg.go ├── dbg_demo.go ├── demo.go ├── desktop.go ├── etc.go ├── event.go ├── handler.go ├── internal └── demoapp │ ├── Makefile │ ├── README │ ├── all_test.go │ ├── demoapp.go │ └── test.go ├── mouse.go ├── nodbg.go ├── theme.go ├── tk ├── Makefile ├── all_test.go ├── scrollbar.go ├── tk.go ├── view.go └── view_demo.go └── window.go /AUTHORS: -------------------------------------------------------------------------------- 1 | # This file lists authors for copyright purposes. This file is distinct from 2 | # the CONTRIBUTORS files. See the latter for an explanation. 3 | # 4 | # Names should be added to this file as: 5 | # Name or Organization 6 | # 7 | # The email address is not required for organizations. 8 | # 9 | # Please keep the list sorted. 10 | 11 | Jan Mercl <0xjnml@gmail.com> 12 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This file lists people who contributed code to this repository. The AUTHORS 2 | # file lists the copyright holders; this file lists people. 3 | # 4 | # Names should be added to this file like so: 5 | # Name 6 | # 7 | # Please keep the list sorted. 8 | 9 | Jan Mercl <0xjnml@gmail.com> 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The WM Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the names of the authors nor the names of the 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The WM Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | .PHONY: all clean cover cpu editor internalError later mem nuke todo edit demo 6 | 7 | grep=--include=*.go --include=*.l --include=*.y --include=*.yy 8 | ngrep='TODOOK\|parser\.go\|scanner\.go\|.*_string\.go' 9 | 10 | all: editor 11 | go vet 2>&1 | grep -v $(ngrep) || true 12 | golint 2>&1 | grep -v $(ngrep) || true 13 | make todo 14 | unused . || true 15 | misspell *.go 16 | gosimple || true 17 | codesweep || true 18 | unconvert || true 19 | maligned || true 20 | 21 | clean: 22 | go clean 23 | rm -f *~ *.test *.out 24 | 25 | cover: 26 | t=$(shell tempfile) ; go test -coverprofile $$t && go tool cover -html $$t && unlink $$t 27 | 28 | cpu: clean 29 | go test -run @ -bench . -cpuprofile cpu.out 30 | go tool pprof -lines *.test cpu.out 31 | 32 | demo: 33 | go run demo.go 34 | 35 | edit: 36 | @ 1>/dev/null 2>/dev/null gvim -p Makefile *.go 37 | 38 | editor: 39 | gofmt -l -s -w *.go 40 | go test 2>&1 | tee log 41 | go install 42 | 43 | internalError: 44 | egrep -ho '"internal error.*"' *.go | sort | cat -n 45 | 46 | later: 47 | @grep -n $(grep) LATER * || true 48 | @grep -n $(grep) MAYBE * || true 49 | 50 | mem: clean 51 | go test -run @ -bench . -memprofile mem.out -memprofilerate 1 -timeout 24h 52 | go tool pprof -lines -web -alloc_space *.test mem.out 53 | 54 | nuke: clean 55 | go clean -i 56 | 57 | todo: 58 | @grep -nr $(grep) ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* * | grep -v $(ngrep) || true 59 | @grep -nr $(grep) TODO * | grep -v $(ngrep) || true 60 | @grep -nr $(grep) BUG * | grep -v $(ngrep) || true 61 | @grep -nr $(grep) [^[:alpha:]]println * | grep -v $(ngrep) || true 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `github.com/cznic/wm` has moved to [`modernc.org/wm`](https://godoc.org/modernc.org/wm) ([vcs](https://gitlab.com/cznic/wm)). 2 | 3 | Please update your import paths to `modernc.org/wm`. 4 | 5 | This repo is now archived. 6 | -------------------------------------------------------------------------------- /all_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wm 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path" 11 | "runtime" 12 | "strings" 13 | "testing" 14 | 15 | "github.com/gdamore/tcell" 16 | ) 17 | 18 | func caller(s string, va ...interface{}) { 19 | if s == "" { 20 | s = strings.Repeat("%v ", len(va)) 21 | } 22 | _, fn, fl, _ := runtime.Caller(2) 23 | fmt.Fprintf(os.Stderr, "// caller: %s:%d: ", path.Base(fn), fl) 24 | fmt.Fprintf(os.Stderr, s, va...) 25 | fmt.Fprintln(os.Stderr) 26 | _, fn, fl, _ = runtime.Caller(1) 27 | fmt.Fprintf(os.Stderr, "// \tcallee: %s:%d: ", path.Base(fn), fl) 28 | fmt.Fprintln(os.Stderr) 29 | os.Stderr.Sync() 30 | } 31 | 32 | func dbg(s string, va ...interface{}) { 33 | if s == "" { 34 | s = strings.Repeat("%v ", len(va)) 35 | } 36 | _, fn, fl, _ := runtime.Caller(1) 37 | fmt.Fprintf(os.Stderr, "// dbg %s:%d: ", path.Base(fn), fl) 38 | fmt.Fprintf(os.Stderr, s, va...) 39 | fmt.Fprintln(os.Stderr) 40 | os.Stderr.Sync() 41 | } 42 | 43 | func TODO(...interface{}) string { //TODOOK 44 | _, fn, fl, _ := runtime.Caller(1) 45 | return fmt.Sprintf("// TODO: %s:%d:\n", path.Base(fn), fl) //TODOOK 46 | } 47 | 48 | func use(...interface{}) {} 49 | 50 | func init() { 51 | use(caller, dbg, TODO) //TODOOK 52 | } 53 | 54 | // ============================================================================ 55 | 56 | func TestJoin(t *testing.T) { 57 | a := Rectangle{Position{X: 46, Y: 12}, Size{Width: 55, Height: 27}} 58 | b := Rectangle{Position{X: 45, Y: 12}, Size{Width: 55, Height: 27}} 59 | a.join(b) 60 | if g, e := a, (Rectangle{Position{X: 45, Y: 12}, Size{Width: 56, Height: 27}}); g != e { 61 | t.Fatal(g, e) 62 | } 63 | } 64 | 65 | func TestDesktopPaintContext(t *testing.T) { 66 | s := tcell.NewSimulationScreen("") 67 | app, err := newApplication(s, &Theme{}) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | 72 | defer func() { 73 | app.PostWait(func() { app.Exit(nil) }) 74 | if err := app.Wait(); err != nil { 75 | t.Fatal(err) 76 | } 77 | }() 78 | 79 | var r *Window 80 | var c, cc PaintContext 81 | ch := make(chan int, 1) 82 | app.PostWait(func() { 83 | d := app.NewDesktop() 84 | r = d.Root() 85 | app.SetDesktop(d) 86 | AddOnPaintHandler(&r.onClearClientArea, func(w *Window, prev OnPaintHandler, ctx PaintContext) { 87 | if prev != nil { 88 | prev(w, nil, ctx) 89 | } 90 | cc = ctx 91 | }, nil) 92 | r.OnPaintClientArea(func(w *Window, prev OnPaintHandler, ctx PaintContext) { 93 | if prev != nil { 94 | prev(w, nil, ctx) 95 | } 96 | c = ctx 97 | ch <- 1 98 | }, nil) 99 | d.Show() 100 | }) 101 | 102 | app.PostWait(func() { 103 | r.InvalidateClientArea(r.ClientArea()) 104 | }) 105 | <-ch 106 | if g, e := cc, (PaintContext{ 107 | Rectangle: Rectangle{Position{}, Size{80, 25}}, 108 | origin: Position{}, 109 | view: Position{}, 110 | }); g != e { 111 | t.Fatalf("\n%+v\n%+v", g, e) 112 | } 113 | if g, e := c, (PaintContext{ 114 | Rectangle: Rectangle{Position{}, Size{80, 25}}, 115 | origin: Position{}, 116 | view: Position{}, 117 | }); g != e { 118 | t.Fatalf("\n%+v\n%+v", g, e) 119 | } 120 | 121 | app.PostWait(func() { 122 | r.SetOrigin(Position{2, 1}) 123 | r.InvalidateClientArea(r.ClientArea()) 124 | }) 125 | <-ch 126 | if g, e := cc, (PaintContext{ 127 | Rectangle: Rectangle{Position{}, Size{80, 25}}, 128 | origin: Position{}, 129 | view: Position{}, 130 | }); g != e { 131 | t.Fatalf("\n%+v\n%+v", g, e) 132 | } 133 | if g, e := c, (PaintContext{ 134 | Rectangle: Rectangle{Position{2, 1}, Size{80, 25}}, 135 | origin: Position{}, 136 | view: Position{2, 1}, 137 | }); g != e { 138 | t.Fatalf("\n%+v\n%+v", g, e) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package wm is a terminal window manager. 6 | // 7 | // Screenshot 8 | // 9 | // An example content of a terminal window (colors cannot be shown here): 10 | // 11 | // Use mouse to resize the window or scroll the view. 12 | // Arrow keys change the viewport of the focused window. 13 | // To focus the desktop, click on it. 14 | // or 'q' to quit. 15 | // 16 | // ┌ view_demo.go ─────────────────────────────────────────────────────[X]┐ 17 | // │ ▴│ 18 | // │ ░│ 19 | // ┌ view.go ─────────│Use mouse to resize the window or scroll the view. ░│ 20 | // │ "github.co│e the viewport of the focused window. ▒│ 21 | // │ "github.co│ktop, click on it. ▒│ 22 | // │) │quit.` ░│ 23 | // │ │ ░│ 24 | // │// Meter provides │ ░│────[X]┐ 25 | // │type Meter interfa│'\n'} ░│ ▴│ 26 | // │ // Metrics│ ░│ ░│ 27 | // │ // result │arent *wm.Window, x, y int, title string, src []byte) { ░│ ░│ 28 | // │ // reflect│rent.Size() ░│ ░│ 29 | // │ // values │ || y < 0 { ░│, len(░│ 30 | // │ // output │x = rand.Intn(sz.Width - sz.Width/5) ░│ ░│ 31 | // │ Metrics(vi│y = rand.Intn(sz.Height - sz.Height/5) ░│) ░│ 32 | // │} │ ░│s:%d: ░│ 33 | // │ │ent.NewChild(wm.Rectangle{wm.Position{x, y}, wm.Size{0, 0}}) ░│ ░│ 34 | // │// View displays c│seButton(true) ░│ ░│ 35 | // │◂▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│le(title) ░│ ░│ 36 | // └──────────────────│.HasSuffix(src, nl) { ░│ ▒│ 37 | // │src = src[:len(src)-1] ░│ ▒│ 38 | // │ ▾│OOK ▒│ 39 | // │◂░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░▸ │) ▒│ 40 | // └──────────────────────────────────────────────────────────────────────┘%d:\n"▒│ 41 | // 42 | // 43 | // Changelog 44 | // 45 | // 46 | // 2016-12-16: Initial release of the accompanying toolkit package: 47 | // http://github.com/cznic/wm/tree/master/tk 48 | // 49 | // 2016-11-25: Windows now support views (viewports). See Windows.Origin and 50 | // friends. 51 | // 52 | // 2015-12-11: WM now uses no locks and renders 2 to 3 times faster. The price 53 | // is that any methods of Application, Desktop or Window must be called only 54 | // from a function that was enqueued by Application.Post or 55 | // Application.PostWait. 56 | package wm 57 | 58 | import ( 59 | "fmt" 60 | rdebug "runtime/debug" 61 | "sync" 62 | "time" 63 | 64 | "github.com/gdamore/tcell" 65 | "github.com/gdamore/tcell/encoding" 66 | ) 67 | 68 | const ( 69 | anyButton = tcell.Button8<<1 - 1 70 | anyWheel = tcell.WheelUp | tcell.WheelDown | tcell.WheelLeft | tcell.WheelRight 71 | ) 72 | 73 | var ( 74 | // App is the instance of Application created by NewApplication. 75 | App *Application 76 | onceNewApplication sync.Once 77 | ) 78 | 79 | // Application represents an interactive terminal application. 80 | // 81 | // Application methods must be called only directly from an event handler 82 | // goroutine or from a function that was enqueued using Application.Post or 83 | // Application.PostWait. The only exception is Application.Wait, it can be 84 | // called from any goroutine. 85 | type Application struct { 86 | click time.Duration // 87 | desktop *Desktop // 88 | doubleClick time.Duration // 89 | exitError error // 90 | mouseButtonFSMs [8]*mouseButtonFSM // 91 | mouseButtonsState tcell.ButtonMask // 92 | mouseX int // 93 | mouseY int // 94 | onKey *onKeyHandlerList // 95 | onSetClick *onSetDurationHandlerList // 96 | onSetDesktop *onSetDesktopHandlerList // 97 | onSetDoubleClick *onSetDurationHandlerList // 98 | onSetSize *OnSetSizeHandlerList // 99 | onceExit sync.Once // 100 | onceFinalize sync.Once // 101 | onceWait sync.Once // 102 | screen tcell.Screen // 103 | size Size // 104 | theme *Theme // 105 | updateLevel int32 // 106 | wait chan error // 107 | } 108 | 109 | // NewApplication returns a newly created Application or an error, if any. 110 | // 111 | // // Skeleton example. 112 | // func main() { 113 | // app, err := wm.NewApplication(theme) 114 | // if err != nil { 115 | // log.Fatalf("error: %v", err) 116 | // } 117 | // 118 | // ... 119 | // 120 | // if err = app.Wait(); err != nil { 121 | // log.Fatal(err) 122 | // } 123 | // } 124 | // 125 | // Calling this function more than once will panic. 126 | func NewApplication(theme *Theme) (*Application, error) { 127 | done := false 128 | var app *Application 129 | var err error 130 | onceNewApplication.Do(func() { 131 | app, err = newApplication(nil, theme) 132 | done = true 133 | }) 134 | if !done { 135 | panic("NewApplication called more than once") 136 | } 137 | 138 | return app, err 139 | } 140 | 141 | func newApplication(screen tcell.Screen, t *Theme) (*Application, error) { 142 | encoding.Register() 143 | var err error 144 | if screen == nil { 145 | if screen, err = tcell.NewScreen(); err != nil { 146 | return nil, err 147 | } 148 | } 149 | 150 | if err = screen.Init(); err != nil { 151 | return nil, err 152 | } 153 | 154 | var size Size 155 | size.Width, size.Height = screen.Size() 156 | theme := *t 157 | App = &Application{ 158 | click: 150 * time.Millisecond, 159 | doubleClick: 120 * time.Millisecond, 160 | screen: screen, 161 | size: size, 162 | theme: &theme, 163 | wait: make(chan error, 1), 164 | } 165 | 166 | mask := tcell.Button1 167 | for i := range App.mouseButtonFSMs { 168 | App.mouseButtonFSMs[i] = newMouseButtonFSM(mask) 169 | mask <<= 1 170 | } 171 | App.screen.EnableMouse() 172 | App.OnKey(App.onKeyHandler, nil) 173 | App.OnSetDesktop(App.onSetDesktopHandler, nil) 174 | App.OnSetSize(App.onSetSizeHandler, nil) 175 | go App.handleEvents() 176 | return App, nil 177 | } 178 | 179 | func (a *Application) handleEvents() { 180 | defer func() { 181 | if err := recover(); err != nil { 182 | a.finalize() 183 | a.Exit(fmt.Errorf("PANIC: %v\n%s", err, rdebug.Stack())) 184 | } 185 | }() 186 | 187 | for { 188 | ev := a.screen.PollEvent() 189 | if ev == nil { 190 | return 191 | } 192 | 193 | d := a.desktop 194 | var r *Window 195 | if d != nil { 196 | r = d.root 197 | } 198 | if r != nil { 199 | r.BeginUpdate() 200 | } 201 | 202 | switch e := ev.(type) { 203 | case *tcell.EventResize: 204 | a.setSize(newSize(e.Size())) 205 | case *tcell.EventKey: 206 | a.onKey.handle(nil, e.Key(), e.Modifiers(), e.Rune()) 207 | case *tcell.EventMouse: 208 | x, y := e.Position() 209 | btn := e.Buttons() 210 | if x != a.mouseX || y != a.mouseY || btn&anyWheel != 0 { 211 | a.mouseX = x 212 | a.mouseY = y 213 | a.screen.PostEvent(newEventMouse(mouseMove, btn, e.Modifiers(), Position{x, y})) 214 | } 215 | if b := btn & anyButton; b != a.mouseButtonsState { 216 | diff := b ^ a.mouseButtonsState 217 | a.mouseButtonsState = b 218 | x := 0 219 | for diff != 0 { 220 | if diff&1 != 0 { 221 | a.mouseButtonFSMs[x].post(e) 222 | } 223 | diff >>= 1 224 | x++ 225 | } 226 | } 227 | case *eventMouse: 228 | w := a.Desktop().Root() 229 | switch e.kind { 230 | case mouseDrag: 231 | w.drag(e.button, e.Position, e.mods) 232 | case mouseDrop: 233 | w.drop(e.button, e.Position, e.mods) 234 | case mouseClick: 235 | w.click(e.button, e.Position, e.mods) 236 | case mouseDoubleClick: 237 | w.doubleClick(e.button, e.Position, e.mods) 238 | case mouseMove: 239 | w.mouseMove(e.button, e.Position, e.mods) 240 | default: 241 | panic(fmt.Errorf("%v", e.kind)) 242 | } 243 | e.dispose() 244 | case *eventFunc: 245 | e.f() 246 | e.dispose() 247 | default: 248 | panic(fmt.Errorf("%T", e)) 249 | } 250 | 251 | if r != nil { 252 | r.EndUpdate() 253 | } 254 | } 255 | } 256 | 257 | func (a *Application) onSetDesktopHandler(_ *Window, prev OnSetDesktopHandler, dst **Desktop, src *Desktop) { 258 | if prev != nil { 259 | prev(nil, nil, dst, src) 260 | } else { 261 | *dst = src 262 | } 263 | 264 | w := a.Desktop().Root() 265 | w.setSize(a.Size()) 266 | w.Invalidate(w.Area()) 267 | } 268 | 269 | func (a *Application) onKeyHandler(w *Window, prev OnKeyHandler, key tcell.Key, mod tcell.ModMask, r rune) bool { 270 | if prev != nil { 271 | panic("internal error") 272 | } 273 | 274 | d := a.Desktop() 275 | if d == nil { 276 | return true 277 | } 278 | 279 | fw := d.FocusedWindow() 280 | if fw == nil { 281 | return false 282 | } 283 | 284 | return fw.onKey.handle(fw, key, mod, r) 285 | } 286 | 287 | func (a *Application) onSetSizeHandler(_ *Window, prev OnSetSizeHandler, dst *Size, src Size) { 288 | if prev != nil { 289 | prev(nil, nil, dst, src) 290 | } else { 291 | *dst = src 292 | } 293 | 294 | d := a.Desktop() 295 | if d == nil { 296 | return 297 | } 298 | 299 | sz := a.Size() 300 | w := d.Root() 301 | w.setSize(sz) 302 | } 303 | 304 | func (a *Application) paintSelection() { 305 | d := a.Desktop() 306 | if d == nil { 307 | return 308 | } 309 | 310 | area := d.Selection() 311 | if area.IsZero() { 312 | return 313 | } 314 | 315 | o := area.Position 316 | for y := 0; y < area.Height; y++ { 317 | sy := o.Y + y 318 | fx := true 319 | for x := 0; x < area.Width; x++ { 320 | sx := o.X + x 321 | if fx { 322 | _, _, _, width := a.screen.GetContent(sx-1, sy) 323 | if width == 2 { 324 | sx-- 325 | x-- 326 | } 327 | } 328 | fx = false 329 | mainc, combc, style, width := a.screen.GetContent(sx, sy) 330 | style ^= tcell.Style(tcell.AttrReverse) 331 | a.screen.SetContent(sx, sy, mainc, combc, style) 332 | if width == 2 { 333 | x++ 334 | } 335 | } 336 | } 337 | } 338 | 339 | var marker = Style{Background: tcell.ColorRed, Foreground: tcell.ColorBlack} 340 | 341 | func (a *Application) setCell(p Position, mainc rune, combc []rune, style tcell.Style) { 342 | switch { 343 | case debug: 344 | // Make screen updates slow enough for human observation. 345 | a.screen.SetContent(p.X, p.Y, tcell.RuneDiamond, nil, marker.TCellStyle()) 346 | a.screen.Show() 347 | a.screen.SetContent(p.X, p.Y, mainc, combc, style) 348 | a.screen.Show() 349 | a.screen.SetContent(p.X, p.Y, tcell.RuneDiamond, nil, marker.TCellStyle()) 350 | a.screen.Show() 351 | a.screen.SetContent(p.X, p.Y, mainc, combc, style) 352 | a.screen.Show() 353 | default: 354 | a.screen.SetContent(p.X, p.Y, mainc, combc, style) 355 | } 356 | } 357 | 358 | func (a *Application) finalize() { a.onceFinalize.Do(func() { a.screen.Fini() }) } 359 | 360 | // ---------------------------------------------------------------------------- 361 | 362 | // BeginUpdate marks the start of one or more updates to the application 363 | // screen. 364 | // 365 | // Failing to properly pair BeginUpdate with a corresponding EndUpdate will 366 | // cause application screen corruption and/or freeze. 367 | func (a *Application) BeginUpdate() { 368 | a.updateLevel++ 369 | if a.updateLevel == 1 { 370 | a.paintSelection() // Remove selection. 371 | } 372 | } 373 | 374 | // ChildWindowStyle returns the style assigned to new child windows. 375 | func (a *Application) ChildWindowStyle() WindowStyle { return a.theme.ChildWindow } 376 | 377 | // ClickDuration returns the maximum duration of a single click. Holding a 378 | // mouse button for any longer duration generates a drag event instead. 379 | func (a *Application) ClickDuration() time.Duration { return a.click } 380 | 381 | // Colors returns the number of colors the host terminal supports. All colors 382 | // are assumed to use the ANSI color map. If a terminal is monochrome, it will 383 | // return 0. 384 | func (a *Application) Colors() int { return a.screen.Colors() } 385 | 386 | // Desktop returns the currently active desktop. 387 | func (a *Application) Desktop() (d *Desktop) { return a.desktop } 388 | 389 | // DesktopStyle returns the style assigned to new desktops. 390 | func (a *Application) DesktopStyle() WindowStyle { return a.theme.Desktop } 391 | 392 | // DoubleClickDuration returns the maximum duration of a double click. Mouse 393 | // click not followed by another one within the DoubleClickDuration is a single 394 | // click. 395 | func (a *Application) DoubleClickDuration() time.Duration { return a.doubleClick } 396 | 397 | // EndUpdate marks the end of one or more updates to the application screen. 398 | // 399 | // Failing to properly pair BeginUpdate with a corresponding EndUpdate will 400 | // cause application screen corruption and/or freeze. 401 | func (a *Application) EndUpdate() { 402 | a.updateLevel-- 403 | if a.updateLevel == 0 { 404 | a.paintSelection() // Show selection. 405 | a.screen.Show() 406 | } 407 | } 408 | 409 | // Exit terminates the interactive terminal application and returns err from 410 | // Wait(). Only the first call of this method is considered. 411 | func (a *Application) Exit(err error) { 412 | a.finalize() 413 | a.onceExit.Do(func() { a.wait <- err }) 414 | } 415 | 416 | // NewDesktop returns a newly created desktop. 417 | func (a *Application) NewDesktop() *Desktop { return newDesktop() } 418 | 419 | // OnKey sets a key event handler. When the event handler is removed, finalize 420 | // is called, if not nil. 421 | func (a *Application) OnKey(h OnKeyHandler, finalize func()) { 422 | addOnKeyHandler(&a.onKey, h, finalize) 423 | } 424 | 425 | // OnSetClickDuration sets a handler invoked on SetClickDuration. When the 426 | // event handler is removed, finalize is called, if not nil. 427 | func (a *Application) OnSetClickDuration(h OnSetDurationHandler, finalize func()) { 428 | addOnSetDurationHandler(&a.onSetClick, h, finalize) 429 | } 430 | 431 | // OnSetDesktop sets a handler invoked on SetDesktop. When the event handler is 432 | // removed, finalize is called, if not nil. 433 | func (a *Application) OnSetDesktop(h OnSetDesktopHandler, finalize func()) { 434 | addOnSetDesktopHandler(&a.onSetDesktop, h, finalize) 435 | } 436 | 437 | // OnSetDoubleClickDuration sets a handler invoked on SetDoubleClickDuration. 438 | // When the event handler is removed, finalize is called, if not nil. 439 | func (a *Application) OnSetDoubleClickDuration(h OnSetDurationHandler, finalize func()) { 440 | addOnSetDurationHandler(&a.onSetDoubleClick, h, finalize) 441 | } 442 | 443 | // OnSetSize sets a handler invoked on resizing the application screen. When 444 | // the event handler is removed, finalize is called, if not nil. 445 | func (a *Application) OnSetSize(h OnSetSizeHandler, finalize func()) { 446 | AddOnSetSizeHandler(&a.onSetSize, h, finalize) 447 | } 448 | 449 | // Post puts f in the event queue, if the queue is not full, and executes it on 450 | // dequeuing the event. 451 | func (a *Application) Post(f func()) { a.screen.PostEvent(newEventFunc(f)) } 452 | 453 | // PostWait puts f in the event queue and executes it on dequeuing the event. 454 | func (a *Application) PostWait(f func()) { a.screen.PostEventWait(newEventFunc(f)) } 455 | 456 | // RemoveOnKey undoes the most recent OnKey call. The function will panic if 457 | // there is no handler set. 458 | func (a *Application) RemoveOnKey() { removeOnKeyHandler(&a.onKey) } 459 | 460 | // RemoveOnSetClickDuration undoes the most recent OnSetClickDuration call. The 461 | // function will panic if there is no handler set. 462 | func (a *Application) RemoveOnSetClickDuration() { removeOnSetDurationHandler(&a.onSetClick) } 463 | 464 | // RemoveOnSetDesktop undoes the most recent OnSetDesktop call. The function 465 | // will panic if there is no handler set. 466 | func (a *Application) RemoveOnSetDesktop() { removeOnSetDesktopHandler(&a.onSetDesktop) } 467 | 468 | // RemoveOnSetDoubleClickDuration undoes the most recent 469 | // OnSetDoubleClickDuration call. The function will panic if there is no 470 | // handler set. 471 | func (a *Application) RemoveOnSetDoubleClickDuration() { 472 | removeOnSetDurationHandler(&a.onSetDoubleClick) 473 | } 474 | 475 | // RemoveOnSetSize undoes the most recent OnSetSize call. The function 476 | // will panic if there is no handler set. 477 | func (a *Application) RemoveOnSetSize() { RemoveOnSetSizeHandler(&a.onSetSize) } 478 | 479 | // Run is a shorthand for 480 | // 481 | // app.PostWait(setup) 482 | // return app.Wait() 483 | func (a *Application) Run(setup func()) error { 484 | a.PostWait(setup) 485 | return a.Wait() 486 | } 487 | 488 | // SetClickDuration sets the maximum duration of a single click. Holding a 489 | // mouse button for any longer duration generates a drag event instead. 490 | func (a *Application) SetClickDuration(d time.Duration) { a.onSetClick.handle(nil, &a.click, d) } 491 | 492 | // SetDesktop sets the currently active desktop. Passing nil d will panic. 493 | func (a *Application) SetDesktop(d *Desktop) { 494 | if d == nil { 495 | panic("cannot set nil desktop") 496 | } 497 | 498 | a.onSetDesktop.handle(nil, &a.desktop, d) 499 | } 500 | 501 | // SetDoubleClickDuration sets the maximum duration of a single click. Mouse 502 | // click not followed by another one within the DoubleClickDuration is a single 503 | // click. 504 | // 505 | // Note: Setting DoubleClickDuration to zero disables double click support. 506 | func (a *Application) SetDoubleClickDuration(d time.Duration) { 507 | a.onSetClick.handle(nil, &a.doubleClick, d) 508 | } 509 | 510 | func (a *Application) setSize(s Size) { a.onSetSize.Handle(nil, &a.size, s) } 511 | 512 | // Size returns the size of the terminal the application runs in. 513 | func (a *Application) Size() (s Size) { return a.size } 514 | 515 | // Sync updates every character cell of the application screen. 516 | func (a *Application) Sync() { a.screen.Sync() } 517 | 518 | // Wait blocks until the interactive terminal application terminates. 519 | // 520 | // Calling this method more than once will panic. 521 | func (a *Application) Wait() error { 522 | err := a.exitError 523 | a.onceWait.Do(func() { err = <-a.wait }) 524 | return err 525 | } 526 | -------------------------------------------------------------------------------- /dbg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build debug.wm 6 | 7 | package wm 8 | 9 | const debug = true 10 | -------------------------------------------------------------------------------- /dbg_demo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build ignore 6 | 7 | // $ go run demo.go 8 | package main 9 | 10 | import ( 11 | "flag" 12 | "fmt" 13 | "log" 14 | "math/rand" 15 | "time" 16 | 17 | "github.com/cznic/wm" 18 | "github.com/cznic/wm/internal/demoapp" 19 | "github.com/gdamore/tcell" 20 | "github.com/golang/glog" 21 | ) 22 | 23 | const ( 24 | block = tcell.RuneBlock 25 | ) 26 | 27 | var ( 28 | theme = &wm.Theme{ 29 | ChildWindow: wm.WindowStyle{ 30 | Border: wm.Style{Background: tcell.ColorFuchsia, Foreground: tcell.ColorGreen}, 31 | ClientArea: wm.Style{Background: tcell.ColorSilver, Foreground: tcell.ColorYellow}, 32 | Title: wm.Style{Background: tcell.ColorGreen, Foreground: tcell.ColorRed}, 33 | }, 34 | Desktop: wm.WindowStyle{ 35 | ClientArea: wm.Style{Background: tcell.ColorBlue, Foreground: tcell.ColorWhite}, 36 | }, 37 | } 38 | mouseMoveWindow *wm.Window 39 | mousePos wm.Position 40 | ) 41 | 42 | func rndColor() tcell.Color { 43 | for { 44 | c := tcell.Color(rand.Intn(wm.App.Colors())) 45 | if c != demoapp.Theme.ChildWindow.ClientArea.Background { 46 | return c 47 | } 48 | } 49 | } 50 | 51 | func newWindow(parent *wm.Window, x, y int) { 52 | a := parent.Size() 53 | if x < 0 || y < 0 { 54 | x = rand.Intn(a.Width - a.Width/5) 55 | y = rand.Intn(a.Height - a.Height/5) 56 | } 57 | w := rand.Intn(a.Width/2) + 10 58 | h := rand.Intn(a.Height/2) + 10 59 | c := parent.NewChild(wm.Rectangle{wm.Position{x, y}, wm.Size{w, h}}) 60 | style := tcell.Style(0).Foreground(rndColor()) 61 | c.SetCloseButton(true) 62 | c.SetTitle(time.Now().Format("15:04:05")) 63 | c.OnPaintClientArea( 64 | func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 65 | if prev != nil { 66 | prev(w, nil, ctx) 67 | } 68 | 69 | w.Printf(0, 0, w.ClientAreaStyle(), "abc %v %v %v", c.Position(), c.Size(), w.ClientArea()) 70 | w.Printf(0, 1, w.ClientAreaStyle(), "def %v %v %v", c.Position(), c.Size(), w.ClientArea()) 71 | w.Printf(0, 2, w.ClientAreaStyle(), "ghi %v %v %v", c.Position(), c.Size(), w.ClientArea()) 72 | w.SetCell(c.ClientArea().Size.Width/2, c.ClientArea().Size.Height/2, block, nil, style) 73 | }, 74 | nil, 75 | ) 76 | c.OnClick( 77 | func(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 78 | if prev != nil && prev(w, nil, button, screenPos, winPos, mods) { 79 | return true 80 | } 81 | 82 | switch { 83 | case mods&tcell.ModCtrl != 0: 84 | newWindow(w, winPos.X, winPos.Y) 85 | return true 86 | default: 87 | style = tcell.Style(0).Foreground(rndColor()) 88 | x, y = winPos.X, winPos.Y 89 | return true 90 | } 91 | }, 92 | nil, 93 | ) 94 | c.OnMouseMove(onMouseMove, nil) 95 | c.SetFocus(true) 96 | } 97 | 98 | func onMouseMove(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 99 | mousePos = winPos 100 | mouseMoveWindow = w 101 | if prev != nil { 102 | return prev(w, nil, button, screenPos, winPos, mods) 103 | } 104 | 105 | return true 106 | } 107 | 108 | func setup(d *wm.Desktop) { 109 | app := wm.App 110 | app.SetDoubleClickDuration(0) 111 | r := d.Root() 112 | r.OnPaintClientArea( 113 | func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 114 | if prev != nil { 115 | prev(w, nil, ctx) 116 | } 117 | 118 | mousePosStr := "" 119 | if w == mouseMoveWindow { 120 | mousePosStr = fmt.Sprintf("Mouse: %+v", mousePos) 121 | } 122 | w.Printf(0, 0, w.ClientAreaStyle(), 123 | `Ctrl-N to create a new random window. 124 | Ctrl-click inside a child window to create a nested random window. 125 | Use mouse to bring to front, drag, resize or close a window. 126 | Arrow keys change the viewport of the focused window. 127 | To focus the desktop, click on it. 128 | or to quit. 129 | Rendered in %s. 130 | %s`, r.Rendered(), mousePosStr) 131 | }, 132 | nil, 133 | ) 134 | r.OnMouseMove(onMouseMove, nil) 135 | app.OnKey( 136 | func(w *wm.Window, prev wm.OnKeyHandler, key tcell.Key, mod tcell.ModMask, r rune) bool { 137 | switch r { 138 | case 'q', 'Q': 139 | app.Exit(nil) 140 | return true 141 | } 142 | 143 | switch key { 144 | case tcell.KeyESC: 145 | app.Exit(nil) 146 | return true 147 | case tcell.KeyCtrlQ: 148 | app.Exit(nil) 149 | return true 150 | case tcell.KeyCtrlN: 151 | glog.Info("==== CTRL-N") 152 | newWindow(app.Desktop().Root(), -1, -1) 153 | return true 154 | case tcell.KeyLeft: 155 | w := d.FocusedWindow() 156 | if w == nil { 157 | return true 158 | } 159 | 160 | o := w.Origin() 161 | w.SetOrigin(wm.Position{X: o.X - 1, Y: o.Y}) 162 | return true 163 | case tcell.KeyRight: 164 | w := d.FocusedWindow() 165 | if w == nil { 166 | return true 167 | } 168 | 169 | o := w.Origin() 170 | w.SetOrigin(wm.Position{X: o.X + 1, Y: o.Y}) 171 | return true 172 | case tcell.KeyUp: 173 | w := d.FocusedWindow() 174 | if w == nil { 175 | return true 176 | } 177 | 178 | o := w.Origin() 179 | w.SetOrigin(wm.Position{X: o.X, Y: o.Y - 1}) 180 | return true 181 | case tcell.KeyDown: 182 | w := d.FocusedWindow() 183 | if w == nil { 184 | return true 185 | } 186 | 187 | o := w.Origin() 188 | w.SetOrigin(wm.Position{X: o.X, Y: o.Y + 1}) 189 | return true 190 | case tcell.KeyF2: 191 | w := d.FocusedWindow() 192 | if w == nil { 193 | return true 194 | } 195 | 196 | w.Invalidate(wm.NewRectangle(1, 1, 11, 11)) 197 | return true 198 | case tcell.KeyF3: 199 | w := d.FocusedWindow() 200 | if w == nil { 201 | return true 202 | } 203 | 204 | w.InvalidateClientArea(wm.NewRectangle(1, 1, 11, 11)) 205 | return true 206 | default: 207 | return false 208 | } 209 | }, 210 | nil, 211 | ) 212 | d.Show() 213 | } 214 | 215 | func main() { 216 | flag.Parse() 217 | rand.Seed(time.Now().UnixNano()) 218 | app, d := demoapp.New() 219 | if err := app.Run(func() { setup(d) }); err != nil { 220 | log.Fatal(err) 221 | } 222 | 223 | glog.Flush() 224 | } 225 | -------------------------------------------------------------------------------- /demo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build ignore 6 | 7 | // $ go run demo.go 8 | package main 9 | 10 | import ( 11 | "flag" 12 | "fmt" 13 | "log" 14 | "math/rand" 15 | "time" 16 | 17 | "github.com/cznic/wm" 18 | "github.com/cznic/wm/internal/demoapp" 19 | "github.com/gdamore/tcell" 20 | ) 21 | 22 | const ( 23 | block = tcell.RuneBlock 24 | ) 25 | 26 | var ( 27 | theme = &wm.Theme{ 28 | ChildWindow: wm.WindowStyle{ 29 | Border: wm.Style{Background: tcell.ColorFuchsia, Foreground: tcell.ColorGreen}, 30 | ClientArea: wm.Style{Background: tcell.ColorSilver, Foreground: tcell.ColorYellow}, 31 | Title: wm.Style{Background: tcell.ColorGreen, Foreground: tcell.ColorRed}, 32 | }, 33 | Desktop: wm.WindowStyle{ 34 | ClientArea: wm.Style{Background: tcell.ColorBlue, Foreground: tcell.ColorWhite}, 35 | }, 36 | } 37 | mouseMoveWindow *wm.Window 38 | mousePos wm.Position 39 | ) 40 | 41 | func rndColor() tcell.Color { 42 | for { 43 | c := tcell.Color(rand.Intn(wm.App.Colors())) 44 | if c != demoapp.Theme.ChildWindow.ClientArea.Background { 45 | return c 46 | } 47 | } 48 | } 49 | 50 | func newWindow(parent *wm.Window, x, y int) { 51 | a := parent.Size() 52 | if x < 0 || y < 0 { 53 | x = rand.Intn(a.Width - a.Width/5) 54 | y = rand.Intn(a.Height - a.Height/5) 55 | } 56 | w := rand.Intn(a.Width/2) + 10 57 | h := rand.Intn(a.Height/2) + 10 58 | c := parent.NewChild(wm.Rectangle{wm.Position{x, y}, wm.Size{w, h}}) 59 | x, y = 0, 0 60 | dx, dy := 1, 1 61 | t := time.NewTicker(time.Millisecond * time.Duration(35+rand.Intn(10))) 62 | style := tcell.Style(0).Foreground(rndColor()) 63 | c.SetCloseButton(true) 64 | c.SetTitle(time.Now().Format("15:04:05")) 65 | c.OnPaintClientArea( 66 | func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 67 | if prev != nil { 68 | prev(w, nil, ctx) 69 | } 70 | 71 | select { 72 | case <-t.C: 73 | view := w.Origin() 74 | a := w.ClientArea() 75 | a.Position.X += view.X 76 | a.Position.Y += view.Y 77 | if x >= a.Width-1 { 78 | if x > a.Width { 79 | x = a.Width 80 | } 81 | dx = -1 82 | } else if x <= 0 { 83 | if x < 0 { 84 | x = -1 85 | } 86 | dx = 1 87 | } 88 | if y >= a.Height-1 { 89 | if y > a.Height { 90 | y = a.Height 91 | } 92 | dy = -1 93 | } else if y <= 0 { 94 | if y < 0 { 95 | y = -1 96 | } 97 | dy = 1 98 | } 99 | 100 | x += dx 101 | y += dy 102 | default: 103 | } 104 | if w == mouseMoveWindow { 105 | w.Printf(0, 0, w.ClientAreaStyle(), "Mouse: %+v", mousePos) 106 | } 107 | w.SetCell(x, y, block, nil, style) 108 | }, 109 | nil, 110 | ) 111 | c.OnClose( 112 | func(w *wm.Window, prev wm.OnCloseHandler) { 113 | if prev != nil { 114 | prev(w, nil) 115 | } 116 | t.Stop() 117 | }, 118 | nil, 119 | ) 120 | c.OnClick( 121 | func(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 122 | if prev != nil && prev(w, nil, button, screenPos, winPos, mods) { 123 | return true 124 | } 125 | 126 | switch { 127 | case mods&tcell.ModCtrl != 0: 128 | newWindow(w, winPos.X, winPos.Y) 129 | return true 130 | default: 131 | style = tcell.Style(0).Foreground(rndColor()) 132 | x, y = winPos.X, winPos.Y 133 | return true 134 | } 135 | }, 136 | nil, 137 | ) 138 | c.OnMouseMove(onMouseMove, nil) 139 | c.SetFocus(true) 140 | } 141 | 142 | func onMouseMove(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 143 | mousePos = winPos 144 | mouseMoveWindow = w 145 | if prev != nil { 146 | return prev(w, nil, button, screenPos, winPos, mods) 147 | } 148 | 149 | return true 150 | } 151 | 152 | func setup(d *wm.Desktop) { 153 | app := wm.App 154 | app.SetDoubleClickDuration(0) 155 | r := d.Root() 156 | var renderedIn time.Duration 157 | r.OnPaintClientArea( 158 | func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 159 | if prev != nil { 160 | prev(w, nil, ctx) 161 | } 162 | 163 | mousePosStr := "" 164 | if w == mouseMoveWindow { 165 | mousePosStr = fmt.Sprintf("Mouse: %+v", mousePos) 166 | } 167 | w.Printf(0, 0, w.ClientAreaStyle(), 168 | `Ctrl-N to create a new random window. 169 | Ctrl-click inside a child window to create a nested random window. 170 | Use mouse to bring to front, drag, resize or close a window. 171 | Arrow keys change the viewport of the focused window. 172 | To focus the desktop, click on it. 173 | or to quit. 174 | Rendered in %s. 175 | %s`, renderedIn, mousePosStr) 176 | }, 177 | nil, 178 | ) 179 | r.OnMouseMove(onMouseMove, nil) 180 | app.OnKey( 181 | func(w *wm.Window, prev wm.OnKeyHandler, key tcell.Key, mod tcell.ModMask, r rune) bool { 182 | switch r { 183 | case 'q', 'Q': 184 | app.Exit(nil) 185 | return true 186 | } 187 | 188 | switch key { 189 | case tcell.KeyESC: 190 | app.Exit(nil) 191 | return true 192 | case tcell.KeyCtrlN: 193 | newWindow(app.Desktop().Root(), -1, -1) 194 | return true 195 | case tcell.KeyLeft: 196 | w := d.FocusedWindow() 197 | if w == nil { 198 | return true 199 | } 200 | 201 | o := w.Origin() 202 | w.SetOrigin(wm.Position{X: o.X - 1, Y: o.Y}) 203 | return true 204 | case tcell.KeyRight: 205 | w := d.FocusedWindow() 206 | if w == nil { 207 | return true 208 | } 209 | 210 | o := w.Origin() 211 | w.SetOrigin(wm.Position{X: o.X + 1, Y: o.Y}) 212 | return true 213 | case tcell.KeyUp: 214 | w := d.FocusedWindow() 215 | if w == nil { 216 | return true 217 | } 218 | 219 | o := w.Origin() 220 | w.SetOrigin(wm.Position{X: o.X, Y: o.Y - 1}) 221 | return true 222 | case tcell.KeyDown: 223 | w := d.FocusedWindow() 224 | if w == nil { 225 | return true 226 | } 227 | 228 | o := w.Origin() 229 | w.SetOrigin(wm.Position{X: o.X, Y: o.Y + 1}) 230 | return true 231 | default: 232 | return false 233 | } 234 | }, 235 | nil, 236 | ) 237 | fps := 25 238 | i := 0 239 | go func() { 240 | for range time.NewTicker(time.Second / time.Duration(fps)).C { 241 | app.Post(func() { 242 | i = (i + 1) % fps 243 | r.Invalidate(r.Area()) 244 | if i == 0 { 245 | renderedIn = r.Rendered() 246 | } 247 | }) 248 | } 249 | }() 250 | d.Show() 251 | } 252 | 253 | func main() { 254 | flag.Parse() 255 | rand.Seed(time.Now().UnixNano()) 256 | app, d := demoapp.New() 257 | if err := app.Run(func() { setup(d) }); err != nil { 258 | log.Fatal(err) 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /desktop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wm 6 | 7 | // Desktop represents a virtual screen. An application has one or more 8 | // independent desktops, of which only one is visible at any given moment. 9 | // 10 | // A desktop initially contains only the automatically created root window. 11 | // 12 | // Desktop methods must be called only directly from an event handler goroutine 13 | // or from a function that was enqueued using Application.Post or 14 | // Application.PostWait. 15 | type Desktop struct { 16 | invalidated Rectangle // 17 | root *Window // Never changes. 18 | updateLevel int // 19 | } 20 | 21 | func newDesktop() *Desktop { 22 | d := &Desktop{} 23 | w := newWindow(d, nil, App.DesktopStyle()) 24 | d.root = w 25 | w.setSize(App.Size()) 26 | d.OnSetSelection(w.onSetSelectionHandler, nil) 27 | d.OnSetFocusedWindow(w.onSetFocusedWindowHandler, nil) 28 | return d 29 | } 30 | 31 | // ---------------------------------------------------------------------------- 32 | 33 | // FocusedWindow returns the window with focus, if any. 34 | func (d *Desktop) FocusedWindow() *Window { 35 | r := d.root 36 | if r == nil { 37 | return nil 38 | } 39 | 40 | return r.focusedWindow 41 | } 42 | 43 | // OnSetFocusedWindow sets a handler invoked on SetFocusedWindow. When the 44 | // event handler is removed, finalize is called, if not nil. 45 | func (d *Desktop) OnSetFocusedWindow(h OnSetWindowHandler, finalize func()) { 46 | r := d.Root() 47 | if r == nil { 48 | return 49 | } 50 | 51 | addOnSetWindowHandler(&r.onSetFocusedWindow, h, finalize) 52 | } 53 | 54 | // OnSetSelection sets a handler invoked on SetSelection. When the event 55 | // handler is removed, finalize is called, if not nil. 56 | func (d *Desktop) OnSetSelection(h OnSetRectangleHandler, finalize func()) { 57 | r := d.Root() 58 | if r == nil { 59 | return 60 | } 61 | 62 | addOnSetRectangleHandler(&r.onSetSelection, h, finalize) 63 | } 64 | 65 | // RemoveOnSetFocusedWindow undoes the most recent OnSetFocusedWindow call. The 66 | // function will panic if there is no handler set. 67 | func (d *Desktop) RemoveOnSetFocusedWindow() { 68 | r := d.Root() 69 | if r == nil { 70 | return 71 | } 72 | 73 | removeOnSetWindowHandler(&r.onSetFocusedWindow) 74 | } 75 | 76 | // RemoveOnSetSelection undoes the most recent OnSetSelection call. The 77 | // function will panic if there is no handler set. 78 | func (d *Desktop) RemoveOnSetSelection() { 79 | r := d.Root() 80 | if r == nil { 81 | return 82 | } 83 | 84 | removeOnSetRectangleHandler(&r.onSetSelection) 85 | } 86 | 87 | // Root returns the root window of d. 88 | func (d *Desktop) Root() *Window { return d.root } 89 | 90 | // Selection returns the area of the desktop shown in reverse. 91 | func (d *Desktop) Selection() Rectangle { 92 | r := d.Root() 93 | if r == nil { 94 | return Rectangle{} 95 | } 96 | 97 | return r.selection 98 | } 99 | 100 | // SetFocusedWindow sets the focused window. 101 | func (d *Desktop) SetFocusedWindow(w *Window) { 102 | r := d.root 103 | if r == nil { 104 | return 105 | } 106 | 107 | r.setFocusedWindow(w) 108 | } 109 | 110 | // SetSelection sets the area of the desktop shown in reverse. 111 | func (d *Desktop) SetSelection(area Rectangle) { 112 | r := d.Root() 113 | if r == nil { 114 | return 115 | } 116 | 117 | r.onSetSelection.handle(r, &r.selection, area) 118 | } 119 | 120 | // Show sets d as the application active desktop. 121 | func (d *Desktop) Show() { App.SetDesktop(d) } 122 | -------------------------------------------------------------------------------- /etc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wm 6 | 7 | import ( 8 | "github.com/cznic/interval" 9 | "github.com/cznic/mathutil" 10 | ) 11 | 12 | // Position represents 2D coordinates. 13 | type Position struct { 14 | X, Y int 15 | } 16 | 17 | func (p Position) add(q Position) Position { return Position{p.X + q.X, p.Y + q.Y} } 18 | func (p Position) sub(q Position) Position { return Position{p.X - q.X, p.Y - q.Y} } 19 | 20 | // In returns whether p is inside r. 21 | func (p Position) In(r Rectangle) bool { return r.Has(p) } 22 | 23 | // Rectangle represents a 2D area. 24 | type Rectangle struct { 25 | Position 26 | Size 27 | } 28 | 29 | // NewRectangle returns a Rectangle from 4 coordinates. 30 | func NewRectangle(x1, y1, x2, y2 int) Rectangle { 31 | if x1 > x2 { 32 | x1, x2 = x2, x1 33 | } 34 | if y1 > y2 { 35 | y1, y2 = y2, y1 36 | } 37 | return Rectangle{Position{x1, y1}, Size{x2 - x1 + 1, y2 - y1 + 1}} 38 | } 39 | 40 | // Clip sets r to the intersection of r and s and returns a boolean value indicating 41 | // whether the result is of non zero size. 42 | func (r *Rectangle) Clip(s Rectangle) bool { 43 | a := interval.Int{Cls: interval.LeftClosed, A: r.X, B: r.X + r.Width} 44 | b := interval.Int{Cls: interval.LeftClosed, A: s.X, B: s.X + s.Width} 45 | h0 := interval.Intersection(&a, &b) 46 | if h0.Class() == interval.Empty { 47 | return false 48 | } 49 | 50 | a = interval.Int{Cls: interval.LeftClosed, A: r.Y, B: r.Y + r.Height} 51 | b = interval.Int{Cls: interval.LeftClosed, A: s.Y, B: s.Y + s.Height} 52 | v0 := interval.Intersection(&a, &b) 53 | if v0.Class() == interval.Empty { 54 | return false 55 | } 56 | 57 | h := h0.(*interval.Int) 58 | v := v0.(*interval.Int) 59 | var y Rectangle 60 | y.X = h.A 61 | y.Y = v.A 62 | y.Width = h.B - h.A 63 | y.Height = v.B - v.A 64 | *r = y 65 | return true 66 | } 67 | 68 | func (r *Rectangle) join(s Rectangle) { 69 | if s.IsZero() { 70 | return 71 | } 72 | 73 | if r.IsZero() { 74 | *r = s 75 | return 76 | } 77 | 78 | x := mathutil.Min(r.X, s.X) 79 | r.Width = mathutil.Max(r.X+r.Width, s.X+s.Width) - x 80 | y := mathutil.Min(r.Y, s.Y) 81 | r.Height = mathutil.Max(r.Y+r.Height, s.Y+s.Height) - y 82 | r.Position = Position{x, y} 83 | } 84 | 85 | // Has returns whether r contains p. 86 | func (r *Rectangle) Has(p Position) bool { 87 | return p.X >= r.X && p.X < r.X+r.Width && 88 | p.Y >= r.Y && p.Y < r.Y+r.Height 89 | } 90 | 91 | // Size represents 2D dimensions. 92 | type Size struct { 93 | Width, Height int 94 | } 95 | 96 | func newSize(w, h int) Size { return Size{w, h} } 97 | 98 | // IsZero returns whether s.Width or s.Height is zero. 99 | func (s *Size) IsZero() bool { return s.Width <= 0 || s.Height <= 0 } 100 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wm 6 | 7 | import ( 8 | "sync" 9 | "time" 10 | 11 | "github.com/gdamore/tcell" 12 | ) 13 | 14 | var ( 15 | _ tcell.Event = (*eventMouse)(nil) 16 | _ tcell.Event = (*eventFunc)(nil) 17 | ) 18 | 19 | var ( 20 | eventFuncPool = sync.Pool{New: func() interface{} { return &eventFunc{} }} 21 | eventMousePool = sync.Pool{New: func() interface{} { return &eventMouse{} }} 22 | ) 23 | 24 | type event struct{} 25 | 26 | func (e event) When() time.Time { return time.Time{} } 27 | 28 | const ( 29 | _ = iota //TODOOK 30 | mouseClick 31 | mouseDoubleClick 32 | mouseDrag 33 | mouseDrop 34 | mouseMove 35 | ) 36 | 37 | type eventMouse struct { 38 | Position 39 | button tcell.ButtonMask 40 | event 41 | kind int 42 | mods tcell.ModMask 43 | } 44 | 45 | func newEventMouse(kind int, button tcell.ButtonMask, mods tcell.ModMask, pos Position) *eventMouse { 46 | e := eventMousePool.Get().(*eventMouse) 47 | e.Position = pos 48 | e.button = button 49 | e.kind = kind 50 | e.mods = mods 51 | return e 52 | } 53 | 54 | func (e *eventMouse) dispose() { 55 | *e = eventMouse{} 56 | eventMousePool.Put(e) 57 | } 58 | 59 | type eventFunc struct { 60 | event 61 | f func() 62 | } 63 | 64 | func newEventFunc(f func()) *eventFunc { 65 | e := eventFuncPool.Get().(*eventFunc) 66 | e.f = f 67 | return e 68 | } 69 | 70 | func (e *eventFunc) dispose() { 71 | *e = eventFunc{} 72 | eventFuncPool.Put(e) 73 | } 74 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wm 6 | 7 | import ( 8 | "github.com/gdamore/tcell" 9 | "time" 10 | ) 11 | 12 | // PaintContext represents the context passed to paint handlers. 13 | type PaintContext struct { 14 | Rectangle 15 | origin Position 16 | view Position 17 | } 18 | 19 | // OnCloseHandler is called on window close. If there was a previous handler 20 | // installed, it's passed in prev. The handler then has the opportunity to call 21 | // the previous handler before or after its own execution. 22 | type OnCloseHandler func(w *Window, prev OnCloseHandler) 23 | 24 | type onCloseHandlerList struct { 25 | prev *onCloseHandlerList 26 | h OnCloseHandler 27 | finalizer func() 28 | } 29 | 30 | func addOnCloseHandler(l **onCloseHandlerList, h OnCloseHandler, finalizer func()) { 31 | prev := *l 32 | if prev == nil { 33 | *l = &onCloseHandlerList{ 34 | h: h, 35 | finalizer: finalizer, 36 | } 37 | return 38 | } 39 | 40 | *l = &onCloseHandlerList{ 41 | prev: prev, 42 | h: func(w *Window, _ OnCloseHandler) { 43 | h(w, prev.h) 44 | }, 45 | finalizer: finalizer, 46 | } 47 | } 48 | 49 | func (l *onCloseHandlerList) clear() { 50 | for l != nil { 51 | if f := l.finalizer; f != nil { 52 | f() 53 | } 54 | l = l.prev 55 | } 56 | } 57 | 58 | func (l *onCloseHandlerList) handle(w *Window) { 59 | if l != nil { 60 | w.BeginUpdate() 61 | l.h(w, nil) 62 | w.EndUpdate() 63 | return 64 | } 65 | 66 | } 67 | 68 | func removeOnCloseHandler(l **onCloseHandlerList) { 69 | node := *l 70 | *l = node.prev 71 | if f := node.finalizer; f != nil { 72 | f() 73 | } 74 | } 75 | 76 | // OnKeyHandler handles key events. If there was a previous handler installed, 77 | // it's passed in prev. The handler then has the opportunity to call the 78 | // previous handler before or after its own execution. The handler should 79 | // return true if it consumes the event and it should not be considered by 80 | // other subscribed handlers. 81 | // 82 | type OnKeyHandler func(w *Window, prev OnKeyHandler, key tcell.Key, mod tcell.ModMask, r rune) bool 83 | 84 | type onKeyHandlerList struct { 85 | prev *onKeyHandlerList 86 | h OnKeyHandler 87 | finalizer func() 88 | } 89 | 90 | func addOnKeyHandler(l **onKeyHandlerList, h OnKeyHandler, finalizer func()) { 91 | prev := *l 92 | if prev == nil { 93 | *l = &onKeyHandlerList{ 94 | h: h, 95 | finalizer: finalizer, 96 | } 97 | return 98 | } 99 | 100 | *l = &onKeyHandlerList{ 101 | prev: prev, 102 | h: func(w *Window, _ OnKeyHandler, key tcell.Key, mod tcell.ModMask, r rune) bool { 103 | return h(w, prev.h, key, mod, r) 104 | }, 105 | finalizer: finalizer, 106 | } 107 | } 108 | 109 | func (l *onKeyHandlerList) clear() { 110 | for l != nil { 111 | if f := l.finalizer; f != nil { 112 | f() 113 | } 114 | l = l.prev 115 | } 116 | } 117 | 118 | func (l *onKeyHandlerList) handle(w *Window, key tcell.Key, mod tcell.ModMask, r rune) bool { 119 | if l != nil { 120 | w.BeginUpdate() 121 | r := l.h(w, nil, key, mod, r) 122 | w.EndUpdate() 123 | return r 124 | } 125 | 126 | return false 127 | } 128 | 129 | func removeOnKeyHandler(l **onKeyHandlerList) { 130 | node := *l 131 | *l = node.prev 132 | if f := node.finalizer; f != nil { 133 | f() 134 | } 135 | } 136 | 137 | // OnMouseHandler handles mouse events. If there was a previous handler 138 | // installed, it's passed in prev. The handler then has the opportunity to call 139 | // the previous handler before or after its own execution. The handler should 140 | // return true if it consumes the event and it should not be considered by 141 | // other subscribed handlers. 142 | type OnMouseHandler func(w *Window, prev OnMouseHandler, button tcell.ButtonMask, screenPos, winPos Position, mods tcell.ModMask) bool 143 | 144 | // OnMouseHandlerList represents a list of handlers subscribed to an event. 145 | type OnMouseHandlerList struct { 146 | prev *OnMouseHandlerList 147 | h OnMouseHandler 148 | finalizer func() 149 | } 150 | 151 | // AddOnMouseHandler adds a handler to the handler list. 152 | func AddOnMouseHandler(l **OnMouseHandlerList, h OnMouseHandler, finalizer func()) { 153 | prev := *l 154 | if prev == nil { 155 | *l = &OnMouseHandlerList{ 156 | h: h, 157 | finalizer: finalizer, 158 | } 159 | return 160 | } 161 | 162 | *l = &OnMouseHandlerList{ 163 | prev: prev, 164 | h: func(w *Window, _ OnMouseHandler, button tcell.ButtonMask, screenPos, winPos Position, mods tcell.ModMask) bool { 165 | return h(w, prev.h, button, screenPos, winPos, mods) 166 | }, 167 | finalizer: finalizer, 168 | } 169 | } 170 | 171 | // Clear calls any finalizers on the handler list. 172 | func (l *OnMouseHandlerList) Clear() { 173 | for l != nil { 174 | if f := l.finalizer; f != nil { 175 | f() 176 | } 177 | l = l.prev 178 | } 179 | } 180 | 181 | // Handle performs handling mouse events. 182 | func (l *OnMouseHandlerList) Handle(w *Window, button tcell.ButtonMask, screenPos, winPos Position, mods tcell.ModMask) bool { 183 | if l != nil { 184 | w.BeginUpdate() 185 | r := l.h(w, nil, button, screenPos, winPos, mods) 186 | w.EndUpdate() 187 | return r 188 | } 189 | 190 | return false 191 | } 192 | 193 | // RemoveOnMouseHandler undoes the most recent call to AddOnMouseHandler. 194 | func RemoveOnMouseHandler(l **OnMouseHandlerList) { 195 | node := *l 196 | *l = node.prev 197 | if f := node.finalizer; f != nil { 198 | f() 199 | } 200 | } 201 | 202 | // OnPaintHandler handles paint requests. If there was a previous handler 203 | // installed, it's passed in prev. The handler then has the opportunity to call 204 | // the previous handler before or after its own execution. 205 | // 206 | // OnPaint handlers can safely try to modify window cells outside of the area 207 | // argument as such attempts will be silently ignored. In other words, an 208 | // OnPaintHandler cannot affect any window cell outside of the area argument. 209 | type OnPaintHandler func(w *Window, prev OnPaintHandler, ctx PaintContext) 210 | 211 | // OnPaintHandlerList represents a list of handlers subscribed to an event. 212 | type OnPaintHandlerList struct { 213 | prev *OnPaintHandlerList 214 | h OnPaintHandler 215 | finalizer func() 216 | } 217 | 218 | // AddOnPaintHandler adds a handler to the handler list. 219 | func AddOnPaintHandler(l **OnPaintHandlerList, h OnPaintHandler, finalizer func()) { 220 | prev := *l 221 | if prev == nil { 222 | *l = &OnPaintHandlerList{ 223 | h: func(w *Window, _ OnPaintHandler, ctx PaintContext) { 224 | save := w.ctx 225 | w.ctx = ctx 226 | h(w, nil, ctx) 227 | w.ctx = save 228 | }, 229 | finalizer: finalizer, 230 | } 231 | return 232 | } 233 | 234 | *l = &OnPaintHandlerList{ 235 | prev: prev, 236 | h: func(w *Window, _ OnPaintHandler, ctx PaintContext) { 237 | save := w.ctx 238 | w.ctx = ctx 239 | h(w, prev.h, ctx) 240 | w.ctx = save 241 | }, 242 | finalizer: finalizer, 243 | } 244 | } 245 | 246 | // Clear calls any finalizers on the handler list. 247 | func (l *OnPaintHandlerList) Clear() { 248 | for l != nil { 249 | if f := l.finalizer; f != nil { 250 | f() 251 | } 252 | l = l.prev 253 | } 254 | } 255 | 256 | // Handle performs painting of ctx. 257 | func (l *OnPaintHandlerList) Handle(w *Window, ctx PaintContext) { 258 | if l == nil || ctx.IsZero() { 259 | return 260 | } 261 | 262 | l.h(w, nil, ctx) 263 | } 264 | 265 | // RemoveOnPaintHandler undoes the most recent call to AddOnPaintHandler. 266 | func RemoveOnPaintHandler(l **OnPaintHandlerList) { 267 | node := *l 268 | *l = node.prev 269 | if f := node.finalizer; f != nil { 270 | f() 271 | } 272 | } 273 | 274 | // OnSetDesktopHandler handles requests to change values of type *Desktop. If 275 | // there was a previous handler installed, it's passed in prev. The handler 276 | // then has the opportunity to call the previous handler before or after its 277 | // own execution. 278 | type OnSetDesktopHandler func(w *Window, prev OnSetDesktopHandler, dst **Desktop, src *Desktop) 279 | 280 | type onSetDesktopHandlerList struct { 281 | prev *onSetDesktopHandlerList 282 | h OnSetDesktopHandler 283 | finalizer func() 284 | } 285 | 286 | func addOnSetDesktopHandler(l **onSetDesktopHandlerList, h OnSetDesktopHandler, finalizer func()) { 287 | prev := *l 288 | if prev == nil { 289 | *l = &onSetDesktopHandlerList{ 290 | h: h, 291 | finalizer: finalizer, 292 | } 293 | return 294 | } 295 | 296 | *l = &onSetDesktopHandlerList{ 297 | prev: prev, 298 | h: func(w *Window, _ OnSetDesktopHandler, dst **Desktop, src *Desktop) { 299 | h(w, prev.h, dst, src) 300 | }, 301 | finalizer: finalizer, 302 | } 303 | } 304 | 305 | func (l *onSetDesktopHandlerList) clear() { 306 | for l != nil { 307 | if f := l.finalizer; f != nil { 308 | f() 309 | } 310 | l = l.prev 311 | } 312 | } 313 | 314 | func (l *onSetDesktopHandlerList) handle(w *Window, dst **Desktop, src *Desktop) { 315 | if *dst == src { 316 | return 317 | } 318 | 319 | if l == nil { 320 | *dst = src 321 | return 322 | } 323 | 324 | w.BeginUpdate() 325 | l.h(w, nil, dst, src) 326 | w.EndUpdate() 327 | } 328 | 329 | func removeOnSetDesktopHandler(l **onSetDesktopHandlerList) { 330 | node := *l 331 | *l = node.prev 332 | if f := node.finalizer; f != nil { 333 | f() 334 | } 335 | 336 | } 337 | 338 | // OnSetDurationHandler handles requests to change values of type time.Duration. 339 | // If there was a previous handler installed, it's passed in prev. The handler 340 | // then has the opportunity to call the previous handler before or after its 341 | // own execution. 342 | type OnSetDurationHandler func(w *Window, prev OnSetDurationHandler, dst *time.Duration, src time.Duration) 343 | 344 | type onSetDurationHandlerList struct { 345 | prev *onSetDurationHandlerList 346 | h OnSetDurationHandler 347 | finalizer func() 348 | } 349 | 350 | func addOnSetDurationHandler(l **onSetDurationHandlerList, h OnSetDurationHandler, finalizer func()) { 351 | prev := *l 352 | if prev == nil { 353 | *l = &onSetDurationHandlerList{ 354 | h: h, 355 | finalizer: finalizer, 356 | } 357 | return 358 | } 359 | 360 | *l = &onSetDurationHandlerList{ 361 | prev: prev, 362 | h: func(w *Window, _ OnSetDurationHandler, dst *time.Duration, src time.Duration) { 363 | h(w, prev.h, dst, src) 364 | }, 365 | finalizer: finalizer, 366 | } 367 | } 368 | 369 | func (l *onSetDurationHandlerList) clear() { 370 | for l != nil { 371 | if f := l.finalizer; f != nil { 372 | f() 373 | } 374 | l = l.prev 375 | } 376 | } 377 | 378 | func (l *onSetDurationHandlerList) handle(w *Window, dst *time.Duration, src time.Duration) { 379 | if *dst == src { 380 | return 381 | } 382 | 383 | if l == nil { 384 | *dst = src 385 | return 386 | } 387 | 388 | w.BeginUpdate() 389 | l.h(w, nil, dst, src) 390 | w.EndUpdate() 391 | } 392 | 393 | func removeOnSetDurationHandler(l **onSetDurationHandlerList) { 394 | node := *l 395 | *l = node.prev 396 | if f := node.finalizer; f != nil { 397 | f() 398 | } 399 | } 400 | 401 | // OnSetBoolHandler handles requests to change values of type bool. 402 | // If there was a previous handler installed, it's passed in prev. The handler 403 | // then has the opportunity to call the previous handler before or after its 404 | // own execution. 405 | type OnSetBoolHandler func(w *Window, prev OnSetBoolHandler, dst *bool, src bool) 406 | 407 | // OnSetBoolHandlerList represents a list of handlers subscribed to an event. 408 | type OnSetBoolHandlerList struct { 409 | prev *OnSetBoolHandlerList 410 | h OnSetBoolHandler 411 | finalizer func() 412 | } 413 | 414 | // AddOnSetBoolHandler adds a handler to the handler list. 415 | func AddOnSetBoolHandler(l **OnSetBoolHandlerList, h OnSetBoolHandler, finalizer func()) { 416 | prev := *l 417 | if prev == nil { 418 | *l = &OnSetBoolHandlerList{ 419 | h: h, 420 | finalizer: finalizer, 421 | } 422 | return 423 | } 424 | 425 | *l = &OnSetBoolHandlerList{ 426 | prev: prev, 427 | h: func(w *Window, _ OnSetBoolHandler, dst *bool, src bool) { 428 | h(w, prev.h, dst, src) 429 | }, 430 | finalizer: finalizer, 431 | } 432 | } 433 | 434 | // Clear calls any finalizers on the handler list. 435 | func (l *OnSetBoolHandlerList) Clear() { 436 | for l != nil { 437 | if f := l.finalizer; f != nil { 438 | f() 439 | } 440 | l = l.prev 441 | } 442 | } 443 | 444 | // Handle performs updating of dst from src or calling and associated handler. 445 | func (l *OnSetBoolHandlerList) Handle(w *Window, dst *bool, src bool) { 446 | if *dst == src { 447 | return 448 | } 449 | 450 | if l == nil { 451 | *dst = src 452 | return 453 | } 454 | 455 | w.BeginUpdate() 456 | l.h(w, nil, dst, src) 457 | w.EndUpdate() 458 | } 459 | 460 | // RemoveOnSetBoolHandler undoes the most recent call to AddOnSetBoolHandler. 461 | func RemoveOnSetBoolHandler(l **OnSetBoolHandlerList) { 462 | node := *l 463 | *l = node.prev 464 | if f := node.finalizer; f != nil { 465 | f() 466 | } 467 | } 468 | 469 | // OnSetIntHandler handles requests to change values of type int. If there 470 | // was a previous handler installed, it's passed in prev. The handler then has 471 | // the opportunity to call the previous handler before or after its own 472 | // execution. 473 | type OnSetIntHandler func(w *Window, prev OnSetIntHandler, dst *int, src int) 474 | 475 | // OnSetIntHandlerList represents a list of handlers subscribed to an event. 476 | type OnSetIntHandlerList struct { 477 | prev *OnSetIntHandlerList 478 | h OnSetIntHandler 479 | finalizer func() 480 | } 481 | 482 | // AddOnSetIntHandler adds a handler to the handler list. 483 | func AddOnSetIntHandler(l **OnSetIntHandlerList, h OnSetIntHandler, finalizer func()) { 484 | prev := *l 485 | if prev == nil { 486 | *l = &OnSetIntHandlerList{ 487 | h: h, 488 | finalizer: finalizer, 489 | } 490 | return 491 | } 492 | 493 | *l = &OnSetIntHandlerList{ 494 | prev: prev, 495 | h: func(w *Window, _ OnSetIntHandler, dst *int, src int) { 496 | h(w, prev.h, dst, src) 497 | }, 498 | finalizer: finalizer, 499 | } 500 | } 501 | 502 | // Clear calls any finalizers on the handler list. 503 | func (l *OnSetIntHandlerList) Clear() { 504 | for l != nil { 505 | if f := l.finalizer; f != nil { 506 | f() 507 | } 508 | l = l.prev 509 | } 510 | } 511 | 512 | // Handle performs updating of dst from src or calling and associated handler. 513 | func (l *OnSetIntHandlerList) Handle(w *Window, dst *int, src int) { 514 | if *dst == src { 515 | return 516 | } 517 | 518 | if l == nil { 519 | *dst = src 520 | return 521 | } 522 | 523 | w.BeginUpdate() 524 | l.h(w, nil, dst, src) 525 | w.EndUpdate() 526 | } 527 | 528 | // RemoveOnSetIntHandler undoes the most recent call to AddOnSetIntHandler. 529 | func RemoveOnSetIntHandler(l **OnSetIntHandlerList) { 530 | node := *l 531 | *l = node.prev 532 | if f := node.finalizer; f != nil { 533 | f() 534 | } 535 | } 536 | 537 | // OnSetPositionHandler handles requests to change values of type Position. If there 538 | // was a previous handler installed, it's passed in prev. The handler then has 539 | // the opportunity to call the previous handler before or after its own 540 | // execution. 541 | type OnSetPositionHandler func(w *Window, prev OnSetPositionHandler, dst *Position, src Position) 542 | 543 | // OnSetPositionHandlerList represents a list of handlers subscribed to an event. 544 | type OnSetPositionHandlerList struct { 545 | prev *OnSetPositionHandlerList 546 | h OnSetPositionHandler 547 | finalizer func() 548 | } 549 | 550 | // AddOnSetPositionHandler adds a handler to the handler list. 551 | func AddOnSetPositionHandler(l **OnSetPositionHandlerList, h OnSetPositionHandler, finalizer func()) { 552 | prev := *l 553 | if prev == nil { 554 | *l = &OnSetPositionHandlerList{ 555 | h: h, 556 | finalizer: finalizer, 557 | } 558 | return 559 | } 560 | 561 | *l = &OnSetPositionHandlerList{ 562 | prev: prev, 563 | h: func(w *Window, _ OnSetPositionHandler, dst *Position, src Position) { 564 | h(w, prev.h, dst, src) 565 | }, 566 | finalizer: finalizer, 567 | } 568 | } 569 | 570 | // Clear calls any finalizers on the handler list. 571 | func (l *OnSetPositionHandlerList) Clear() { 572 | for l != nil { 573 | if f := l.finalizer; f != nil { 574 | f() 575 | } 576 | l = l.prev 577 | } 578 | } 579 | 580 | // Handle performs updating of dst from src or calling and associated handler. 581 | func (l *OnSetPositionHandlerList) Handle(w *Window, dst *Position, src Position) { 582 | if *dst == src { 583 | return 584 | } 585 | 586 | if l == nil { 587 | *dst = src 588 | return 589 | } 590 | 591 | w.BeginUpdate() 592 | l.h(w, nil, dst, src) 593 | w.EndUpdate() 594 | } 595 | 596 | // RemoveOnSetPositionHandler undoes the most recent call to 597 | // AddOnSetPositionHandler. 598 | func RemoveOnSetPositionHandler(l **OnSetPositionHandlerList) { 599 | node := *l 600 | *l = node.prev 601 | if f := node.finalizer; f != nil { 602 | f() 603 | } 604 | } 605 | 606 | // OnSetRectangleHandler handles requests to change values of type Rectangle. 607 | // If there was a previous handler installed, it's passed in prev. The handler 608 | // then has the opportunity to call the previous handler before or after its 609 | // own execution. 610 | type OnSetRectangleHandler func(w *Window, prev OnSetRectangleHandler, dst *Rectangle, src Rectangle) 611 | 612 | type onSetRectangleHandlerList struct { 613 | prev *onSetRectangleHandlerList 614 | h OnSetRectangleHandler 615 | finalizer func() 616 | } 617 | 618 | func addOnSetRectangleHandler(l **onSetRectangleHandlerList, h OnSetRectangleHandler, finalizer func()) { 619 | prev := *l 620 | if prev == nil { 621 | *l = &onSetRectangleHandlerList{ 622 | h: h, 623 | finalizer: finalizer, 624 | } 625 | return 626 | } 627 | 628 | *l = &onSetRectangleHandlerList{ 629 | prev: prev, 630 | h: func(w *Window, _ OnSetRectangleHandler, dst *Rectangle, src Rectangle) { 631 | h(w, prev.h, dst, src) 632 | }, 633 | finalizer: finalizer, 634 | } 635 | } 636 | 637 | func (l *onSetRectangleHandlerList) clear() { 638 | for l != nil { 639 | if f := l.finalizer; f != nil { 640 | f() 641 | } 642 | l = l.prev 643 | } 644 | } 645 | 646 | func (l *onSetRectangleHandlerList) handle(w *Window, dst *Rectangle, src Rectangle) { 647 | if *dst == src { 648 | return 649 | } 650 | 651 | if l == nil { 652 | *dst = src 653 | return 654 | } 655 | 656 | w.BeginUpdate() 657 | l.h(w, nil, dst, src) 658 | w.EndUpdate() 659 | } 660 | 661 | func removeOnSetRectangleHandler(l **onSetRectangleHandlerList) { 662 | node := *l 663 | *l = node.prev 664 | if f := node.finalizer; f != nil { 665 | f() 666 | } 667 | } 668 | 669 | // OnSetSizeHandler handles requests to change values of type Size. If there 670 | // was a previous handler installed, it's passed in prev. The handler then has 671 | // the opportunity to call the previous handler before or after its own 672 | // execution. 673 | type OnSetSizeHandler func(w *Window, prev OnSetSizeHandler, dst *Size, src Size) 674 | 675 | // OnSetSizeHandlerList represents a list of handlers subscribed to an event. 676 | type OnSetSizeHandlerList struct { 677 | prev *OnSetSizeHandlerList 678 | h OnSetSizeHandler 679 | finalizer func() 680 | } 681 | 682 | // AddOnSetSizeHandler adds a handler to the handler list. 683 | func AddOnSetSizeHandler(l **OnSetSizeHandlerList, h OnSetSizeHandler, finalizer func()) { 684 | prev := *l 685 | if prev == nil { 686 | *l = &OnSetSizeHandlerList{ 687 | h: h, 688 | finalizer: finalizer, 689 | } 690 | return 691 | } 692 | 693 | *l = &OnSetSizeHandlerList{ 694 | prev: prev, 695 | h: func(w *Window, _ OnSetSizeHandler, dst *Size, src Size) { 696 | h(w, prev.h, dst, src) 697 | }, 698 | finalizer: finalizer, 699 | } 700 | } 701 | 702 | // Clear calls any finalizers on the handler list. 703 | func (l *OnSetSizeHandlerList) Clear() { 704 | for l != nil { 705 | if f := l.finalizer; f != nil { 706 | f() 707 | } 708 | l = l.prev 709 | } 710 | } 711 | 712 | // Handle performs updating of dst from src or calling and associated handler. 713 | func (l *OnSetSizeHandlerList) Handle(w *Window, dst *Size, src Size) { 714 | if *dst == src { 715 | return 716 | } 717 | 718 | if l == nil { 719 | *dst = src 720 | return 721 | } 722 | 723 | w.BeginUpdate() 724 | l.h(w, nil, dst, src) 725 | w.EndUpdate() 726 | } 727 | 728 | // RemoveOnSetSizeHandler undoes the most recent call to AddOnSetSizeHandler. 729 | func RemoveOnSetSizeHandler(l **OnSetSizeHandlerList) { 730 | node := *l 731 | *l = node.prev 732 | if f := node.finalizer; f != nil { 733 | f() 734 | } 735 | } 736 | 737 | // OnSetStringHandler handles requests to change values of type String. If there 738 | // was a previous handler installed, it's passed in prev. The handler then has 739 | // the opportunity to call the previous handler before or after its own 740 | // execution. 741 | type OnSetStringHandler func(w *Window, prev OnSetStringHandler, dst *string, src string) 742 | 743 | type onSetStringHandlerList struct { 744 | prev *onSetStringHandlerList 745 | h OnSetStringHandler 746 | finalizer func() 747 | } 748 | 749 | func addOnSetStringHandler(l **onSetStringHandlerList, h OnSetStringHandler, finalizer func()) { 750 | prev := *l 751 | if prev == nil { 752 | *l = &onSetStringHandlerList{ 753 | h: h, 754 | finalizer: finalizer, 755 | } 756 | return 757 | } 758 | 759 | *l = &onSetStringHandlerList{ 760 | prev: prev, 761 | h: func(w *Window, _ OnSetStringHandler, dst *string, src string) { 762 | h(w, prev.h, dst, src) 763 | }, 764 | finalizer: finalizer, 765 | } 766 | } 767 | 768 | func (l *onSetStringHandlerList) clear() { 769 | for l != nil { 770 | if f := l.finalizer; f != nil { 771 | f() 772 | } 773 | l = l.prev 774 | } 775 | } 776 | 777 | func (l *onSetStringHandlerList) handle(w *Window, dst *string, src string) { 778 | if *dst == src { 779 | return 780 | } 781 | 782 | if l == nil { 783 | *dst = src 784 | return 785 | } 786 | 787 | w.BeginUpdate() 788 | l.h(w, nil, dst, src) 789 | w.EndUpdate() 790 | } 791 | 792 | func removeOnSetStringHandler(l **onSetStringHandlerList) { 793 | node := *l 794 | *l = node.prev 795 | if f := node.finalizer; f != nil { 796 | f() 797 | } 798 | } 799 | 800 | // OnSetStyleHandler handles requests to change values of type Style. If there 801 | // was a previous handler installed, it's passed in prev. The handler then has 802 | // the opportunity to call the previous handler before or after its own 803 | // execution. 804 | type OnSetStyleHandler func(w *Window, prev OnSetStyleHandler, dst *Style, src Style) 805 | 806 | // OnSetStyleHandlerList represents a list of handlers subscribed to an event. 807 | type OnSetStyleHandlerList struct { 808 | prev *OnSetStyleHandlerList 809 | h OnSetStyleHandler 810 | finalizer func() 811 | } 812 | 813 | // AddOnSetStyleHandler adds a handler to the handler list. 814 | func AddOnSetStyleHandler(l **OnSetStyleHandlerList, h OnSetStyleHandler, finalizer func()) { 815 | prev := *l 816 | if prev == nil { 817 | *l = &OnSetStyleHandlerList{ 818 | h: h, 819 | finalizer: finalizer, 820 | } 821 | return 822 | } 823 | 824 | *l = &OnSetStyleHandlerList{ 825 | prev: prev, 826 | h: func(w *Window, _ OnSetStyleHandler, dst *Style, src Style) { 827 | h(w, prev.h, dst, src) 828 | }, 829 | finalizer: finalizer, 830 | } 831 | } 832 | 833 | // Clear calls any finalizers on the handler list. 834 | func (l *OnSetStyleHandlerList) Clear() { 835 | for l != nil { 836 | if f := l.finalizer; f != nil { 837 | f() 838 | } 839 | l = l.prev 840 | } 841 | } 842 | 843 | // Handle performs updating of dst from src or calling and associated handler. 844 | func (l *OnSetStyleHandlerList) Handle(w *Window, dst *Style, src Style) { 845 | if *dst == src { 846 | return 847 | } 848 | 849 | if l == nil { 850 | *dst = src 851 | return 852 | } 853 | 854 | w.BeginUpdate() 855 | l.h(w, nil, dst, src) 856 | w.EndUpdate() 857 | } 858 | 859 | // RemoveOnSetStyleHandler undoes the most recent call to AddOnSetStyleHandler. 860 | func RemoveOnSetStyleHandler(l **OnSetStyleHandlerList) { 861 | node := *l 862 | *l = node.prev 863 | if f := node.finalizer; f != nil { 864 | f() 865 | } 866 | } 867 | 868 | // OnSetWindowHandler handles requests to change values of type *Window. If 869 | // there was a previous handler installed, it's passed in prev. The handler 870 | // then has the opportunity to call the previous handler before or after its 871 | // own execution. 872 | type OnSetWindowHandler func(w *Window, prev OnSetWindowHandler, dst **Window, src *Window) 873 | 874 | type onSetWindowHandlerList struct { 875 | prev *onSetWindowHandlerList 876 | h OnSetWindowHandler 877 | finalizer func() 878 | } 879 | 880 | func addOnSetWindowHandler(l **onSetWindowHandlerList, h OnSetWindowHandler, finalizer func()) { 881 | prev := *l 882 | if prev == nil { 883 | *l = &onSetWindowHandlerList{ 884 | h: h, 885 | finalizer: finalizer, 886 | } 887 | return 888 | } 889 | 890 | *l = &onSetWindowHandlerList{ 891 | prev: prev, 892 | h: func(w *Window, _ OnSetWindowHandler, dst **Window, src *Window) { 893 | h(w, prev.h, dst, src) 894 | }, 895 | finalizer: finalizer, 896 | } 897 | } 898 | 899 | func (l *onSetWindowHandlerList) clear() { 900 | for l != nil { 901 | if f := l.finalizer; f != nil { 902 | f() 903 | } 904 | l = l.prev 905 | } 906 | } 907 | 908 | func (l *onSetWindowHandlerList) handle(w *Window, dst **Window, src *Window) { 909 | if *dst == src { 910 | return 911 | } 912 | 913 | if l == nil { 914 | *dst = src 915 | return 916 | } 917 | 918 | w.BeginUpdate() 919 | l.h(w, nil, dst, src) 920 | w.EndUpdate() 921 | } 922 | 923 | func removeOnSetWindowHandler(l **onSetWindowHandlerList) { 924 | node := *l 925 | *l = node.prev 926 | if f := node.finalizer; f != nil { 927 | f() 928 | } 929 | 930 | } 931 | 932 | // OnSetWindowStyleHandler handles requests to change values of type 933 | // WindowStyle. If there was a previous handler installed, it's passed in prev. 934 | // The handler then has the opportunity to call the previous handler before or 935 | // after its own execution. 936 | type OnSetWindowStyleHandler func(w *Window, prev OnSetWindowStyleHandler, dst *WindowStyle, src WindowStyle) 937 | 938 | type onSetWindowStyleHandlerList struct { 939 | prev *onSetWindowStyleHandlerList 940 | h OnSetWindowStyleHandler 941 | finalizer func() 942 | } 943 | 944 | func addOnSetWindowStyleHandler(l **onSetWindowStyleHandlerList, h OnSetWindowStyleHandler, finalizer func()) { 945 | prev := *l 946 | if prev == nil { 947 | *l = &onSetWindowStyleHandlerList{ 948 | h: h, 949 | finalizer: finalizer, 950 | } 951 | return 952 | } 953 | 954 | *l = &onSetWindowStyleHandlerList{ 955 | prev: prev, 956 | h: func(w *Window, _ OnSetWindowStyleHandler, dst *WindowStyle, src WindowStyle) { 957 | h(w, prev.h, dst, src) 958 | }, 959 | finalizer: finalizer, 960 | } 961 | } 962 | 963 | func (l *onSetWindowStyleHandlerList) clear() { 964 | for l != nil { 965 | if f := l.finalizer; f != nil { 966 | f() 967 | } 968 | l = l.prev 969 | } 970 | } 971 | 972 | func (l *onSetWindowStyleHandlerList) handle(w *Window, dst *WindowStyle, src WindowStyle) { 973 | if *dst == src { 974 | return 975 | } 976 | 977 | if l == nil { 978 | *dst = src 979 | return 980 | } 981 | 982 | w.BeginUpdate() 983 | l.h(w, nil, dst, src) 984 | w.EndUpdate() 985 | } 986 | 987 | func removeOnSetWindowStyleHandler(l **onSetWindowStyleHandlerList) { 988 | node := *l 989 | *l = node.prev 990 | if f := node.finalizer; f != nil { 991 | f() 992 | } 993 | } 994 | -------------------------------------------------------------------------------- /internal/demoapp/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The WM Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | .PHONY: all clean cover cpu editor internalError later mem nuke todo edit demo 6 | 7 | grep=--include=*.go --include=*.l --include=*.y --include=*.yy 8 | ngrep='TODOOK\|parser\.go\|scanner\.go\|.*_string\.go' 9 | 10 | all: editor 11 | go vet 2>&1 | grep -v $(ngrep) || true 12 | golint 2>&1 | grep -v $(ngrep) || true 13 | make todo 14 | unused . || true 15 | misspell *.go 16 | gosimple || true 17 | codesweep || true 18 | unconvert || true 19 | maligned || true 20 | 21 | clean: 22 | go clean 23 | rm -f *~ *.test *.out 24 | 25 | cover: 26 | t=$(shell tempfile) ; go test -coverprofile $$t && go tool cover -html $$t && unlink $$t 27 | 28 | demo: 29 | go run test.go 30 | 31 | cpu: clean 32 | go test -run @ -bench . -cpuprofile cpu.out 33 | go tool pprof -lines *.test cpu.out 34 | 35 | edit: 36 | @ 1>/dev/null 2>/dev/null gvim -p Makefile *.go 37 | 38 | editor: 39 | gofmt -l -s -w *.go 40 | go test 41 | go install 42 | 43 | internalError: 44 | egrep -ho '"internal error.*"' *.go | sort | cat -n 45 | 46 | later: 47 | @grep -n $(grep) LATER * || true 48 | @grep -n $(grep) MAYBE * || true 49 | 50 | mem: clean 51 | go test -run @ -bench . -memprofile mem.out -memprofilerate 1 -timeout 24h 52 | go tool pprof -lines -web -alloc_space *.test mem.out 53 | 54 | nuke: clean 55 | go clean -i 56 | 57 | todo: 58 | @grep -nr $(grep) ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* * | grep -v $(ngrep) || true 59 | @grep -nr $(grep) TODO * | grep -v $(ngrep) || true 60 | @grep -nr $(grep) BUG * | grep -v $(ngrep) || true 61 | @grep -nr $(grep) [^[:alpha:]]println * | grep -v $(ngrep) || true 62 | -------------------------------------------------------------------------------- /internal/demoapp/README: -------------------------------------------------------------------------------- 1 | # demoapp 2 | 3 | Package demoapp is terminal demo application skeleton. 4 | -------------------------------------------------------------------------------- /internal/demoapp/all_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package demoapp 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path" 11 | "runtime" 12 | "strings" 13 | "testing" 14 | ) 15 | 16 | func caller(s string, va ...interface{}) { 17 | if s == "" { 18 | s = strings.Repeat("%v ", len(va)) 19 | } 20 | _, fn, fl, _ := runtime.Caller(2) 21 | fmt.Fprintf(os.Stderr, "// caller: %s:%d: ", path.Base(fn), fl) 22 | fmt.Fprintf(os.Stderr, s, va...) 23 | fmt.Fprintln(os.Stderr) 24 | _, fn, fl, _ = runtime.Caller(1) 25 | fmt.Fprintf(os.Stderr, "// \tcallee: %s:%d: ", path.Base(fn), fl) 26 | fmt.Fprintln(os.Stderr) 27 | os.Stderr.Sync() 28 | } 29 | 30 | func dbg(s string, va ...interface{}) { 31 | if s == "" { 32 | s = strings.Repeat("%v ", len(va)) 33 | } 34 | _, fn, fl, _ := runtime.Caller(1) 35 | fmt.Fprintf(os.Stderr, "// dbg %s:%d: ", path.Base(fn), fl) 36 | fmt.Fprintf(os.Stderr, s, va...) 37 | fmt.Fprintln(os.Stderr) 38 | os.Stderr.Sync() 39 | } 40 | 41 | func TODO(...interface{}) string { //TODOOK 42 | _, fn, fl, _ := runtime.Caller(1) 43 | return fmt.Sprintf("// TODO: %s:%d:\n", path.Base(fn), fl) //TODOOK 44 | } 45 | 46 | func use(...interface{}) {} 47 | 48 | func init() { 49 | use(caller, dbg, TODO) //TODOOK 50 | } 51 | 52 | // ============================================================================ 53 | 54 | func Test(t *testing.T) { 55 | t.Logf("TODO") 56 | } 57 | -------------------------------------------------------------------------------- /internal/demoapp/demoapp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package demoapp is a terminal application skeleton. 6 | package demoapp 7 | 8 | import ( 9 | "flag" 10 | "fmt" 11 | "log" 12 | "os" 13 | 14 | "github.com/cznic/wm" 15 | "github.com/gdamore/tcell" 16 | ) 17 | 18 | var ( 19 | windowStyle = wm.WindowStyle{ 20 | Border: wm.Style{Background: tcell.ColorNavy, Foreground: tcell.ColorGreen}, 21 | ClientArea: wm.Style{Background: tcell.ColorSilver, Foreground: tcell.ColorNavy}, 22 | Title: wm.Style{Background: tcell.ColorNavy, Foreground: tcell.ColorSilver}, 23 | } 24 | 25 | Theme = &wm.Theme{ 26 | ChildWindow: windowStyle, 27 | Desktop: wm.WindowStyle{ 28 | ClientArea: wm.Style{Background: tcell.ColorTeal, Foreground: tcell.ColorWhite}, 29 | }, 30 | } 31 | 32 | logoStyle = wm.Style{Background: Theme.Desktop.ClientArea.Background, Foreground: tcell.ColorWhite} 33 | pnameStyle = wm.Style{Background: Theme.Desktop.ClientArea.Background, Foreground: tcell.ColorNavy} 34 | 35 | oLog = flag.String("log", "", "log file") 36 | ) 37 | 38 | // New returns a newly created terminal application and a newly created desktop 39 | // which is not yet shown. 40 | // 41 | // Double click events are disabled to improve single click response time. To 42 | // enable double click events use wm.App.SetDoubleClickDuration to a nonzero 43 | // value. 44 | func New() (*wm.Application, *wm.Desktop) { 45 | logf := *oLog 46 | if logf == "" { 47 | logf = os.DevNull 48 | } 49 | var f *os.File 50 | fi, err := os.Stat(logf) 51 | switch { 52 | case err == nil: 53 | if fi.Mode()&os.ModeNamedPipe != 0 { 54 | if f, err = os.OpenFile(logf, os.O_WRONLY, 0666); err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | break 59 | } 60 | 61 | fallthrough 62 | default: 63 | if f, err = os.Create(logf); err != nil { 64 | log.Fatal(err) 65 | } 66 | } 67 | 68 | log.SetOutput(f) 69 | app, err := wm.NewApplication(Theme) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | 74 | const ( 75 | logo = "github.com/cznic/wm" 76 | border = 1 77 | ) 78 | pname := os.Args[0] 79 | if fi, err = os.Stat(pname); err == nil { 80 | pname = fmt.Sprintf("%s %s", pname, fi.ModTime().Format("2006-01-02 15:04:05")) 81 | } 82 | d := app.NewDesktop() 83 | app.PostWait(func() { 84 | app.SetDoubleClickDuration(0) 85 | d := app.NewDesktop() 86 | d.Root().OnPaintClientArea(func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 87 | if prev != nil { 88 | prev(w, nil, ctx) 89 | } 90 | 91 | sz := w.Size() 92 | w.Printf(sz.Width-border-len(pname), 0, pnameStyle, "%s", pname) 93 | w.Printf(sz.Width-border-len(logo), sz.Height-border-1, logoStyle, "%s", logo) 94 | }, nil) 95 | }) 96 | return app, d 97 | } 98 | -------------------------------------------------------------------------------- /internal/demoapp/test.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | // Copyright 2016 The WM Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | // $ go run test.go 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "flag" 13 | "io/ioutil" 14 | "log" 15 | "math/rand" 16 | "path/filepath" 17 | "strings" 18 | "time" 19 | 20 | "github.com/cznic/mathutil" 21 | "github.com/cznic/wm" 22 | "github.com/cznic/wm/internal/demoapp" 23 | "github.com/cznic/wm/tk" 24 | "github.com/gdamore/tcell" 25 | ) 26 | 27 | var nl = []byte{'\n'} 28 | 29 | type meter wm.Size 30 | 31 | func (m meter) Metrics(_ wm.Rectangle) wm.Size { return wm.Size(m) } 32 | 33 | func newWindow(parent *wm.Window, x, y int, title string, src []byte) { 34 | sz := parent.Size() 35 | if x < 0 || y < 0 { 36 | x = rand.Intn(sz.Width - sz.Width/5) 37 | y = rand.Intn(sz.Height - sz.Height/5) 38 | } 39 | c := parent.NewChild(wm.Rectangle{wm.Position{x, y}, wm.Size{0, 0}}) 40 | c.SetCloseButton(true) 41 | c.SetTitle(title) 42 | if bytes.HasSuffix(src, nl) { 43 | src = src[:len(src)-1] 44 | } 45 | a := bytes.Split([]byte(src), nl) 46 | max := -1 47 | for _, v := range a { 48 | var x int 49 | for _, c := range v { 50 | switch c { 51 | case '\t': 52 | x += 8 - x%8 53 | default: 54 | x++ 55 | } 56 | } 57 | max = mathutil.Max(max, x) 58 | } 59 | c.OnPaintClientArea( 60 | func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 61 | if prev != nil { 62 | prev(w, nil, ctx) 63 | } 64 | w.Printf(0, 0, w.ClientAreaStyle(), "%s", src) 65 | }, 66 | nil, 67 | ) 68 | v := tk.NewView(c, meter{max, len(a)}) 69 | c.OnKey( 70 | func(w *wm.Window, prev wm.OnKeyHandler, key tcell.Key, mod tcell.ModMask, r rune) bool { 71 | if prev != nil && prev(w, nil, key, mod, r) { 72 | return true 73 | } 74 | 75 | switch key { 76 | case tcell.KeyHome: 77 | v.Home() 78 | return true 79 | case tcell.KeyEnd: 80 | v.End() 81 | return true 82 | case tcell.KeyPgDn: 83 | v.PageDown() 84 | return true 85 | case tcell.KeyPgUp: 86 | v.PageUp() 87 | return true 88 | } 89 | return false 90 | }, 91 | nil, 92 | ) 93 | c.SetSize(wm.Size{Width: rand.Intn(sz.Width/2) + 20, Height: rand.Intn(sz.Height/2) + 15}) 94 | c.SetFocus(true) 95 | } 96 | 97 | func setup(d *wm.Desktop) { 98 | app := wm.App 99 | r := d.Root() 100 | r.OnPaintClientArea( 101 | func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 102 | if prev != nil { 103 | prev(w, nil, ctx) 104 | } 105 | 106 | w.Printf(0, 0, w.ClientAreaStyle(), `Demo terminal application. 107 | 108 | Press or to quit. 109 | 110 | Terminal colors: %v`, app.Colors()) 111 | }, 112 | nil, 113 | ) 114 | app.OnKey( 115 | func(w *wm.Window, prev wm.OnKeyHandler, key tcell.Key, mod tcell.ModMask, r rune) bool { 116 | if prev != nil && prev(w, nil, key, mod, r) { 117 | return true 118 | } 119 | 120 | switch { 121 | case key == tcell.KeyESC, r == 'q', r == 'Q', key == tcell.KeyCtrlQ: 122 | app.Exit(nil) 123 | return true 124 | default: 125 | return false 126 | } 127 | }, 128 | nil, 129 | ) 130 | m, err := filepath.Glob("*") 131 | if err == nil { 132 | for _, v := range m { 133 | if strings.HasPrefix(v, ".") { 134 | continue 135 | } 136 | 137 | b, err := ioutil.ReadFile(v) 138 | if err != nil { 139 | continue 140 | } 141 | 142 | newWindow(r, -1, -1, v, b) 143 | } 144 | } 145 | d.Show() 146 | } 147 | 148 | func main() { 149 | flag.Parse() 150 | rand.Seed(time.Now().UnixNano()) 151 | app, d := demoapp.New() 152 | if err := app.Run(func() { setup(d) }); err != nil { 153 | log.Fatal(err) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /mouse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wm 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | 11 | "github.com/gdamore/tcell" 12 | ) 13 | 14 | type mbState int 15 | 16 | const ( 17 | mbsIdle mbState = iota 18 | mbsDown 19 | mbsUp 20 | mbsDown2 21 | mbsDrag 22 | ) 23 | 24 | type mouseButtonFSM struct { 25 | in chan *tcell.EventMouse // 26 | button tcell.ButtonMask // 27 | mods tcell.ModMask // 28 | pos Position // 29 | quit chan struct{} // 30 | state mbState // 31 | timeout <-chan time.Time // 32 | } 33 | 34 | func newMouseButtonFSM(button tcell.ButtonMask) *mouseButtonFSM { 35 | m := &mouseButtonFSM{ 36 | in: make(chan *tcell.EventMouse, 1), 37 | button: button, 38 | quit: make(chan struct{}, 1), 39 | } 40 | go m.run() 41 | return m 42 | } 43 | 44 | func (m *mouseButtonFSM) post(e *tcell.EventMouse) { m.in <- e } 45 | 46 | func (m *mouseButtonFSM) close() { 47 | select { 48 | case m.quit <- struct{}{}: 49 | default: 50 | } 51 | } 52 | 53 | func (m *mouseButtonFSM) run() { 54 | for { 55 | switch m.state { 56 | case mbsIdle: 57 | select { 58 | case e := <-m.in: 59 | switch e.Buttons() & m.button { 60 | case 0: // Button up. 61 | // nop 62 | default: // Button down. 63 | m.mods = e.Modifiers() 64 | x, y := e.Position() 65 | m.pos = Position{x, y} 66 | m.timeout = time.After(App.ClickDuration()) 67 | m.state = mbsDown 68 | } 69 | case <-m.timeout: 70 | m.timeout = nil 71 | case <-m.quit: 72 | return 73 | } 74 | case mbsDown: 75 | select { 76 | case e := <-m.in: 77 | switch e.Buttons() & m.button { 78 | case 0: // Button up. 79 | if d := App.DoubleClickDuration(); d != 0 { 80 | m.timeout = time.After(d) 81 | m.state = mbsUp 82 | break 83 | } 84 | 85 | App.screen.PostEvent(newEventMouse(mouseClick, m.button, m.mods, m.pos)) 86 | m.state = mbsIdle 87 | m.timeout = nil 88 | default: // Button down. 89 | m.state = mbsIdle 90 | } 91 | case <-m.timeout: 92 | App.screen.PostEvent(newEventMouse(mouseDrag, m.button, m.mods, m.pos)) 93 | m.state = mbsDrag 94 | case <-m.quit: 95 | return 96 | } 97 | case mbsUp: 98 | select { 99 | case e := <-m.in: 100 | switch e.Buttons() & m.button { 101 | case 0: // Button up. 102 | m.state = mbsIdle 103 | default: // Button down. 104 | App.screen.PostEvent(newEventMouse(mouseDoubleClick, m.button, m.mods, m.pos)) 105 | m.state = mbsDown2 106 | } 107 | case <-m.timeout: 108 | App.screen.PostEvent(newEventMouse(mouseClick, m.button, m.mods, m.pos)) 109 | m.state = mbsIdle 110 | m.timeout = nil 111 | case <-m.quit: 112 | return 113 | } 114 | case mbsDown2: 115 | select { 116 | case e := <-m.in: 117 | switch e.Buttons() & m.button { 118 | case 0: // Button up. 119 | m.state = mbsIdle 120 | default: // Button down. 121 | m.state = mbsIdle 122 | } 123 | case <-m.timeout: 124 | m.timeout = nil 125 | case <-m.quit: 126 | return 127 | } 128 | case mbsDrag: 129 | select { 130 | case e := <-m.in: 131 | switch e.Buttons() & m.button { 132 | case 0: // Button up. 133 | x, y := e.Position() 134 | App.screen.PostEvent(newEventMouse(mouseDrop, m.button, e.Modifiers(), Position{x, y})) 135 | m.state = mbsIdle 136 | m.timeout = nil 137 | default: // Button down. 138 | m.state = mbsIdle 139 | } 140 | case <-m.timeout: 141 | m.timeout = nil 142 | case <-m.quit: 143 | return 144 | } 145 | default: 146 | panic(fmt.Errorf("%v", m.state)) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /nodbg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !debug.wm 6 | 7 | package wm 8 | 9 | const debug = false 10 | -------------------------------------------------------------------------------- /theme.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wm 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | "io/ioutil" 11 | 12 | "github.com/gdamore/tcell" 13 | ) 14 | 15 | var ( 16 | zeroStyle Style 17 | ) 18 | 19 | // Style represents a text style. 20 | type Style struct { 21 | Foreground tcell.Color 22 | Background tcell.Color 23 | Attr tcell.AttrMask 24 | } 25 | 26 | // IsZero returns whether s is the zero value of Style. 27 | func (s *Style) IsZero() bool { return *s == zeroStyle } 28 | 29 | // NewStyle returns Style having values filled from s. 30 | func NewStyle(s tcell.Style) Style { 31 | f, b, a := s.Decompose() 32 | return Style{f, b, a} 33 | } 34 | 35 | // TCellStyle converts a Style to a tcell.Style value. 36 | func (s Style) TCellStyle() tcell.Style { 37 | return tcell.Style(0). 38 | Foreground(s.Foreground). 39 | Background(s.Background). 40 | Bold(s.Attr&tcell.AttrBold != 0). 41 | Blink(s.Attr&tcell.AttrBlink != 0). 42 | Reverse(s.Attr&tcell.AttrReverse != 0). 43 | Underline(s.Attr&tcell.AttrUnderline != 0). 44 | Dim(s.Attr&tcell.AttrDim != 0) 45 | 46 | } 47 | 48 | // Theme represents visual styles of UI elements. 49 | type Theme struct { 50 | ChildWindow WindowStyle 51 | Desktop WindowStyle 52 | } 53 | 54 | // WindowStyle represents visual styles of a Window. 55 | type WindowStyle struct { 56 | Border Style 57 | ClientArea Style 58 | Title Style 59 | } 60 | 61 | // Clear sets t to its zero value. 62 | func (t *Theme) Clear() { *t = Theme{} } 63 | 64 | // WriteTo writes t to w in JSON format. 65 | func (t *Theme) WriteTo(w io.Writer) (int64, error) { 66 | b, err := json.Marshal(t) 67 | if err != nil { 68 | return 0, err 69 | } 70 | 71 | n, err := w.Write(b) 72 | return int64(n), err 73 | } 74 | 75 | // ReadFrom reads t from r in JSON format. Values of fields having no JSON data 76 | // are preserved. 77 | func (t *Theme) ReadFrom(r io.Reader) (int64, error) { 78 | b, err := ioutil.ReadAll(r) 79 | if err != nil { 80 | return 0, err 81 | } 82 | 83 | return int64(len(b)), json.Unmarshal(b, t) 84 | } 85 | -------------------------------------------------------------------------------- /tk/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The WM Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | .PHONY: all clean cover cpu editor internalError later mem nuke todo edit 6 | 7 | grep=--include=*.go --include=*.l --include=*.y --include=*.yy 8 | ngrep='TODOOK\|parser\.go\|scanner\.go\|.*_string\.go' 9 | 10 | all: editor 11 | go vet 2>&1 | grep -v $(ngrep) || true 12 | golint 2>&1 | grep -v $(ngrep) || true 13 | make todo 14 | unused . || true 15 | misspell *.go 16 | gosimple || true 17 | codesweep || true 18 | unconvert || true 19 | maligned || true 20 | 21 | clean: 22 | go clean 23 | rm -f *~ *.test *.out 24 | 25 | cover: 26 | t=$(shell tempfile) ; go test -coverprofile $$t && go tool cover -html $$t && unlink $$t 27 | 28 | cpu: clean 29 | go test -run @ -bench . -cpuprofile cpu.out 30 | go tool pprof -lines *.test cpu.out 31 | 32 | edit: 33 | @ 1>/dev/null 2>/dev/null gvim -p Makefile *.go 34 | 35 | editor: 36 | go generate 37 | gofmt -l -s -w *.go 38 | go test 39 | go build 40 | 41 | internalError: 42 | egrep -ho '"internal error.*"' *.go | sort | cat -n 43 | 44 | later: 45 | @grep -n $(grep) LATER * || true 46 | @grep -n $(grep) MAYBE * || true 47 | 48 | mem: clean 49 | go test -run @ -bench . -memprofile mem.out -memprofilerate 1 -timeout 24h 50 | go tool pprof -lines -web -alloc_space *.test mem.out 51 | 52 | nuke: clean 53 | go clean -i 54 | 55 | todo: 56 | @grep -nr $(grep) ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* * | grep -v $(ngrep) || true 57 | @grep -nr $(grep) TODO * | grep -v $(ngrep) || true 58 | @grep -nr $(grep) BUG * | grep -v $(ngrep) || true 59 | @grep -nr $(grep) [^[:alpha:]]println * | grep -v $(ngrep) || true 60 | -------------------------------------------------------------------------------- /tk/all_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package tk 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path" 11 | "runtime" 12 | "strings" 13 | "testing" 14 | ) 15 | 16 | func caller(s string, va ...interface{}) { 17 | if s == "" { 18 | s = strings.Repeat("%v ", len(va)) 19 | } 20 | _, fn, fl, _ := runtime.Caller(2) 21 | fmt.Fprintf(os.Stderr, "// caller: %s:%d: ", path.Base(fn), fl) 22 | fmt.Fprintf(os.Stderr, s, va...) 23 | fmt.Fprintln(os.Stderr) 24 | _, fn, fl, _ = runtime.Caller(1) 25 | fmt.Fprintf(os.Stderr, "// \tcallee: %s:%d: ", path.Base(fn), fl) 26 | fmt.Fprintln(os.Stderr) 27 | os.Stderr.Sync() 28 | } 29 | 30 | func dbg(s string, va ...interface{}) { 31 | if s == "" { 32 | s = strings.Repeat("%v ", len(va)) 33 | } 34 | _, fn, fl, _ := runtime.Caller(1) 35 | fmt.Fprintf(os.Stderr, "// dbg %s:%d: ", path.Base(fn), fl) 36 | fmt.Fprintf(os.Stderr, s, va...) 37 | fmt.Fprintln(os.Stderr) 38 | os.Stderr.Sync() 39 | } 40 | 41 | func TODO(...interface{}) string { //TODOOK 42 | _, fn, fl, _ := runtime.Caller(1) 43 | return fmt.Sprintf("// TODO: %s:%d:\n", path.Base(fn), fl) //TODOOK 44 | } 45 | 46 | func use(...interface{}) {} 47 | 48 | func init() { 49 | use(caller, dbg, TODO) //TODOOK 50 | } 51 | 52 | // ============================================================================ 53 | 54 | func Test(t *testing.T) { 55 | t.Logf("TODO") 56 | } 57 | -------------------------------------------------------------------------------- /tk/scrollbar.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package tk 6 | 7 | import ( 8 | "github.com/cznic/mathutil" 9 | "github.com/cznic/wm" 10 | "github.com/gdamore/tcell" 11 | ) 12 | 13 | // Scrollbar represents an UI element used to show that a View content 14 | // overflows its window and provide visual feedback of the position of its 15 | // viewport. 16 | // 17 | // Scrollbar methods must be called only directly from an event handler 18 | // goroutine or from a function that was enqueued using wm.Application.Post or 19 | // wm.Application.PostWait. 20 | type Scrollbar struct { 21 | dragHandlePos0 int // 22 | dragScreenPos0 wm.Position // 23 | draggingHandle bool // 24 | handlePos int // 25 | handleSize int // 26 | onClickDecrement *wm.OnMouseHandlerList // 27 | onClickDecrementPage *wm.OnMouseHandlerList // 28 | onClickIncrement *wm.OnMouseHandlerList // 29 | onClickIncrementPage *wm.OnMouseHandlerList // 30 | onPaint *wm.OnPaintHandlerList // 31 | onSetHandlePos *wm.OnSetIntHandlerList // 32 | onSetHandleSize *wm.OnSetIntHandlerList // 33 | onSetPosition *wm.OnSetPositionHandlerList // 34 | onSetSize *wm.OnSetSizeHandlerList // 35 | onSetStyle *wm.OnSetStyleHandlerList // 36 | position wm.Position // 37 | size wm.Size // 38 | style wm.Style // 39 | w *wm.Window // 40 | } 41 | 42 | // NewScrollbar returns a newly created Scrollbar. 43 | func NewScrollbar(w *wm.Window) *Scrollbar { 44 | s := &Scrollbar{w: w} 45 | s.OnPaint(s.onPaintHandler, nil) 46 | s.OnSetHandlePosition(s.onSetHandlePosHandler, nil) 47 | s.OnSetHandleSize(s.onSetHandleSizeHandler, nil) 48 | s.OnSetPosition(s.onSetPositionHandler, nil) 49 | s.OnSetSize(s.onSetSizeHandler, nil) 50 | s.OnSetStyle(s.onSetStyleHandler, nil) 51 | w.OnClickBorder(s.onClickBorderHandler, nil) 52 | w.OnClose(s.onCloseHandler, nil) 53 | w.OnDragBorder(s.onDragBorderHandler, nil) 54 | return s 55 | } 56 | 57 | func (s *Scrollbar) onCloseHandler(w *wm.Window, prev wm.OnCloseHandler) { 58 | if prev != nil { 59 | prev(w, nil) 60 | } 61 | s.onClickDecrement.Clear() 62 | s.onClickDecrementPage.Clear() 63 | s.onClickIncrement.Clear() 64 | s.onClickIncrementPage.Clear() 65 | s.onPaint.Clear() 66 | s.onSetHandlePos.Clear() 67 | s.onSetHandleSize.Clear() 68 | s.onSetPosition.Clear() 69 | s.onSetSize.Clear() 70 | s.onSetStyle.Clear() 71 | } 72 | 73 | const ( 74 | decrementArrow = iota 75 | decrementPage 76 | scrollbarHandle 77 | incrementPage 78 | incrementArrow 79 | ) 80 | 81 | func (s *Scrollbar) place(w *wm.Window, winPos wm.Position) int { 82 | pos := s.position 83 | sz := s.Size() 84 | switch { 85 | case s.isVertical(): 86 | area := w.BorderRightArea() 87 | if !area.Clip(wm.Rectangle{Position: wm.Position{X: area.X + pos.X, Y: area.Y + pos.Y}, Size: sz}) || !winPos.In(area) { 88 | return -1 89 | } 90 | 91 | switch { 92 | case winPos.Y == pos.Y+sz.Height-1: 93 | return incrementArrow 94 | case winPos.Y == pos.Y: 95 | return decrementArrow 96 | case winPos.Y < pos.Y+1+s.HandlePosition(): 97 | return decrementPage 98 | case winPos.Y > pos.Y+s.HandlePosition()+s.HandleSize(): 99 | return incrementPage 100 | default: 101 | return scrollbarHandle 102 | } 103 | default: 104 | area := w.BorderBottomArea() 105 | if !area.Clip(wm.Rectangle{Position: wm.Position{X: area.X + pos.X, Y: area.Y + pos.Y}, Size: sz}) || !winPos.In(area) { 106 | return -1 107 | } 108 | 109 | switch { 110 | case winPos.X == pos.X+sz.Width-1: 111 | return incrementArrow 112 | case winPos.X == pos.X: 113 | return decrementArrow 114 | case winPos.X < pos.X+1+s.HandlePosition(): 115 | return decrementPage 116 | case winPos.X > pos.X+s.HandlePosition()+s.HandleSize(): 117 | return incrementPage 118 | default: 119 | return scrollbarHandle 120 | } 121 | } 122 | } 123 | 124 | func (s *Scrollbar) onMouseMoveHandler(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 125 | if s.draggingHandle { 126 | switch { 127 | case s.isVertical(): 128 | dy := screenPos.Y - s.dragScreenPos0.Y 129 | s.SetHandlePosition(s.dragHandlePos0 + dy) 130 | default: 131 | dx := screenPos.X - s.dragScreenPos0.X 132 | s.SetHandlePosition(s.dragHandlePos0 + dx) 133 | } 134 | return true 135 | } 136 | 137 | return prev != nil && prev(w, nil, button, screenPos, winPos, mods) 138 | } 139 | 140 | func (s *Scrollbar) onDropHandler(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 141 | if s.draggingHandle { 142 | r := s.w.Desktop().Root() 143 | r.RemoveOnDrop() 144 | r.RemoveOnMouseMove() 145 | s.w.BringToFront() 146 | s.w.SetFocus(true) 147 | if w := s.w; w != r { 148 | w.RemoveOnDrop() 149 | w.RemoveOnMouseMove() 150 | } 151 | s.draggingHandle = false 152 | return true 153 | } 154 | 155 | return prev != nil && prev(w, nil, button, screenPos, winPos, mods) 156 | } 157 | 158 | func (s *Scrollbar) onDragBorderHandler(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 159 | if prev != nil && prev(w, nil, button, screenPos, winPos, mods) { 160 | return true 161 | } 162 | 163 | if button != tcell.Button1 || mods != 0 { 164 | return false 165 | } 166 | 167 | switch s.place(w, winPos) { 168 | case scrollbarHandle: 169 | s.draggingHandle = true 170 | r := s.w.Desktop().Root() 171 | r.OnDrop(s.onDropHandler, nil) 172 | r.OnMouseMove(s.onMouseMoveHandler, nil) 173 | s.dragHandlePos0 = s.HandlePosition() 174 | s.dragScreenPos0 = screenPos 175 | if w := s.w; w != r { 176 | w.OnDrop(s.onDropHandler, nil) 177 | w.OnMouseMove(s.onMouseMoveHandler, nil) 178 | } 179 | s.w.BringToFront() 180 | s.w.SetFocus(true) 181 | return true 182 | default: 183 | return false 184 | } 185 | 186 | } 187 | 188 | func (s *Scrollbar) onClickBorderHandler(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 189 | if prev != nil && prev(w, nil, button, screenPos, winPos, mods) { 190 | return true 191 | } 192 | 193 | if button != tcell.Button1 || mods != 0 { 194 | return false 195 | } 196 | 197 | switch s.place(w, winPos) { 198 | case decrementArrow: 199 | return s.onClickDecrement.Handle(w, button, screenPos, winPos, mods) 200 | case decrementPage: 201 | return s.onClickDecrementPage.Handle(w, button, screenPos, winPos, mods) 202 | case incrementPage: 203 | return s.onClickIncrementPage.Handle(w, button, screenPos, winPos, mods) 204 | case incrementArrow: 205 | return s.onClickIncrement.Handle(w, button, screenPos, winPos, mods) 206 | default: 207 | return false 208 | } 209 | } 210 | 211 | func (s *Scrollbar) onSetHandlePosHandler(w *wm.Window, prev wm.OnSetIntHandler, dst *int, src int) { 212 | if prev != nil { 213 | panic("internal error") 214 | } 215 | 216 | sz := s.Size().Width - 2 217 | if s.isVertical() { 218 | sz = s.Size().Height - 2 219 | } 220 | src = mathutil.Max(0, mathutil.Min(sz-s.HandleSize(), src)) 221 | *dst = src 222 | w.Invalidate(w.Area()) 223 | } 224 | 225 | func (s *Scrollbar) onSetHandleSizeHandler(w *wm.Window, prev wm.OnSetIntHandler, dst *int, src int) { 226 | if prev != nil { 227 | panic("internal error") 228 | } 229 | 230 | sz := s.Size().Width - 2 231 | if s.isVertical() { 232 | sz = s.Size().Height - 2 233 | } 234 | src = mathutil.Max(0, mathutil.Min(sz-s.HandlePosition(), src)) 235 | *dst = src 236 | w.Invalidate(w.Area()) 237 | } 238 | 239 | func (s *Scrollbar) onSetPositionHandler(w *wm.Window, prev wm.OnSetPositionHandler, dst *wm.Position, src wm.Position) { 240 | if prev != nil { 241 | panic("internal error") 242 | } 243 | 244 | *dst = src 245 | w.Invalidate(w.Area()) 246 | } 247 | 248 | func (s *Scrollbar) onSetSizeHandler(w *wm.Window, prev wm.OnSetSizeHandler, dst *wm.Size, src wm.Size) { 249 | if prev != nil { 250 | panic("internal error") 251 | } 252 | 253 | *dst = src 254 | w.Invalidate(w.Area()) 255 | } 256 | 257 | func (s *Scrollbar) onSetStyleHandler(w *wm.Window, prev wm.OnSetStyleHandler, dst *wm.Style, src wm.Style) { 258 | if prev != nil { 259 | panic("internal error") 260 | } 261 | 262 | *dst = src 263 | w.Invalidate(w.Area()) 264 | } 265 | 266 | func (s *Scrollbar) onPaintHandler(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 267 | if prev != nil { 268 | panic("internal error") 269 | } 270 | 271 | sz := s.Size() 272 | pos := s.Position() 273 | style := s.Style().TCellStyle() 274 | switch { 275 | case s.isVertical(): 276 | if w.ClientSize().Width == 0 { 277 | break 278 | } 279 | 280 | origin := pos.Y 281 | for y := 1; y < sz.Height-1; y++ { 282 | w.SetCell(pos.X, origin+y, tcell.RuneBoard, nil, style) 283 | } 284 | if sz.Height < 2 { 285 | break 286 | } 287 | 288 | for y := 1 + s.handlePos; y < 1+s.handlePos+s.handleSize && y < sz.Height-1; y++ { 289 | w.SetCell(pos.X, origin+y, tcell.RuneCkBoard, nil, style) 290 | } 291 | w.SetCell(pos.X, origin, '▴', nil, style) 292 | w.SetCell(pos.X, origin+sz.Height-1, '▾', nil, style) 293 | default: 294 | if w.ClientSize().Height == 0 { 295 | break 296 | } 297 | 298 | origin := pos.X 299 | for x := 1; x < sz.Width-1; x++ { 300 | w.SetCell(origin+x, pos.Y, tcell.RuneBoard, nil, style) 301 | } 302 | if sz.Width < 2 { 303 | break 304 | } 305 | 306 | for x := 1 + s.handlePos; x < 1+s.handlePos+s.handleSize && x < sz.Width-1; x++ { 307 | w.SetCell(origin+x, pos.Y, tcell.RuneCkBoard, nil, style) 308 | } 309 | 310 | w.SetCell(origin, pos.Y, '◂', nil, style) 311 | w.SetCell(origin+sz.Width-1, pos.Y, '▸', nil, style) 312 | } 313 | } 314 | 315 | func (s *Scrollbar) isVertical() bool { return s.Size().Width == 1 } 316 | 317 | // ---------------------------------------------------------------------------- 318 | 319 | // HandlePosition returns the position of the scrollbar handle. 320 | func (s *Scrollbar) HandlePosition() int { return s.handlePos } 321 | 322 | // HandleSize returns the size of the scrollbar handle. 323 | func (s *Scrollbar) HandleSize() int { return s.handleSize } 324 | 325 | // OnClickIncrement sets a handler invokend on clicking the right arrow of a 326 | // horizontal scrollbar or the down arrow of a vertical scrollbar. When the 327 | // event handler is removed, finalize is called, if not nil. 328 | func (s *Scrollbar) OnClickIncrement(h wm.OnMouseHandler, finalize func()) { 329 | wm.AddOnMouseHandler(&s.onClickIncrement, h, finalize) 330 | } 331 | 332 | // OnClickIncrementPage sets a handler invokend on clicking the area between 333 | // the scrollbar handle and the right arrow of a horizontal scrollbar or 334 | // between the scrollbar handle an dthe down arrow of a vertical scrollbar. 335 | // When the event handler is removed, finalize is called, if not nil. 336 | func (s *Scrollbar) OnClickIncrementPage(h wm.OnMouseHandler, finalize func()) { 337 | wm.AddOnMouseHandler(&s.onClickIncrementPage, h, finalize) 338 | } 339 | 340 | // OnClickDecrement sets a handler invokend on clicking the left arrow of a 341 | // horizontal scrollbar or the up arrow of a vertical scrollbar. When the 342 | // event handler is removed, finalize is called, if not nil. 343 | func (s *Scrollbar) OnClickDecrement(h wm.OnMouseHandler, finalize func()) { 344 | wm.AddOnMouseHandler(&s.onClickDecrement, h, finalize) 345 | } 346 | 347 | // OnClickDecrementPage sets a handler invokend on clicking the area between 348 | // the left arrow of and the scrollbar handle of a horizontal scrollbar or 349 | // between the the up arrow and the scrollbar handle of a vertical scrollbar. 350 | // When the event handler is removed, finalize is called, if not nil. 351 | func (s *Scrollbar) OnClickDecrementPage(h wm.OnMouseHandler, finalize func()) { 352 | wm.AddOnMouseHandler(&s.onClickDecrementPage, h, finalize) 353 | } 354 | 355 | // OnPaint sets a paint handler. When the event handler is removed, finalize is 356 | // called, if not nil. 357 | func (s *Scrollbar) OnPaint(h wm.OnPaintHandler, finalize func()) { 358 | wm.AddOnPaintHandler(&s.onPaint, h, finalize) 359 | } 360 | 361 | // OnSetHandlePosition sets a handler invoked on SetHandlePosition. When the 362 | // event handler is removed, finalize is called, if not nil. 363 | func (s *Scrollbar) OnSetHandlePosition(h wm.OnSetIntHandler, finalize func()) { 364 | wm.AddOnSetIntHandler(&s.onSetHandlePos, h, finalize) 365 | } 366 | 367 | // OnSetHandleSize sets a handler invoked on SetHandleSize. When the 368 | // event handler is removed, finalize is called, if not nil. 369 | func (s *Scrollbar) OnSetHandleSize(h wm.OnSetIntHandler, finalize func()) { 370 | wm.AddOnSetIntHandler(&s.onSetHandleSize, h, finalize) 371 | } 372 | 373 | // OnSetPosition sets a handler invoked on SetPosition. When the event handler 374 | // is removed, finalize is called, if not nil. 375 | func (s *Scrollbar) OnSetPosition(h wm.OnSetPositionHandler, finalize func()) { 376 | wm.AddOnSetPositionHandler(&s.onSetPosition, h, finalize) 377 | } 378 | 379 | // OnSetSize sets a handler invoked on SetSize. When the event handler 380 | // is removed, finalize is called, if not nil. 381 | func (s *Scrollbar) OnSetSize(h wm.OnSetSizeHandler, finalize func()) { 382 | wm.AddOnSetSizeHandler(&s.onSetSize, h, finalize) 383 | } 384 | 385 | // OnSetStyle sets a handler invoked on SetStyle. When the event handler 386 | // is removed, finalize is called, if not nil. 387 | func (s *Scrollbar) OnSetStyle(h wm.OnSetStyleHandler, finalize func()) { 388 | wm.AddOnSetStyleHandler(&s.onSetStyle, h, finalize) 389 | } 390 | 391 | // Paint ask the scrollbar to render itself. It is intended to be only called 392 | // from a "hook" paint handler that determines where the scrollbar appears. 393 | func (s *Scrollbar) Paint(ctx wm.PaintContext) { s.onPaint.Handle(s.w, ctx) } 394 | 395 | // Position returns the position of the scrollbar. 396 | func (s *Scrollbar) Position() wm.Position { return s.position } 397 | 398 | // RemoveOnClickIncrement undoes the most recent OnClickIncrement call. The 399 | // function will panic if there is no handler set. 400 | func (s *Scrollbar) RemoveOnClickIncrement() { wm.RemoveOnMouseHandler(&s.onClickIncrement) } 401 | 402 | // RemoveOnClickIncrementPage undoes the most recent OnClickIncrementPage call. 403 | // The function will panic if there is no handler set. 404 | func (s *Scrollbar) RemoveOnClickIncrementPage() { wm.RemoveOnMouseHandler(&s.onClickIncrementPage) } 405 | 406 | // RemoveOnClickDecrement undoes the most recent OnClickDecrement call. The 407 | // function will panic if there is no handler set. 408 | func (s *Scrollbar) RemoveOnClickDecrement() { wm.RemoveOnMouseHandler(&s.onClickDecrement) } 409 | 410 | // RemoveOnClickDecrementPage undoes the most recent OnClickDecrement call. The 411 | // function will panic if there is no handler set. 412 | func (s *Scrollbar) RemoveOnClickDecrementPage() { wm.RemoveOnMouseHandler(&s.onClickDecrementPage) } 413 | 414 | // RemoveOnPaint undoes the most recent OnPaint call. The function will panic 415 | // if there is no handler set. 416 | func (s *Scrollbar) RemoveOnPaint() { wm.RemoveOnPaintHandler(&s.onPaint) } 417 | 418 | // RemoveOnSetHandlePosition undoes the most recent OnSetHandlePosition call. 419 | // The function will panic if there is no handler set. 420 | func (s *Scrollbar) RemoveOnSetHandlePosition() { wm.RemoveOnSetIntHandler(&s.onSetHandlePos) } 421 | 422 | // RemoveOnSetHandleSize undoes the most recent OnSetHandleSize call. 423 | // The function will panic if there is no handler set. 424 | func (s *Scrollbar) RemoveOnSetHandleSize() { wm.RemoveOnSetIntHandler(&s.onSetHandleSize) } 425 | 426 | // RemoveOnSetPosition undoes the most recent OnSetPosition call. The function 427 | // will panic if there is no handler set. 428 | func (s *Scrollbar) RemoveOnSetPosition() { wm.RemoveOnSetPositionHandler(&s.onSetPosition) } 429 | 430 | // RemoveOnSetSize undoes the most recent OnSetSize call. The function 431 | // will panic if there is no handler set. 432 | func (s *Scrollbar) RemoveOnSetSize() { wm.RemoveOnSetSizeHandler(&s.onSetSize) } 433 | 434 | // RemoveOnSetStyle undoes the most recent OnSetStyle call. The function 435 | // will panic if there is no handler set. 436 | func (s *Scrollbar) RemoveOnSetStyle() { wm.RemoveOnSetStyleHandler(&s.onSetStyle) } 437 | 438 | // SetPosition sets the scrollbar position. 439 | func (s *Scrollbar) SetPosition(v wm.Position) { s.onSetPosition.Handle(s.w, &s.position, v) } 440 | 441 | // SetSize sets the scrollbar size. 442 | func (s *Scrollbar) SetSize(v wm.Size) { s.onSetSize.Handle(s.w, &s.size, v) } 443 | 444 | // Size returns the size of the scrollbar. 445 | func (s *Scrollbar) Size() wm.Size { return s.size } 446 | 447 | // SetHandlePosition sets the scrollbar handle position. 448 | func (s *Scrollbar) SetHandlePosition(v int) { s.onSetHandlePos.Handle(s.w, &s.handlePos, v) } 449 | 450 | // SetHandleSize sets the scrollbar handle size. 451 | func (s *Scrollbar) SetHandleSize(v int) { s.onSetHandleSize.Handle(s.w, &s.handleSize, v) } 452 | 453 | // SetStyle sets the scrollbar style. 454 | func (s *Scrollbar) SetStyle(v wm.Style) { s.onSetStyle.Handle(s.w, &s.style, v) } 455 | 456 | // SetView sets the scrollbar parameters based on the view parameters. SetView panics when origin < 0. 457 | func (s *Scrollbar) SetView(origin, viewportSize, contentSize int) { 458 | if origin < 0 { 459 | panic("Scrollbar.SetView: invalid origin") 460 | } 461 | 462 | if contentSize < 1 { // Unknown content size. 463 | s.SetHandlePosition(0) 464 | s.SetHandleSize(0) 465 | s.w.Invalidate(s.w.Area()) 466 | return 467 | } 468 | 469 | clip := wm.NewRectangle(-origin, 0, contentSize, 0) 470 | clip.Clip(wm.NewRectangle(0, 0, viewportSize, 0)) 471 | 472 | scrollbarSize := s.size.Width - 2 // Sans arrows. 473 | if s.isVertical() { 474 | scrollbarSize = s.size.Height - 2 475 | } 476 | handlePos := mathutil.Min(scrollbarSize-1, (origin*scrollbarSize+contentSize/2)/contentSize) 477 | handleSize := mathutil.Max(1, clip.Width*scrollbarSize/contentSize) 478 | s.SetHandlePosition(handlePos) 479 | s.SetHandleSize(handleSize) 480 | s.w.Invalidate(s.w.Area()) 481 | } 482 | 483 | // Style returns the style of the scrollbar. 484 | func (s *Scrollbar) Style() wm.Style { return s.style } 485 | -------------------------------------------------------------------------------- /tk/tk.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package tk is a wm[0] toolkit. 6 | // 7 | // [0]: https://godoc.org/github.com/cznic/wm 8 | package tk 9 | -------------------------------------------------------------------------------- /tk/view.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package tk 6 | 7 | import ( 8 | "github.com/cznic/mathutil" 9 | "github.com/cznic/wm" 10 | "github.com/gdamore/tcell" 11 | ) 12 | 13 | // Meter provides metrics of content displayed in the client area of a window. 14 | type Meter interface { 15 | // Metrics is called when window's viewport is set or updated. The 16 | // result .Height reflects the total contents height. The result .Width 17 | // reflect the maximum width of the content in area. If any of the 18 | // values are unknown a negative number is returned in the respective 19 | // output variable. 20 | Metrics(viewport wm.Rectangle) wm.Size 21 | } 22 | 23 | // View displays content possibly overflowing the size of its client area. 24 | // 25 | // View methods must be called only directly from an event handler goroutine or 26 | // from a function that was enqueued using wm.Application.Post or 27 | // wm.Application.PostWait. 28 | type View struct { 29 | *wm.Window // Underlying window. 30 | hs *Scrollbar 31 | hsEnabled bool 32 | hsShown bool 33 | meter Meter 34 | metrics wm.Size 35 | onSetHSEnabled *wm.OnSetBoolHandlerList 36 | onSetVSEnabled *wm.OnSetBoolHandlerList 37 | updating bool 38 | vs *Scrollbar 39 | vsEnabled bool 40 | vsShown bool 41 | } 42 | 43 | // NewView configures w to show scrollbars when content, measured using the 44 | // meter parameter, overflows the client area of w and returns the resulting 45 | // View. The user can use the scrollbars to control the View's Origin. 46 | // 47 | // NewView must be called only directly from an event handler goroutine or from 48 | // a function that was enqueued using wm.Application.Post or 49 | // wm.Application.PostWait. 50 | func NewView(w *wm.Window, meter Meter) *View { 51 | vs := NewScrollbar(w) 52 | vs.SetStyle(wm.Style{Background: tcell.ColorSilver, Foreground: tcell.ColorBlack}) 53 | hs := NewScrollbar(w) 54 | hs.SetStyle(vs.Style()) 55 | v := &View{ 56 | Window: w, 57 | hs: hs, 58 | hsEnabled: true, 59 | meter: meter, 60 | vs: vs, 61 | vsEnabled: true, 62 | } 63 | hs.OnClickDecrement(v.onClickDecrementHS, nil) 64 | hs.OnClickDecrementPage(v.onClickDecrementHSPage, nil) 65 | hs.OnClickIncrement(v.onClickIncrementHS, nil) 66 | hs.OnClickIncrementPage(v.onClickIncrementHSPage, nil) 67 | hs.OnSetHandlePosition(v.onSetHandlePositionHS, nil) 68 | v.OnSetHorizontalScrollbarEnabled(v.onSetHorizontalScrollbarEnabledHandler, nil) 69 | v.OnSetVerticalScrollbarEnabled(v.onSetVerticalScrollbarEnabledHandler, nil) 70 | vs.OnClickDecrement(v.onClickDecrementVS, nil) 71 | vs.OnClickDecrementPage(v.onClickDecrementVSPage, nil) 72 | vs.OnClickIncrement(v.onClickIncrementVS, nil) 73 | vs.OnClickIncrementPage(v.onClickIncrementVSPage, nil) 74 | vs.OnSetHandlePosition(v.onSetHandlePositionVS, nil) 75 | w.OnClose(v.onCloseHandler, nil) 76 | w.OnMouseMove(v.onMouseMoveHandler, nil) 77 | w.OnPaintBorderBottom(v.onPaintBorderBottomHandler, nil) 78 | w.OnPaintBorderRight(v.onPaintBorderRightHandler, nil) 79 | w.OnSetClientSize(v.onSetClientSizeHandler, nil) 80 | w.OnSetOrigin(v.onSetOriginHandler, nil) 81 | return v 82 | } 83 | 84 | func (v *View) onCloseHandler(w *wm.Window, prev wm.OnCloseHandler) { 85 | if prev != nil { 86 | prev(w, nil) 87 | } 88 | v.onSetHSEnabled.Clear() 89 | v.onSetVSEnabled.Clear() 90 | } 91 | 92 | func (v *View) onMouseMoveHandler(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 93 | if prev != nil && prev(w, nil, button, screenPos, winPos, mods) { 94 | return true 95 | } 96 | 97 | switch button { 98 | case tcell.WheelLeft: 99 | o := v.Origin() 100 | o.X = mathutil.Max(0, o.X-1) 101 | v.SetOrigin(o) 102 | return true 103 | case tcell.WheelRight: 104 | o := v.Origin() 105 | o.X++ 106 | v.SetOrigin(o) 107 | return true 108 | case tcell.WheelUp: 109 | o := v.Origin() 110 | o.Y = mathutil.Max(0, o.Y-1) 111 | v.SetOrigin(o) 112 | return true 113 | case tcell.WheelDown: 114 | o := v.Origin() 115 | o.Y++ 116 | v.SetOrigin(o) 117 | return true 118 | default: 119 | return false 120 | } 121 | } 122 | 123 | func (v *View) onClickDecrementHSPage(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 124 | if !v.hsShown { 125 | return false 126 | } 127 | 128 | o := v.Origin() 129 | o.X = mathutil.Max(0, o.X-v.ClientSize().Width) 130 | v.SetOrigin(o) 131 | return true 132 | } 133 | 134 | func (v *View) onClickIncrementHSPage(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 135 | if !v.hsShown { 136 | return false 137 | } 138 | 139 | o := v.Origin() 140 | o.X += v.ClientSize().Width 141 | v.SetOrigin(o) 142 | return true 143 | } 144 | 145 | func (v *View) onSetHandlePositionHS(w *wm.Window, prev wm.OnSetIntHandler, dst *int, src int) { 146 | if prev != nil { 147 | prev(w, nil, dst, src) 148 | src = *dst 149 | } 150 | 151 | if !v.hs.draggingHandle || v.updating { 152 | return 153 | } 154 | 155 | if v.metrics.Width < 0 { 156 | return 157 | } 158 | 159 | if src+v.hs.HandleSize() == v.hs.Size().Width-2 { 160 | v.SetOrigin(wm.Position{X: v.metrics.Width - v.ClientArea().Width, Y: v.Origin().Y}) 161 | return 162 | } 163 | 164 | x := (2*v.metrics.Width*src - v.metrics.Width) / (2*v.hs.Size().Width - 4) 165 | v.SetOrigin(wm.Position{X: x, Y: v.Origin().Y}) 166 | } 167 | 168 | func (v *View) onSetHandlePositionVS(w *wm.Window, prev wm.OnSetIntHandler, dst *int, src int) { 169 | if prev != nil { 170 | prev(w, nil, dst, src) 171 | src = *dst 172 | } 173 | 174 | if !v.vs.draggingHandle || v.updating { 175 | return 176 | } 177 | 178 | if v.metrics.Height < 0 { 179 | return 180 | } 181 | 182 | if src+v.vs.HandleSize() == v.vs.Size().Height-2 { 183 | v.SetOrigin(wm.Position{X: v.Origin().X, Y: v.metrics.Height - v.ClientArea().Height}) 184 | return 185 | } 186 | 187 | y := (2*v.metrics.Height*src - v.metrics.Height) / (2*v.vs.Size().Height - 4) 188 | v.SetOrigin(wm.Position{X: v.Origin().X, Y: y}) 189 | } 190 | 191 | func (v *View) onClickDecrementVSPage(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 192 | if !v.vsShown { 193 | return false 194 | } 195 | 196 | v.PageUp() 197 | return true 198 | } 199 | 200 | func (v *View) onClickIncrementVSPage(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 201 | if !v.vsShown { 202 | return false 203 | } 204 | 205 | v.PageDown() 206 | return true 207 | } 208 | 209 | func (v *View) onClickDecrementHS(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 210 | if !v.hsShown { 211 | return false 212 | } 213 | 214 | o := v.Origin() 215 | o.X = mathutil.Max(0, o.X-1) 216 | v.SetOrigin(o) 217 | return true 218 | } 219 | 220 | func (v *View) onClickIncrementHS(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 221 | if !v.hsShown { 222 | return false 223 | } 224 | 225 | o := v.Origin() 226 | o.X++ 227 | v.SetOrigin(o) 228 | return true 229 | } 230 | 231 | func (v *View) onClickDecrementVS(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 232 | if !v.vsShown { 233 | return false 234 | } 235 | 236 | o := v.Origin() 237 | o.Y = mathutil.Max(0, o.Y-1) 238 | v.SetOrigin(o) 239 | return true 240 | } 241 | 242 | func (v *View) onClickIncrementVS(w *wm.Window, prev wm.OnMouseHandler, button tcell.ButtonMask, screenPos, winPos wm.Position, mods tcell.ModMask) bool { 243 | if !v.vsShown { 244 | return false 245 | } 246 | 247 | o := v.Origin() 248 | o.Y++ 249 | v.SetOrigin(o) 250 | return true 251 | } 252 | 253 | func (v *View) onPaintBorderRightHandler(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 254 | if prev != nil { 255 | prev(w, nil, ctx) 256 | } 257 | v.vs.Paint(ctx) 258 | } 259 | 260 | func (v *View) onPaintBorderBottomHandler(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 261 | if prev != nil { 262 | prev(w, nil, ctx) 263 | } 264 | v.hs.Paint(ctx) 265 | } 266 | 267 | func (v *View) onSetOriginHandler(w *wm.Window, prev wm.OnSetPositionHandler, dst *wm.Position, src wm.Position) { 268 | if w := v.metrics.Width; w >= 0 { 269 | src.X = mathutil.Max(0, mathutil.Min(src.X, w-v.ClientSize().Width)) 270 | } 271 | if h := v.metrics.Height; h >= 0 { 272 | src.Y = mathutil.Max(0, mathutil.Min(src.Y, h-v.ClientSize().Height)) 273 | } 274 | 275 | if prev != nil { 276 | prev(w, nil, dst, src) 277 | src = *dst 278 | } 279 | *dst = src 280 | v.updateScrollBars() 281 | } 282 | 283 | func (v *View) onSetClientSizeHandler(w *wm.Window, prev wm.OnSetSizeHandler, dst *wm.Size, src wm.Size) { 284 | if prev != nil { 285 | prev(w, nil, dst, src) 286 | } 287 | *dst = src 288 | v.updateScrollBars() 289 | } 290 | 291 | func (v *View) onSetHorizontalScrollbarEnabledHandler(w *wm.Window, prev wm.OnSetBoolHandler, dst *bool, src bool) { 292 | if prev != nil { 293 | panic("internal error") 294 | } 295 | 296 | *dst = src 297 | v.updateScrollBars() 298 | } 299 | 300 | func (v *View) onSetVerticalScrollbarEnabledHandler(w *wm.Window, prev wm.OnSetBoolHandler, dst *bool, src bool) { 301 | if prev != nil { 302 | panic("internal error") 303 | } 304 | 305 | *dst = src 306 | v.updateScrollBars() 307 | } 308 | 309 | func checkHS(sz wm.Size, viewport wm.Rectangle) bool { 310 | return viewport.Width >= 2 && (viewport.X != 0 && sz.Width > 0 || sz.Width > viewport.Width || sz.Width < 0) 311 | } 312 | 313 | func checkVS(sz wm.Size, viewport wm.Rectangle) bool { 314 | return viewport.Height >= 2 && (viewport.Y != 0 && sz.Height > 0 || sz.Height > viewport.Height || sz.Height < 0) 315 | } 316 | 317 | func (v *View) updateScrollBars() { 318 | if v.updating { 319 | return 320 | } 321 | 322 | v.updating = true 323 | if v.hsShown { 324 | v.hs.SetPosition(wm.Position{Y: -1}) 325 | v.SetBorderBottom(v.BorderBottom() - 1) 326 | } 327 | if v.vsShown { 328 | v.vs.SetPosition(wm.Position{X: -1}) 329 | v.SetBorderRight(v.BorderRight() - 1) 330 | } 331 | 332 | viewport := v.ClientArea() 333 | viewport.Position = v.Origin() 334 | v.metrics = v.meter.Metrics(viewport) 335 | var showHS, showVS bool 336 | if showHS = v.hsEnabled && checkHS(v.metrics, viewport); showHS { 337 | viewport.Height-- 338 | showVS = v.vsEnabled && checkVS(v.metrics, viewport) 339 | } else if showVS = v.vsEnabled && checkVS(v.metrics, viewport); showVS { 340 | viewport.Width-- 341 | showHS = v.hsEnabled && checkHS(v.metrics, viewport) 342 | } 343 | 344 | if showHS { 345 | v.SetBorderBottom(v.BorderBottom() + 1) 346 | } 347 | if showVS { 348 | v.SetBorderRight(v.BorderRight() + 1) 349 | } 350 | 351 | cla := v.ClientArea() 352 | if showHS { 353 | v.hs.SetSize(wm.Size{Width: cla.Width, Height: 1}) 354 | v.hs.SetPosition(wm.Position{X: v.BorderLeft()}) 355 | v.hs.SetView(v.Origin().X, cla.Width, v.metrics.Width) 356 | } 357 | if showVS { 358 | v.vs.SetSize(wm.Size{Width: 1, Height: cla.Height}) 359 | v.vs.SetPosition(wm.Position{Y: v.BorderTop()}) 360 | v.vs.SetView(v.Origin().Y, cla.Height, v.metrics.Height) 361 | } 362 | 363 | v.hsShown = showHS 364 | v.vsShown = showVS 365 | v.updating = false 366 | } 367 | 368 | // ---------------------------------------------------------------------------- 369 | 370 | // HorizontalScrollbarEnabled reports whether the horizontal scrollbar is 371 | // enabled. 372 | func (v *View) HorizontalScrollbarEnabled() bool { return v.hsEnabled } 373 | 374 | // SetHorizontalScrollbarEnabled sets whether the horizontal scrollbar is 375 | // enabled. 376 | func (v *View) SetHorizontalScrollbarEnabled(b bool) { 377 | v.onSetHSEnabled.Handle(v.Window, &v.hsEnabled, b) 378 | } 379 | 380 | // OnSetHorizontalScrollbarEnabled sets a handler invoked on 381 | // SetHorizontalScrollbarEnabled. When the event handler is removed, finalize 382 | // is called, if not nil. 383 | func (v *View) OnSetHorizontalScrollbarEnabled(h wm.OnSetBoolHandler, finalize func()) { 384 | wm.AddOnSetBoolHandler(&v.onSetHSEnabled, h, finalize) 385 | } 386 | 387 | // RemoveOnSetHorizontalScrollbarEnabled undoes the most recent 388 | // OnSetHorizontalScrollbarEnabled call. The function will panic if there is 389 | // no handler set. 390 | func (v *View) RemoveOnSetHorizontalScrollbarEnabled() { wm.RemoveOnSetBoolHandler(&v.onSetHSEnabled) } 391 | 392 | // VerticalScrollbarEnabled reports whether the vertical scrollbar is enabled. 393 | func (v *View) VerticalScrollbarEnabled() bool { return v.vsEnabled } 394 | 395 | // SetVerticalScrollbarEnabled sets whether the vertical scrollbar is enabled. 396 | func (v *View) SetVerticalScrollbarEnabled(b bool) { v.onSetVSEnabled.Handle(v.Window, &v.vsEnabled, b) } 397 | 398 | // OnSetVerticalScrollbarEnabled sets a handler invoked on 399 | // SetVerticalScrollbarEnabled. When the event handler is removed, finalize is 400 | // called, if not nil. 401 | func (v *View) OnSetVerticalScrollbarEnabled(h wm.OnSetBoolHandler, finalize func()) { 402 | wm.AddOnSetBoolHandler(&v.onSetVSEnabled, h, finalize) 403 | } 404 | 405 | // RemoveOnSetVerticalScrollbarEnabled undoes the most recent 406 | // OnSetVerticalScrollbarEnabled call. The function will panic if there is no 407 | // handler set. 408 | func (v *View) RemoveOnSetVerticalScrollbarEnabled() { wm.RemoveOnSetBoolHandler(&v.onSetVSEnabled) } 409 | 410 | // Home makes the view show the beginning of its content. 411 | func (v *View) Home() { v.SetOrigin(wm.Position{}) } 412 | 413 | // End makes the view show the ending of its content. 414 | func (v *View) End() { 415 | if m := v.meter.Metrics(wm.Rectangle{Size: wm.Size{Width: 1, Height: 1}}); m.Height >= 0 { 416 | v.SetOrigin(wm.Position{Y: m.Height - v.ClientArea().Height}) 417 | } 418 | } 419 | 420 | // PageDown makes the view show the next page of content. 421 | func (v *View) PageDown() { 422 | o := v.Origin() 423 | o.Y += v.ClientSize().Height 424 | v.SetOrigin(o) 425 | } 426 | 427 | // PageUp makes the view show the previous page of content. 428 | func (v *View) PageUp() { 429 | o := v.Origin() 430 | o.Y -= v.ClientSize().Height 431 | v.SetOrigin(o) 432 | } 433 | -------------------------------------------------------------------------------- /tk/view_demo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build ignore 6 | 7 | // $ go run view_demo.go 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "flag" 13 | "io/ioutil" 14 | "log" 15 | "math/rand" 16 | "path/filepath" 17 | "strings" 18 | "time" 19 | 20 | "github.com/cznic/mathutil" 21 | "github.com/cznic/wm" 22 | "github.com/cznic/wm/internal/demoapp" 23 | "github.com/cznic/wm/tk" 24 | "github.com/gdamore/tcell" 25 | ) 26 | 27 | const ( 28 | help = `Use mouse to resize the window or scroll the view. 29 | Arrow keys change the viewport of the focused window. 30 | To focus the desktop, click on it. 31 | or 'q' to quit.` 32 | ) 33 | 34 | var nl = []byte{'\n'} 35 | 36 | func newWindow(parent *wm.Window, x, y int, title string, src []byte) { 37 | sz := parent.Size() 38 | if x < 0 || y < 0 { 39 | x = rand.Intn(sz.Width - sz.Width/5) 40 | y = rand.Intn(sz.Height - sz.Height/5) 41 | } 42 | c := parent.NewChild(wm.Rectangle{wm.Position{x, y}, wm.Size{0, 0}}) 43 | c.SetCloseButton(true) 44 | c.SetTitle(title) 45 | if bytes.HasSuffix(src, nl) { 46 | src = src[:len(src)-1] 47 | } 48 | a := bytes.Split([]byte(src), nl) 49 | max := -1 50 | for _, v := range a { 51 | var x int 52 | for _, c := range v { 53 | switch c { 54 | case '\t': 55 | x += 8 - x%8 56 | default: 57 | x++ 58 | } 59 | } 60 | max = mathutil.Max(max, x) 61 | } 62 | c.OnPaintClientArea( 63 | func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 64 | if prev != nil { 65 | prev(w, nil, ctx) 66 | } 67 | cpY := w.ClientPosition().Y 68 | for i := 0; i < ctx.Height; i++ { 69 | line := ctx.Y - cpY + i 70 | if line >= len(a) { 71 | break 72 | } 73 | 74 | w.Printf(0, line, w.ClientAreaStyle(), "%s", a[line]) 75 | } 76 | }, 77 | nil, 78 | ) 79 | v := tk.NewView(c, meter{max, len(a)}) 80 | c.OnKey( 81 | func(w *wm.Window, prev wm.OnKeyHandler, key tcell.Key, mod tcell.ModMask, r rune) bool { 82 | if prev != nil && prev(w, nil, key, mod, r) { 83 | return true 84 | } 85 | 86 | switch key { 87 | case tcell.KeyHome: 88 | v.Home() 89 | return true 90 | case tcell.KeyEnd: 91 | v.End() 92 | return true 93 | case tcell.KeyPgDn: 94 | v.PageDown() 95 | return true 96 | case tcell.KeyPgUp: 97 | v.PageUp() 98 | return true 99 | } 100 | return false 101 | }, 102 | nil, 103 | ) 104 | c.SetSize(wm.Size{Width: rand.Intn(sz.Width/2) + 20, Height: rand.Intn(sz.Height/2) + 15}) 105 | c.SetFocus(true) 106 | } 107 | 108 | func setup(d *wm.Desktop) { 109 | app := wm.App 110 | app.SetDoubleClickDuration(0) 111 | 112 | defer d.Show() 113 | 114 | r := d.Root() 115 | r.OnPaintClientArea( 116 | func(w *wm.Window, prev wm.OnPaintHandler, ctx wm.PaintContext) { 117 | if prev != nil { 118 | prev(w, nil, ctx) 119 | } 120 | 121 | w.Printf(0, 0, w.ClientAreaStyle(), "%s", help) 122 | }, nil, 123 | ) 124 | app.OnKey( 125 | func(w *wm.Window, prev wm.OnKeyHandler, key tcell.Key, mod tcell.ModMask, rune rune) bool { 126 | if prev != nil && prev(w, nil, key, mod, rune) { 127 | return true 128 | } 129 | 130 | switch rune { 131 | case 'q', 'Q': 132 | app.Exit(nil) 133 | return true 134 | } 135 | 136 | switch key { 137 | case tcell.KeyCtrlQ: 138 | app.Exit(nil) 139 | return true 140 | case tcell.KeyTAB: 141 | if n := r.Children(); n != 0 { 142 | c := r.Child(0) 143 | c.BringToFront() 144 | c.SetFocus(true) 145 | return true 146 | } 147 | case tcell.KeyESC: 148 | app.Exit(nil) 149 | return true 150 | case tcell.KeyLeft: 151 | w := d.FocusedWindow() 152 | if w == nil { 153 | return true 154 | } 155 | 156 | o := w.Origin() 157 | w.SetOrigin(wm.Position{X: o.X - 1, Y: o.Y}) 158 | r.Invalidate(r.Area()) 159 | return true 160 | case tcell.KeyRight: 161 | w := d.FocusedWindow() 162 | if w == nil { 163 | return true 164 | } 165 | 166 | o := w.Origin() 167 | w.SetOrigin(wm.Position{X: o.X + 1, Y: o.Y}) 168 | r.Invalidate(r.Area()) 169 | return true 170 | case tcell.KeyUp: 171 | w := d.FocusedWindow() 172 | if w == nil { 173 | return true 174 | } 175 | 176 | o := w.Origin() 177 | w.SetOrigin(wm.Position{X: o.X, Y: o.Y - 1}) 178 | r.Invalidate(r.Area()) 179 | return true 180 | case tcell.KeyDown: 181 | w := d.FocusedWindow() 182 | if w == nil { 183 | return true 184 | } 185 | 186 | o := w.Origin() 187 | w.SetOrigin(wm.Position{X: o.X, Y: o.Y + 1}) 188 | r.Invalidate(r.Area()) 189 | return true 190 | } 191 | return false 192 | }, 193 | nil, 194 | ) 195 | m, err := filepath.Glob("*") 196 | if err == nil { 197 | for _, v := range m { 198 | if strings.HasPrefix(v, ".") { 199 | continue 200 | } 201 | 202 | b, err := ioutil.ReadFile(v) 203 | if err != nil { 204 | continue 205 | } 206 | 207 | newWindow(r, -1, -1, v, b) 208 | } 209 | } 210 | } 211 | 212 | type meter wm.Size 213 | 214 | func (m meter) Metrics(_ wm.Rectangle) wm.Size { return wm.Size(m) } 215 | 216 | func main() { 217 | flag.Parse() 218 | rand.Seed(time.Now().UnixNano()) 219 | app, d := demoapp.New() 220 | if err := app.Run(func() { setup(d) }); err != nil { 221 | log.Fatal(err) 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /window.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The WM Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wm 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | 11 | "github.com/cznic/mathutil" 12 | "github.com/gdamore/tcell" 13 | "github.com/mattn/go-runewidth" 14 | ) 15 | 16 | const ( 17 | closeButtonOffset = 4 // X coordinate: Top border area width - closeButtonOffset 18 | closeButtonWidth = 3 19 | ) 20 | 21 | const ( 22 | _ = iota //TODOOK 23 | dragPos 24 | dragRightSize 25 | dragLeftSize 26 | dragBottomSize 27 | dragULC 28 | dragURC 29 | dragLLC 30 | dragLRC 31 | ) 32 | 33 | // Window represents a rectangular area of a screen. A window can have borders 34 | // on all of its sides and a title. 35 | // 36 | // Window methods must be called only directly from an event handler goroutine 37 | // or from a function that was enqueued using Application.Post or 38 | // Application.PostWait. 39 | type Window struct { 40 | borderBottom int // Height. 41 | borderLeft int // Width. 42 | borderRight int // Width. 43 | borderTop int // Height. 44 | children []*Window // In z-order. 45 | clientArea Rectangle // In window coordinates, excludes any borders. 46 | closeButton bool // Enable. 47 | ctx PaintContext // Valid during painting. 48 | desktop *Desktop // Which Desktop this window belongs to. Never changes. 49 | dragScreenPos0 Position // Mouse screen position on drag event. 50 | dragState int // One of the drag{Pos,RightSize,...} constants, 51 | dragWinPos0 Position // Window position on drag event. 52 | dragWinSize0 Size // Window size on drag event. 53 | dragWindow *Window // Which window will receive mouse move and drop events. 54 | dragWindowPos Position // In parent window coordinates. 55 | focus bool // Whether this window has focus. 56 | focusedWindow *Window // Root window only. 57 | onClearBorders *OnPaintHandlerList // 58 | onClearClientArea *OnPaintHandlerList // 59 | onClick *OnMouseHandlerList // 60 | onClickBorder *OnMouseHandlerList // 61 | onClose *onCloseHandlerList // 62 | onDoubleClick *OnMouseHandlerList // 63 | onDoubleClickBorder *OnMouseHandlerList // 64 | onDrag *OnMouseHandlerList // 65 | onDragBorder *OnMouseHandlerList // 66 | onDrop *OnMouseHandlerList // 67 | onKey *onKeyHandlerList // 68 | onMouseMove *OnMouseHandlerList // 69 | onPaintBorderBottom *OnPaintHandlerList // 70 | onPaintBorderLeft *OnPaintHandlerList // 71 | onPaintBorderRight *OnPaintHandlerList // 72 | onPaintBorderTop *OnPaintHandlerList // 73 | onPaintChildren *OnPaintHandlerList // 74 | onPaintClientArea *OnPaintHandlerList // 75 | onPaintTitle *OnPaintHandlerList // 76 | onSetBorderBotom *OnSetIntHandlerList // 77 | onSetBorderLeft *OnSetIntHandlerList // 78 | onSetBorderRight *OnSetIntHandlerList // 79 | onSetBorderStyle *OnSetStyleHandlerList // 80 | onSetBorderTop *OnSetIntHandlerList // 81 | onSetClientAreaStyle *OnSetStyleHandlerList // 82 | onSetClientSize *OnSetSizeHandlerList // 83 | onSetCloseButton *OnSetBoolHandlerList // 84 | onSetFocus *OnSetBoolHandlerList // 85 | onSetFocusedWindow *onSetWindowHandlerList // Root window only. 86 | onSetOrigin *OnSetPositionHandlerList // 87 | onSetPosition *OnSetPositionHandlerList // 88 | onSetSelection *onSetRectangleHandlerList // Root window only. 89 | onSetSize *OnSetSizeHandlerList // 90 | onSetStyle *onSetWindowStyleHandlerList // 91 | onSetTitle *onSetStringHandlerList // 92 | parent *Window // Nil for root window. 93 | position Position // In parent window coordinates. 94 | rendered time.Duration // 95 | selection Rectangle // Root window only. 96 | size Size // 97 | style WindowStyle // 98 | title string // 99 | view Position // Viewport origin. 100 | } 101 | 102 | func newWindow(desktop *Desktop, parent *Window, style WindowStyle) *Window { 103 | w := &Window{ 104 | desktop: desktop, 105 | parent: parent, 106 | style: style, 107 | } 108 | AddOnPaintHandler(&w.onClearBorders, w.onClearBordersHandler, nil) 109 | AddOnPaintHandler(&w.onClearClientArea, w.onClearClientAreaHandler, nil) 110 | AddOnPaintHandler(&w.onPaintChildren, w.onPaintChildrenHandler, nil) 111 | w.OnClickBorder(w.onClickBorderHandler, nil) 112 | w.OnDragBorder(w.onDragBorderHandler, nil) 113 | w.OnPaintBorderBottom(w.onPaintBorderBottomHandler, nil) 114 | w.OnPaintBorderLeft(w.onPaintBorderLeftHandler, nil) 115 | w.OnPaintBorderRight(w.onPaintBorderRightHandler, nil) 116 | w.OnPaintBorderTop(w.onPaintBorderTopHandler, nil) 117 | w.OnPaintTitle(w.onPaintTitleHandler, nil) 118 | w.OnSetBorderBottom(w.onSetBorderBottomHandler, nil) 119 | w.OnSetBorderLeft(w.onSetBorderLeftHandler, nil) 120 | w.OnSetBorderRight(w.onSetBorderRightHandler, nil) 121 | w.OnSetBorderStyle(w.onSetBorderStyleHandler, nil) 122 | w.OnSetBorderTop(w.onSetBorderTopHandler, nil) 123 | w.OnSetClientAreaStyle(w.onSetClientAreaStyleHandler, nil) 124 | w.OnSetClientSize(w.onSetClientSizeHandler, nil) 125 | w.OnSetCloseButton(w.onSetCloseButtonHandler, nil) 126 | w.OnSetFocus(w.onSetFocusHandler, nil) 127 | w.OnSetOrigin(w.onSetOriginHandler, nil) 128 | w.OnSetPosition(w.onSetPositionHandler, nil) 129 | w.OnSetSize(w.onSetSizeHandler, nil) 130 | w.OnSetStyle(w.onSetStyleHandler, nil) 131 | w.OnSetTitle(w.onSetTitleHandler, nil) 132 | return w 133 | } 134 | 135 | func (w *Window) setCell(p Position, mainc rune, combc []rune, style tcell.Style) { 136 | if !w.ctx.origin.add(p).In(w.ctx.Rectangle) { 137 | return 138 | } 139 | 140 | p = p.add(w.position).add(w.ctx.origin).sub(w.ctx.view) 141 | switch w := w.Parent(); w { 142 | case nil: 143 | App.setCell(p, mainc, combc, style) 144 | default: 145 | w.setCell(p, mainc, combc, style) 146 | } 147 | } 148 | 149 | func (w *Window) onPaintTitleHandler(_ *Window, prev OnPaintHandler, _ PaintContext) { 150 | if prev != nil { 151 | panic("internal error") 152 | } 153 | 154 | title := w.Title() 155 | if title == "" { 156 | return 157 | } 158 | 159 | w.Printf(0, 0, w.Style().Title, " %s ", title) 160 | } 161 | 162 | func (w *Window) onSetTitleHandler(_ *Window, prev OnSetStringHandler, dst *string, src string) { 163 | if prev != nil { 164 | panic("internal error") 165 | } 166 | 167 | *dst = src 168 | w.Invalidate(w.BorderTopArea()) 169 | } 170 | 171 | func (w *Window) onSetCloseButtonHandler(_ *Window, prev OnSetBoolHandler, dst *bool, src bool) { 172 | if prev != nil { 173 | panic("internal error") 174 | } 175 | 176 | *dst = src 177 | w.Invalidate(w.BorderTopArea()) 178 | } 179 | 180 | func (w *Window) onClickBorderHandler(_ *Window, prev OnMouseHandler, button tcell.ButtonMask, screenPos, pos Position, mods tcell.ModMask) bool { 181 | if button != tcell.Button1 || mods != 0 { 182 | return false 183 | } 184 | 185 | w.BringToFront() 186 | w.SetFocus(true) 187 | if w.CloseButton() && pos.In(w.closeButtonArea()) { 188 | w.Close() //TODO CloseQuery 189 | return true 190 | } 191 | 192 | return false 193 | 194 | } 195 | 196 | func (w *Window) onDragBorderHandler(_ *Window, prev OnMouseHandler, button tcell.ButtonMask, screenPos, pos Position, mods tcell.ModMask) bool { 197 | if prev != nil { 198 | panic("internal error") 199 | } 200 | 201 | if button != tcell.Button1 || mods != 0 || w.Parent() == nil { 202 | return false 203 | } 204 | 205 | switch { 206 | case pos.In(w.topBorderDragMoveArea()): 207 | w.BringToFront() 208 | w.SetFocus(true) 209 | w.dragState = dragPos 210 | w.dragScreenPos0 = screenPos 211 | w.dragWinPos0 = w.position 212 | return true 213 | case pos.In(w.rightBorderDragResizeArea()): 214 | w.BringToFront() 215 | w.SetFocus(true) 216 | w.dragState = dragRightSize 217 | w.dragScreenPos0 = screenPos 218 | w.dragWinSize0 = w.size 219 | return true 220 | case pos.In(w.leftBorderDragResizeArea()): 221 | w.BringToFront() 222 | w.SetFocus(true) 223 | w.dragState = dragLeftSize 224 | w.dragScreenPos0 = screenPos 225 | w.dragWinPos0 = w.position 226 | w.dragWinSize0 = w.size 227 | return true 228 | case pos.In(w.bottomBorderDragResizeArea()): 229 | w.BringToFront() 230 | w.SetFocus(true) 231 | w.dragState = dragBottomSize 232 | w.dragScreenPos0 = screenPos 233 | w.dragWinSize0 = w.size 234 | return true 235 | case pos.In(w.borderLRCArea()): 236 | w.BringToFront() 237 | w.SetFocus(true) 238 | w.dragState = dragLRC 239 | w.dragScreenPos0 = screenPos 240 | w.dragWinSize0 = w.size 241 | return true 242 | case pos.In(w.borderURCArea()): 243 | w.BringToFront() 244 | w.SetFocus(true) 245 | w.dragState = dragURC 246 | w.dragScreenPos0 = screenPos 247 | w.dragWinPos0 = w.position 248 | w.dragWinSize0 = w.size 249 | return true 250 | case pos.In(w.borderLLCArea()): 251 | w.BringToFront() 252 | w.SetFocus(true) 253 | w.dragState = dragLLC 254 | w.dragScreenPos0 = screenPos 255 | w.dragWinPos0 = w.position 256 | w.dragWinSize0 = w.size 257 | return true 258 | case pos.In(w.borderULCArea()): 259 | w.BringToFront() 260 | w.SetFocus(true) 261 | w.dragState = dragULC 262 | w.dragScreenPos0 = screenPos 263 | w.dragWinPos0 = w.position 264 | w.dragWinSize0 = w.size 265 | return true 266 | default: 267 | return false 268 | } 269 | 270 | } 271 | 272 | func (w *Window) onClearBordersHandler(_ *Window, prev OnPaintHandler, ctx PaintContext) { 273 | if prev != nil { 274 | panic("internal error") 275 | } 276 | 277 | style := w.Style().Border.TCellStyle() 278 | if a := w.BorderTopArea(); a.Clip(ctx.Rectangle) { 279 | w.clear(a, style) 280 | } 281 | if a := w.BorderLeftArea(); a.Clip(ctx.Rectangle) { 282 | w.clear(a, style) 283 | } 284 | if a := w.BorderRightArea(); a.Clip(ctx.Rectangle) { 285 | w.clear(a, style) 286 | } 287 | if a := w.BorderBottomArea(); a.Clip(ctx.Rectangle) { 288 | w.clear(a, style) 289 | } 290 | } 291 | 292 | func (w *Window) onPaintBorderTopHandler(_ *Window, prev OnPaintHandler, _ PaintContext) { 293 | if prev != nil { 294 | panic("internal error") 295 | } 296 | 297 | style := w.Style().Border 298 | tstyle := w.Style().Border.TCellStyle() 299 | sz := w.Size() 300 | borderArea := w.BorderTopArea() 301 | if borderArea.Width == 1 { 302 | w.SetCell(borderArea.X, borderArea.Y, ' ', nil, tstyle) 303 | return 304 | } 305 | 306 | for x := 0; x < borderArea.Width; x++ { 307 | var r rune 308 | switch x { 309 | case 0: 310 | r = tcell.RuneULCorner 311 | if sz.Height < 2 { 312 | r = ' ' 313 | } 314 | case borderArea.Width - 1: 315 | r = tcell.RuneURCorner 316 | if sz.Height < 2 { 317 | r = ' ' 318 | } 319 | default: 320 | r = tcell.RuneHLine 321 | } 322 | w.SetCell(x, 0, r, nil, tstyle) 323 | } 324 | 325 | if x := borderArea.Width - closeButtonOffset; x > 0 && w.CloseButton() { 326 | w.Printf(x, 0, style, "[X]") 327 | } 328 | } 329 | 330 | func (w *Window) onPaintBorderLeftHandler(_ *Window, prev OnPaintHandler, _ PaintContext) { 331 | if prev != nil { 332 | panic("internal error") 333 | } 334 | 335 | style := w.Style().Border.TCellStyle() 336 | sz := w.Size() 337 | borderArea := w.BorderLeftArea() 338 | if borderArea.Height == 1 { 339 | w.SetCell(borderArea.X, borderArea.Y, ' ', nil, style) 340 | return 341 | } 342 | 343 | for y := 0; y < borderArea.Height; y++ { 344 | var r rune 345 | switch y { 346 | case 0: 347 | r = tcell.RuneULCorner 348 | if sz.Width < 2 { 349 | r = ' ' 350 | } 351 | case borderArea.Height - 1: 352 | r = tcell.RuneLLCorner 353 | if sz.Width < 2 { 354 | r = ' ' 355 | } 356 | default: 357 | r = tcell.RuneVLine 358 | } 359 | w.SetCell(0, y, r, nil, style) 360 | } 361 | } 362 | 363 | func (w *Window) onPaintBorderRightHandler(_ *Window, prev OnPaintHandler, _ PaintContext) { 364 | if prev != nil { 365 | panic("internal error") 366 | } 367 | 368 | style := w.Style().Border.TCellStyle() 369 | sz := w.Size() 370 | borderArea := w.BorderRightArea() 371 | if borderArea.Height == 1 { 372 | w.SetCell(borderArea.X, borderArea.Y, ' ', nil, style) 373 | return 374 | } 375 | 376 | x := borderArea.Width - 1 377 | for y := 0; y < borderArea.Height; y++ { 378 | var r rune 379 | switch y { 380 | case 0: 381 | r = tcell.RuneURCorner 382 | if sz.Width < 2 { 383 | r = ' ' 384 | } 385 | case borderArea.Height - 1: 386 | r = tcell.RuneLRCorner 387 | if sz.Width < 2 { 388 | r = ' ' 389 | } 390 | default: 391 | r = tcell.RuneVLine 392 | } 393 | w.SetCell(x, y, r, nil, style) 394 | } 395 | } 396 | 397 | func (w *Window) onPaintBorderBottomHandler(_ *Window, prev OnPaintHandler, _ PaintContext) { 398 | if prev != nil { 399 | panic("internal error") 400 | } 401 | 402 | style := w.Style().Border.TCellStyle() 403 | sz := w.Size() 404 | borderArea := w.BorderBottomArea() 405 | if borderArea.Width == 1 { 406 | w.SetCell(borderArea.X, borderArea.Y, ' ', nil, style) 407 | return 408 | } 409 | 410 | y := borderArea.Height - 1 411 | for x := 0; x < borderArea.Width; x++ { 412 | var r rune 413 | switch x { 414 | case 0: 415 | r = tcell.RuneLLCorner 416 | if sz.Height < 2 { 417 | r = ' ' 418 | } 419 | case borderArea.Width - 1: 420 | r = tcell.RuneLRCorner 421 | if sz.Height < 2 { 422 | r = ' ' 423 | } 424 | default: 425 | r = tcell.RuneHLine 426 | } 427 | w.SetCell(x, y, r, nil, style) 428 | } 429 | } 430 | 431 | func (w *Window) onSetSelectionHandler(_ *Window, prev OnSetRectangleHandler, dst *Rectangle, src Rectangle) { 432 | if prev != nil { 433 | panic("internal error") 434 | } 435 | 436 | App.BeginUpdate() 437 | *dst = src 438 | App.EndUpdate() 439 | } 440 | 441 | func (w *Window) setFocusedWindow(u *Window) { w.onSetFocusedWindow.handle(w, &w.focusedWindow, u) } 442 | 443 | // ATM root window only. 444 | func (w *Window) onSetFocusedWindowHandler(_ *Window, prev OnSetWindowHandler, dst **Window, src *Window) { 445 | if prev != nil || w != w.Desktop().Root() { 446 | panic("internal error") 447 | } 448 | 449 | old := *dst 450 | *dst = src 451 | if old != nil { 452 | old.SetFocus(false) 453 | if old.Parent() != nil { 454 | old.Invalidate(old.Area()) 455 | } 456 | } 457 | 458 | if src != nil { 459 | src.SetFocus(true) 460 | if src.Parent() != nil { 461 | src.Invalidate(src.Area()) 462 | } 463 | } 464 | } 465 | 466 | func (w *Window) onSetFocusHandler(_ *Window, prev OnSetBoolHandler, dst *bool, src bool) { 467 | if prev != nil { 468 | panic("internal error") 469 | } 470 | 471 | *dst = src 472 | d := w.desktop 473 | w.style.Border.Attr ^= tcell.AttrReverse 474 | 475 | switch { 476 | case src: 477 | d.SetFocusedWindow(w) 478 | default: 479 | d.SetFocusedWindow(nil) 480 | } 481 | } 482 | 483 | func (w *Window) onSetBorderStyleHandler(_ *Window, prev OnSetStyleHandler, dst *Style, src Style) { 484 | if prev != nil { 485 | panic("internal error") 486 | } 487 | 488 | *dst = src 489 | w.Invalidate(w.BorderTopArea()) 490 | w.Invalidate(w.BorderLeftArea()) 491 | w.Invalidate(w.BorderRightArea()) 492 | w.Invalidate(w.BorderBottomArea()) 493 | } 494 | 495 | func (w *Window) onSetClientAreaStyleHandler(_ *Window, prev OnSetStyleHandler, dst *Style, src Style) { 496 | if prev != nil { 497 | panic("internal error") 498 | } 499 | 500 | *dst = src 501 | w.InvalidateClientArea(w.ClientArea()) 502 | } 503 | 504 | func (w *Window) onSetStyleHandler(_ *Window, prev OnSetWindowStyleHandler, dst *WindowStyle, src WindowStyle) { 505 | if prev != nil { 506 | panic("internal error") 507 | } 508 | 509 | *dst = src 510 | w.Invalidate(w.Area()) 511 | } 512 | 513 | func (w *Window) clear(area Rectangle, style tcell.Style) { 514 | for y := area.Y; y < area.Y+area.Height; y++ { 515 | for x := area.X; x < area.X+area.Width; x++ { 516 | w.SetCell(x, y, ' ', nil, style) 517 | } 518 | } 519 | } 520 | 521 | func (w *Window) onClearClientAreaHandler(_ *Window, prev OnPaintHandler, ctx PaintContext) { 522 | if prev != nil { 523 | panic("internal error") 524 | } 525 | 526 | w.clear(Rectangle{ctx.sub(ctx.origin), ctx.Rectangle.Size}, w.Style().ClientArea.TCellStyle()) 527 | } 528 | 529 | func (w *Window) onPaintChildrenHandler(_ *Window, prev OnPaintHandler, ctx PaintContext) { 530 | if prev != nil { 531 | panic("internal error") 532 | } 533 | 534 | clPos := w.ClientPosition() 535 | for i := 0; ; i++ { 536 | c := w.Child(i) 537 | if c == nil { 538 | break 539 | } 540 | 541 | chPos := c.Position().add(clPos) 542 | if area := (Rectangle{chPos, c.Size()}); area.Clip(ctx.Rectangle) { 543 | c.paint(Rectangle{area.sub(chPos), area.Size}) 544 | } 545 | } 546 | } 547 | 548 | func (w *Window) onSetOriginHandler(_ *Window, prev OnSetPositionHandler, dst *Position, src Position) { 549 | if prev != nil { 550 | panic("internal error") 551 | } 552 | 553 | w.Invalidate(w.ClientArea()) 554 | *dst = src 555 | } 556 | 557 | func (w *Window) onSetPositionHandler(_ *Window, prev OnSetPositionHandler, dst *Position, src Position) { 558 | if prev != nil { 559 | panic("internal error") 560 | } 561 | 562 | w.Invalidate(w.Area()) 563 | *dst = src 564 | w.Invalidate(w.Area()) 565 | } 566 | 567 | func (w *Window) onSetSizeHandler(_ *Window, prev OnSetSizeHandler, dst *Size, src Size) { 568 | if prev != nil { 569 | panic("internal error") 570 | } 571 | 572 | src.Width = mathutil.Max(0, src.Width) 573 | src.Height = mathutil.Max(0, src.Height) 574 | w.Invalidate(w.Area()) 575 | *dst = src 576 | csz := Size{ 577 | mathutil.Max(0, src.Width-(w.borderLeft+w.borderRight)), 578 | mathutil.Max(0, src.Height-(w.borderTop+w.borderBottom)), 579 | } 580 | w.SetClientSize(csz) 581 | w.Invalidate(w.Area()) 582 | } 583 | 584 | func (w *Window) onSetClientSizeHandler(_ *Window, prev OnSetSizeHandler, dst *Size, src Size) { 585 | if prev != nil { 586 | panic("internal error") 587 | } 588 | 589 | src.Width = mathutil.Max(0, src.Width) 590 | src.Height = mathutil.Max(0, src.Height) 591 | w.Invalidate(w.Area()) 592 | *dst = src 593 | wsz := Size{ 594 | w.borderLeft + src.Width + w.borderRight, 595 | w.borderTop + src.Height + w.borderBottom, 596 | } 597 | p := w.parent 598 | 599 | w.SetSize(wsz) 600 | if p != nil { 601 | w.Invalidate(w.Area()) 602 | return 603 | } 604 | 605 | w.InvalidateClientArea(w.ClientArea()) 606 | } 607 | 608 | func (w *Window) onSetBorderBottomHandler(_ *Window, prev OnSetIntHandler, dst *int, src int) { 609 | if prev != nil { 610 | panic("internal error") 611 | } 612 | 613 | *dst = src 614 | sz := Size{w.clientArea.Width, mathutil.Max(0, w.size.Height-(w.borderTop+w.borderBottom))} 615 | w.SetClientSize(sz) 616 | } 617 | 618 | func (w *Window) onSetBorderLeftHandler(_ *Window, prev OnSetIntHandler, dst *int, src int) { 619 | if prev != nil { 620 | panic("internal error") 621 | } 622 | 623 | *dst = src 624 | w.clientArea.X = src 625 | sz := Size{mathutil.Max(0, w.size.Width-(w.borderLeft+w.borderRight)), w.clientArea.Height} 626 | w.SetClientSize(sz) 627 | } 628 | 629 | func (w *Window) onSetBorderRightHandler(_ *Window, prev OnSetIntHandler, dst *int, src int) { 630 | if prev != nil { 631 | panic("internal error") 632 | } 633 | 634 | *dst = src 635 | sz := Size{mathutil.Max(0, w.size.Width-(w.borderLeft+w.borderRight)), w.clientArea.Height} 636 | w.SetClientSize(sz) 637 | } 638 | 639 | func (w *Window) onSetBorderTopHandler(_ *Window, prev OnSetIntHandler, dst *int, src int) { 640 | if prev != nil { 641 | panic("internal error") 642 | } 643 | 644 | *dst = src 645 | w.clientArea.Y = src 646 | sz := Size{w.clientArea.Width, mathutil.Max(0, w.size.Height-(w.borderTop+w.borderBottom))} 647 | w.SetClientSize(sz) 648 | } 649 | 650 | func (w *Window) printCell(x, y, width int, main rune, comb []rune, style tcell.Style) (int, int) { 651 | switch main { 652 | case '\t': 653 | return x + 8 - x%8, y 654 | case '\n': 655 | return 0, y + 1 656 | case '\r': 657 | return 0, y 658 | default: 659 | w.SetCell(x, y, main, comb, style) 660 | return x + width, y 661 | } 662 | } 663 | 664 | // BeginUpdate marks the start of one or more updates to w. 665 | // 666 | // Failing to properly pair BeginUpdate with a corresponding EndUpdate will 667 | // cause application screen corruption and/or freeze. 668 | func (w *Window) BeginUpdate() { 669 | if w != nil { 670 | d := w.Desktop() 671 | d.updateLevel++ 672 | if d.updateLevel == 1 { 673 | d.invalidated = Rectangle{} 674 | } 675 | return 676 | } 677 | 678 | App.BeginUpdate() 679 | } 680 | 681 | // EndUpdate marks the end of one or more updates to w. 682 | // 683 | // Failing to properly pair BeginUpdate with a corresponding EndUpdate will 684 | // cause application screen corruption and/or freeze. 685 | func (w *Window) EndUpdate() { 686 | if w != nil { 687 | d := w.Desktop() 688 | d.updateLevel-- 689 | invalidated := d.invalidated 690 | if d.updateLevel == 0 && !invalidated.IsZero() { 691 | App.BeginUpdate() 692 | r := d.Root() 693 | t := time.Now() 694 | r.paint(invalidated) 695 | r.rendered = time.Since(t) 696 | App.EndUpdate() 697 | } 698 | return 699 | } 700 | 701 | App.EndUpdate() 702 | } 703 | 704 | // setSize sets the window size. 705 | func (w *Window) setSize(s Size) { w.onSetSize.Handle(w, &w.size, s) } 706 | 707 | func (w *Window) findEventTarget(winPos Position, clientAreaHandler, borderHandler func(*Window, Position)) (*Window, Position, func(*Window, Position)) { 708 | search: 709 | winPos2 := winPos.add(w.view) 710 | clArea := w.ClientArea() 711 | if winPos.In(clArea) { 712 | winPos = winPos2.sub(clArea.Position) 713 | var chArea Rectangle 714 | for i := len(w.children) - 1; i >= 0; i-- { 715 | ch := w.children[i] 716 | chArea = ch.Area() 717 | chArea.Position = ch.Position() 718 | if winPos.In(chArea) { 719 | winPos = winPos.sub(chArea.Position) 720 | w = ch 721 | goto search 722 | } 723 | } 724 | 725 | return w, winPos, clientAreaHandler 726 | } 727 | 728 | return w, winPos, borderHandler 729 | } 730 | 731 | func (w *Window) event(winPos Position, clientAreaHandler, borderHandler func(*Window, Position), setFocus bool) { 732 | w, pos, handler := w.findEventTarget(winPos, clientAreaHandler, borderHandler) 733 | if setFocus { 734 | w.BringToFront() 735 | w.SetFocus(true) 736 | } 737 | handler(w, pos) 738 | } 739 | 740 | func (w *Window) click(button tcell.ButtonMask, screenPos Position, mods tcell.ModMask) { 741 | w.event( 742 | screenPos, 743 | func(w *Window, winPos Position) { 744 | w.onClick.Handle(w, button, screenPos, winPos, mods) 745 | }, 746 | func(w *Window, winPos Position) { 747 | w.onClickBorder.Handle(w, button, screenPos, winPos, mods) 748 | }, 749 | true, 750 | ) 751 | } 752 | 753 | func (w *Window) doubleClick(button tcell.ButtonMask, screenPos Position, mods tcell.ModMask) { 754 | w.event( 755 | screenPos, 756 | func(w *Window, winPos Position) { 757 | w.onDoubleClick.Handle(w, button, screenPos, winPos, mods) 758 | }, 759 | func(w *Window, winPos Position) { 760 | w.onDoubleClickBorder.Handle(w, button, screenPos, winPos, mods) 761 | }, 762 | true, 763 | ) 764 | } 765 | 766 | func (w *Window) drag(button tcell.ButtonMask, screenPos Position, mods tcell.ModMask) { 767 | w.dragWindow = nil 768 | w.event( 769 | screenPos, 770 | func(cw *Window, winPos Position) { 771 | if cw.onDrag.Handle(cw, button, screenPos, winPos, mods) { 772 | w.dragWindow = cw 773 | w.dragWindowPos = winPos 774 | } 775 | }, 776 | func(cw *Window, winPos Position) { 777 | if cw.onDragBorder.Handle(cw, button, screenPos, winPos, mods) { 778 | w.dragWindow = cw 779 | w.dragWindowPos = winPos 780 | } 781 | }, 782 | true, 783 | ) 784 | } 785 | func (w *Window) drop(button tcell.ButtonMask, screenPos Position, mods tcell.ModMask) { 786 | defer func() { w.dragWindow = nil }() 787 | 788 | if fw := w.Desktop().FocusedWindow(); fw != nil && button == tcell.Button1 && mods == 0 { 789 | ds := fw.dragState 790 | fw.dragState = 0 791 | screenPos0 := fw.dragScreenPos0 792 | winPos0 := fw.dragWinPos0 793 | winSize0 := fw.dragWinSize0 794 | dx := screenPos.X - screenPos0.X 795 | dy := screenPos.Y - screenPos0.Y 796 | 797 | switch ds { 798 | case dragPos: 799 | fw.SetPosition(Position{winPos0.X + dx, winPos0.Y + dy}) 800 | return 801 | case dragRightSize: 802 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width+dx), winSize0.Height}) 803 | return 804 | case dragLeftSize: 805 | if dx > winSize0.Width { 806 | dx = winSize0.Width - 1 807 | } 808 | fw.SetPosition(Position{winPos0.X + dx, winPos0.Y}) 809 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width-dx), winSize0.Height}) 810 | return 811 | case dragBottomSize: 812 | fw.SetSize(Size{winSize0.Width, mathutil.Max(1, winSize0.Height+dy)}) 813 | return 814 | case dragLRC: 815 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width+dx), mathutil.Max(1, winSize0.Height+dy)}) 816 | return 817 | case dragURC: 818 | if dy > winSize0.Height { 819 | dy = winSize0.Height - 1 820 | } 821 | fw.SetPosition(Position{winPos0.X, winPos0.Y + dy}) 822 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width+dx), mathutil.Max(1, winSize0.Height-dy)}) 823 | return 824 | case dragLLC: 825 | if dx > winSize0.Width { 826 | dx = winSize0.Width - 1 827 | } 828 | fw.SetPosition(Position{winPos0.X + dx, winPos0.Y}) 829 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width-dx), mathutil.Max(1, winSize0.Height+dy)}) 830 | return 831 | case dragULC: 832 | if dx > winSize0.Width { 833 | dx = winSize0.Width - 1 834 | } 835 | if dy > winSize0.Height { 836 | dy = winSize0.Height - 1 837 | } 838 | fw.SetPosition(Position{winPos0.X + dx, winPos0.Y + dy}) 839 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width-dx), mathutil.Max(1, winSize0.Height-dy)}) 840 | return 841 | default: 842 | if fw == w.dragWindow { 843 | fw.onDrop.Handle(fw, button, screenPos, w.dragWindowPos, mods) 844 | return 845 | } 846 | } 847 | } 848 | 849 | w.event( 850 | screenPos, 851 | func(w *Window, winPos Position) { 852 | w.onDrop.Handle(w, button, screenPos, winPos, mods) 853 | }, 854 | func(w *Window, winPos Position) { 855 | w.onDrop.Handle(w, button, screenPos, winPos, mods) 856 | }, 857 | true, 858 | ) 859 | } 860 | func (w *Window) mouseMove(button tcell.ButtonMask, screenPos Position, mods tcell.ModMask) { 861 | if fw := w.Desktop().FocusedWindow(); fw != nil { 862 | ds := fw.dragState 863 | screenPos0 := fw.dragScreenPos0 864 | winPos0 := fw.dragWinPos0 865 | winSize0 := fw.dragWinSize0 866 | dx := screenPos.X - screenPos0.X 867 | dy := screenPos.Y - screenPos0.Y 868 | 869 | switch ds { 870 | case dragPos: 871 | fw.SetPosition(Position{winPos0.X + dx, winPos0.Y + dy}) 872 | return 873 | case dragRightSize: 874 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width+dx), winSize0.Height}) 875 | return 876 | case dragLeftSize: 877 | if dx > winSize0.Width { 878 | dx = winSize0.Width - 1 879 | } 880 | fw.SetPosition(Position{winPos0.X + dx, winPos0.Y}) 881 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width-dx), winSize0.Height}) 882 | return 883 | case dragBottomSize: 884 | fw.SetSize(Size{winSize0.Width, mathutil.Max(1, winSize0.Height+dy)}) 885 | return 886 | case dragLRC: 887 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width+dx), mathutil.Max(1, winSize0.Height+dy)}) 888 | return 889 | case dragURC: 890 | if dy > winSize0.Height { 891 | dy = winSize0.Height - 1 892 | } 893 | fw.SetPosition(Position{winPos0.X, winPos0.Y + dy}) 894 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width+dx), mathutil.Max(1, winSize0.Height-dy)}) 895 | return 896 | case dragLLC: 897 | if dx > winSize0.Width { 898 | dx = winSize0.Width - 1 899 | } 900 | fw.SetPosition(Position{winPos0.X + dx, winPos0.Y}) 901 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width-dx), mathutil.Max(1, winSize0.Height+dy)}) 902 | return 903 | case dragULC: 904 | if dx > winSize0.Width { 905 | dx = winSize0.Width - 1 906 | } 907 | if dy > winSize0.Height { 908 | dy = winSize0.Height - 1 909 | } 910 | fw.SetPosition(Position{winPos0.X + dx, winPos0.Y + dy}) 911 | fw.SetSize(Size{mathutil.Max(1, winSize0.Width-dx), mathutil.Max(1, winSize0.Height-dy)}) 912 | return 913 | default: 914 | if fw == w.dragWindow { 915 | fw.onMouseMove.Handle(fw, button, screenPos, w.dragWindowPos, mods) 916 | return 917 | } 918 | } 919 | } 920 | 921 | w.event( 922 | screenPos, 923 | func(w *Window, winPos Position) { 924 | w.onMouseMove.Handle(w, button, screenPos, winPos, mods) 925 | }, 926 | func(w *Window, winPos Position) {}, 927 | false, 928 | ) 929 | } 930 | 931 | // paint asks w to render an area. 932 | func (w *Window) paint(area Rectangle) { 933 | d := w.Desktop() 934 | if area.IsZero() || !area.Clip(Rectangle{Size: w.size}) || d != App.Desktop() { 935 | return 936 | } 937 | 938 | if d.updateLevel != 0 { 939 | for { 940 | p := w.Parent() 941 | if p == nil { 942 | d.invalidated.join(area) 943 | return 944 | } 945 | 946 | area.Position = area.add(w.Position()).add(p.ClientPosition().sub(p.Origin())) 947 | if !area.Clip(p.ClientArea()) { 948 | return 949 | } 950 | 951 | w = p 952 | } 953 | } 954 | 955 | a0 := w.Area() 956 | if a := a0; a.Clip(area) { 957 | w.onClearBorders.Handle(w, PaintContext{a, a0.Position, Position{}}) 958 | } 959 | 960 | a0 = w.BorderTopArea() 961 | if a := a0; a.Clip(area) { 962 | w.onPaintBorderTop.Handle(w, PaintContext{a, a0.Position, Position{}}) 963 | } 964 | 965 | if !a0.IsZero() && w.Title() != "" { 966 | a0.X++ 967 | a0.Width-- 968 | if w.CloseButton() { 969 | a0.Width -= closeButtonOffset 970 | } 971 | a0.Height = 1 972 | if a := a0; a.Clip(area) { 973 | w.onPaintTitle.Handle(w, PaintContext{a, a0.Position, Position{}}) 974 | } 975 | } 976 | 977 | a0 = w.BorderLeftArea() 978 | if a := a0; a.Clip(area) { 979 | w.onPaintBorderLeft.Handle(w, PaintContext{a, a0.Position, Position{}}) 980 | } 981 | 982 | a0 = w.ClientArea() 983 | if a := a0; a.Clip(area) { 984 | ctx := PaintContext{a, a0.Position, Position{}} 985 | w.onClearClientArea.Handle(w, ctx) 986 | } 987 | 988 | a0 = w.ClientArea() 989 | if a := a0; a.Clip(area) { 990 | a.Position = a.add(w.view) 991 | ctx := PaintContext{a, a0.Position, w.view} 992 | w.onPaintClientArea.Handle(w, ctx) 993 | w.onPaintChildren.Handle(w, ctx) 994 | } 995 | 996 | a0 = w.BorderRightArea() 997 | if a := a0; a.Clip(area) { 998 | w.onPaintBorderRight.Handle(w, PaintContext{a, a0.Position, Position{}}) 999 | } 1000 | 1001 | a0 = w.BorderBottomArea() 1002 | if a := a0; a.Clip(area) { 1003 | w.onPaintBorderBottom.Handle(w, PaintContext{a, a0.Position, Position{}}) 1004 | } 1005 | } 1006 | 1007 | func (w *Window) print(x, y int, style tcell.Style, s string) { 1008 | if s == "" { 1009 | return 1010 | } 1011 | 1012 | if w.ctx.IsZero() { // Zero sized window or not in OnPaint. 1013 | return 1014 | } 1015 | 1016 | var main rune 1017 | var comb []rune 1018 | var state, width int 1019 | 1020 | const ( 1021 | st0 = iota // main, width, comb not valid. 1022 | stCheckComb // main, width, comb valid, checking if followed by combining char(s). 1023 | stComb // main, width, comb valid, collecting combining chars. 1024 | ) 1025 | 1026 | for _, r := range s { 1027 | if r == 0 { 1028 | continue 1029 | } 1030 | 1031 | switch runewidth.RuneWidth(r) { 1032 | case 0: // Combining char. 1033 | switch state { 1034 | case st0: 1035 | main = ' ' 1036 | width = 1 1037 | comb = append(comb[:0], r) 1038 | state = stComb 1039 | case stCheckComb: 1040 | comb = append(comb, r) 1041 | state = stComb 1042 | case stComb: 1043 | comb = append(comb, r) 1044 | state = stComb 1045 | default: 1046 | panic("internal error") 1047 | } 1048 | case 1: // Normal width. 1049 | switch state { 1050 | case st0: 1051 | main = r 1052 | width = 1 1053 | comb = comb[:0] 1054 | state = stCheckComb 1055 | case stCheckComb: 1056 | x, y = w.printCell(x, y, width, main, comb, style) 1057 | main = r 1058 | width = 1 1059 | comb = comb[:0] 1060 | state = stCheckComb 1061 | case stComb: 1062 | comb = append(comb, r) 1063 | state = stComb 1064 | default: 1065 | panic("internal error") 1066 | } 1067 | case 2: // Double width. 1068 | switch state { 1069 | case st0: 1070 | main = r 1071 | width = 2 1072 | comb = comb[:0] 1073 | state = stCheckComb 1074 | case stCheckComb: 1075 | x, y = w.printCell(x, y, width, main, comb, style) 1076 | main = r 1077 | width = 2 1078 | comb = comb[:0] 1079 | state = stCheckComb 1080 | case stComb: 1081 | comb = append(comb, r) 1082 | state = stComb 1083 | default: 1084 | panic("internal error") 1085 | } 1086 | default: 1087 | panic("internal error") 1088 | } 1089 | } 1090 | switch state { 1091 | case stCheckComb, stComb: 1092 | w.printCell(x, y, width, main, comb, style) 1093 | default: 1094 | panic(fmt.Errorf("%q: %v", s, state)) 1095 | } 1096 | } 1097 | 1098 | func (w *Window) bringChildWindowToFront(c *Window) { 1099 | if w == nil { 1100 | return 1101 | } 1102 | 1103 | if p := w.Parent(); p != nil { 1104 | p.bringChildWindowToFront(w) 1105 | } 1106 | for i, v := range w.children { 1107 | if v == c { 1108 | if i == len(w.children)-1 { // Already in front. 1109 | return 1110 | } 1111 | 1112 | copy(w.children[i:], w.children[i+1:]) 1113 | w.children[len(w.children)-1] = c 1114 | break 1115 | } 1116 | } 1117 | w.InvalidateClientArea(Rectangle{c.Position(), c.Size()}) 1118 | } 1119 | 1120 | func (w *Window) removeChild(ch *Window) { 1121 | for i, v := range w.children { 1122 | if v == ch { 1123 | copy(w.children[i:], w.children[i+1:]) 1124 | w.children = w.children[:len(w.children)-1] 1125 | break 1126 | } 1127 | } 1128 | } 1129 | 1130 | func (w *Window) closeButtonArea() (r Rectangle) { 1131 | if w.BorderTop() > 0 { 1132 | r.X = w.size.Width - closeButtonOffset 1133 | r.Width = closeButtonWidth 1134 | r.Height = 1 1135 | } 1136 | return r 1137 | } 1138 | 1139 | func (w *Window) topBorderDragMoveArea() (r Rectangle) { 1140 | r = w.BorderTopArea() 1141 | if !r.IsZero() { 1142 | r.X++ 1143 | r.Width -= 2 1144 | r.Height = 1 1145 | } 1146 | return r 1147 | } 1148 | 1149 | func (w *Window) leftBorderDragResizeArea() (r Rectangle) { 1150 | r = w.BorderLeftArea() 1151 | if !r.IsZero() { 1152 | r.Y++ 1153 | r.Height -= 2 1154 | r.Width = 1 1155 | } 1156 | return r 1157 | } 1158 | 1159 | func (w *Window) rightBorderDragResizeArea() (r Rectangle) { 1160 | r = w.BorderRightArea() 1161 | if !r.IsZero() { 1162 | r.Y++ 1163 | r.Height -= 2 1164 | r.X += r.Width - 1 1165 | } 1166 | return r 1167 | } 1168 | 1169 | func (w *Window) bottomBorderDragResizeArea() (r Rectangle) { 1170 | r = w.BorderBottomArea() 1171 | if !r.IsZero() { 1172 | r.X++ 1173 | r.Width -= 2 1174 | r.Y += r.Height - 1 1175 | r.Height = 1 1176 | } 1177 | return r 1178 | } 1179 | 1180 | func (w *Window) borderLRCArea() (r Rectangle) { 1181 | r = w.BorderRightArea() 1182 | if !r.IsZero() { 1183 | r.X += r.Width - 1 1184 | r.Y += r.Height - 1 1185 | r.Width = 1 1186 | r.Height = 1 1187 | } 1188 | return r 1189 | } 1190 | 1191 | func (w *Window) borderURCArea() (r Rectangle) { 1192 | r = w.BorderRightArea() 1193 | if !r.IsZero() { 1194 | r.X += r.Width - 1 1195 | r.Width = 1 1196 | r.Height = 1 1197 | } 1198 | return r 1199 | } 1200 | 1201 | func (w *Window) borderLLCArea() (r Rectangle) { 1202 | r = w.BorderBottomArea() 1203 | if !r.IsZero() { 1204 | r.Y += r.Height - 1 1205 | r.Width = 1 1206 | r.Height = 1 1207 | } 1208 | return r 1209 | } 1210 | 1211 | func (w *Window) borderULCArea() (r Rectangle) { 1212 | r = w.BorderTopArea() 1213 | if !r.IsZero() { 1214 | r.Width = 1 1215 | r.Height = 1 1216 | } 1217 | return r 1218 | } 1219 | 1220 | // ---------------------------------------------------------------------------- 1221 | 1222 | // Area returns the area of the window. 1223 | func (w *Window) Area() Rectangle { return Rectangle{Size: w.size} } 1224 | 1225 | // BorderBottom returns the height of the bottom border. 1226 | func (w *Window) BorderBottom() int { return w.borderBottom } 1227 | 1228 | // BorderBottomArea returns the area of the bottom border. 1229 | func (w *Window) BorderBottomArea() (r Rectangle) { 1230 | r.Y = w.size.Height - w.borderBottom 1231 | r.Width = w.size.Width 1232 | r.Height = w.borderBottom 1233 | return r 1234 | } 1235 | 1236 | // BorderLeft returns the width of the left border. 1237 | func (w *Window) BorderLeft() int { return w.borderLeft } 1238 | 1239 | // BorderLeftArea returns the area of the left border. 1240 | func (w *Window) BorderLeftArea() (r Rectangle) { 1241 | r.Width = w.borderLeft 1242 | r.Height = w.size.Height 1243 | return r 1244 | } 1245 | 1246 | // BorderRight returns the width of the right border. 1247 | func (w *Window) BorderRight() int { return w.borderRight } 1248 | 1249 | // BorderRightArea returns the area of the right border. 1250 | func (w *Window) BorderRightArea() (r Rectangle) { 1251 | r.X = w.size.Width - w.borderRight 1252 | r.Width = w.borderRight 1253 | r.Height = w.size.Height 1254 | return r 1255 | } 1256 | 1257 | // BorderStyle returns the border style. 1258 | func (w *Window) BorderStyle() Style { return w.style.Border } 1259 | 1260 | // BorderTop returns the height of the top border. 1261 | func (w *Window) BorderTop() int { return w.borderTop } 1262 | 1263 | // BorderTopArea returns the area of the top border. 1264 | func (w *Window) BorderTopArea() (r Rectangle) { 1265 | r.Width = w.size.Width 1266 | r.Height = w.borderTop 1267 | return r 1268 | } 1269 | 1270 | // BringToFront puts a child window on top of all its siblings. The method has 1271 | // no effect if w is a root window. 1272 | func (w *Window) BringToFront() { w.Parent().bringChildWindowToFront(w) } 1273 | 1274 | // Child returns the nth child window or nil if no such exists. 1275 | func (w *Window) Child(n int) (r *Window) { 1276 | if n < len(w.children) { 1277 | return w.children[n] 1278 | } 1279 | 1280 | return nil 1281 | } 1282 | 1283 | // Children returns the number of child windows. 1284 | func (w *Window) Children() (r int) { 1285 | r = len(w.children) 1286 | return r 1287 | } 1288 | 1289 | // ClientArea returns the client area. 1290 | func (w *Window) ClientArea() Rectangle { return w.clientArea } 1291 | 1292 | // ClientPosition returns the position of the client area relative to w. 1293 | func (w *Window) ClientPosition() Position { return w.clientArea.Position } 1294 | 1295 | // ClientSize returns the size of the client area. 1296 | func (w *Window) ClientSize() Size { return w.clientArea.Size } 1297 | 1298 | // ClientAreaStyle returns the client area style. 1299 | func (w *Window) ClientAreaStyle() Style { return w.style.ClientArea } 1300 | 1301 | // Close closes w. 1302 | func (w *Window) Close() { 1303 | w.onClose.handle(w) 1304 | w.SetFocus(false) 1305 | for w.Children() != 0 { 1306 | if c := w.Child(0); c != nil { 1307 | c.Close() 1308 | } 1309 | } 1310 | if p := w.Parent(); p != nil { 1311 | p.removeChild(w) 1312 | p.InvalidateClientArea(p.ClientArea()) 1313 | } 1314 | 1315 | w.onClearBorders.Clear() 1316 | w.onClearClientArea.Clear() 1317 | w.onClick.Clear() 1318 | w.onClickBorder.Clear() 1319 | w.onClose.clear() 1320 | w.onDoubleClick.Clear() 1321 | w.onDoubleClickBorder.Clear() 1322 | w.onDrag.Clear() 1323 | w.onDragBorder.Clear() 1324 | w.onDrop.Clear() 1325 | w.onKey.clear() 1326 | w.onMouseMove.Clear() 1327 | w.onPaintBorderBottom.Clear() 1328 | w.onPaintBorderLeft.Clear() 1329 | w.onPaintBorderRight.Clear() 1330 | w.onPaintBorderTop.Clear() 1331 | w.onPaintChildren.Clear() 1332 | w.onPaintClientArea.Clear() 1333 | w.onPaintTitle.Clear() 1334 | w.onSetBorderBotom.Clear() 1335 | w.onSetBorderLeft.Clear() 1336 | w.onSetBorderRight.Clear() 1337 | w.onSetBorderStyle.Clear() 1338 | w.onSetBorderTop.Clear() 1339 | w.onSetClientAreaStyle.Clear() 1340 | w.onSetClientSize.Clear() 1341 | w.onSetCloseButton.Clear() 1342 | w.onSetFocus.Clear() 1343 | w.onSetFocusedWindow.clear() 1344 | w.onSetOrigin.Clear() 1345 | w.onSetPosition.Clear() 1346 | w.onSetSelection.clear() 1347 | w.onSetSize.Clear() 1348 | w.onSetStyle.clear() 1349 | w.onSetTitle.clear() 1350 | } 1351 | 1352 | // CloseButton returns whether the window shows a close button. 1353 | func (w *Window) CloseButton() bool { return w.closeButton } 1354 | 1355 | // Desktop returns which Desktop w appears on. 1356 | func (w *Window) Desktop() *Desktop { return w.desktop } 1357 | 1358 | // Focus returns wheter the window is focused. 1359 | func (w *Window) Focus() bool { return w.focus } 1360 | 1361 | // Invalidate marks a window area for repaint. 1362 | func (w *Window) Invalidate(area Rectangle) { 1363 | if !area.Clip(Rectangle{Size: w.size}) { 1364 | return 1365 | } 1366 | 1367 | w.BeginUpdate() 1368 | w.paint(area) 1369 | w.EndUpdate() 1370 | } 1371 | 1372 | // InvalidateClientArea marks an area of the client area for repaint. 1373 | func (w *Window) InvalidateClientArea(area Rectangle) { 1374 | area.Position = area.Position.add(w.ClientPosition()).sub(w.Origin()) 1375 | if !area.Clip(w.clientArea) { 1376 | return 1377 | } 1378 | 1379 | w.BeginUpdate() 1380 | w.paint(area) 1381 | w.EndUpdate() 1382 | } 1383 | 1384 | // NewChild creates a child window. 1385 | func (w *Window) NewChild(area Rectangle) *Window { 1386 | w.BeginUpdate() 1387 | c := newWindow(w.desktop, w, App.ChildWindowStyle()) 1388 | w.children = append(w.children, c) 1389 | c.SetBorderTop(1) 1390 | c.SetBorderLeft(1) 1391 | c.SetBorderRight(1) 1392 | c.SetBorderBottom(1) 1393 | c.SetPosition(area.Position) 1394 | c.SetSize(area.Size) 1395 | w.EndUpdate() 1396 | return c 1397 | } 1398 | 1399 | // OnClick sets a mouse click event handler. When the event handler is removed, 1400 | // finalize is called, if not nil. 1401 | func (w *Window) OnClick(h OnMouseHandler, finalize func()) { 1402 | AddOnMouseHandler(&w.onClick, h, finalize) 1403 | } 1404 | 1405 | // OnClickBorder sets a mouse click border event handler. When the event 1406 | // handler is removed, finalize is called, if not nil. 1407 | func (w *Window) OnClickBorder(h OnMouseHandler, finalize func()) { 1408 | AddOnMouseHandler(&w.onClickBorder, h, finalize) 1409 | } 1410 | 1411 | // OnClose sets a window close event handler. When the event handler is 1412 | // removed, finalize is called, if not nil. 1413 | func (w *Window) OnClose(h OnCloseHandler, finalize func()) { 1414 | addOnCloseHandler(&w.onClose, h, finalize) 1415 | } 1416 | 1417 | // OnDoubleClick sets a mouse double click event handler. When the event 1418 | // handler is removed, finalize is called, if not nil. 1419 | func (w *Window) OnDoubleClick(h OnMouseHandler, finalize func()) { 1420 | AddOnMouseHandler(&w.onDoubleClick, h, finalize) 1421 | } 1422 | 1423 | // OnDoubleClickBorder sets a mouse double click border event handler. When the 1424 | // event handler is removed, finalize is called, if not nil. 1425 | func (w *Window) OnDoubleClickBorder(h OnMouseHandler, finalize func()) { 1426 | AddOnMouseHandler(&w.onDoubleClickBorder, h, finalize) 1427 | } 1428 | 1429 | // OnDrag sets a mouse drag event handler. When the event handler is removed, 1430 | // finalize is called, if not nil. 1431 | func (w *Window) OnDrag(h OnMouseHandler, finalize func()) { 1432 | AddOnMouseHandler(&w.onDrag, h, finalize) 1433 | } 1434 | 1435 | // OnDragBorder sets a mouse drag border event handler. When the event handler 1436 | // is removed, finalize is called, if not nil. 1437 | func (w *Window) OnDragBorder(h OnMouseHandler, finalize func()) { 1438 | AddOnMouseHandler(&w.onDragBorder, h, finalize) 1439 | } 1440 | 1441 | // OnDrop sets a mouse drop event handler. When the event handler is removed, 1442 | // finalize is called, if not nil. 1443 | func (w *Window) OnDrop(h OnMouseHandler, finalize func()) { 1444 | AddOnMouseHandler(&w.onDrop, h, finalize) 1445 | } 1446 | 1447 | // OnKey sets a key event handler. When the event handler is removed, finalize 1448 | // is called, if not nil. 1449 | func (w *Window) OnKey(h OnKeyHandler, finalize func()) { 1450 | addOnKeyHandler(&w.onKey, h, finalize) 1451 | } 1452 | 1453 | // OnMouseMove sets a mouse move event handler. When the event handler is 1454 | // removed, finalize is called, if not nil. 1455 | func (w *Window) OnMouseMove(h OnMouseHandler, finalize func()) { 1456 | AddOnMouseHandler(&w.onMouseMove, h, finalize) 1457 | } 1458 | 1459 | // OnPaintClientArea sets a client area paint handler. When the event handler 1460 | // is removed, finalize is called, if not nil. Example: 1461 | // 1462 | // func onPaintClientArea(w *wm.Window, prev wm.OnPaintHandler, area wm.Rectangle) { 1463 | // if prev != nil { 1464 | // prev(w, nil, area) 1465 | // } 1466 | // w.Printf(0, 0, w.Style(), "Hello 世界!\nTime: %s", time.Now()) 1467 | // } 1468 | // 1469 | // ... 1470 | // 1471 | // w.OnPaintClientArea(onPaintClientArea, nil) 1472 | func (w *Window) OnPaintClientArea(h OnPaintHandler, finalize func()) { 1473 | AddOnPaintHandler(&w.onPaintClientArea, h, finalize) 1474 | } 1475 | 1476 | // OnPaintBorderBottom sets a bottom border paint handler. When the event 1477 | // handler is removed, finalize is called, if not nil. Example: 1478 | // 1479 | // func onPaintBorderBottom(w *wm.Window, prev wm.OnPaintHandler, area wm.Rectangle) { 1480 | // if prev != nil { 1481 | // prev(w, nil, area) 1482 | // } 1483 | // style := w.Style().TCellStyle() 1484 | // w := w.BorderBottomArea().Width 1485 | // for x := 0; x < w; x++ { 1486 | // var r rune 1487 | // switch x { 1488 | // case 0: 1489 | // r = tcell.RuneLLCorner 1490 | // case w - 1: 1491 | // r = tcell.RuneLRCorner 1492 | // default: 1493 | // r = tcell.RuneHLine 1494 | // } 1495 | // w.SetCell(x, 0, r, nil, style) 1496 | // } 1497 | // } 1498 | // 1499 | // ... 1500 | // 1501 | // w.OnPaintBorderBottom(onPaintBorderBottom, nil) 1502 | // w.SetBorderBottom(1) 1503 | func (w *Window) OnPaintBorderBottom(h OnPaintHandler, finalize func()) { 1504 | AddOnPaintHandler(&w.onPaintBorderBottom, h, finalize) 1505 | } 1506 | 1507 | // OnPaintBorderLeft sets a left border paint handler. When the event handler 1508 | // is removed, finalize is called, if not nil. Example: 1509 | // 1510 | // func onPaintBorderLeft(w *wm.Window, prev wm.OnPaintHandler, area wm.Rectangle) { 1511 | // if prev != nil { 1512 | // prev(w, nil, area) 1513 | // } 1514 | // style := w.Style().TCellStyle() 1515 | // h := w.BorderLeftArea().Height 1516 | // for y := 0; y < h; y++ { 1517 | // var r rune 1518 | // switch y { 1519 | // case 0: 1520 | // r = tcell.RuneULCorner 1521 | // case h - 1: 1522 | // r = tcell.RuneLLCorner 1523 | // default: 1524 | // r = tcell.RuneVLine 1525 | // } 1526 | // w.SetCell(0, y, r, nil, style) 1527 | // } 1528 | // } 1529 | // 1530 | // ... 1531 | // 1532 | // w.OnPaintBorderLeft(onPaintBorderLeft, nil) 1533 | // w.SetBorderLeft(1) 1534 | func (w *Window) OnPaintBorderLeft(h OnPaintHandler, finalize func()) { 1535 | AddOnPaintHandler(&w.onPaintBorderLeft, h, finalize) 1536 | } 1537 | 1538 | // OnPaintBorderRight sets a right border paint handler. When the event handler 1539 | // is removed, finalize is called, if not nil. Example: 1540 | // 1541 | // func onPaintBorderRight(w *wm.Window, prev wm.OnPaintHandler, area wm.Rectangle) { 1542 | // if prev != nil { 1543 | // prev(w, nil, area) 1544 | // } 1545 | // style := w.Style().TCellStyle() 1546 | // h := w.BorderRightArea().Height 1547 | // for y := 0; y < h; y++ { 1548 | // var r rune 1549 | // switch y { 1550 | // case 0: 1551 | // r = tcell.RuneURCorner 1552 | // case h - 1: 1553 | // r = tcell.RuneLRCorner 1554 | // default: 1555 | // r = tcell.RuneVLine 1556 | // } 1557 | // w.SetCell(0, y, r, nil, style) 1558 | // } 1559 | // } 1560 | // 1561 | // ... 1562 | // 1563 | // w.OnPaintBorderRight(onPaintBorderRight, nil) 1564 | // w.SetBorderRight(1) 1565 | func (w *Window) OnPaintBorderRight(h OnPaintHandler, finalize func()) { 1566 | AddOnPaintHandler(&w.onPaintBorderRight, h, finalize) 1567 | } 1568 | 1569 | // OnPaintBorderTop sets a top border paint handler. When the event handler 1570 | // is removed, finalize is called, if not nil. Example: 1571 | // 1572 | // func onPaintBorderTop(w *wm.Window, prev wm.OnPaintHandler, area wm.Rectangle) { 1573 | // if prev != nil { 1574 | // prev(w, nil, area) 1575 | // } 1576 | // style := w.Style().TCellStyle() 1577 | // w := w.BorderTopArea().Width 1578 | // for x := 0; x < w; x++ { 1579 | // var r rune 1580 | // switch x { 1581 | // case 0: 1582 | // r = tcell.RuneULCorner 1583 | // case w - 1: 1584 | // r = tcell.RuneURCorner 1585 | // default: 1586 | // r = tcell.RuneHLine 1587 | // } 1588 | // w.SetCell(x, 0, r, nil, style) 1589 | // } 1590 | // } 1591 | // 1592 | // ... 1593 | // 1594 | // w.OnPaintBorderTop(onPaintBorderTop, nil) 1595 | // w.SetBorderTop(1) 1596 | func (w *Window) OnPaintBorderTop(h OnPaintHandler, finalize func()) { 1597 | AddOnPaintHandler(&w.onPaintBorderTop, h, finalize) 1598 | } 1599 | 1600 | // OnPaintTitle sets a window title paint handler. When the event handler is 1601 | // removed, finalize is called, if not nil. Example: 1602 | // 1603 | // if s := w.Title(); s != "" { 1604 | // w.Printf(0, 0, w.Style().Title, " %s ", s) 1605 | // } 1606 | func (w *Window) OnPaintTitle(h OnPaintHandler, finalize func()) { 1607 | AddOnPaintHandler(&w.onPaintTitle, h, finalize) 1608 | } 1609 | 1610 | // OnSetBorderBottom sets a handler invoked on SetBorderBottom. When the event 1611 | // handler is removed, finalize is called, if not nil. 1612 | func (w *Window) OnSetBorderBottom(h OnSetIntHandler, finalize func()) { 1613 | AddOnSetIntHandler(&w.onSetBorderBotom, h, finalize) 1614 | } 1615 | 1616 | // OnSetBorderLeft sets a handler invoked on SetBorderLeft. When the event 1617 | // handler is removed, finalize is called, if not nil. 1618 | func (w *Window) OnSetBorderLeft(h OnSetIntHandler, finalize func()) { 1619 | AddOnSetIntHandler(&w.onSetBorderLeft, h, finalize) 1620 | } 1621 | 1622 | // OnSetBorderRight sets a handler invoked on SetBorderRight. When the event 1623 | // handler is removed, finalize is called, if not nil. 1624 | func (w *Window) OnSetBorderRight(h OnSetIntHandler, finalize func()) { 1625 | AddOnSetIntHandler(&w.onSetBorderRight, h, finalize) 1626 | } 1627 | 1628 | // OnSetBorderStyle sets a handler invoked on SetBorderStyle. When the event 1629 | // handler is removed, finalize is called, if not nil. 1630 | func (w *Window) OnSetBorderStyle(h OnSetStyleHandler, finalize func()) { 1631 | AddOnSetStyleHandler(&w.onSetBorderStyle, h, finalize) 1632 | } 1633 | 1634 | // OnSetBorderTop sets a handler invoked on SetBorderTop. When the event 1635 | // handler is removed, finalize is called, if not nil. 1636 | func (w *Window) OnSetBorderTop(h OnSetIntHandler, finalize func()) { 1637 | AddOnSetIntHandler(&w.onSetBorderTop, h, finalize) 1638 | } 1639 | 1640 | // OnSetClientAreaStyle sets a handler invoked on SetClientAreaStyle. When the 1641 | // event handler is removed, finalize is called, if not nil. 1642 | func (w *Window) OnSetClientAreaStyle(h OnSetStyleHandler, finalize func()) { 1643 | AddOnSetStyleHandler(&w.onSetClientAreaStyle, h, finalize) 1644 | } 1645 | 1646 | // OnSetClientSize sets a handler invoked on SetClientSize. When the event 1647 | // handler is removed, finalize is called, if not nil. 1648 | func (w *Window) OnSetClientSize(h OnSetSizeHandler, finalize func()) { 1649 | AddOnSetSizeHandler(&w.onSetClientSize, h, finalize) 1650 | } 1651 | 1652 | // OnSetCloseButton sets a handler invoked on SetCloseButton. When the event 1653 | // handler is removed, finalize is called, if not nil. 1654 | func (w *Window) OnSetCloseButton(h OnSetBoolHandler, finalize func()) { 1655 | AddOnSetBoolHandler(&w.onSetCloseButton, h, finalize) 1656 | } 1657 | 1658 | // OnSetFocus sets a handler invoked on SetFocus. When the event handler is 1659 | // removed, finalize is called, if not nil. 1660 | func (w *Window) OnSetFocus(h OnSetBoolHandler, finalize func()) { 1661 | AddOnSetBoolHandler(&w.onSetFocus, h, finalize) 1662 | } 1663 | 1664 | // OnSetOrigin sets a handler invoked on SetOrigin. When the event handler 1665 | // is removed, finalize is called, if not nil. 1666 | func (w *Window) OnSetOrigin(h OnSetPositionHandler, finalize func()) { 1667 | AddOnSetPositionHandler(&w.onSetOrigin, h, finalize) 1668 | } 1669 | 1670 | // OnSetPosition sets a handler invoked on SetPosition. When the event handler 1671 | // is removed, finalize is called, if not nil. 1672 | func (w *Window) OnSetPosition(h OnSetPositionHandler, finalize func()) { 1673 | AddOnSetPositionHandler(&w.onSetPosition, h, finalize) 1674 | } 1675 | 1676 | // OnSetSize sets a handler invoked on SetSize. When the event handler is 1677 | // removed, finalize is called, if not nil. 1678 | func (w *Window) OnSetSize(h OnSetSizeHandler, finalize func()) { 1679 | AddOnSetSizeHandler(&w.onSetSize, h, finalize) 1680 | } 1681 | 1682 | // OnSetStyle sets a handler invoked on SetStyle. When the event handler is 1683 | // removed, finalize is called, if not nil. 1684 | func (w *Window) OnSetStyle(h OnSetWindowStyleHandler, finalize func()) { 1685 | addOnSetWindowStyleHandler(&w.onSetStyle, h, finalize) 1686 | } 1687 | 1688 | // OnSetTitle sets a handler invoked on SetTitle. When the event handler is 1689 | // removed, finalize is called, if not nil. 1690 | func (w *Window) OnSetTitle(h OnSetStringHandler, finalize func()) { 1691 | addOnSetStringHandler(&w.onSetTitle, h, finalize) 1692 | } 1693 | 1694 | // Origin returns the window's origin.. 1695 | func (w *Window) Origin() Position { return w.view } 1696 | 1697 | // Printf prints format with arguments at x, y. Calling this method outside of 1698 | // an OnPaint handler is ignored. 1699 | // 1700 | // Special handling: 1701 | // 1702 | // '\t' x, y = x + 8 - x%8, y 1703 | // '\n' x, y = 0, y+1 1704 | // '\r' x, y = 0, y 1705 | func (w *Window) Printf(x, y int, style Style, format string, arg ...interface{}) { 1706 | if w.ctx.IsZero() { // Zero sized window or not in OnPaint. 1707 | return 1708 | } 1709 | 1710 | w.print(x, y, style.TCellStyle(), fmt.Sprintf(format, arg...)) 1711 | } 1712 | 1713 | // Parent returns the window's parent. Root windows have nil parent. 1714 | func (w *Window) Parent() *Window { return w.parent } 1715 | 1716 | // Position returns the window position relative to its parent. 1717 | func (w *Window) Position() Position { return w.position } 1718 | 1719 | // RemoveOnClick undoes the most recent OnClick call. The function will panic if 1720 | // there is no handler set. 1721 | func (w *Window) RemoveOnClick() { RemoveOnMouseHandler(&w.onClick) } 1722 | 1723 | // RemoveOnClickBorder undoes the most recent OnClickBorder call. The function 1724 | // will panic if there is no handler set. 1725 | func (w *Window) RemoveOnClickBorder() { RemoveOnMouseHandler(&w.onClickBorder) } 1726 | 1727 | // RemoveOnClose undoes the most recent OnClose call. The function will panic 1728 | // if there is no handler set. 1729 | func (w *Window) RemoveOnClose() { removeOnCloseHandler(&w.onClose) } 1730 | 1731 | // RemoveOnDoubleClick undoes the most recent OnDoubleClick call. The function 1732 | // will panic if there is no handler set. 1733 | func (w *Window) RemoveOnDoubleClick() { RemoveOnMouseHandler(&w.onDoubleClick) } 1734 | 1735 | // RemoveOnDoubleClickBorder undoes the most recent OnDoubleClickBorder call. 1736 | // The function will panic if there is no handler set. 1737 | func (w *Window) RemoveOnDoubleClickBorder() { RemoveOnMouseHandler(&w.onDoubleClickBorder) } 1738 | 1739 | // RemoveOnDrag undoes the most recent OnDrag call. The function will panic if 1740 | // there is no handler set. 1741 | func (w *Window) RemoveOnDrag() { RemoveOnMouseHandler(&w.onDrag) } 1742 | 1743 | // RemoveOnDragBorder undoes the most recent OnDragBorder call. The function 1744 | // will panic if there is no handler set. 1745 | func (w *Window) RemoveOnDragBorder() { RemoveOnMouseHandler(&w.onDragBorder) } 1746 | 1747 | // RemoveOnDrop undoes the most recent OnDrop call. The function will panic if 1748 | // there is no handler set. 1749 | func (w *Window) RemoveOnDrop() { RemoveOnMouseHandler(&w.onDrop) } 1750 | 1751 | // RemoveOnKey undoes the most recent OnKey call. The function will panic if 1752 | // there is no handler set. 1753 | func (w *Window) RemoveOnKey() { removeOnKeyHandler(&w.onKey) } 1754 | 1755 | // RemoveOnMouseMove undoes the most recent OnMouseMove call. The function will 1756 | // panic if there is no handler set. 1757 | func (w *Window) RemoveOnMouseMove() { RemoveOnMouseHandler(&w.onMouseMove) } 1758 | 1759 | // RemoveOnPaintClientArea undoes the most recent OnPaintClientArea call. The 1760 | // function will panic if there is no handler set. 1761 | func (w *Window) RemoveOnPaintClientArea() { RemoveOnPaintHandler(&w.onPaintClientArea) } 1762 | 1763 | // RemoveOnPaintBorderBottom undoes the most recent OnPaintBorderBottom call. 1764 | // The function will panic if there is no handler set. 1765 | func (w *Window) RemoveOnPaintBorderBottom() { RemoveOnPaintHandler(&w.onPaintBorderBottom) } 1766 | 1767 | // RemoveOnPaintBorderLeft undoes the most recent OnPaintBorderLeft call. 1768 | // The function will panic if there is no handler set. 1769 | func (w *Window) RemoveOnPaintBorderLeft() { RemoveOnPaintHandler(&w.onPaintBorderLeft) } 1770 | 1771 | // RemoveOnPaintBorderRight undoes the most recent OnPaintBorderRight call. 1772 | // The function will panic if there is no handler set. 1773 | func (w *Window) RemoveOnPaintBorderRight() { RemoveOnPaintHandler(&w.onPaintBorderRight) } 1774 | 1775 | // RemoveOnPaintBorderTop undoes the most recent OnPaintBorderTop call. 1776 | // The function will panic if there is no handler set. 1777 | func (w *Window) RemoveOnPaintBorderTop() { RemoveOnPaintHandler(&w.onPaintBorderTop) } 1778 | 1779 | // RemoveOnPaintTitle undoes the most recent OnPaintTitle call. The function 1780 | // will panic if there is no handler set. 1781 | func (w *Window) RemoveOnPaintTitle() { RemoveOnPaintHandler(&w.onPaintTitle) } 1782 | 1783 | // RemoveOnSetBorderBottom undoes the most recent OnSetBorderBottom call. The 1784 | // function will panic if there is no handler set. 1785 | func (w *Window) RemoveOnSetBorderBottom() { RemoveOnSetIntHandler(&w.onSetBorderBotom) } 1786 | 1787 | // RemoveOnSetBorderLeft undoes the most recent OnSetBorderLeft call. The 1788 | // function will panic if there is no handler set. 1789 | func (w *Window) RemoveOnSetBorderLeft() { RemoveOnSetIntHandler(&w.onSetBorderLeft) } 1790 | 1791 | // RemoveOnSetBorderRight undoes the most recent OnSetBorderRight call. The 1792 | // function will panic if there is no handler set. 1793 | func (w *Window) RemoveOnSetBorderRight() { RemoveOnSetIntHandler(&w.onSetBorderRight) } 1794 | 1795 | // RemoveOnSetBorderStyle undoes the most recent OnSetBorderStyle call. The 1796 | // function will panic if there is no handler set. 1797 | func (w *Window) RemoveOnSetBorderStyle() { RemoveOnSetStyleHandler(&w.onSetBorderStyle) } 1798 | 1799 | // RemoveOnSetBorderTop undoes the most recent OnSetBorderTop call. The 1800 | // function will panic if there is no handler set. 1801 | func (w *Window) RemoveOnSetBorderTop() { RemoveOnSetIntHandler(&w.onSetBorderTop) } 1802 | 1803 | // RemoveOnSetClientAreaStyle undoes the most recent OnSetClientAreaStyle call. 1804 | // The function will panic if there is no handler set. 1805 | func (w *Window) RemoveOnSetClientAreaStyle() { RemoveOnSetStyleHandler(&w.onSetClientAreaStyle) } 1806 | 1807 | // RemoveOnSetClientSize undoes the most recent OnSetClientSize call. The 1808 | // function will panic if there is no handler set. 1809 | func (w *Window) RemoveOnSetClientSize() { RemoveOnSetSizeHandler(&w.onSetClientSize) } 1810 | 1811 | // RemoveOnSetCloseButton undoes the most recent OnSetCloseButton call. The 1812 | // function will panic if there is no handler set. 1813 | func (w *Window) RemoveOnSetCloseButton() { RemoveOnSetBoolHandler(&w.onSetCloseButton) } 1814 | 1815 | // RemoveOnSetFocus undoes the most recent OnSetFocus call. The function will 1816 | // panic if there is no handler set. 1817 | func (w *Window) RemoveOnSetFocus() { RemoveOnSetBoolHandler(&w.onSetFocus) } 1818 | 1819 | // RemoveOnSetOrigin undoes the most recent OnSetOrigin call. The function 1820 | // will panic if there is no handler set. 1821 | func (w *Window) RemoveOnSetOrigin() { RemoveOnSetPositionHandler(&w.onSetOrigin) } 1822 | 1823 | // RemoveOnSetPosition undoes the most recent OnSetPosition call. The function 1824 | // will panic if there is no handler set. 1825 | func (w *Window) RemoveOnSetPosition() { RemoveOnSetPositionHandler(&w.onSetPosition) } 1826 | 1827 | // RemoveOnSetSize undoes the most recent OnSetSize call. The function will 1828 | // panic if there is no handler set. 1829 | func (w *Window) RemoveOnSetSize() { RemoveOnSetSizeHandler(&w.onSetSize) } 1830 | 1831 | // RemoveOnSetStyle undoes the most recent OnSetStyle call. The function will 1832 | // panic if there is no handler set. 1833 | func (w *Window) RemoveOnSetStyle() { removeOnSetWindowStyleHandler(&w.onSetStyle) } 1834 | 1835 | // RemoveOnSetTitle undoes the most recent OnSetTitle call. The function will 1836 | // panic if there is no handler set. 1837 | func (w *Window) RemoveOnSetTitle() { removeOnSetStringHandler(&w.onSetTitle) } 1838 | 1839 | // Rendered returns how long the last desktop rendering took. Valid only for 1840 | // desktop's root window. 1841 | func (w *Window) Rendered() time.Duration { return w.rendered } 1842 | 1843 | // SetBorderBottom sets the height of the bottom border. 1844 | func (w *Window) SetBorderBottom(v int) { w.onSetBorderBotom.Handle(w, &w.borderBottom, v) } 1845 | 1846 | // SetBorderLeft sets the width of the left border. 1847 | func (w *Window) SetBorderLeft(v int) { w.onSetBorderLeft.Handle(w, &w.borderLeft, v) } 1848 | 1849 | // SetBorderRight sets the width of the right border. 1850 | func (w *Window) SetBorderRight(v int) { w.onSetBorderRight.Handle(w, &w.borderRight, v) } 1851 | 1852 | // SetBorderStyle sets the border style. 1853 | func (w *Window) SetBorderStyle(s Style) { w.onSetBorderStyle.Handle(w, &w.style.Border, s) } 1854 | 1855 | // SetBorderTop sets the height of the top border. 1856 | func (w *Window) SetBorderTop(v int) { w.onSetBorderTop.Handle(w, &w.borderTop, v) } 1857 | 1858 | // SetCell renders a single character cell. Calling this method outside of an 1859 | // OnPaint* handler is ignored. 1860 | func (w *Window) SetCell(x, y int, mainc rune, combc []rune, style tcell.Style) { 1861 | w.BeginUpdate() 1862 | w.setCell(Position{x, y}, mainc, combc, style) 1863 | w.EndUpdate() 1864 | } 1865 | 1866 | // SetClientAreaStyle sets the client area style. 1867 | func (w *Window) SetClientAreaStyle(s Style) { w.onSetClientAreaStyle.Handle(w, &w.style.ClientArea, s) } 1868 | 1869 | // SetClientSize sets the size of the client area. 1870 | func (w *Window) SetClientSize(s Size) { w.onSetClientSize.Handle(w, &w.clientArea.Size, s) } 1871 | 1872 | // SetCloseButton sets whether the window shows a close button. 1873 | func (w *Window) SetCloseButton(v bool) { 1874 | if w.parent != nil { 1875 | w.onSetCloseButton.Handle(w, &w.closeButton, v) 1876 | } 1877 | } 1878 | 1879 | // SetFocus sets whether the window is focused. 1880 | func (w *Window) SetFocus(v bool) { w.onSetFocus.Handle(w, &w.focus, v) } 1881 | 1882 | // SetOrigin sets the origin of the window. By default the origin of a window 1883 | // is (0, 0). When a paint handler is invoked the window's origin is 1884 | // subtracted from the coordinates the handler paints to. Also, the 1885 | // PaintContext.Rectangle value passed to the handler is adjusted accordingly. 1886 | // 1887 | // Setting origin other than (0, 0) provides a view into the content created by 1888 | // the respective paint handlers. The mechanism aids in displaying scrolling 1889 | // content. 1890 | // 1891 | // Negative values of p.X or p.Y are ignored. 1892 | func (w *Window) SetOrigin(p Position) { 1893 | w.onSetOrigin.Handle(w, &w.view, Position{X: mathutil.Max(p.X, 0), Y: mathutil.Max(p.Y, 0)}) 1894 | } 1895 | 1896 | // SetPosition sets the window position relative to its parent. 1897 | func (w *Window) SetPosition(p Position) { 1898 | if w.parent != nil { 1899 | w.onSetPosition.Handle(w, &w.position, p) 1900 | } 1901 | } 1902 | 1903 | // SetSize sets the window size. 1904 | func (w *Window) SetSize(s Size) { 1905 | if w.parent != nil { 1906 | w.setSize(s) 1907 | } 1908 | } 1909 | 1910 | // SetStyle sets the window style. 1911 | func (w *Window) SetStyle(s WindowStyle) { w.onSetStyle.handle(w, &w.style, s) } 1912 | 1913 | // SetTitle sets the window title. 1914 | func (w *Window) SetTitle(s string) { w.onSetTitle.handle(w, &w.title, s) } 1915 | 1916 | // Size returns the window size. 1917 | func (w *Window) Size() Size { return w.size } 1918 | 1919 | // Style returns the window style. 1920 | func (w *Window) Style() WindowStyle { return w.style } 1921 | 1922 | // Title returns the window title. 1923 | func (w *Window) Title() string { return w.title } 1924 | --------------------------------------------------------------------------------