165 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
 | |
| // Use of this source code is governed by a MIT license that can
 | |
| // be found in the LICENSE file.
 | |
| 
 | |
| package termui
 | |
| 
 | |
| import (
 | |
| 	"image"
 | |
| 	"io"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"fmt"
 | |
| 
 | |
| 	"os"
 | |
| 
 | |
| 	"runtime/debug"
 | |
| 
 | |
| 	"bytes"
 | |
| 
 | |
| 	"github.com/maruel/panicparse/stack"
 | |
| 	tm "github.com/nsf/termbox-go"
 | |
| )
 | |
| 
 | |
| // Bufferer should be implemented by all renderable components.
 | |
| type Bufferer interface {
 | |
| 	Buffer() Buffer
 | |
| }
 | |
| 
 | |
| // Init initializes termui library. This function should be called before any others.
 | |
| // After initialization, the library must be finalized by 'Close' function.
 | |
| func Init() error {
 | |
| 	if err := tm.Init(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	sysEvtChs = make([]chan Event, 0)
 | |
| 	go hookTermboxEvt()
 | |
| 
 | |
| 	renderJobs = make(chan []Bufferer)
 | |
| 	//renderLock = new(sync.RWMutex)
 | |
| 
 | |
| 	Body = NewGrid()
 | |
| 	Body.X = 0
 | |
| 	Body.Y = 0
 | |
| 	Body.BgColor = ThemeAttr("bg")
 | |
| 	Body.Width = TermWidth()
 | |
| 
 | |
| 	DefaultEvtStream.Init()
 | |
| 	DefaultEvtStream.Merge("termbox", NewSysEvtCh())
 | |
| 	DefaultEvtStream.Merge("timer", NewTimerCh(time.Second))
 | |
| 	DefaultEvtStream.Merge("custom", usrEvtCh)
 | |
| 
 | |
| 	DefaultEvtStream.Handle("/", DefualtHandler)
 | |
| 	DefaultEvtStream.Handle("/sys/wnd/resize", func(e Event) {
 | |
| 		w := e.Data.(EvtWnd)
 | |
| 		Body.Width = w.Width
 | |
| 	})
 | |
| 
 | |
| 	DefaultWgtMgr = NewWgtMgr()
 | |
| 	DefaultEvtStream.Hook(DefaultWgtMgr.WgtHandlersHook())
 | |
| 
 | |
| 	go func() {
 | |
| 		for bs := range renderJobs {
 | |
| 			render(bs...)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Close finalizes termui library,
 | |
| // should be called after successful initialization when termui's functionality isn't required anymore.
 | |
| func Close() {
 | |
| 	tm.Close()
 | |
| }
 | |
| 
 | |
| var renderLock sync.Mutex
 | |
| 
 | |
| func termSync() {
 | |
| 	renderLock.Lock()
 | |
| 	tm.Sync()
 | |
| 	termWidth, termHeight = tm.Size()
 | |
| 	renderLock.Unlock()
 | |
| }
 | |
| 
 | |
| // TermWidth returns the current terminal's width.
 | |
| func TermWidth() int {
 | |
| 	termSync()
 | |
| 	return termWidth
 | |
| }
 | |
| 
 | |
| // TermHeight returns the current terminal's height.
 | |
| func TermHeight() int {
 | |
| 	termSync()
 | |
| 	return termHeight
 | |
| }
 | |
| 
 | |
| // Render renders all Bufferer in the given order from left to right,
 | |
| // right could overlap on left ones.
 | |
| func render(bs ...Bufferer) {
 | |
| 	defer func() {
 | |
| 		if e := recover(); e != nil {
 | |
| 			Close()
 | |
| 			fmt.Fprintf(os.Stderr, "Captured a panic(value=%v) when rendering Bufferer. Exit termui and clean terminal...\nPrint stack trace:\n\n", e)
 | |
| 			//debug.PrintStack()
 | |
| 			gs, err := stack.ParseDump(bytes.NewReader(debug.Stack()), os.Stderr)
 | |
| 			if err != nil {
 | |
| 				debug.PrintStack()
 | |
| 				os.Exit(1)
 | |
| 			}
 | |
| 			p := &stack.Palette{}
 | |
| 			buckets := stack.SortBuckets(stack.Bucketize(gs, stack.AnyValue))
 | |
| 			srcLen, pkgLen := stack.CalcLengths(buckets, false)
 | |
| 			for _, bucket := range buckets {
 | |
| 				io.WriteString(os.Stdout, p.BucketHeader(&bucket, false, len(buckets) > 1))
 | |
| 				io.WriteString(os.Stdout, p.StackLines(&bucket.Signature, srcLen, pkgLen, false))
 | |
| 			}
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 	}()
 | |
| 	for _, b := range bs {
 | |
| 
 | |
| 		buf := b.Buffer()
 | |
| 		// set cels in buf
 | |
| 		for p, c := range buf.CellMap {
 | |
| 			if p.In(buf.Area) {
 | |
| 
 | |
| 				tm.SetCell(p.X, p.Y, c.Ch, toTmAttr(c.Fg), toTmAttr(c.Bg))
 | |
| 
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	renderLock.Lock()
 | |
| 	// render
 | |
| 	tm.Flush()
 | |
| 	renderLock.Unlock()
 | |
| }
 | |
| 
 | |
| func Clear() {
 | |
| 	tm.Clear(tm.ColorDefault, toTmAttr(ThemeAttr("bg")))
 | |
| }
 | |
| 
 | |
| func clearArea(r image.Rectangle, bg Attribute) {
 | |
| 	for i := r.Min.X; i < r.Max.X; i++ {
 | |
| 		for j := r.Min.Y; j < r.Max.Y; j++ {
 | |
| 			tm.SetCell(i, j, ' ', tm.ColorDefault, toTmAttr(bg))
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func ClearArea(r image.Rectangle, bg Attribute) {
 | |
| 	clearArea(r, bg)
 | |
| 	tm.Flush()
 | |
| }
 | |
| 
 | |
| var renderJobs chan []Bufferer
 | |
| 
 | |
| func Render(bs ...Bufferer) {
 | |
| 	//go func() { renderJobs <- bs }()
 | |
| 	renderJobs <- bs
 | |
| }
 |