Refactor UI: Use a stack of views to handle what to display

Previously we dispatched calls to different drawing function with
a bunch of if statements in the main draw function.

Use a stack of drawing functions instead.
pull/110/head
lawl 5 years ago
parent fe14585a37
commit 6f901716ed

@ -209,16 +209,20 @@ func main() {
go updateCheck(&ctx) go updateCheck(&ctx)
} }
go paConnectionWatchdog(&ctx)
ctx.haveCapabilities = hasCapSysResource(getCurrentCaps()) ctx.haveCapabilities = hasCapSysResource(getCurrentCaps())
ctx.capsMismatch = hasCapSysResource(getCurrentCaps()) != hasCapSysResource(getSelfFileCaps()) ctx.capsMismatch = hasCapSysResource(getCurrentCaps()) != hasCapSysResource(getSelfFileCaps())
resetUI(&ctx)
wnd := nucular.NewMasterWindowSize(0, appName, image.Point{600, 400}, func(w *nucular.Window) { wnd := nucular.NewMasterWindowSize(0, appName, image.Point{600, 400}, func(w *nucular.Window) {
updatefn(&ctx, w) updatefn(&ctx, w)
}) })
ctx.masterWindow = &wnd ctx.masterWindow = &wnd
(*ctx.masterWindow).Changed()
go paConnectionWatchdog(&ctx)
style := style.FromTheme(style.DarkTheme, 2.0) style := style.FromTheme(style.DarkTheme, 2.0)
style.Font = font.DefaultFont(16, 1) style.Font = font.DefaultFont(16, 1)
wnd.SetStyle(style) wnd.SetStyle(style)
@ -315,6 +319,9 @@ func paConnectionWatchdog(ctx *ntcontext) {
continue continue
} }
ctx.views.Push(connectScreen)
(*ctx.masterWindow).Changed()
paClient, err := pulseaudio.NewClient() paClient, err := pulseaudio.NewClient()
if err != nil { if err != nil {
log.Printf("Couldn't create pulseaudio client: %v\n", err) log.Printf("Couldn't create pulseaudio client: %v\n", err)
@ -328,6 +335,7 @@ func paConnectionWatchdog(ctx *ntcontext) {
ctx.outputList = preselectDevice(ctx, getSinks(paClient), ctx.config.LastUsedOutput, getDefaultSinkID) ctx.outputList = preselectDevice(ctx, getSinks(paClient), ctx.config.LastUsedOutput, getDefaultSinkID)
resetUI(ctx) resetUI(ctx)
(*ctx.masterWindow).Changed()
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }

77
ui.go

@ -26,16 +26,13 @@ type ntcontext struct {
librnnoise string librnnoise string
sourceListColdWidthIndex int sourceListColdWidthIndex int
config *config config *config
loadingScreen bool
licenseScreen bool
versionScreen bool
licenseTextArea nucular.TextEditor licenseTextArea nucular.TextEditor
masterWindow *nucular.MasterWindow masterWindow *nucular.MasterWindow
update updateui update updateui
reloadRequired bool reloadRequired bool
haveCapabilities bool haveCapabilities bool
errorMsg string
capsMismatch bool capsMismatch bool
views *ViewStack
} }
var green = color.RGBA{34, 187, 69, 255} var green = color.RGBA{34, 187, 69, 255}
@ -45,38 +42,11 @@ var orange = color.RGBA{255, 140, 0, 255}
var patreonImg *image.RGBA var patreonImg *image.RGBA
func updatefn(ctx *ntcontext, w *nucular.Window) { func updatefn(ctx *ntcontext, w *nucular.Window) {
currView := ctx.views.Peek()
currView(ctx, w)
}
//TODO: this is disgusting func mainScreen(ctx *ntcontext, w *nucular.Window) {
if ctx.errorMsg != "" {
errorScreen(ctx, w)
return
}
if !ctx.haveCapabilities {
capabilitiesScreen(ctx, w)
return
}
if !ctx.paClient.Connected() {
connectScreen(ctx, w)
return
}
if ctx.loadingScreen {
loadingScreen(ctx, w)
return
}
if ctx.licenseScreen {
licenseScreen(ctx, w)
return
}
if ctx.versionScreen {
versionScreen(ctx, w)
return
}
w.MenubarBegin() w.MenubarBegin()
@ -84,14 +54,14 @@ func updatefn(ctx *ntcontext, w *nucular.Window) {
if w := w.Menu(label.TA("About", "LC"), 120, nil); w != nil { if w := w.Menu(label.TA("About", "LC"), 120, nil); w != nil {
w.Row(10).Dynamic(1) w.Row(10).Dynamic(1)
if w.MenuItem(label.T("Licenses")) { if w.MenuItem(label.T("Licenses")) {
ctx.licenseScreen = true ctx.views.Push(licenseScreen)
} }
w.Row(10).Dynamic(1) w.Row(10).Dynamic(1)
if w.MenuItem(label.T("Source code")) { if w.MenuItem(label.T("Source code")) {
exec.Command("xdg-open", "https://github.com/lawl/NoiseTorch").Run() exec.Command("xdg-open", "https://github.com/lawl/NoiseTorch").Run()
} }
if w.MenuItem(label.T("Version")) { if w.MenuItem(label.T("Version")) {
ctx.versionScreen = true ctx.views.Push(versionScreen)
} }
} }
@ -248,7 +218,7 @@ func updatefn(ctx *ntcontext, w *nucular.Window) {
w.Row(25).Dynamic(2) w.Row(25).Dynamic(2)
if ctx.noiseSupressorState != unloaded { if ctx.noiseSupressorState != unloaded {
if w.ButtonText("Unload NoiseTorch") { if w.ButtonText("Unload NoiseTorch") {
ctx.loadingScreen = true ctx.views.Push(loadingScreen)
ctx.reloadRequired = false ctx.reloadRequired = false
go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads
if err := unloadSupressor(ctx); err != nil { if err := unloadSupressor(ctx); err != nil {
@ -260,7 +230,7 @@ func updatefn(ctx *ntcontext, w *nucular.Window) {
time.Sleep(time.Millisecond * 500) time.Sleep(time.Millisecond * 500)
} }
} }
ctx.loadingScreen = false ctx.views.Pop()
(*ctx.masterWindow).Changed() (*ctx.masterWindow).Changed()
}() }()
} }
@ -280,7 +250,7 @@ func updatefn(ctx *ntcontext, w *nucular.Window) {
((ctx.config.FilterOutput && ctx.config.GuiltTripped) || !ctx.config.FilterOutput) && ((ctx.config.FilterOutput && ctx.config.GuiltTripped) || !ctx.config.FilterOutput) &&
ctx.noiseSupressorState != inconsistent { ctx.noiseSupressorState != inconsistent {
if w.ButtonText(txt) { if w.ButtonText(txt) {
ctx.loadingScreen = true ctx.views.Push(loadingScreen)
ctx.reloadRequired = false ctx.reloadRequired = false
go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads
if ctx.noiseSupressorState == loaded { if ctx.noiseSupressorState == loaded {
@ -301,7 +271,7 @@ func updatefn(ctx *ntcontext, w *nucular.Window) {
ctx.config.LastUsedInput = inp.ID ctx.config.LastUsedInput = inp.ID
ctx.config.LastUsedOutput = out.ID ctx.config.LastUsedOutput = out.ID
go writeConfig(ctx.config) go writeConfig(ctx.config)
ctx.loadingScreen = false ctx.views.Pop()
(*ctx.masterWindow).Changed() (*ctx.masterWindow).Changed()
}() }()
} }
@ -367,7 +337,7 @@ func licenseScreen(ctx *ntcontext, w *nucular.Window) {
w.Row(20).Dynamic(2) w.Row(20).Dynamic(2)
w.Spacing(1) w.Spacing(1)
if w.ButtonText("OK") { if w.ButtonText("OK") {
ctx.licenseScreen = false ctx.views.Pop()
} }
} }
@ -381,7 +351,7 @@ func versionScreen(ctx *ntcontext, w *nucular.Window) {
w.Row(20).Dynamic(2) w.Row(20).Dynamic(2)
w.Spacing(1) w.Spacing(1)
if w.ButtonText("OK") { if w.ButtonText("OK") {
ctx.versionScreen = false ctx.views.Pop()
} }
} }
@ -406,40 +376,43 @@ func capabilitiesScreen(ctx *ntcontext, w *nucular.Window) {
if w.ButtonText("Grant capability (requires root)") { if w.ButtonText("Grant capability (requires root)") {
err := pkexecSetcapSelf() err := pkexecSetcapSelf()
if err != nil { if err != nil {
ctx.errorMsg = err.Error() ctx.views.Push(makeErrorScreen(ctx, w, err.Error()))
return return
} }
self, err := os.Executable() self, err := os.Executable()
if err != nil { if err != nil {
ctx.errorMsg = err.Error() ctx.views.Push(makeErrorScreen(ctx, w, err.Error()))
return return
} }
err = syscall.Exec(self, []string{""}, os.Environ()) err = syscall.Exec(self, []string{""}, os.Environ())
if err != nil { if err != nil {
ctx.errorMsg = err.Error() ctx.views.Push(makeErrorScreen(ctx, w, err.Error()))
return return
} }
} }
} }
func errorScreen(ctx *ntcontext, w *nucular.Window) { func makeErrorScreen(ctx *ntcontext, w *nucular.Window, errorMsg string) func(ctx *ntcontext, w *nucular.Window) {
return func(ctx *ntcontext, w *nucular.Window) {
w.Row(15).Dynamic(1) w.Row(15).Dynamic(1)
w.Label("Error", "CB") w.Label("Error", "CB")
w.Row(15).Dynamic(1) w.Row(15).Dynamic(1)
w.Label(ctx.errorMsg, "CB") w.Label(errorMsg, "CB")
w.Row(40).Dynamic(1) w.Row(40).Dynamic(1)
w.Row(25).Dynamic(1) w.Row(25).Dynamic(1)
if w.ButtonText("OK") { if w.ButtonText("OK") {
ctx.errorMsg = "" ctx.views.Pop()
return return
} }
}
} }
func resetUI(ctx *ntcontext) { func resetUI(ctx *ntcontext) {
ctx.loadingScreen = false ctx.views = NewViewStack()
ctx.views.Push(mainScreen)
if ctx.masterWindow != nil { if !ctx.haveCapabilities {
(*ctx.masterWindow).Changed() ctx.views.Push(capabilitiesScreen)
} }
} }

@ -0,0 +1,50 @@
package main
import (
"fmt"
"sync"
"github.com/aarzilli/nucular"
)
type ViewFunc func(ctx *ntcontext, w *nucular.Window)
type ViewStack struct {
stack [100]ViewFunc
sp int8
mu sync.Mutex
}
func NewViewStack() *ViewStack {
return &ViewStack{sp: -1}
}
func (v *ViewStack) Push(f ViewFunc) {
v.mu.Lock()
defer v.mu.Unlock()
v.stack[v.sp+1] = f
v.sp++
}
func (v *ViewStack) Pop() (ViewFunc, error) {
v.mu.Lock()
if v.sp <= 0 {
return nil, fmt.Errorf("Cannot pop root element from ViewStack")
}
defer (func() {
v.stack[v.sp] = nil
v.sp--
v.mu.Unlock()
})()
return v.stack[v.sp], nil
}
func (v *ViewStack) Peek() ViewFunc {
v.mu.Lock()
defer v.mu.Unlock()
return v.stack[v.sp]
}
Loading…
Cancel
Save