From 6f901716ed6394ed503a22eda8b8f9c3fe09e526 Mon Sep 17 00:00:00 2001 From: lawl Date: Sat, 20 Mar 2021 13:49:25 +0100 Subject: 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. --- main.go | 12 +++++++-- ui.go | 91 +++++++++++++++++++++++----------------------------------------- views.go | 50 +++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 61 deletions(-) create mode 100644 views.go diff --git a/main.go b/main.go index e75b7b2..c195765 100644 --- a/main.go +++ b/main.go @@ -209,16 +209,20 @@ func main() { go updateCheck(&ctx) } - go paConnectionWatchdog(&ctx) - ctx.haveCapabilities = hasCapSysResource(getCurrentCaps()) ctx.capsMismatch = hasCapSysResource(getCurrentCaps()) != hasCapSysResource(getSelfFileCaps()) + resetUI(&ctx) + wnd := nucular.NewMasterWindowSize(0, appName, image.Point{600, 400}, func(w *nucular.Window) { updatefn(&ctx, w) }) ctx.masterWindow = &wnd + (*ctx.masterWindow).Changed() + + go paConnectionWatchdog(&ctx) + style := style.FromTheme(style.DarkTheme, 2.0) style.Font = font.DefaultFont(16, 1) wnd.SetStyle(style) @@ -315,6 +319,9 @@ func paConnectionWatchdog(ctx *ntcontext) { continue } + ctx.views.Push(connectScreen) + (*ctx.masterWindow).Changed() + paClient, err := pulseaudio.NewClient() if err != nil { 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) resetUI(ctx) + (*ctx.masterWindow).Changed() time.Sleep(500 * time.Millisecond) } diff --git a/ui.go b/ui.go index 7792c3a..a516ce4 100644 --- a/ui.go +++ b/ui.go @@ -26,16 +26,13 @@ type ntcontext struct { librnnoise string sourceListColdWidthIndex int config *config - loadingScreen bool - licenseScreen bool - versionScreen bool licenseTextArea nucular.TextEditor masterWindow *nucular.MasterWindow update updateui reloadRequired bool haveCapabilities bool - errorMsg string capsMismatch bool + views *ViewStack } var green = color.RGBA{34, 187, 69, 255} @@ -45,38 +42,11 @@ var orange = color.RGBA{255, 140, 0, 255} var patreonImg *image.RGBA func updatefn(ctx *ntcontext, w *nucular.Window) { + currView := ctx.views.Peek() + currView(ctx, w) +} - //TODO: this is disgusting - - 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 - } +func mainScreen(ctx *ntcontext, w *nucular.Window) { 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 { w.Row(10).Dynamic(1) if w.MenuItem(label.T("Licenses")) { - ctx.licenseScreen = true + ctx.views.Push(licenseScreen) } w.Row(10).Dynamic(1) if w.MenuItem(label.T("Source code")) { exec.Command("xdg-open", "https://github.com/lawl/NoiseTorch").Run() } 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) if ctx.noiseSupressorState != unloaded { if w.ButtonText("Unload NoiseTorch") { - ctx.loadingScreen = true + ctx.views.Push(loadingScreen) ctx.reloadRequired = false 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 { @@ -260,7 +230,7 @@ func updatefn(ctx *ntcontext, w *nucular.Window) { time.Sleep(time.Millisecond * 500) } } - ctx.loadingScreen = false + ctx.views.Pop() (*ctx.masterWindow).Changed() }() } @@ -280,7 +250,7 @@ func updatefn(ctx *ntcontext, w *nucular.Window) { ((ctx.config.FilterOutput && ctx.config.GuiltTripped) || !ctx.config.FilterOutput) && ctx.noiseSupressorState != inconsistent { if w.ButtonText(txt) { - ctx.loadingScreen = true + ctx.views.Push(loadingScreen) ctx.reloadRequired = false 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 { @@ -301,7 +271,7 @@ func updatefn(ctx *ntcontext, w *nucular.Window) { ctx.config.LastUsedInput = inp.ID ctx.config.LastUsedOutput = out.ID go writeConfig(ctx.config) - ctx.loadingScreen = false + ctx.views.Pop() (*ctx.masterWindow).Changed() }() } @@ -367,7 +337,7 @@ func licenseScreen(ctx *ntcontext, w *nucular.Window) { w.Row(20).Dynamic(2) w.Spacing(1) 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.Spacing(1) 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)") { err := pkexecSetcapSelf() if err != nil { - ctx.errorMsg = err.Error() + ctx.views.Push(makeErrorScreen(ctx, w, err.Error())) return } self, err := os.Executable() if err != nil { - ctx.errorMsg = err.Error() + ctx.views.Push(makeErrorScreen(ctx, w, err.Error())) return } err = syscall.Exec(self, []string{""}, os.Environ()) if err != nil { - ctx.errorMsg = err.Error() + ctx.views.Push(makeErrorScreen(ctx, w, err.Error())) return } } } -func errorScreen(ctx *ntcontext, w *nucular.Window) { - w.Row(15).Dynamic(1) - w.Label("Error", "CB") - w.Row(15).Dynamic(1) - w.Label(ctx.errorMsg, "CB") - w.Row(40).Dynamic(1) - w.Row(25).Dynamic(1) - if w.ButtonText("OK") { - ctx.errorMsg = "" - return +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.Label("Error", "CB") + w.Row(15).Dynamic(1) + w.Label(errorMsg, "CB") + w.Row(40).Dynamic(1) + w.Row(25).Dynamic(1) + if w.ButtonText("OK") { + ctx.views.Pop() + return + } } } func resetUI(ctx *ntcontext) { - ctx.loadingScreen = false + ctx.views = NewViewStack() + ctx.views.Push(mainScreen) - if ctx.masterWindow != nil { - (*ctx.masterWindow).Changed() + if !ctx.haveCapabilities { + ctx.views.Push(capabilitiesScreen) } } diff --git a/views.go b/views.go new file mode 100644 index 0000000..0e3d95e --- /dev/null +++ b/views.go @@ -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] +} -- cgit v1.2.3-54-g00ecf