1
0

main.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. a := application.New(application.Options{
  39. Name: "MPCkit",
  40. Description: "Desktop kit editor for the MPC Sample",
  41. Services: []application.Service{
  42. application.NewService(svc),
  43. },
  44. Assets: application.AssetOptions{
  45. Handler: application.AssetFileServerFS(assets),
  46. Middleware: svc.AudioMiddleware,
  47. },
  48. Mac: application.MacOptions{
  49. ApplicationShouldTerminateAfterLastWindowClosed: true,
  50. },
  51. })
  52. d.a = a
  53. // Start hidden and size the window once the screens are known, so it
  54. // fits the display instead of being clamped by it.
  55. w := a.Window.NewWithOptions(application.WebviewWindowOptions{
  56. Title: "MPCkit",
  57. Width: maxWidth,
  58. Height: maxHeight,
  59. MinWidth: minWidth,
  60. MinHeight: minHeight,
  61. Hidden: true,
  62. EnableFileDrop: true,
  63. BackgroundColour: application.NewRGB(0x15, 0x17, 0x1A),
  64. URL: "/",
  65. })
  66. a.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(*application.ApplicationEvent) {
  67. if s := a.Screen.GetPrimary(); s != nil {
  68. width, height := fitSize(s.WorkArea.Width, s.WorkArea.Height)
  69. w.SetSize(width, height)
  70. }
  71. w.Center()
  72. w.Show()
  73. })
  74. // Closing with unsaved changes: stop, and let the frontend ask. If the
  75. // user chooses to discard them, the frontend calls CloseWindow, which
  76. // closes for real.
  77. var closeConfirmed atomic.Bool
  78. svc.SetCloser(func() {
  79. closeConfirmed.Store(true)
  80. w.Close()
  81. })
  82. w.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
  83. if closeConfirmed.Load() || !svc.Dirty() {
  84. return
  85. }
  86. e.Cancel()
  87. a.Event.Emit(eventClose)
  88. })
  89. // A WAV dropped on a pad (an element with data-file-drop-target and
  90. // data-slot) is assigned to that pad.
  91. w.OnWindowEvent(events.Common.WindowFilesDropped, func(e *application.WindowEvent) {
  92. ctx := e.Context()
  93. target := ctx.DropTargetDetails()
  94. if target == nil {
  95. return
  96. }
  97. slot, err := strconv.Atoi(target.Attributes["data-slot"])
  98. if err != nil {
  99. return
  100. }
  101. for _, f := range ctx.DroppedFiles() {
  102. if strings.EqualFold(filepath.Ext(f), ".wav") {
  103. st, err := svc.AssignSamplePath(slot, f)
  104. a.Event.Emit(eventState, st)
  105. if err != nil {
  106. a.Event.Emit(eventError, err.Error())
  107. }
  108. return
  109. }
  110. }
  111. a.Event.Emit(eventError, "Only WAV files can be assigned to a pad.")
  112. })
  113. if err := a.Run(); err != nil {
  114. log.Fatal(err)
  115. }
  116. }
  117. // fitSize returns a 16:9 window size that takes screenShare of the work
  118. // area (areaW x areaH), capped at maxWidth x maxHeight and no smaller than
  119. // minWidth x minHeight.
  120. func fitSize(areaW, areaH int) (int, int) {
  121. width := min(maxWidth, int(float64(areaW)*screenShare))
  122. height := width * 9 / 16
  123. if limit := int(float64(areaH) * screenShare); height > limit {
  124. height = limit
  125. width = height * 16 / 9
  126. }
  127. return max(width, minWidth), max(height, minHeight)
  128. }