feat: embed license in executable and add menu item to show it

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-06-23 18:45:13 +03:00
parent 6f1db6b392
commit fb0fa4af92
10 changed files with 48 additions and 19 deletions

View File

@ -5,6 +5,7 @@ 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])
- 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.
@ -337,3 +338,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[i176]: https://github.com/vsariola/sointu/issues/176 [i176]: https://github.com/vsariola/sointu/issues/176
[i186]: https://github.com/vsariola/sointu/issues/186 [i186]: https://github.com/vsariola/sointu/issues/186
[i192]: https://github.com/vsariola/sointu/issues/192 [i192]: https://github.com/vsariola/sointu/issues/192
[i196]: https://github.com/vsariola/sointu/issues/196

View File

@ -1,7 +1,8 @@
MIT License MIT License
Copyright (c) 2018 Dominik Ries Copyright (c) 2018 Dominik Ries
(c) 2020 Veikko Sariola (c) 2020-2025 Veikko Sariola, moitias, qm210, LeStahl,
petersalomonsen, anticore
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,7 @@
package sointu package sointu
import ( import (
_ "embed"
"errors" "errors"
) )
@ -92,6 +93,9 @@ type (
} }
) )
//go:embed LICENSE
var License string
func (s *Score) SongPos(songRow int) SongPos { func (s *Score) SongPos(songRow int) SongPos {
if s.RowsPerPattern == 0 { if s.RowsPerPattern == 0 {
return SongPos{OrderRow: 0, PatternRow: 0} return SongPos{OrderRow: 0, PatternRow: 0}

View File

@ -92,6 +92,7 @@ type (
Item MIDIDevice Item MIDIDevice
*Model *Model
} }
ShowLicense Model
) )
// Action methods // Action methods
@ -123,6 +124,9 @@ func (a Action) Do() {
} }
func (a Action) Enabled() bool { func (a Action) Enabled() bool {
if a.doer == nil {
return false // no doer, not allowed
}
if a.enabler == nil { if a.enabler == nil {
return true // no enabler, always allowed return true // no enabler, always allowed
} }
@ -588,6 +592,9 @@ func (m *ExportFloat) Do() { m.dialog = ExportFloatExplorer }
func (m *Model) ExportInt16() Action { return MakeEnabledAction((*ExportInt16)(m)) } func (m *Model) ExportInt16() Action { return MakeEnabledAction((*ExportInt16)(m)) }
func (m *ExportInt16) Do() { m.dialog = ExportInt16Explorer } func (m *ExportInt16) Do() { m.dialog = ExportInt16Explorer }
func (m *Model) ShowLicense() Action { return MakeEnabledAction((*ShowLicense)(m)) }
func (m *ShowLicense) Do() { m.dialog = License }
func (m *Model) SelectMidiInput(item MIDIDevice) Action { func (m *Model) SelectMidiInput(item MIDIDevice) Action {
return MakeEnabledAction(SelectMidiInput{Item: item, Model: m}) return MakeEnabledAction(SelectMidiInput{Item: item, Model: m})
} }

View File

@ -92,13 +92,15 @@ func (d *Dialog) handleKeys(gtx C) {
for d.BtnCancel.Clicked(gtx) { for d.BtnCancel.Clicked(gtx) {
d.cancel.Do() d.cancel.Do()
} }
if d.alt.Enabled() { if d.alt.Enabled() && d.cancel.Enabled() {
d.handleKeysForButton(gtx, &d.BtnAlt, &d.BtnCancel, &d.BtnOk) d.handleKeysForButton(gtx, &d.BtnAlt, &d.BtnCancel, &d.BtnOk)
d.handleKeysForButton(gtx, &d.BtnCancel, &d.BtnOk, &d.BtnAlt) d.handleKeysForButton(gtx, &d.BtnCancel, &d.BtnOk, &d.BtnAlt)
d.handleKeysForButton(gtx, &d.BtnOk, &d.BtnAlt, &d.BtnCancel) d.handleKeysForButton(gtx, &d.BtnOk, &d.BtnAlt, &d.BtnCancel)
} else { } else if d.ok.Enabled() {
d.handleKeysForButton(gtx, &d.BtnOk, &d.BtnCancel, &d.BtnCancel) d.handleKeysForButton(gtx, &d.BtnOk, &d.BtnCancel, &d.BtnCancel)
d.handleKeysForButton(gtx, &d.BtnCancel, &d.BtnOk, &d.BtnOk) d.handleKeysForButton(gtx, &d.BtnCancel, &d.BtnOk, &d.BtnOk)
} else {
d.handleKeysForButton(gtx, &d.BtnCancel, &d.BtnCancel, &d.BtnCancel)
} }
} }
@ -117,18 +119,17 @@ func (d *DialogStyle) Layout(gtx C) D {
layout.Rigid(Label(d.Theme, &d.Theme.Dialog.Text, d.Text).Layout), layout.Rigid(Label(d.Theme, &d.Theme.Dialog.Text, d.Text).Layout),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layout.E.Layout(gtx, func(gtx C) D { return layout.E.Layout(gtx, func(gtx C) D {
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(120)) fl := layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}
if d.dialog.alt.Enabled() { ok := layout.Rigid(d.OkStyle.Layout)
return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, alt := layout.Rigid(d.AltStyle.Layout)
layout.Rigid(d.OkStyle.Layout), cancel := layout.Rigid(d.CancelStyle.Layout)
layout.Rigid(d.AltStyle.Layout), if d.dialog.alt.Enabled() && d.dialog.cancel.Enabled() {
layout.Rigid(d.CancelStyle.Layout), return fl.Layout(gtx, ok, alt, cancel)
)
} }
return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, if d.dialog.ok.Enabled() {
layout.Rigid(d.OkStyle.Layout), return fl.Layout(gtx, ok, cancel)
layout.Rigid(d.CancelStyle.Layout), }
) return fl.Layout(gtx, cancel)
}) })
}), }),
) )

