feat(tracker/gioui): "Ask Help", "Report Bug" and "Manual" menuitems

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-06-23 19:17:00 +03:00
parent fb0fa4af92
commit 5f43bc3067
4 changed files with 64 additions and 18 deletions

View File

@ -5,7 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added ### Added
- Help menu, with a menu item to show the license ([#196][i196]) - Help menu, with a menu item to show the license in a dialog, and also menu
items to open manual, Github Discussions & Github Issues in a browser
([#196][i196])
- Show CPU load percentage in the song panel ([#192][i192]) - Show CPU load percentage in the song panel ([#192][i192])
- Theme can be user configured, in theme.yml. This theme.yml should be placed in - Theme can be user configured, in theme.yml. This theme.yml should be placed in
the usual sointu config directory (i.e. the usual sointu config directory (i.e.

View File

@ -185,6 +185,12 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
t.SplitTrack().Do() t.SplitTrack().Do()
case "SplitInstrument": case "SplitInstrument":
t.SplitInstrument().Do() t.SplitInstrument().Do()
case "ShowManual":
t.ShowManual().Do()
case "AskHelp":
t.AskHelp().Do()
case "ReportBug":
t.ReportBug().Do()
case "ShowLicense": case "ShowLicense":
t.ShowLicense().Do() t.ShowLicense().Do()
// Booleans // Booleans

View File

@ -37,15 +37,15 @@ type SongPanel struct {
PlayBar *PlayBar PlayBar *PlayBar
} }
func NewSongPanel(model *tracker.Model) *SongPanel { func NewSongPanel(tr *Tracker) *SongPanel {
ret := &SongPanel{ ret := &SongPanel{
BPM: NewNumericUpDownState(), BPM: NewNumericUpDownState(),
RowsPerPattern: NewNumericUpDownState(), RowsPerPattern: NewNumericUpDownState(),
RowsPerBeat: NewNumericUpDownState(), RowsPerBeat: NewNumericUpDownState(),
Step: NewNumericUpDownState(), Step: NewNumericUpDownState(),
SongLength: NewNumericUpDownState(), SongLength: NewNumericUpDownState(),
Scope: NewOscilloscope(model), Scope: NewOscilloscope(tr.Model),
MenuBar: NewMenuBar(model), MenuBar: NewMenuBar(tr),
PlayBar: NewPlayBar(), PlayBar: NewPlayBar(),
WeightingTypeBtn: new(Clickable), WeightingTypeBtn: new(Clickable),
@ -313,7 +313,7 @@ type MenuBar struct {
PanicBtn *Clickable PanicBtn *Clickable
} }
func NewMenuBar(model *tracker.Model) *MenuBar { func NewMenuBar(tr *Tracker) *MenuBar {
ret := &MenuBar{ ret := &MenuBar{
Clickables: make([]Clickable, 4), Clickables: make([]Clickable, 4),
Menus: make([]Menu, 4), Menus: make([]Menu, 4),
@ -321,29 +321,32 @@ func NewMenuBar(model *tracker.Model) *MenuBar {
panicHint: makeHint("Panic", " (%s)", "PanicToggle"), panicHint: makeHint("Panic", " (%s)", "PanicToggle"),
} }
ret.fileMenuItems = []MenuItem{ ret.fileMenuItems = []MenuItem{
{IconBytes: icons.ContentClear, Text: "New Song", ShortcutText: keyActionMap["NewSong"], Doer: model.NewSong()}, {IconBytes: icons.ContentClear, Text: "New Song", ShortcutText: keyActionMap["NewSong"], Doer: tr.NewSong()},
{IconBytes: icons.FileFolder, Text: "Open Song", ShortcutText: keyActionMap["OpenSong"], Doer: model.OpenSong()}, {IconBytes: icons.FileFolder, Text: "Open Song", ShortcutText: keyActionMap["OpenSong"], Doer: tr.OpenSong()},
{IconBytes: icons.ContentSave, Text: "Save Song", ShortcutText: keyActionMap["SaveSong"], Doer: model.SaveSong()}, {IconBytes: icons.ContentSave, Text: "Save Song", ShortcutText: keyActionMap["SaveSong"], Doer: tr.SaveSong()},
{IconBytes: icons.ContentSave, Text: "Save Song As...", ShortcutText: keyActionMap["SaveSongAs"], Doer: model.SaveSongAs()}, {IconBytes: icons.ContentSave, Text: "Save Song As...", ShortcutText: keyActionMap["SaveSongAs"], Doer: tr.SaveSongAs()},
{IconBytes: icons.ImageAudiotrack, Text: "Export Wav...", ShortcutText: keyActionMap["ExportWav"], Doer: model.Export()}, {IconBytes: icons.ImageAudiotrack, Text: "Export Wav...", ShortcutText: keyActionMap["ExportWav"], Doer: tr.Export()},
} }
if canQuit { if canQuit {
ret.fileMenuItems = append(ret.fileMenuItems, MenuItem{IconBytes: icons.ActionExitToApp, Text: "Quit", ShortcutText: keyActionMap["Quit"], Doer: model.RequestQuit()}) ret.fileMenuItems = append(ret.fileMenuItems, MenuItem{IconBytes: icons.ActionExitToApp, Text: "Quit", ShortcutText: keyActionMap["Quit"], Doer: tr.RequestQuit()})
} }
ret.editMenuItems = []MenuItem{ ret.editMenuItems = []MenuItem{
{IconBytes: icons.ContentUndo, Text: "Undo", ShortcutText: keyActionMap["Undo"], Doer: model.Undo()}, {IconBytes: icons.ContentUndo, Text: "Undo", ShortcutText: keyActionMap["Undo"], Doer: tr.Undo()},
{IconBytes: icons.ContentRedo, Text: "Redo", ShortcutText: keyActionMap["Redo"], Doer: model.Redo()}, {IconBytes: icons.ContentRedo, Text: "Redo", ShortcutText: keyActionMap["Redo"], Doer: tr.Redo()},
{IconBytes: icons.ImageCrop, Text: "Remove unused data", ShortcutText: keyActionMap["RemoveUnused"], Doer: model.RemoveUnused()}, {IconBytes: icons.ImageCrop, Text: "Remove unused data", ShortcutText: keyActionMap["RemoveUnused"], Doer: tr.RemoveUnused()},
} }
for input := range model.MIDI.InputDevices { for input := range tr.MIDI.InputDevices {
ret.midiMenuItems = append(ret.midiMenuItems, MenuItem{ ret.midiMenuItems = append(ret.midiMenuItems, MenuItem{
IconBytes: icons.ImageControlPoint, IconBytes: icons.ImageControlPoint,
Text: input.String(), Text: input.String(),
Doer: model.SelectMidiInput(input), Doer: tr.SelectMidiInput(input),
}) })
} }
ret.helpMenuItems = []MenuItem{ ret.helpMenuItems = []MenuItem{
{IconBytes: icons.ActionCopyright, Text: "License", ShortcutText: keyActionMap["ShowLicense"], Doer: model.ShowLicense()}, {IconBytes: icons.AVLibraryBooks, Text: "Manual", ShortcutText: keyActionMap["ShowManual"], Doer: tr.ShowManual()},
{IconBytes: icons.ActionHelp, Text: "Ask help", ShortcutText: keyActionMap["AskHelp"], Doer: tr.AskHelp()},
{IconBytes: icons.ActionBugReport, Text: "Report bug", ShortcutText: keyActionMap["ReportBug"], Doer: tr.ReportBug()},
{IconBytes: icons.ActionCopyright, Text: "License", ShortcutText: keyActionMap["ShowLicense"], Doer: tr.ShowLicense()},
} }
return ret return ret
} }

View File

@ -4,7 +4,9 @@ import (
"fmt" "fmt"
"image" "image"
"io" "io"
"os/exec"
"path/filepath" "path/filepath"
"runtime"
"time" "time"
"gioui.org/app" "gioui.org/app"
@ -58,6 +60,10 @@ type (
*tracker.Model *tracker.Model
} }
ShowManual Tracker
AskHelp Tracker
ReportBug Tracker
C = layout.Context C = layout.Context
D = layout.Dimensions D = layout.Dimensions
) )
@ -85,7 +91,6 @@ func NewTracker(model *tracker.Model) *Tracker {
InstrumentEditor: NewInstrumentEditor(model), InstrumentEditor: NewInstrumentEditor(model),
OrderEditor: NewOrderEditor(model), OrderEditor: NewOrderEditor(model),
TrackEditor: NewNoteEditor(model), TrackEditor: NewNoteEditor(model),
SongPanel: NewSongPanel(model),
Zoom: 6, Zoom: 6,
@ -93,6 +98,7 @@ func NewTracker(model *tracker.Model) *Tracker {
filePathString: model.FilePath(), filePathString: model.FilePath(),
} }
t.SongPanel = NewSongPanel(t)
t.KeyNoteMap = MakeKeyboard[key.Name](model.Broker()) t.KeyNoteMap = MakeKeyboard[key.Name](model.Broker())
t.PopupAlert = NewPopupAlert(model.Alerts()) t.PopupAlert = NewPopupAlert(model.Alerts())
var warn error var warn error
@ -352,3 +358,32 @@ func (t *Tracker) layoutTop(gtx layout.Context) layout.Dimensions {
}, },
) )
} }
func (t *Tracker) ShowManual() tracker.Action { return tracker.MakeEnabledAction((*ShowManual)(t)) }
func (t *ShowManual) Do() { (*Tracker)(t).openUrl("https://github.com/vsariola/sointu/wiki") }
func (t *Tracker) AskHelp() tracker.Action { return tracker.MakeEnabledAction((*AskHelp)(t)) }
func (t *AskHelp) Do() {
(*Tracker)(t).openUrl("https://github.com/vsariola/sointu/discussions/categories/help-needed")
}
func (t *Tracker) ReportBug() tracker.Action { return tracker.MakeEnabledAction((*ReportBug)(t)) }
func (t *ReportBug) Do() { (*Tracker)(t).openUrl("https://github.com/vsariola/sointu/issues") }
func (t *Tracker) openUrl(url string) {
var err error
// following https://gist.github.com/hyg/9c4afcd91fe24316cbf0
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform for opening urls %s", runtime.GOOS)
}
if err != nil {
t.Alerts().Add(err.Error(), tracker.Error)
}
}