1
0

main.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // Command mpckit is MPCkit, the desktop kit editor for the MPC Sample.
  2. package main
  3. import (
  4. "embed"
  5. "log"
  6. "path/filepath"
  7. "strconv"
  8. "strings"
  9. "sync/atomic"
  10. "github.com/wailsapp/wails/v3/pkg/application"
  11. "github.com/wailsapp/wails/v3/pkg/events"
  12. "git.monoexp.com/bajdev/mpckit/internal/app"
  13. )
  14. // The built Svelte app (frontend/dist), embedded in the binary.
  15. //
  16. //go:embed all:frontend/dist
  17. var assets embed.FS
  18. // Window sizes are in device-independent pixels, 16:9 like most displays.
  19. const (
  20. maxWidth, maxHeight = 1280, 720 // largest default size
  21. minWidth, minHeight = 960, 540 // smallest the layout is designed for
  22. screenShare = 0.9 // default size: this share of the screen's work area
  23. )
  24. // Events Go sends to the frontend.
  25. const (
  26. eventState = "state" // a new app.State after a change the frontend did not ask for
  27. eventError = "error" // a message to show
  28. eventClose = "close" // the window was asked to close with unsaved changes
  29. )
  30. func init() {
  31. application.RegisterEvent[app.State](eventState)
  32. application.RegisterEvent[string](eventError)
  33. application.RegisterEvent[application.Void](eventClose)
  34. }
  35. func main() {
  36. d := &dialogs{}
  37. svc := app.NewService(d)
  38. var a *application.App
  39. a = application.New(application.Options{
  40. Name: "MPCkit",
  41. Description: "Desktop kit editor for the MPC Sample",
  42. // Wails exits on a panic in an event handler unless this is set.
  43. // Log it and tell the user instead, so a case the checks missed
  44. // cannot close the app and lose their changes.
  45. PanicHandler: func(p *application.PanicDetails) {
  46. log.Printf("panic: %v\n%s", p.Error, p.StackTrace)
  47. a.Event.Emit(eventError, "Something went wrong inside MPCkit, and the last action may not have finished.")
  48. },
  49. Services: []application.Service{
  50. application.NewService(svc),
  51. },
  52. Assets: application.AssetOptions{
  53. Handler: application.AssetFileServerFS(assets),
  54. Middleware: svc.AudioMiddleware,
  55. },
  56. Mac: application.MacOptions{
  57. ApplicationShouldTerminateAfterLastWindowClosed: true,
  58. },
  59. })
  60. d.a = a
  61. // Start hidden and size the window once the screens are known, so it
  62. // fits the display instead of being clamped by it.
  63. w := a.Window.NewWithOptions(application.WebviewWindowOptions{
  64. Title: "MPCkit",
  65. Width: maxWidth,
  66. Height: maxHeight,
  67. MinWidth: minWidth,
  68. MinHeight: minHeight,
  69. Hidden: true,
  70. EnableFileDrop: true,
  71. BackgroundColour: application.NewRGB(0x15, 0x17, 0x1A),
  72. URL: "/",
  73. })
  74. a.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
  75. if s := a.Screen.GetPrimary(); s != nil {
  76. width, height := fitSize(s.WorkArea.Width, s.WorkArea.Height)
  77. w.SetSize(width, height)
  78. }
  79. w.Center()
  80. w.Show()
  81. })
  82. // Closing with unsaved changes: stop, and let the frontend ask. If the
  83. // user chooses to discard them, the frontend calls CloseWindow, which
  84. // closes for real.
  85. var closeConfirmed atomic.Bool
  86. svc.SetCloser(func() {
  87. closeConfirmed.Store(true)
  88. w.Close()
  89. })
  90. w.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
  91. if closeConfirmed.Load() || !svc.Dirty() {
  92. return
  93. }
  94. e.Cancel()
  95. a.Event.Emit(eventClose)
  96. })
  97. // A WAV dropped on a pad (an element with data-file-drop-target and
  98. // data-slot) is assigned to that pad.
  99. w.OnWindowEvent(events.Common.WindowFilesDropped, func(e *application.WindowEvent) {
  100. ctx := e.Context()
  101. target := ctx.DropTargetDetails()
  102. if target == nil {
  103. return
  104. }
  105. slot, err := strconv.Atoi(target.Attributes["data-slot"])
  106. if err != nil {
  107. return
  108. }
  109. for _, f := range ctx.DroppedFiles() {
  110. if strings.EqualFold(filepath.Ext(f), ".wav") {
  111. st, err := svc.AssignSamplePath(slot, f)
  112. a.Event.Emit(eventState, st)
  113. if err != nil {
  114. a.Event.Emit(eventError, err.Error())
  115. }
  116. return
  117. }
  118. }
  119. a.Event.Emit(eventError, "Only WAV files can be assigned to a pad.")
  120. })
  121. if err := a.Run(); err != nil {
  122. log.Fatal(err)
  123. }
  124. }
  125. // fitSize returns a 16:9 window size that takes screenShare of the work
  126. // area (areaW x areaH), capped at maxWidth x maxHeight and no smaller than
  127. // minWidth x minHeight.
  128. func fitSize(areaW, areaH int) (int, int) {
  129. width := min(maxWidth, int(float64(areaW)*screenShare))
  130. height := width * 9 / 16
  131. if limit := int(float64(areaH) * screenShare); height > limit {
  132. height = limit
  133. width = height * 16 / 9
  134. }
  135. return max(width, minWidth), max(height, minHeight)
  136. }