package cviewimport ()const (// The size of the event/update/redraw channels. queueSize = 100// The minimum duration between resize event callbacks. resizeEventThrottle = 50 * time.Millisecond)// Application represents the top node of an application.//// It is not strictly required to use this class as none of the other classes// depend on it. However, it provides useful tools to set up an application and// plays nicely with all widgets.//// The following command displays a primitive p on the screen until Ctrl-C is// pressed://// if err := cview.NewApplication().SetRoot(p, true).Run(); err != nil {// panic(err)// }typeApplicationstruct {// The application's screen. Apart from Run(), this variable should never be // set directly. Always use the screenReplacement channel after calling // Fini(), to set a new screen (or nil to stop the application). screen tcell.Screen// The size of the application's screen. width, height int// The primitive which currently has the keyboard focus. focus Primitive// The root primitive to be seen on the screen. root Primitive// Whether or not the application resizes the root primitive. rootFullscreen bool// Whether or not to enable bracketed paste mode. enableBracketedPaste bool// Whether or not to enable mouse events. enableMouse bool// An optional capture function which receives a key event and returns the // event to be forwarded to the default input handler (nil if nothing should // be forwarded). inputCapture func(event *tcell.EventKey) *tcell.EventKey// Time a resize event was last processed. lastResize time.Time// Timer limiting how quickly resize events are processed. throttleResize *time.Timer// An optional callback function which is invoked when the application's // window is initialized, and when the application's window size changes. // After invoking this callback the screen is cleared and the application // is drawn. afterResize func(width int, height int)// An optional callback function which is invoked before the application's // focus changes. beforeFocus func(p Primitive) bool// An optional callback function which is invoked after the application's // focus changes. afterFocus func(p Primitive)// An optional callback function which is invoked just before the root // primitive is drawn. beforeDraw func(screen tcell.Screen) bool// An optional callback function which is invoked after the root primitive // was drawn. afterDraw func(screen tcell.Screen)// Used to send screen events from separate goroutine to main event loop events chantcell.Event// Functions queued from goroutines, used to serialize updates to primitives. updates chanfunc()// An object that the screen variable will be set to after Fini() was called. // Use this channel to set a new screen object for the application // (screen.Init() and draw() will be called implicitly). A value of nil will // stop the application. screenReplacement chantcell.Screen// An optional capture function which receives a mouse event and returns the // event to be forwarded to the default mouse handler (nil if nothing should // be forwarded). mouseCapture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)// doubleClickInterval specifies the maximum time between clicks to register a // double click rather than a single click. doubleClickInterval time.Duration mouseCapturingPrimitive Primitive// A Primitive returned by a MouseHandler which will capture future mouse events. lastMouseX, lastMouseY int// The last position of the mouse. mouseDownX, mouseDownY int// The position of the mouse when its button was last pressed. lastMouseClick time.Time// The time when a mouse button was last clicked. lastMouseButtons tcell.ButtonMask// The last mouse button state.sync.RWMutex}// NewApplication creates and returns a new application.func () *Application {return &Application{enableBracketedPaste: true,events: make(chantcell.Event, queueSize),updates: make(chanfunc(), queueSize),screenReplacement: make(chantcell.Screen, 1), }}// HandlePanic (when deferred at the start of a goroutine) handles panics// gracefully. The terminal is returned to its original state before the panic// message is printed.//// Panics may only be handled by the panicking goroutine. Because of this,// HandlePanic must be deferred at the start of each goroutine (including main).func ( *Application) () { := recover()if == nil {return } .Lock()defer .Unlock() .finalizeScreen()panic()}// SetInputCapture sets a function which captures all key events before they are// forwarded to the key event handler of the primitive which currently has// focus. This function can then choose to forward that key event (or a// different one) by returning it or stop the key event processing by returning// nil.//// Note that this also affects the default event handling of the application// itself: Such a handler can intercept the Ctrl-C event which closes the// application.func ( *Application) ( func( *tcell.EventKey) *tcell.EventKey) { .Lock()defer .Unlock() .inputCapture = }// GetInputCapture returns the function installed with SetInputCapture() or nil// if no such function has been installed.func ( *Application) () func( *tcell.EventKey) *tcell.EventKey { .RLock()defer .RUnlock()return .inputCapture}// SetMouseCapture sets a function which captures mouse events (consisting of// the original tcell mouse event and the semantic mouse action) before they are// forwarded to the appropriate mouse event handler. This function can then// choose to forward that event (or a different one) by returning it or stop// the event processing by returning a nil mouse event.func ( *Application) ( func( *tcell.EventMouse, MouseAction) (*tcell.EventMouse, MouseAction)) { .mouseCapture = }// GetMouseCapture returns the function installed with SetMouseCapture() or nil// if no such function has been installed.func ( *Application) () func( *tcell.EventMouse, MouseAction) (*tcell.EventMouse, MouseAction) {return .mouseCapture}// SetDoubleClickInterval sets the maximum time between clicks to register a// double click rather than a single click. A standard duration is provided as// StandardDoubleClick. No interval is set by default, disabling double clicks.func ( *Application) ( time.Duration) { .doubleClickInterval = }// SetScreen allows you to provide your own tcell.Screen object. For most// applications, this is not needed and you should be familiar with// tcell.Screen when using this function.//// This function is typically called before the first call to Run(). Init() need// not be called on the screen.func ( *Application) ( tcell.Screen) {if == nil {return// Invalid input. Do nothing. } .Lock()if .screen == nil {// Run() has not been called yet. .screen = .Unlock()return }// Run() is already in progress. Exchange screen. := .screen .Unlock() .Fini() .screenReplacement <- }// GetScreen returns the current tcell.Screen of the application. Lock the// application when manipulating the screen to prevent race conditions. This// value is only available after calling Init or Run.func ( *Application) () tcell.Screen { .RLock()defer .RUnlock()return .screen}// GetScreenSize returns the size of the application's screen. These values are// only available after calling Init or Run.func ( *Application) () (, int) { .RLock()defer .RUnlock()return .width, .height}// Init initializes the application screen. Calling Init before running is not// required. Its primary use is to populate screen dimensions before running an// application.func ( *Application) () error { .Lock()defer .Unlock()return .init()}func ( *Application) () error {if .screen != nil {returnnil }varerror .screen, = tcell.NewScreen()if != nil {return }if = .screen.Init(); != nil {return } .width, .height = .screen.Size()if .enableBracketedPaste { .screen.EnablePaste() }if .enableMouse { .screen.EnableMouse() }returnnil}// EnableBracketedPaste enables bracketed paste mode, which is enabled by default.func ( *Application) ( bool) { .Lock()defer .Unlock()if != .enableBracketedPaste && .screen != nil {if { .screen.EnablePaste() } else { .screen.DisablePaste() } } .enableBracketedPaste = }// EnableMouse enables mouse events.func ( *Application) ( bool) { .Lock()defer .Unlock()if != .enableMouse && .screen != nil {if { .screen.EnableMouse() } else { .screen.DisableMouse() } } .enableMouse = }// Run starts the application and thus the event loop. This function returns// when Stop() was called.func ( *Application) () error { .Lock()// Initialize screen := .init()if != nil { .Unlock()return }// defer a.HandlePanic()// Draw the screen for the first time. .Unlock() .draw()// Separate loop to wait for screen replacement events.varsync.WaitGroup .Add(1)gofunc() {defer .HandlePanic()defer .Done()for { .RLock() := .screen .RUnlock()if == nil {// We have no screen. Let's stop. .QueueEvent(nil)break }// A screen was finalized (event is nil). Wait for a new screen. = <-.screenReplacementif == nil {// No new screen. We're done. .QueueEvent(nil)return }// We have a new screen. Keep going. .Lock() .screen = .Unlock()// Initialize and draw this screen.if := .Init(); != nil {panic() }if .enableBracketedPaste { .EnablePaste() }if .enableMouse { .EnableMouse() } .draw() } }() := func( interface{}) { .RLock() := .focus := .inputCapture := .screen .RUnlock()switch event := .(type) {case *tcell.EventKey:// Intercept keys.if != nil { = ()if == nil { .draw()return// Don't forward event. } }// Ctrl-C closes the application.if .Key() == tcell.KeyCtrlC { .Stop()return }// Pass other key events to the currently focused primitive.if != nil {if := .InputHandler(); != nil { (, func( Primitive) { .SetFocus() }) .draw() } }case *tcell.EventResize:// Throttle resize events.iftime.Since(.lastResize) < resizeEventThrottle {// Stop timerif .throttleResize != nil && !.throttleResize.Stop() {select {case<-.throttleResize.C:default: } } := // Capture// Start timer .throttleResize = time.AfterFunc(resizeEventThrottle, func() { .events <- })return } .lastResize = time.Now()if == nil {return } .Clear() .width, .height = .Size()// Call afterResize handler if there is one.if .afterResize != nil { .afterResize(.width, .height) } .draw()case *tcell.EventMouse: , := .fireMouseActions()if { .draw() } .lastMouseButtons = .Buttons()if { .mouseDownX, .mouseDownY = .Position() } } } := &sync.Mutex{}gofunc() {defer .HandlePanic()for := range .updates { .Lock() () .Unlock() } }()gofunc() {defer .HandlePanic()for := range .events { .Lock() () .Unlock() } }()// Start screen event loop.for { .Lock() := .screen .Unlock()if == nil {break }// Wait for next event. := .PollEvent()if == nil {break } .Lock() () .Unlock() }// Wait for the screen replacement event loop to finish. .Wait() .Lock()defer .Unlock() .screen = nilreturnnil}// fireMouseActions analyzes the provided mouse event, derives mouse actions// from it and then forwards them to the corresponding primitives.func ( *Application) ( *tcell.EventMouse) (, bool) {// We want to relay follow-up events to the same target primitive.varPrimitive// Helper function to fire a mouse action. := func( MouseAction) {switch {caseMouseLeftDown, MouseMiddleDown, MouseRightDown: = true }// Intercept event.if .mouseCapture != nil { , = .mouseCapture(, )if == nil { = truereturn// Don't forward event. } }// Determine the target primitive.var , Primitiveif .mouseCapturingPrimitive != nil { = .mouseCapturingPrimitive = .mouseCapturingPrimitive } elseif != nil { = } else { = .root }if != nil {if := .MouseHandler(); != nil {varbool , = (, , func( Primitive) { .SetFocus() })if { = true } } } .mouseCapturingPrimitive = } , := .Position() := .Buttons() := != .mouseDownX || != .mouseDownY := ^ .lastMouseButtonsif != .lastMouseX || != .lastMouseY { (MouseMove) .lastMouseX = .lastMouseY = }for , := range []struct {tcell.ButtonMask , , , MouseAction }{ {tcell.ButtonPrimary, MouseLeftDown, MouseLeftUp, MouseLeftClick, MouseLeftDoubleClick}, {tcell.ButtonMiddle, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick}, {tcell.ButtonSecondary, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick}, } {if &. != 0 {if &. != 0 { (.) } else { (.)if ! {if .doubleClickInterval == 0 || .lastMouseClick.Add(.doubleClickInterval).Before(time.Now()) { (.) .lastMouseClick = time.Now() } else { (.) .lastMouseClick = time.Time{} // reset } } } } }for , := range []struct {tcell.ButtonMaskMouseAction }{ {tcell.WheelUp, MouseScrollUp}, {tcell.WheelDown, MouseScrollDown}, {tcell.WheelLeft, MouseScrollLeft}, {tcell.WheelRight, MouseScrollRight}} {if &. != 0 { (.) } }return , }// Stop stops the application, causing Run() to return.func ( *Application) () { .Lock()defer .Unlock() .finalizeScreen() .screenReplacement <- nil}func ( *Application) () { := .screenif == nil {return } .screen = nil .Fini()}// Suspend temporarily suspends the application by exiting terminal UI mode and// invoking the provided function "f". When "f" returns, terminal UI mode is// entered again and the application resumes.//// A return value of true indicates that the application was suspended and "f"// was called. If false is returned, the application was already suspended,// terminal UI mode was not exited, and "f" was not called.func ( *Application) ( func()) bool { .Lock()if .screen == nil { .Unlock()returnfalse// Screen has not yet been initialized. } := .screen.Suspend() .Unlock()if != nil {panic() }// Wait for "f" to return. () .Lock() = .screen.Resume() .Unlock()if != nil {panic() }returntrue}// Draw draws the provided primitives on the screen, or when no primitives are// provided, draws the application's root primitive (i.e. the entire screen).//// When one or more primitives are supplied, the Draw functions of the// primitives are called. Handlers set via BeforeDrawFunc and AfterDrawFunc are// not called.//// When no primitives are provided, the Draw function of the application's root// primitive is called. This results in drawing the entire screen. Handlers set// via BeforeDrawFunc and AfterDrawFunc are also called.func ( *Application) ( ...Primitive) { .QueueUpdate(func() {iflen() == 0 { .draw()return } .Lock()if .screen != nil {for , := range { .Draw(.screen) } .screen.Show() } .Unlock() })}// draw actually does what Draw() promises to do.func ( *Application) () { .Lock() := .screen := .root := .rootFullscreen := .beforeDraw := .afterDraw// Maybe we're not ready yet or not anymore.if == nil || == nil { .Unlock()return }// Resize if requested.if { .SetRect(0, 0, .width, .height) }// Call before handler if there is one.if != nil { .Unlock()if () { .Show()return } } else { .Unlock() }// Draw all primitives. .Draw()// Call after handler if there is one.if != nil { () }// Sync screen. .Show()}// SetBeforeDrawFunc installs a callback function which is invoked just before// the root primitive is drawn during screen updates. If the function returns// true, drawing will not continue, i.e. the root primitive will not be drawn// (and an after-draw-handler will not be called).//// Note that the screen is not cleared by the application. To clear the screen,// you may call screen.Clear().//// Provide nil to uninstall the callback function.func ( *Application) ( func( tcell.Screen) bool) { .Lock()defer .Unlock() .beforeDraw = }// GetBeforeDrawFunc returns the callback function installed with// SetBeforeDrawFunc() or nil if none has been installed.func ( *Application) () func( tcell.Screen) bool { .RLock()defer .RUnlock()return .beforeDraw}// SetAfterDrawFunc installs a callback function which is invoked after the root// primitive was drawn during screen updates.//// Provide nil to uninstall the callback function.func ( *Application) ( func( tcell.Screen)) { .Lock()defer .Unlock() .afterDraw = }// GetAfterDrawFunc returns the callback function installed with// SetAfterDrawFunc() or nil if none has been installed.func ( *Application) () func( tcell.Screen) { .RLock()defer .RUnlock()return .afterDraw}// SetRoot sets the root primitive for this application. If "fullscreen" is set// to true, the root primitive's position will be changed to fill the screen.//// This function must be called at least once or nothing will be displayed when// the application starts.//// It also calls SetFocus() on the primitive and draws the application.func ( *Application) ( Primitive, bool) { .Lock() .root = .rootFullscreen = if .screen != nil { .screen.Clear() } .Unlock() .SetFocus() .Draw()}// ResizeToFullScreen resizes the given primitive such that it fills the entire// screen.func ( *Application) ( Primitive) { .RLock() , := .width, .height .RUnlock() .SetRect(0, 0, , )}// SetAfterResizeFunc installs a callback function which is invoked when the// application's window is initialized, and when the application's window size// changes. After invoking this callback the screen is cleared and the// application is drawn.//// Provide nil to uninstall the callback function.func ( *Application) ( func( int, int)) { .Lock()defer .Unlock() .afterResize = }// GetAfterResizeFunc returns the callback function installed with// SetAfterResizeFunc() or nil if none has been installed.func ( *Application) () func( int, int) { .RLock()defer .RUnlock()return .afterResize}// SetFocus sets the focus on a new primitive. All key events will be redirected// to that primitive. Callers must ensure that the primitive will handle key// events.//// Blur() will be called on the previously focused primitive. Focus() will be// called on the new primitive.func ( *Application) ( Primitive) { .Lock()if .beforeFocus != nil { .Unlock() := .beforeFocus()if ! {return } .Lock() }if .focus != nil { .focus.Blur() } .focus = if .screen != nil { .screen.HideCursor() } .Unlock()if .afterFocus != nil { .afterFocus() }if != nil { .Focus(func( Primitive) { .() }) }}// GetFocus returns the primitive which has the current focus. If none has it,// nil is returned.func ( *Application) () Primitive { .RLock()defer .RUnlock()return .focus}// SetBeforeFocusFunc installs a callback function which is invoked before the// application's focus changes. Return false to maintain the current focus.//// Provide nil to uninstall the callback function.func ( *Application) ( func( Primitive) bool) { .Lock()defer .Unlock() .beforeFocus = }// SetAfterFocusFunc installs a callback function which is invoked after the// application's focus changes.//// Provide nil to uninstall the callback function.func ( *Application) ( func( Primitive)) { .Lock()defer .Unlock() .afterFocus = }// QueueUpdate queues a function to be executed as part of the event loop.//// Note that Draw() is not implicitly called after the execution of f as that// may not be desirable. You can call Draw() from f if the screen should be// refreshed after each update. Alternatively, use QueueUpdateDraw() to follow// up with an immediate refresh of the screen.func ( *Application) ( func()) { .updates <- }// QueueUpdateDraw works like QueueUpdate() except, when one or more primitives// are provided, the primitives are drawn after the provided function returns.// When no primitives are provided, the entire screen is drawn after the// provided function returns.func ( *Application) ( func(), ...Primitive) { .QueueUpdate(func() { ()iflen() == 0 { .draw()return } .Lock()if .screen != nil {for , := range { .Draw(.screen) } .screen.Show() } .Unlock() })}// QueueEvent sends an event to the Application event loop.//// It is not recommended for event to be nil.func ( *Application) ( tcell.Event) { .events <- }// RingBell sends a bell code to the terminal.func ( *Application) () { .QueueUpdate(func() {fmt.Print(string(byte(7))) })}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.