fractal, theme, err := flags(flag.CommandLine, os.Args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "cannot parse flags: %v\n", err)
os.Exit(1)
}
slog.SetDefault(slog.New(slog.DiscardHandler))
ctx, cancel := context.WithCancel(context.Background())
ui := new(ui)
ui.newProgressBar(ctx)
defer ui.progClose()
ui.app = &gogpuApp{App: gogpu.NewApp(gogpu.DefaultConfig().
WithAppName("Fractal").
WithTitle(fractal.titleText).
WithSize(mainWidth, mainHeight).
WithResizable(false))}
ui.app.SetQuitOnLastWindowClosed(false)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGHUP)
go func() {
<-sigChan
ui.app.Quit()
}()
shaderCode := map[string]string{
"mandelbrot": removeComments(commonShaderCode + "\n" + mandelbrotShaderCode),
"julia": removeComments(commonShaderCode + "\n" + juliaShaderCode),
}
ui.renderer.Store(newRenderer(fractal, shaderCode[fractal.kind], theme))
ui.app.OnSurfaceAvailable(func() {
ui.aboutWindow, err = ui.app.NewWindow(gogpu.DefaultConfig().
WithTitle("").
WithSize(aboutWidth, aboutHeight).
WithTransparent(true).
WithResizable(false).
WithHeaderAlignment(gogpu.HeaderAlignLeft))
if err != nil {
panic(err)
}
ui.aboutWindow.Hide()
ui.aboutWindowHasFocus.Store(false)
ui.primaryWindow = ui.app.PrimaryWindow()
ui.primaryWindow.SetOnKeyPress(func(key gpucontext.Key, mods gpucontext.Modifiers) {
if mods.HasSuper() && key == gpucontext.KeyW {
if ui.aboutWindowHasFocus.Load() {
ui.hideAboutWin()
} else if ui.primaryWindow.Visible() {
ui.hidePrimaryWin()
}
}
})
ui.primaryWindow.SetOnClose(func() bool {
if ui.primaryWindow.Visible() {
ui.hidePrimaryWin()
}
return false
})
ui.primaryWindow.SetOnPointer(func(e gpucontext.PointerEvent) {
if e.Type == gpucontext.PointerDown {
ui.aboutWindowHasFocus.Store(false)
}
})
cr := ui.renderer.Load().(*renderer)
cr.init(ui.app, cr.theme)
setAppMenu(ui)
addFractalsMenu(ctx, ui, shaderCode)
rebuildThemesMenu(ctx, ui, shaderCode)
addWindowMenu(ui.app)
})
ui.app.EventSource().OnKeyPress(func(key gpucontext.Key, _ gpucontext.Modifiers) {
if key == gpucontext.KeySpace {
ui.toggleAnimation()
}
})
ui.app.OnUpdate(func(_ float64) {
if ui.pendingMenuRebuild.Swap(false) {
rebuildThemesMenu(ctx, ui, shaderCode)
}
if ui.hidePrimaryWindow.Swap(false) {
ui.primaryWindow.Hide()
if ui.aboutWindow.Visible() {
ui.aboutWindow.Show()
ui.aboutWindowHasFocus.Store(true)
}
}
if ui.hideAboutWindow.Swap(false) {
ui.hideAboutWin()
}
})
ui.app.OnDraw(func(dc *gogpu.Context) {
ui.initTokenOnce.Do(func() {
ui.animToken.Store(tokenRef{token: ui.app.StartAnimation()})
ui.animating.Store(true)
})
cr := ui.renderer.Load().(*renderer)
cr.draw(dc, &ui.animToken)
elapsed := time.Since(ui.lastFrameTime).Milliseconds()
if elapsed > 0 {
cr.state.fps = float64(1000.0 / elapsed)
}
ui.lastFrameTime = time.Now()
if cr.state.frameCount <= maxPrecisionFrames {
ui.prog.Report(1, strconv.Itoa(cr.state.frameCount))
} else {
ui.prog.Close()
}
if ui.loadToken() != nil {
ui.app.RequestRedraw()
}
})
ui.app.OnClose(func() {
ui.renderer.Load().(*renderer).release()
gg.CloseAccelerator()
cancel()
ui.prog.Close()
})
ui.lastFrameTime = time.Now()
if err := ui.app.Run(); err != nil {
ui.renderer.Load().(*renderer).release()
gg.CloseAccelerator()
cancel()
panic(err)
}
cancel()
}