1// Copyright 2018 The Tcell Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use file except in compliance with the License. 5// You may obtain a copy of the license at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package views 16 17import ( 18 "sync" 19 20 "github.com/gdamore/tcell" 21) 22 23// Application represents an event-driven application running on a screen. 24type Application struct { 25 widget Widget 26 screen tcell.Screen 27 style tcell.Style 28 err error 29 wg sync.WaitGroup 30} 31 32// SetRootWidget sets the primary (root, main) Widget to be displayed. 33func (app *Application) SetRootWidget(widget Widget) { 34 app.widget = widget 35} 36 37// initialize initializes the application. It will normally attempt to 38// allocate a default screen if one is not already established. 39func (app *Application) initialize() error { 40 if app.screen == nil { 41 if app.screen, app.err = tcell.NewScreen(); app.err != nil { 42 return app.err 43 } 44 app.screen.SetStyle(app.style) 45 } 46 return nil 47} 48 49// Quit causes the application to shutdown gracefully. It does not wait 50// for the application to exit, but returns immediately. 51func (app *Application) Quit() { 52 ev := &eventAppQuit{} 53 ev.SetEventNow() 54 if scr := app.screen; scr != nil { 55 go func() { scr.PostEventWait(ev) }() 56 } 57} 58 59// Refresh causes the application forcibly redraw everything. Use this 60// to clear up screen corruption, etc. 61func (app *Application) Refresh() { 62 ev := &eventAppRefresh{} 63 ev.SetEventNow() 64 if scr := app.screen; scr != nil { 65 go func() { scr.PostEventWait(ev) }() 66 } 67} 68 69// Update asks the application to draw any screen updates that have not 70// been drawn yet. 71func (app *Application) Update() { 72 ev := &eventAppUpdate{} 73 ev.SetEventNow() 74 if scr := app.screen; scr != nil { 75 go func() { scr.PostEventWait(ev) }() 76 } 77} 78 79// PostFunc posts a function to be executed in the context of the 80// application's event loop. Functions that need to update displayed 81// state, etc. can do this to avoid holding locks. 82func (app *Application) PostFunc(fn func()) { 83 ev := &eventAppFunc{fn: fn} 84 ev.SetEventNow() 85 if scr := app.screen; scr != nil { 86 go func() { scr.PostEventWait(ev) }() 87 } 88} 89 90// SetScreen sets the screen to use for the application. This must be 91// done before the application starts to run or is initialized. 92func (app *Application) SetScreen(scr tcell.Screen) { 93 if app.screen == nil { 94 app.screen = scr 95 app.err = nil 96 } 97} 98 99// SetStyle sets the default style (background) to be used for Widgets 100// that have not specified any other style. 101func (app *Application) SetStyle(style tcell.Style) { 102 app.style = style 103 if app.screen != nil { 104 app.screen.SetStyle(style) 105 } 106} 107 108func (app *Application) run() { 109 110 screen := app.screen 111 widget := app.widget 112 113 if widget == nil { 114 app.wg.Done() 115 return 116 } 117 if screen == nil { 118 if e := app.initialize(); e != nil { 119 app.wg.Done() 120 return 121 } 122 screen = app.screen 123 } 124 defer func() { 125 screen.Fini() 126 app.wg.Done() 127 }() 128 screen.Init() 129 screen.Clear() 130 widget.SetView(screen) 131 132loop: 133 for { 134 if widget = app.widget; widget == nil { 135 break 136 } 137 widget.Draw() 138 screen.Show() 139 140 ev := screen.PollEvent() 141 switch nev := ev.(type) { 142 case *eventAppQuit: 143 break loop 144 case *eventAppUpdate: 145 screen.Show() 146 case *eventAppRefresh: 147 screen.Sync() 148 case *eventAppFunc: 149 nev.fn() 150 case *tcell.EventResize: 151 screen.Sync() 152 widget.Resize() 153 default: 154 widget.HandleEvent(ev) 155 } 156 } 157} 158 159// Start starts the application loop, initializing the screen 160// and starting the Event loop. The application will run in a goroutine 161// until Quit is called. 162func (app *Application) Start() { 163 app.wg.Add(1) 164 go app.run() 165} 166 167// Wait waits until the application finishes. 168func (app *Application) Wait() error { 169 app.wg.Wait() 170 return app.err 171} 172 173// Run runs the application, waiting until the application loop exits. 174// It is equivalent to app.Start() followed by app.Wait() 175func (app *Application) Run() error { 176 app.Start() 177 return app.Wait() 178} 179 180type eventAppUpdate struct { 181 tcell.EventTime 182} 183 184type eventAppQuit struct { 185 tcell.EventTime 186} 187 188type eventAppRefresh struct { 189 tcell.EventTime 190} 191 192type eventAppFunc struct { 193 tcell.EventTime 194 fn func() 195} 196