1// Copyright 2014 The gocui 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
5package gocui
6
7import (
8	"errors"
9
10	"github.com/nsf/termbox-go"
11)
12
13var (
14	// ErrQuit is used to decide if the MainLoop finished successfully.
15	ErrQuit = errors.New("quit")
16
17	// ErrUnknownView allows to assert if a View must be initialized.
18	ErrUnknownView = errors.New("unknown view")
19)
20
21// OutputMode represents the terminal's output mode (8 or 256 colors).
22type OutputMode termbox.OutputMode
23
24const (
25	// OutputNormal provides 8-colors terminal mode.
26	OutputNormal = OutputMode(termbox.OutputNormal)
27
28	// Output256 provides 256-colors terminal mode.
29	Output256 = OutputMode(termbox.Output256)
30)
31
32// Gui represents the whole User Interface, including the views, layouts
33// and keybindings.
34type Gui struct {
35	tbEvents    chan termbox.Event
36	userEvents  chan userEvent
37	views       []*View
38	currentView *View
39	managers    []Manager
40	keybindings []*keybinding
41	maxX, maxY  int
42	outputMode  OutputMode
43
44	// BgColor and FgColor allow to configure the background and foreground
45	// colors of the GUI.
46	BgColor, FgColor Attribute
47
48	// SelBgColor and SelFgColor allow to configure the background and
49	// foreground colors of the frame of the current view.
50	SelBgColor, SelFgColor Attribute
51
52	// If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
53	// frame of the current view.
54	Highlight bool
55
56	// If Cursor is true then the cursor is enabled.
57	Cursor bool
58
59	// If Mouse is true then mouse events will be enabled.
60	Mouse bool
61
62	// If InputEsc is true, when ESC sequence is in the buffer and it doesn't
63	// match any known sequence, ESC means KeyEsc.
64	InputEsc bool
65
66	// If ASCII is true then use ASCII instead of unicode to draw the
67	// interface. Using ASCII is more portable.
68	ASCII bool
69}
70
71// NewGui returns a new Gui object with a given output mode.
72func NewGui(mode OutputMode) (*Gui, error) {
73	if err := termbox.Init(); err != nil {
74		return nil, err
75	}
76
77	g := &Gui{}
78
79	g.outputMode = mode
80	termbox.SetOutputMode(termbox.OutputMode(mode))
81
82	g.tbEvents = make(chan termbox.Event, 20)
83	g.userEvents = make(chan userEvent, 20)
84
85	g.maxX, g.maxY = termbox.Size()
86
87	g.BgColor, g.FgColor = ColorDefault, ColorDefault
88	g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
89
90	return g, nil
91}
92
93// Close finalizes the library. It should be called after a successful
94// initialization and when gocui is not needed anymore.
95func (g *Gui) Close() {
96	termbox.Close()
97}
98
99// Size returns the terminal's size.
100func (g *Gui) Size() (x, y int) {
101	return g.maxX, g.maxY
102}
103
104// SetRune writes a rune at the given point, relative to the top-left
105// corner of the terminal. It checks if the position is valid and applies
106// the given colors.
107func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
108	if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
109		return errors.New("invalid point")
110	}
111	termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor))
112	return nil
113}
114
115// Rune returns the rune contained in the cell at the given position.
116// It checks if the position is valid.
117func (g *Gui) Rune(x, y int) (rune, error) {
118	if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
119		return ' ', errors.New("invalid point")
120	}
121	c := termbox.CellBuffer()[y*g.maxX+x]
122	return c.Ch, nil
123}
124
125// SetView creates a new view with its top-left corner at (x0, y0)
126// and the bottom-right one at (x1, y1). If a view with the same name
127// already exists, its dimensions are updated; otherwise, the error
128// ErrUnknownView is returned, which allows to assert if the View must
129// be initialized. It checks if the position is valid.
130func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
131	if x0 >= x1 || y0 >= y1 {
132		return nil, errors.New("invalid dimensions")
133	}
134	if name == "" {
135		return nil, errors.New("invalid name")
136	}
137
138	if v, err := g.View(name); err == nil {
139		v.x0 = x0
140		v.y0 = y0
141		v.x1 = x1
142		v.y1 = y1
143		v.tainted = true
144		return v, nil
145	}
146
147	v := newView(name, x0, y0, x1, y1, g.outputMode)
148	v.BgColor, v.FgColor = g.BgColor, g.FgColor
149	v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
150	g.views = append(g.views, v)
151	return v, ErrUnknownView
152}
153
154// SetViewOnTop sets the given view on top of the existing ones.
155func (g *Gui) SetViewOnTop(name string) (*View, error) {
156	for i, v := range g.views {
157		if v.name == name {
158			s := append(g.views[:i], g.views[i+1:]...)
159			g.views = append(s, v)
160			return v, nil
161		}
162	}
163	return nil, ErrUnknownView
164}
165
166// SetViewOnBottom sets the given view on bottom of the existing ones.
167func (g *Gui) SetViewOnBottom(name string) (*View, error) {
168	for i, v := range g.views {
169		if v.name == name {
170			s := append(g.views[:i], g.views[i+1:]...)
171			g.views = append([]*View{v}, s...)
172			return v, nil
173		}
174	}
175	return nil, ErrUnknownView
176}
177
178// Views returns all the views in the GUI.
179func (g *Gui) Views() []*View {
180	return g.views
181}
182
183// View returns a pointer to the view with the given name, or error
184// ErrUnknownView if a view with that name does not exist.
185func (g *Gui) View(name string) (*View, error) {
186	for _, v := range g.views {
187		if v.name == name {
188			return v, nil
189		}
190	}
191	return nil, ErrUnknownView
192}
193
194// ViewByPosition returns a pointer to a view matching the given position, or
195// error ErrUnknownView if a view in that position does not exist.
196func (g *Gui) ViewByPosition(x, y int) (*View, error) {
197	// traverse views in reverse order checking top views first
198	for i := len(g.views); i > 0; i-- {
199		v := g.views[i-1]
200		if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
201			return v, nil
202		}
203	}
204	return nil, ErrUnknownView
205}
206
207// ViewPosition returns the coordinates of the view with the given name, or
208// error ErrUnknownView if a view with that name does not exist.
209func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) {
210	for _, v := range g.views {
211		if v.name == name {
212			return v.x0, v.y0, v.x1, v.y1, nil
213		}
214	}
215	return 0, 0, 0, 0, ErrUnknownView
216}
217
218// DeleteView deletes a view by name.
219func (g *Gui) DeleteView(name string) error {
220	for i, v := range g.views {
221		if v.name == name {
222			g.views = append(g.views[:i], g.views[i+1:]...)
223			return nil
224		}
225	}
226	return ErrUnknownView
227}
228
229// SetCurrentView gives the focus to a given view.
230func (g *Gui) SetCurrentView(name string) (*View, error) {
231	for _, v := range g.views {
232		if v.name == name {
233			g.currentView = v
234			return v, nil
235		}
236	}
237	return nil, ErrUnknownView
238}
239
240// CurrentView returns the currently focused view, or nil if no view
241// owns the focus.
242func (g *Gui) CurrentView() *View {
243	return g.currentView
244}
245
246// SetKeybinding creates a new keybinding. If viewname equals to ""
247// (empty string) then the keybinding will apply to all views. key must
248// be a rune or a Key.
249func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
250	var kb *keybinding
251
252	k, ch, err := getKey(key)
253	if err != nil {
254		return err
255	}
256	kb = newKeybinding(viewname, k, ch, mod, handler)
257	g.keybindings = append(g.keybindings, kb)
258	return nil
259}
260
261// DeleteKeybinding deletes a keybinding.
262func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error {
263	k, ch, err := getKey(key)
264	if err != nil {
265		return err
266	}
267
268	for i, kb := range g.keybindings {
269		if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod {
270			g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...)
271			return nil
272		}
273	}
274	return errors.New("keybinding not found")
275}
276
277// DeleteKeybindings deletes all keybindings of view.
278func (g *Gui) DeleteKeybindings(viewname string) {
279	var s []*keybinding
280	for _, kb := range g.keybindings {
281		if kb.viewName != viewname {
282			s = append(s, kb)
283		}
284	}
285	g.keybindings = s
286}
287
288// getKey takes an empty interface with a key and returns the corresponding
289// typed Key or rune.
290func getKey(key interface{}) (Key, rune, error) {
291	switch t := key.(type) {
292	case Key:
293		return t, 0, nil
294	case rune:
295		return 0, t, nil
296	default:
297		return 0, 0, errors.New("unknown type")
298	}
299}
300
301// userEvent represents an event triggered by the user.
302type userEvent struct {
303	f func(*Gui) error
304}
305
306// Update executes the passed function. This method can be called safely from a
307// goroutine in order to update the GUI. It is important to note that the
308// passed function won't be executed immediately, instead it will be added to
309// the user events queue. Given that Update spawns a goroutine, the order in
310// which the user events will be handled is not guaranteed.
311func (g *Gui) Update(f func(*Gui) error) {
312	go func() { g.userEvents <- userEvent{f: f} }()
313}
314
315// A Manager is in charge of GUI's layout and can be used to build widgets.
316type Manager interface {
317	// Layout is called every time the GUI is redrawn, it must contain the
318	// base views and its initializations.
319	Layout(*Gui) error
320}
321
322// The ManagerFunc type is an adapter to allow the use of ordinary functions as
323// Managers. If f is a function with the appropriate signature, ManagerFunc(f)
324// is an Manager object that calls f.
325type ManagerFunc func(*Gui) error
326
327// Layout calls f(g)
328func (f ManagerFunc) Layout(g *Gui) error {
329	return f(g)
330}
331
332// SetManager sets the given GUI managers. It deletes all views and
333// keybindings.
334func (g *Gui) SetManager(managers ...Manager) {
335	g.managers = managers
336	g.currentView = nil
337	g.views = nil
338	g.keybindings = nil
339
340	go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
341}
342
343// SetManagerFunc sets the given manager function. It deletes all views and
344// keybindings.
345func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
346	g.SetManager(ManagerFunc(manager))
347}
348
349// MainLoop runs the main loop until an error is returned. A successful
350// finish should return ErrQuit.
351func (g *Gui) MainLoop() error {
352	go func() {
353		for {
354			g.tbEvents <- termbox.PollEvent()
355		}
356	}()
357
358	inputMode := termbox.InputAlt
359	if g.InputEsc {
360		inputMode = termbox.InputEsc
361	}
362	if g.Mouse {
363		inputMode |= termbox.InputMouse
364	}
365	termbox.SetInputMode(inputMode)
366
367	if err := g.flush(); err != nil {
368		return err
369	}
370	for {
371		select {
372		case ev := <-g.tbEvents:
373			if err := g.handleEvent(&ev); err != nil {
374				return err
375			}
376		case ev := <-g.userEvents:
377			if err := ev.f(g); err != nil {
378				return err
379			}
380		}
381		if err := g.consumeevents(); err != nil {
382			return err
383		}
384		if err := g.flush(); err != nil {
385			return err
386		}
387	}
388}
389
390// consumeevents handles the remaining events in the events pool.
391func (g *Gui) consumeevents() error {
392	for {
393		select {
394		case ev := <-g.tbEvents:
395			if err := g.handleEvent(&ev); err != nil {
396				return err
397			}
398		case ev := <-g.userEvents:
399			if err := ev.f(g); err != nil {
400				return err
401			}
402		default:
403			return nil
404		}
405	}
406}
407
408// handleEvent handles an event, based on its type (key-press, error,
409// etc.)
410func (g *Gui) handleEvent(ev *termbox.Event) error {
411	switch ev.Type {
412	case termbox.EventKey, termbox.EventMouse:
413		return g.onKey(ev)
414	case termbox.EventError:
415		return ev.Err
416	default:
417		return nil
418	}
419}
420
421// flush updates the gui, re-drawing frames and buffers.
422func (g *Gui) flush() error {
423	termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
424
425	maxX, maxY := termbox.Size()
426	// if GUI's size has changed, we need to redraw all views
427	if maxX != g.maxX || maxY != g.maxY {
428		for _, v := range g.views {
429			v.tainted = true
430		}
431	}
432	g.maxX, g.maxY = maxX, maxY
433
434	for _, m := range g.managers {
435		if err := m.Layout(g); err != nil {
436			return err
437		}
438	}
439	for _, v := range g.views {
440		if v.Frame {
441			var fgColor, bgColor Attribute
442			if g.Highlight && v == g.currentView {
443				fgColor = g.SelFgColor
444				bgColor = g.SelBgColor
445			} else {
446				fgColor = g.FgColor
447				bgColor = g.BgColor
448			}
449
450			if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
451				return err
452			}
453			if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
454				return err
455			}
456			if v.Title != "" {
457				if err := g.drawTitle(v, fgColor, bgColor); err != nil {
458					return err
459				}
460			}
461		}
462		if err := g.draw(v); err != nil {
463			return err
464		}
465	}
466	termbox.Flush()
467	return nil
468}
469
470// drawFrameEdges draws the horizontal and vertical edges of a view.
471func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
472	runeH, runeV := '─', '│'
473	if g.ASCII {
474		runeH, runeV = '-', '|'
475	}
476
477	for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
478		if x < 0 {
479			continue
480		}
481		if v.y0 > -1 && v.y0 < g.maxY {
482			if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil {
483				return err
484			}
485		}
486		if v.y1 > -1 && v.y1 < g.maxY {
487			if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil {
488				return err
489			}
490		}
491	}
492	for y := v.y0 + 1; y < v.y1 && y < g.maxY; y++ {
493		if y < 0 {
494			continue
495		}
496		if v.x0 > -1 && v.x0 < g.maxX {
497			if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil {
498				return err
499			}
500		}
501		if v.x1 > -1 && v.x1 < g.maxX {
502			if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
503				return err
504			}
505		}
506	}
507	return nil
508}
509
510// drawFrameCorners draws the corners of the view.
511func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
512	runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
513	if g.ASCII {
514		runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
515	}
516
517	corners := []struct {
518		x, y int
519		ch   rune
520	}{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}}
521
522	for _, c := range corners {
523		if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
524			if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
525				return err
526			}
527		}
528	}
529	return nil
530}
531
532// drawTitle draws the title of the view.
533func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
534	if v.y0 < 0 || v.y0 >= g.maxY {
535		return nil
536	}
537
538	for i, ch := range v.Title {
539		x := v.x0 + i + 2
540		if x < 0 {
541			continue
542		} else if x > v.x1-2 || x >= g.maxX {
543			break
544		}
545		if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
546			return err
547		}
548	}
549	return nil
550}
551
552// draw manages the cursor and calls the draw function of a view.
553func (g *Gui) draw(v *View) error {
554	if g.Cursor {
555		if curview := g.currentView; curview != nil {
556			vMaxX, vMaxY := curview.Size()
557			if curview.cx < 0 {
558				curview.cx = 0
559			} else if curview.cx >= vMaxX {
560				curview.cx = vMaxX - 1
561			}
562			if curview.cy < 0 {
563				curview.cy = 0
564			} else if curview.cy >= vMaxY {
565				curview.cy = vMaxY - 1
566			}
567
568			gMaxX, gMaxY := g.Size()
569			cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1
570			if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
571				termbox.SetCursor(cx, cy)
572			} else {
573				termbox.HideCursor()
574			}
575		}
576	} else {
577		termbox.HideCursor()
578	}
579
580	v.clearRunes()
581	if err := v.draw(); err != nil {
582		return err
583	}
584	return nil
585}
586
587// onKey manages key-press events. A keybinding handler is called when
588// a key-press or mouse event satisfies a configured keybinding. Furthermore,
589// currentView's internal buffer is modified if currentView.Editable is true.
590func (g *Gui) onKey(ev *termbox.Event) error {
591	switch ev.Type {
592	case termbox.EventKey:
593		matched, err := g.execKeybindings(g.currentView, ev)
594		if err != nil {
595			return err
596		}
597		if matched {
598			break
599		}
600		if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
601			g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
602		}
603	case termbox.EventMouse:
604		mx, my := ev.MouseX, ev.MouseY
605		v, err := g.ViewByPosition(mx, my)
606		if err != nil {
607			break
608		}
609		if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
610			return err
611		}
612		if _, err := g.execKeybindings(v, ev); err != nil {
613			return err
614		}
615	}
616
617	return nil
618}
619
620// execKeybindings executes the keybinding handlers that match the passed view
621// and event. The value of matched is true if there is a match and no errors.
622func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
623	matched = false
624	for _, kb := range g.keybindings {
625		if kb.handler == nil {
626			continue
627		}
628		if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
629			if err := kb.handler(g, v); err != nil {
630				return false, err
631			}
632			matched = true
633		}
634	}
635	return matched, nil
636}
637