View File

@ -185,6 +185,8 @@ 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 "ShowLicense":
t.ShowLicense().Do()
// Booleans // Booleans
case "PanicToggle": case "PanicToggle":
t.Panic().Toggle() t.Panic().Toggle()

View File

@ -32,7 +32,6 @@ func (l LabelWidget) Layout(gtx C) D {
textColor := textColorMacro.Stop() textColor := textColorMacro.Stop()
t := widget.Label{ t := widget.Label{
Alignment: l.Alignment, Alignment: l.Alignment,
MaxLines: 1,
} }
if l.ShadowColor.A > 0 { if l.ShadowColor.A > 0 {
shadowColorMacro := op.Record(gtx.Ops) shadowColorMacro := op.Record(gtx.Ops)

View File

@ -307,6 +307,7 @@ type MenuBar struct {
fileMenuItems []MenuItem fileMenuItems []MenuItem
editMenuItems []MenuItem editMenuItems []MenuItem
midiMenuItems []MenuItem midiMenuItems []MenuItem
helpMenuItems []MenuItem
panicHint string panicHint string
PanicBtn *Clickable PanicBtn *Clickable
@ -314,8 +315,8 @@ type MenuBar struct {
func NewMenuBar(model *tracker.Model) *MenuBar { func NewMenuBar(model *tracker.Model) *MenuBar {
ret := &MenuBar{ ret := &MenuBar{
Clickables: make([]Clickable, 3), Clickables: make([]Clickable, 4),
Menus: make([]Menu, 3), Menus: make([]Menu, 4),
PanicBtn: new(Clickable), PanicBtn: new(Clickable),
panicHint: makeHint("Panic", " (%s)", "PanicToggle"), panicHint: makeHint("Panic", " (%s)", "PanicToggle"),
} }
@ -341,6 +342,9 @@ func NewMenuBar(model *tracker.Model) *MenuBar {
Doer: model.SelectMidiInput(input), Doer: model.SelectMidiInput(input),
}) })
} }
ret.helpMenuItems = []MenuItem{
{IconBytes: icons.ActionCopyright, Text: "License", ShortcutText: keyActionMap["ShowLicense"], Doer: model.ShowLicense()},
}
return ret return ret
} }
@ -356,11 +360,12 @@ func (t *MenuBar) Layout(gtx C, tr *Tracker) D {
fileFC := layout.Rigid(tr.layoutMenu(gtx, "File", &t.Clickables[0], &t.Menus[0], unit.Dp(200), t.fileMenuItems...)) fileFC := layout.Rigid(tr.layoutMenu(gtx, "File", &t.Clickables[0], &t.Menus[0], unit.Dp(200), t.fileMenuItems...))
editFC := layout.Rigid(tr.layoutMenu(gtx, "Edit", &t.Clickables[1], &t.Menus[1], unit.Dp(200), t.editMenuItems...)) editFC := layout.Rigid(tr.layoutMenu(gtx, "Edit", &t.Clickables[1], &t.Menus[1], unit.Dp(200), t.editMenuItems...))
midiFC := layout.Rigid(tr.layoutMenu(gtx, "MIDI", &t.Clickables[2], &t.Menus[2], unit.Dp(200), t.midiMenuItems...)) midiFC := layout.Rigid(tr.layoutMenu(gtx, "MIDI", &t.Clickables[2], &t.Menus[2], unit.Dp(200), t.midiMenuItems...))
helpFC := layout.Rigid(tr.layoutMenu(gtx, "?", &t.Clickables[3], &t.Menus[3], unit.Dp(200), t.helpMenuItems...))
panicFC := layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, panicBtn.Layout) }) panicFC := layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, panicBtn.Layout) })
if len(t.midiMenuItems) > 0 { if len(t.midiMenuItems) > 0 {
return flex.Layout(gtx, fileFC, editFC, midiFC, panicFC) return flex.Layout(gtx, fileFC, editFC, midiFC, helpFC, panicFC)
} }
return flex.Layout(gtx, fileFC, editFC, panicFC) return flex.Layout(gtx, fileFC, editFC, helpFC, panicFC)
} }
type PlayBar struct { type PlayBar struct {

View File

@ -20,6 +20,7 @@ import (
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text" "gioui.org/text"
"gioui.org/x/explorer" "gioui.org/x/explorer"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
@ -39,6 +40,7 @@ type (
SaveChangesDialog *Dialog SaveChangesDialog *Dialog
WaveTypeDialog *Dialog WaveTypeDialog *Dialog
LicenseDialog *Dialog
ModalDialog layout.Widget ModalDialog layout.Widget
InstrumentEditor *InstrumentEditor InstrumentEditor *InstrumentEditor
@ -79,6 +81,7 @@ func NewTracker(model *tracker.Model) *Tracker {
SaveChangesDialog: NewDialog(model.SaveSong(), model.DiscardSong(), model.Cancel()), SaveChangesDialog: NewDialog(model.SaveSong(), model.DiscardSong(), model.Cancel()),
WaveTypeDialog: NewDialog(model.ExportInt16(), model.ExportFloat(), model.Cancel()), WaveTypeDialog: NewDialog(model.ExportInt16(), model.ExportFloat(), model.Cancel()),
LicenseDialog: NewDialog(tracker.MakeAction(nil), tracker.MakeAction(nil), model.Cancel()),
InstrumentEditor: NewInstrumentEditor(model), InstrumentEditor: NewInstrumentEditor(model),
OrderEditor: NewOrderEditor(model), OrderEditor: NewOrderEditor(model),
TrackEditor: NewNoteEditor(model), TrackEditor: NewNoteEditor(model),
@ -291,6 +294,10 @@ func (t *Tracker) showDialog(gtx C) {
t.explorerCreateFile(func(wc io.WriteCloser) { t.explorerCreateFile(func(wc io.WriteCloser) {
t.WriteWav(wc, t.Dialog() == tracker.ExportInt16Explorer) t.WriteWav(wc, t.Dialog() == tracker.ExportInt16Explorer)
}, filename) }, filename)
case tracker.License:
dstyle := ConfirmDialog(gtx, t.Theme, t.LicenseDialog, "License", sointu.License)
dstyle.CancelStyle.Text = "Close"
dstyle.Layout(gtx)
} }
} }

View File

@ -155,6 +155,7 @@ const (
ExportInt16Explorer ExportInt16Explorer
QuitChanges QuitChanges
QuitSaveExplorer QuitSaveExplorer
License
) )
const maxUndo = 64 const maxUndo = 64