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