mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
Merge f0c19c901a
into 8d71cf3ca7
This commit is contained in:
commit
baea1e0a46
@ -35,6 +35,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
the command line tools.
|
the command line tools.
|
||||||
- If a parameter is controlled by a `send`, the slider is now colored differently
|
- If a parameter is controlled by a `send`, the slider is now colored differently
|
||||||
and there's a tooltip over the value to see where it comes from and its amount
|
and there's a tooltip over the value to see where it comes from and its amount
|
||||||
|
([#176][p176])
|
||||||
|
- If a parameter has an invalid value (for now only `port` of a `send`),
|
||||||
|
value is printed grey ([#176][p176])
|
||||||
|
- "Multi-Unit View" to see all units as column next to each other ([#173][i173])
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- We try to honor the MIDI event time stamps, so that the timing between MIDI
|
- We try to honor the MIDI event time stamps, so that the timing between MIDI
|
||||||
@ -75,6 +79,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
matches the compiled output better, as usually compiled intros output sound in
|
matches the compiled output better, as usually compiled intros output sound in
|
||||||
floating point. This might be important if OS sound drivers apply some audio
|
floating point. This might be important if OS sound drivers apply some audio
|
||||||
enhancemenets e.g. compressors to the audio.
|
enhancemenets e.g. compressors to the audio.
|
||||||
|
- Performance improvement: derived model that is useful for the UI is cached
|
||||||
|
on each score/patch change instead of evaluated on each draw ([#176][p176])
|
||||||
|
|
||||||
## [0.4.1]
|
## [0.4.1]
|
||||||
### Added
|
### Added
|
||||||
|
@ -11,21 +11,22 @@ type (
|
|||||||
setValue(bool)
|
setValue(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
Panic Model
|
Panic Model
|
||||||
IsRecording Model
|
IsRecording Model
|
||||||
Playing Model
|
Playing Model
|
||||||
InstrEnlarged Model
|
InstrEnlarged Model
|
||||||
Effect Model
|
Effect Model
|
||||||
TrackMidiIn Model
|
TrackMidiIn Model
|
||||||
CommentExpanded Model
|
CommentExpanded Model
|
||||||
Follow Model
|
Follow Model
|
||||||
UnitSearching Model
|
UnitSearching Model
|
||||||
UnitDisabled Model
|
UnitDisabled Model
|
||||||
LoopToggle Model
|
LoopToggle Model
|
||||||
UniquePatterns Model
|
UniquePatterns Model
|
||||||
Mute Model
|
Mute Model
|
||||||
Solo Model
|
Solo Model
|
||||||
LinkInstrTrack Model
|
LinkInstrTrack Model
|
||||||
|
EnableMultiUnits Model
|
||||||
)
|
)
|
||||||
|
|
||||||
func (v Bool) Toggle() {
|
func (v Bool) Toggle() {
|
||||||
@ -40,21 +41,22 @@ func (v Bool) Set(value bool) {
|
|||||||
|
|
||||||
// Model methods
|
// Model methods
|
||||||
|
|
||||||
func (m *Model) Panic() *Panic { return (*Panic)(m) }
|
func (m *Model) Panic() *Panic { return (*Panic)(m) }
|
||||||
func (m *Model) IsRecording() *IsRecording { return (*IsRecording)(m) }
|
func (m *Model) IsRecording() *IsRecording { return (*IsRecording)(m) }
|
||||||
func (m *Model) Playing() *Playing { return (*Playing)(m) }
|
func (m *Model) Playing() *Playing { return (*Playing)(m) }
|
||||||
func (m *Model) InstrEnlarged() *InstrEnlarged { return (*InstrEnlarged)(m) }
|
func (m *Model) InstrEnlarged() *InstrEnlarged { return (*InstrEnlarged)(m) }
|
||||||
func (m *Model) Effect() *Effect { return (*Effect)(m) }
|
func (m *Model) Effect() *Effect { return (*Effect)(m) }
|
||||||
func (m *Model) TrackMidiIn() *TrackMidiIn { return (*TrackMidiIn)(m) }
|
func (m *Model) TrackMidiIn() *TrackMidiIn { return (*TrackMidiIn)(m) }
|
||||||
func (m *Model) CommentExpanded() *CommentExpanded { return (*CommentExpanded)(m) }
|
func (m *Model) CommentExpanded() *CommentExpanded { return (*CommentExpanded)(m) }
|
||||||
func (m *Model) Follow() *Follow { return (*Follow)(m) }
|
func (m *Model) Follow() *Follow { return (*Follow)(m) }
|
||||||
func (m *Model) UnitSearching() *UnitSearching { return (*UnitSearching)(m) }
|
func (m *Model) UnitSearching() *UnitSearching { return (*UnitSearching)(m) }
|
||||||
func (m *Model) UnitDisabled() *UnitDisabled { return (*UnitDisabled)(m) }
|
func (m *Model) UnitDisabled() *UnitDisabled { return (*UnitDisabled)(m) }
|
||||||
func (m *Model) LoopToggle() *LoopToggle { return (*LoopToggle)(m) }
|
func (m *Model) LoopToggle() *LoopToggle { return (*LoopToggle)(m) }
|
||||||
func (m *Model) UniquePatterns() *UniquePatterns { return (*UniquePatterns)(m) }
|
func (m *Model) UniquePatterns() *UniquePatterns { return (*UniquePatterns)(m) }
|
||||||
func (m *Model) Mute() *Mute { return (*Mute)(m) }
|
func (m *Model) Mute() *Mute { return (*Mute)(m) }
|
||||||
func (m *Model) Solo() *Solo { return (*Solo)(m) }
|
func (m *Model) Solo() *Solo { return (*Solo)(m) }
|
||||||
func (m *Model) LinkInstrTrack() *LinkInstrTrack { return (*LinkInstrTrack)(m) }
|
func (m *Model) LinkInstrTrack() *LinkInstrTrack { return (*LinkInstrTrack)(m) }
|
||||||
|
func (m *Model) EnableMultiUnits() *EnableMultiUnits { return (*EnableMultiUnits)(m) }
|
||||||
|
|
||||||
// Panic methods
|
// Panic methods
|
||||||
|
|
||||||
@ -267,3 +269,10 @@ func (m *LinkInstrTrack) Bool() Bool { return Bool{m} }
|
|||||||
func (m *LinkInstrTrack) Value() bool { return m.linkInstrTrack }
|
func (m *LinkInstrTrack) Value() bool { return m.linkInstrTrack }
|
||||||
func (m *LinkInstrTrack) setValue(val bool) { m.linkInstrTrack = val }
|
func (m *LinkInstrTrack) setValue(val bool) { m.linkInstrTrack = val }
|
||||||
func (m *LinkInstrTrack) Enabled() bool { return true }
|
func (m *LinkInstrTrack) Enabled() bool { return true }
|
||||||
|
|
||||||
|
// EnableMultiUnits methods
|
||||||
|
|
||||||
|
func (m *EnableMultiUnits) Bool() Bool { return Bool{m} }
|
||||||
|
func (m *EnableMultiUnits) Value() bool { return m.enableMultiUnits }
|
||||||
|
func (m *EnableMultiUnits) setValue(val bool) { m.enableMultiUnits = val }
|
||||||
|
func (m *EnableMultiUnits) Enabled() bool { return true }
|
||||||
|
@ -168,6 +168,9 @@ type Clickable struct {
|
|||||||
history []widget.Press
|
history []widget.Press
|
||||||
|
|
||||||
requestClicks int
|
requestClicks int
|
||||||
|
|
||||||
|
// optional callback for custom interactions
|
||||||
|
OnClick func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click executes a simple programmatic click.
|
// Click executes a simple programmatic click.
|
||||||
@ -177,7 +180,11 @@ func (b *Clickable) Click() {
|
|||||||
|
|
||||||
// Clicked calls Update and reports whether a click was registered.
|
// Clicked calls Update and reports whether a click was registered.
|
||||||
func (b *Clickable) Clicked(gtx layout.Context) bool {
|
func (b *Clickable) Clicked(gtx layout.Context) bool {
|
||||||
return b.clicked(b, gtx)
|
clicked := b.clicked(b, gtx)
|
||||||
|
if clicked && b.OnClick != nil {
|
||||||
|
b.OnClick()
|
||||||
|
}
|
||||||
|
return clicked
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Clickable) clicked(t event.Tag, gtx layout.Context) bool {
|
func (b *Clickable) clicked(t event.Tag, gtx layout.Context) bool {
|
||||||
|
@ -31,6 +31,7 @@ type DragList struct {
|
|||||||
swapped bool
|
swapped bool
|
||||||
focused bool
|
focused bool
|
||||||
requestFocus bool
|
requestFocus bool
|
||||||
|
onSelect func(index int)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilledDragListStyle struct {
|
type FilledDragListStyle struct {
|
||||||
@ -185,6 +186,9 @@ func (s FilledDragListStyle) Layout(gtx C) D {
|
|||||||
if !e.Modifiers.Contain(key.ModShift) {
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
s.dragList.TrackerList.SetSelected2(index)
|
s.dragList.TrackerList.SetSelected2(index)
|
||||||
}
|
}
|
||||||
|
if s.dragList.onSelect != nil {
|
||||||
|
s.dragList.onSelect(index)
|
||||||
|
}
|
||||||
gtx.Execute(key.FocusCmd{Tag: s.dragList})
|
gtx.Execute(key.FocusCmd{Tag: s.dragList})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,11 @@ func NewInstrumentEditor(model *tracker.Model) *InstrumentEditor {
|
|||||||
ret.linkDisabledHint = makeHint("Instrument-Track\nlinking disabled", "\n(%s)", "LinkInstrTrackToggle")
|
ret.linkDisabledHint = makeHint("Instrument-Track\nlinking disabled", "\n(%s)", "LinkInstrTrackToggle")
|
||||||
ret.linkEnabledHint = makeHint("Instrument-Track\nlinking enabled", "\n(%s)", "LinkInstrTrackToggle")
|
ret.linkEnabledHint = makeHint("Instrument-Track\nlinking enabled", "\n(%s)", "LinkInstrTrackToggle")
|
||||||
ret.splitInstrumentHint = makeHint("Split instrument", " (%s)", "SplitInstrument")
|
ret.splitInstrumentHint = makeHint("Split instrument", " (%s)", "SplitInstrument")
|
||||||
|
ret.unitDragList.onSelect = func(index int) {
|
||||||
|
if model.EnableMultiUnits().Value() {
|
||||||
|
ret.unitEditor.ScrollToUnit(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +122,7 @@ func (ie *InstrumentEditor) Focused() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ie *InstrumentEditor) childFocused(gtx C) bool {
|
func (ie *InstrumentEditor) childFocused(gtx C) bool {
|
||||||
return ie.unitEditor.sliderList.Focused() ||
|
return ie.unitEditor.sliderColumns.Focused() ||
|
||||||
ie.instrumentDragList.Focused() || gtx.Source.Focused(ie.commentEditor) || gtx.Source.Focused(ie.nameEditor) || gtx.Source.Focused(ie.searchEditor) ||
|
ie.instrumentDragList.Focused() || gtx.Source.Focused(ie.commentEditor) || gtx.Source.Focused(ie.nameEditor) || gtx.Source.Focused(ie.searchEditor) ||
|
||||||
gtx.Source.Focused(ie.addUnitBtn.Clickable) || gtx.Source.Focused(ie.commentExpandBtn.Clickable) || gtx.Source.Focused(ie.presetMenuBtn.Clickable) ||
|
gtx.Source.Focused(ie.addUnitBtn.Clickable) || gtx.Source.Focused(ie.commentExpandBtn.Clickable) || gtx.Source.Focused(ie.presetMenuBtn.Clickable) ||
|
||||||
gtx.Source.Focused(ie.deleteInstrumentBtn.Clickable) || gtx.Source.Focused(ie.copyInstrumentBtn.Clickable)
|
gtx.Source.Focused(ie.deleteInstrumentBtn.Clickable) || gtx.Source.Focused(ie.copyInstrumentBtn.Clickable)
|
||||||
@ -277,7 +282,7 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
|
|||||||
element := func(gtx C, i int) D {
|
element := func(gtx C, i int) D {
|
||||||
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36))
|
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36))
|
||||||
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(30))
|
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(30))
|
||||||
grabhandle := LabelStyle{Text: strconv.Itoa(i + 1), ShadeColor: black, Color: mediumEmphasisTextColor, FontSize: unit.Sp(10), Alignment: layout.Center, Shaper: t.Theme.Shaper}
|
grabhandle := LabelStyle{Text: strconv.Itoa(i + 1), ShadeColor: black, Color: mediumEmphasisTextColor, FontSize: unit.Sp(10), Direction: layout.Center, Shaper: t.Theme.Shaper}
|
||||||
label := func(gtx C) D {
|
label := func(gtx C) D {
|
||||||
name, level, mute, ok := (*tracker.Instruments)(t.Model).Item(i)
|
name, level, mute, ok := (*tracker.Instruments)(t.Model).Item(i)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -375,9 +380,10 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
|
|||||||
addUnitBtnStyle.IconButtonStyle.Inset = layout.UniformInset(unit.Dp(4))
|
addUnitBtnStyle.IconButtonStyle.Inset = layout.UniformInset(unit.Dp(4))
|
||||||
|
|
||||||
var units [256]tracker.UnitListItem
|
var units [256]tracker.UnitListItem
|
||||||
for i, item := range (*tracker.Units)(t.Model).Iterate {
|
for i, item := range t.Model.Units().Iterate {
|
||||||
if i >= 256 {
|
if i >= 256 {
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
units[i] = item
|
units[i] = item
|
||||||
}
|
}
|
||||||
@ -386,7 +392,7 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
|
|||||||
if ie.searchEditor.requestFocus {
|
if ie.searchEditor.requestFocus {
|
||||||
// for now, only the searchEditor has its requestFocus flag
|
// for now, only the searchEditor has its requestFocus flag
|
||||||
ie.searchEditor.requestFocus = false
|
ie.searchEditor.requestFocus = false
|
||||||
gtx.Execute(key.FocusCmd{Tag: &ie.searchEditor.Editor})
|
gtx.Execute(key.FocusCmd{Tag: ie.searchEditor.Editor})
|
||||||
}
|
}
|
||||||
|
|
||||||
element := func(gtx C, i int) D {
|
element := func(gtx C, i int) D {
|
||||||
@ -486,7 +492,7 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
|
|||||||
case key.NameEscape:
|
case key.NameEscape:
|
||||||
ie.instrumentDragList.Focus()
|
ie.instrumentDragList.Focus()
|
||||||
case key.NameRightArrow:
|
case key.NameRightArrow:
|
||||||
ie.unitEditor.sliderList.Focus()
|
ie.unitEditor.sliderColumns.Focus()
|
||||||
case key.NameDeleteBackward:
|
case key.NameDeleteBackward:
|
||||||
t.Units().SetSelectedType("")
|
t.Units().SetSelectedType("")
|
||||||
t.UnitSearching().Bool().Set(true)
|
t.UnitSearching().Bool().Set(true)
|
||||||
|
@ -285,12 +285,12 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
|||||||
case "FocusPrev":
|
case "FocusPrev":
|
||||||
switch {
|
switch {
|
||||||
case t.OrderEditor.scrollTable.Focused():
|
case t.OrderEditor.scrollTable.Focused():
|
||||||
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
t.InstrumentEditor.unitEditor.sliderColumns.Focus()
|
||||||
case t.TrackEditor.scrollTable.Focused():
|
case t.TrackEditor.scrollTable.Focused():
|
||||||
t.OrderEditor.scrollTable.Focus()
|
t.OrderEditor.scrollTable.Focus()
|
||||||
case t.InstrumentEditor.Focused():
|
case t.InstrumentEditor.Focused():
|
||||||
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
||||||
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
t.InstrumentEditor.unitEditor.sliderColumns.Focus()
|
||||||
} else {
|
} else {
|
||||||
t.TrackEditor.scrollTable.Focus()
|
t.TrackEditor.scrollTable.Focus()
|
||||||
}
|
}
|
||||||
@ -304,7 +304,7 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
|||||||
case t.TrackEditor.scrollTable.Focused():
|
case t.TrackEditor.scrollTable.Focused():
|
||||||
t.InstrumentEditor.Focus()
|
t.InstrumentEditor.Focus()
|
||||||
case t.InstrumentEditor.Focused():
|
case t.InstrumentEditor.Focused():
|
||||||
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
t.InstrumentEditor.unitEditor.sliderColumns.Focus()
|
||||||
default:
|
default:
|
||||||
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
||||||
t.InstrumentEditor.Focus()
|
t.InstrumentEditor.Focus()
|
||||||
|
@ -17,14 +17,14 @@ type LabelStyle struct {
|
|||||||
Text string
|
Text string
|
||||||
Color color.NRGBA
|
Color color.NRGBA
|
||||||
ShadeColor color.NRGBA
|
ShadeColor color.NRGBA
|
||||||
Alignment layout.Direction
|
Direction layout.Direction
|
||||||
Font font.Font
|
Font font.Font
|
||||||
FontSize unit.Sp
|
FontSize unit.Sp
|
||||||
Shaper *text.Shaper
|
Shaper *text.Shaper
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
|
func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||||
return l.Alignment.Layout(gtx, func(gtx C) D {
|
return l.Direction.Layout(gtx, func(gtx C) D {
|
||||||
gtx.Constraints.Min = image.Point{}
|
gtx.Constraints.Min = image.Point{}
|
||||||
paint.ColorOp{Color: l.ShadeColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: l.ShadeColor}.Add(gtx.Ops)
|
||||||
offs := op.Offset(image.Pt(2, 2)).Push(gtx.Ops)
|
offs := op.Offset(image.Pt(2, 2)).Push(gtx.Ops)
|
||||||
@ -46,5 +46,13 @@ func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Label(str string, color color.NRGBA, shaper *text.Shaper) layout.Widget {
|
func Label(str string, color color.NRGBA, shaper *text.Shaper) layout.Widget {
|
||||||
return LabelStyle{Text: str, Color: color, ShadeColor: black, Font: labelDefaultFont, FontSize: labelDefaultFontSize, Alignment: layout.W, Shaper: shaper}.Layout
|
return LabelStyle{
|
||||||
|
Text: str,
|
||||||
|
Color: color,
|
||||||
|
ShadeColor: black,
|
||||||
|
Font: labelDefaultFont,
|
||||||
|
FontSize: labelDefaultFontSize,
|
||||||
|
Direction: layout.W,
|
||||||
|
Shaper: shaper,
|
||||||
|
}.Layout
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
|||||||
h := gtx.Dp(trackColTitleHeight)
|
h := gtx.Dp(trackColTitleHeight)
|
||||||
gtx.Constraints = layout.Exact(image.Pt(pxWidth, h))
|
gtx.Constraints = layout.Exact(image.Pt(pxWidth, h))
|
||||||
LabelStyle{
|
LabelStyle{
|
||||||
Alignment: layout.N,
|
Direction: layout.N,
|
||||||
Text: t.Model.TrackTitle(i),
|
Text: t.Model.TrackTitle(i),
|
||||||
FontSize: unit.Sp(12),
|
FontSize: unit.Sp(12),
|
||||||
Color: mediumEmphasisTextColor,
|
Color: mediumEmphasisTextColor,
|
||||||
|
@ -69,7 +69,7 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
|
|||||||
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(h)})).Push(gtx.Ops).Pop()
|
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(h)})).Push(gtx.Ops).Pop()
|
||||||
gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
|
gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
|
||||||
LabelStyle{
|
LabelStyle{
|
||||||
Alignment: layout.NW,
|
Direction: layout.NW,
|
||||||
Text: t.Model.TrackTitle(i),
|
Text: t.Model.TrackTitle(i),
|
||||||
FontSize: unit.Sp(12),
|
FontSize: unit.Sp(12),
|
||||||
Color: mediumEmphasisTextColor,
|
Color: mediumEmphasisTextColor,
|
||||||
|
@ -55,7 +55,7 @@ func (a *PopupAlert) Layout(gtx C) D {
|
|||||||
}.Op())
|
}.Op())
|
||||||
return D{Size: gtx.Constraints.Min}
|
return D{Size: gtx.Constraints.Min}
|
||||||
}
|
}
|
||||||
labelStyle := LabelStyle{Text: alert.Message, Color: textColor, ShadeColor: shadeColor, Font: labelDefaultFont, Alignment: layout.Center, FontSize: unit.Sp(16), Shaper: a.shaper}
|
labelStyle := LabelStyle{Text: alert.Message, Color: textColor, ShadeColor: shadeColor, Font: labelDefaultFont, Direction: layout.Center, FontSize: unit.Sp(16), Shaper: a.shaper}
|
||||||
alertMargin.Layout(gtx, func(gtx C) D {
|
alertMargin.Layout(gtx, func(gtx C) D {
|
||||||
return layout.S.Layout(gtx, func(gtx C) D {
|
return layout.S.Layout(gtx, func(gtx C) D {
|
||||||
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
||||||
|
@ -25,46 +25,61 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UnitEditor struct {
|
type UnitEditor struct {
|
||||||
sliderList *DragList
|
sliderRows []*DragList
|
||||||
|
sliderColumns *DragList
|
||||||
searchList *DragList
|
searchList *DragList
|
||||||
Parameters []*ParameterWidget
|
Parameters [][]*ParameterWidget
|
||||||
DeleteUnitBtn *ActionClickable
|
DeleteUnitBtn *ActionClickable
|
||||||
CopyUnitBtn *TipClickable
|
CopyUnitBtn *TipClickable
|
||||||
ClearUnitBtn *ActionClickable
|
ClearUnitBtn *ActionClickable
|
||||||
DisableUnitBtn *BoolClickable
|
DisableUnitBtn *BoolClickable
|
||||||
SelectTypeBtn *widget.Clickable
|
SelectTypeBtn *widget.Clickable
|
||||||
|
MultiUnitsBtn *BoolClickable
|
||||||
commentEditor *Editor
|
commentEditor *Editor
|
||||||
caser cases.Caser
|
caser cases.Caser
|
||||||
|
|
||||||
copyHint string
|
copyHint string
|
||||||
disableUnitHint string
|
disableUnitHint string
|
||||||
enableUnitHint string
|
enableUnitHint string
|
||||||
|
multiUnitsHint string
|
||||||
|
|
||||||
|
totalWidthForUnit map[int]int
|
||||||
|
paramWidthForUnit map[int]int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUnitEditor(m *tracker.Model) *UnitEditor {
|
func NewUnitEditor(m *tracker.Model) *UnitEditor {
|
||||||
ret := &UnitEditor{
|
ret := &UnitEditor{
|
||||||
DeleteUnitBtn: NewActionClickable(m.DeleteUnit()),
|
DeleteUnitBtn: NewActionClickable(m.DeleteUnit()),
|
||||||
ClearUnitBtn: NewActionClickable(m.ClearUnit()),
|
ClearUnitBtn: NewActionClickable(m.ClearUnit()),
|
||||||
DisableUnitBtn: NewBoolClickable(m.UnitDisabled().Bool()),
|
DisableUnitBtn: NewBoolClickable(m.UnitDisabled().Bool()),
|
||||||
CopyUnitBtn: new(TipClickable),
|
MultiUnitsBtn: NewBoolClickable(m.EnableMultiUnits().Bool()),
|
||||||
SelectTypeBtn: new(widget.Clickable),
|
CopyUnitBtn: new(TipClickable),
|
||||||
commentEditor: NewEditor(widget.Editor{SingleLine: true, Submit: true}),
|
SelectTypeBtn: new(widget.Clickable),
|
||||||
sliderList: NewDragList(m.Params().List(), layout.Vertical),
|
commentEditor: NewEditor(widget.Editor{SingleLine: true, Submit: true}),
|
||||||
searchList: NewDragList(m.SearchResults().List(), layout.Vertical),
|
sliderColumns: NewDragList(m.Units().List(), layout.Horizontal),
|
||||||
|
searchList: NewDragList(m.SearchResults().List(), layout.Vertical),
|
||||||
|
totalWidthForUnit: make(map[int]int),
|
||||||
|
paramWidthForUnit: make(map[int]int),
|
||||||
}
|
}
|
||||||
ret.caser = cases.Title(language.English)
|
ret.caser = cases.Title(language.English)
|
||||||
ret.copyHint = makeHint("Copy unit", " (%s)", "Copy")
|
ret.copyHint = makeHint("Copy unit", " (%s)", "Copy")
|
||||||
ret.disableUnitHint = makeHint("Disable unit", " (%s)", "UnitDisabledToggle")
|
ret.disableUnitHint = makeHint("Disable unit", " (%s)", "UnitDisabledToggle")
|
||||||
ret.enableUnitHint = makeHint("Enable unit", " (%s)", "UnitDisabledToggle")
|
ret.enableUnitHint = makeHint("Enable unit", " (%s)", "UnitDisabledToggle")
|
||||||
|
ret.multiUnitsHint = "Toggle Multi-Unit View"
|
||||||
|
|
||||||
|
ret.MultiUnitsBtn.Clickable.OnClick = func() {
|
||||||
|
ret.ScrollToUnit(m.Units().Selected())
|
||||||
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *UnitEditor) Layout(gtx C, t *Tracker) D {
|
func (pe *UnitEditor) Layout(gtx C, t *Tracker) D {
|
||||||
for {
|
for {
|
||||||
e, ok := gtx.Event(
|
e, ok := gtx.Event(
|
||||||
key.Filter{Focus: pe.sliderList, Name: key.NameLeftArrow, Optional: key.ModShift},
|
key.Filter{Focus: pe.sliderColumns, Name: key.NameLeftArrow, Optional: key.ModShift},
|
||||||
key.Filter{Focus: pe.sliderList, Name: key.NameRightArrow, Optional: key.ModShift},
|
key.Filter{Focus: pe.sliderColumns, Name: key.NameRightArrow, Optional: key.ModShift},
|
||||||
key.Filter{Focus: pe.sliderList, Name: key.NameEscape},
|
key.Filter{Focus: pe.sliderColumns, Name: key.NameEscape},
|
||||||
)
|
)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
@ -78,9 +93,9 @@ func (pe *UnitEditor) Layout(gtx C, t *Tracker) D {
|
|||||||
}
|
}
|
||||||
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
||||||
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
|
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
|
||||||
editorFunc := pe.layoutSliders
|
editorFunc := pe.layoutColumns
|
||||||
|
|
||||||
if t.UnitSearching().Value() || pe.sliderList.TrackerList.Count() == 0 {
|
if t.UnitSearching().Value() || pe.sliderColumns.TrackerList.Count() == 0 {
|
||||||
editorFunc = pe.layoutUnitTypeChooser
|
editorFunc = pe.layoutUnitTypeChooser
|
||||||
}
|
}
|
||||||
return Surface{Gray: 24, Focus: t.InstrumentEditor.wasFocused}.Layout(gtx, func(gtx C) D {
|
return Surface{Gray: 24, Focus: t.InstrumentEditor.wasFocused}.Layout(gtx, func(gtx C) D {
|
||||||
@ -95,35 +110,85 @@ func (pe *UnitEditor) Layout(gtx C, t *Tracker) D {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *UnitEditor) layoutSliders(gtx C, t *Tracker) D {
|
func (pe *UnitEditor) layoutColumns(gtx C, t *Tracker) D {
|
||||||
numItems := pe.sliderList.TrackerList.Count()
|
numUnits := pe.sliderColumns.TrackerList.Count()
|
||||||
|
for len(pe.Parameters) < numUnits {
|
||||||
for len(pe.Parameters) < numItems {
|
pe.Parameters = append(pe.Parameters, []*ParameterWidget{})
|
||||||
pe.Parameters = append(pe.Parameters, new(ParameterWidget))
|
}
|
||||||
|
for u := len(pe.sliderRows); u < numUnits; u++ {
|
||||||
|
paramList := NewDragList(t.ParamsForUnit(u).List(), layout.Vertical)
|
||||||
|
pe.sliderRows = append(pe.sliderRows, paramList)
|
||||||
}
|
}
|
||||||
|
|
||||||
index := 0
|
if !t.Model.EnableMultiUnits().Value() {
|
||||||
for param := range t.Model.Params().Iterate {
|
return pe.layoutSliderColumn(gtx, t, t.Model.Units().Selected(), false)
|
||||||
pe.Parameters[index].Parameter = param
|
|
||||||
index++
|
|
||||||
}
|
}
|
||||||
element := func(gtx C, index int) D {
|
|
||||||
if index < 0 || index >= numItems {
|
column := func(gtx C, index int) D {
|
||||||
|
if index < 0 || index > numUnits {
|
||||||
return D{}
|
return D{}
|
||||||
}
|
}
|
||||||
paramStyle := t.ParamStyle(t.Theme, pe.Parameters[index])
|
dims := pe.layoutSliderColumn(gtx, t, index, true)
|
||||||
paramStyle.Focus = pe.sliderList.TrackerList.Selected() == index
|
return D{Size: image.Pt(dims.Size.X, gtx.Constraints.Max.Y)}
|
||||||
dims := paramStyle.Layout(gtx)
|
|
||||||
return D{Size: image.Pt(gtx.Constraints.Max.X, dims.Size.Y)}
|
|
||||||
}
|
}
|
||||||
|
fdl := FilledDragList(t.Theme, pe.sliderColumns, column, nil)
|
||||||
fdl := FilledDragList(t.Theme, pe.sliderList, element, nil)
|
|
||||||
dims := fdl.Layout(gtx)
|
dims := fdl.Layout(gtx)
|
||||||
gtx.Constraints = layout.Exact(dims.Size)
|
gtx.Constraints = layout.Exact(dims.Size)
|
||||||
fdl.LayoutScrollBar(gtx)
|
fdl.LayoutScrollBar(gtx)
|
||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pe *UnitEditor) layoutSliderColumn(gtx C, t *Tracker, u int, multiUnits bool) D {
|
||||||
|
numParams := 0
|
||||||
|
for param := range t.Model.ParamsForUnit(u).Iterate {
|
||||||
|
for len(pe.Parameters[u]) < numParams+1 {
|
||||||
|
pe.Parameters[u] = append(pe.Parameters[u], new(ParameterWidget))
|
||||||
|
}
|
||||||
|
|
||||||
|
pe.Parameters[u][numParams].Parameter = param
|
||||||
|
numParams++
|
||||||
|
}
|
||||||
|
|
||||||
|
unitId := t.Model.Units().CurrentInstrumentUnitAt(u).ID
|
||||||
|
columnWidth := gtx.Constraints.Max.X
|
||||||
|
if multiUnits {
|
||||||
|
columnWidth = pe.totalWidthForUnit[unitId]
|
||||||
|
}
|
||||||
|
|
||||||
|
element := func(gtx C, index int) D {
|
||||||
|
if index < 0 || index >= numParams {
|
||||||
|
return D{}
|
||||||
|
}
|
||||||
|
paramStyle := t.ParamStyle(t.Theme, pe.Parameters[u][index])
|
||||||
|
paramStyle.Focus = pe.sliderRows[u].TrackerList.Selected() == index
|
||||||
|
dims := paramStyle.Layout(gtx, pe.paramWidthForUnit, unitId)
|
||||||
|
if multiUnits && pe.totalWidthForUnit[unitId] < dims.Size.X {
|
||||||
|
pe.totalWidthForUnit[unitId] = dims.Size.X
|
||||||
|
}
|
||||||
|
return D{Size: image.Pt(columnWidth, dims.Size.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
fdl := FilledDragList(t.Theme, pe.sliderRows[u], element, nil)
|
||||||
|
var dims D
|
||||||
|
if multiUnits {
|
||||||
|
name := buildUnitName(t.Model.Units().CurrentInstrumentUnitAt(u))
|
||||||
|
dims = layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Flexed(1, fdl.Layout),
|
||||||
|
layout.Rigid(func(gtx C) D {
|
||||||
|
gtx.Constraints.Min.X = columnWidth
|
||||||
|
gtx.Constraints.Min.Y = gtx.Sp(t.Theme.TextSize * 3)
|
||||||
|
return layout.Center.Layout(gtx, Label(name, primaryColor, t.Theme.Shaper))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
dims.Size.Y -= gtx.Dp(fdl.ScrollBarWidth)
|
||||||
|
} else {
|
||||||
|
dims = fdl.Layout(gtx)
|
||||||
|
}
|
||||||
|
gtx.Constraints = layout.Exact(dims.Size)
|
||||||
|
fdl.LayoutScrollBar(gtx)
|
||||||
|
return D{Size: image.Pt(columnWidth, dims.Size.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
|
func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
|
||||||
for pe.CopyUnitBtn.Clickable.Clicked(gtx) {
|
for pe.CopyUnitBtn.Clickable.Clicked(gtx) {
|
||||||
if contents, ok := t.Units().List().CopyElements(); ok {
|
if contents, ok := t.Units().List().CopyElements(); ok {
|
||||||
@ -134,6 +199,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
|
|||||||
copyUnitBtnStyle := TipIcon(t.Theme, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint)
|
copyUnitBtnStyle := TipIcon(t.Theme, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint)
|
||||||
deleteUnitBtnStyle := ActionIcon(gtx, t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
|
deleteUnitBtnStyle := ActionIcon(gtx, t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
|
||||||
disableUnitBtnStyle := ToggleIcon(gtx, t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint)
|
disableUnitBtnStyle := ToggleIcon(gtx, t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint)
|
||||||
|
multiUnitsBtnStyle := ToggleIcon(gtx, t.Theme, pe.MultiUnitsBtn, icons.ActionViewWeek, icons.ActionViewWeek, pe.multiUnitsHint, pe.multiUnitsHint)
|
||||||
text := t.Units().SelectedType()
|
text := t.Units().SelectedType()
|
||||||
if text == "" {
|
if text == "" {
|
||||||
text = "Choose unit type"
|
text = "Choose unit type"
|
||||||
@ -172,6 +238,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
|
|||||||
s.Set(pe.commentEditor.Text())
|
s.Set(pe.commentEditor.Text())
|
||||||
return ret
|
return ret
|
||||||
}),
|
}),
|
||||||
|
layout.Rigid(multiUnitsBtnStyle.Layout),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +277,7 @@ func (pe *UnitEditor) command(e key.Event, t *Tracker) {
|
|||||||
if sel == nil {
|
if sel == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
i := (&tracker.Int{IntData: sel})
|
i := &tracker.Int{IntData: sel}
|
||||||
if e.Modifiers.Contain(key.ModShift) {
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
i.Set(i.Value() - sel.LargeStep())
|
i.Set(i.Value() - sel.LargeStep())
|
||||||
} else {
|
} else {
|
||||||
@ -221,7 +288,7 @@ func (pe *UnitEditor) command(e key.Event, t *Tracker) {
|
|||||||
if sel == nil {
|
if sel == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
i := (&tracker.Int{IntData: sel})
|
i := &tracker.Int{IntData: sel}
|
||||||
if e.Modifiers.Contain(key.ModShift) {
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
i.Set(i.Value() + sel.LargeStep())
|
i.Set(i.Value() + sel.LargeStep())
|
||||||
} else {
|
} else {
|
||||||
@ -267,13 +334,25 @@ func (t *Tracker) ParamStyle(th *material.Theme, paramWidget *ParameterWidget) P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ParameterStyle) Layout(gtx C) D {
|
func spacer(px int) layout.FlexChild {
|
||||||
isSendTarget, info := p.tryDerivedParameterInfo()
|
return layout.Rigid(func(gtx C) D {
|
||||||
|
return D{Size: image.Pt(px, px)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ParameterStyle) Layout(gtx C, paramWidthMap map[int]int, unitId int) D {
|
||||||
|
isSendTarget, info := p.tryDerivedParameterInfo(unitId)
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
spacer(24),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(110))
|
dims := layout.E.Layout(gtx, Label(p.w.Parameter.Name(), white, p.tracker.Theme.Shaper))
|
||||||
return layout.E.Layout(gtx, Label(p.w.Parameter.Name(), white, p.tracker.Theme.Shaper))
|
if paramWidthMap[unitId] < dims.Size.X {
|
||||||
|
paramWidthMap[unitId] = dims.Size.X
|
||||||
|
}
|
||||||
|
dims.Size.X = paramWidthMap[unitId]
|
||||||
|
return dims
|
||||||
}),
|
}),
|
||||||
|
spacer(8),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
switch p.w.Parameter.Type() {
|
switch p.w.Parameter.Type() {
|
||||||
case tracker.IntegerParameter:
|
case tracker.IntegerParameter:
|
||||||
@ -371,6 +450,7 @@ func (p ParameterStyle) Layout(gtx C) D {
|
|||||||
}
|
}
|
||||||
return D{}
|
return D{}
|
||||||
}),
|
}),
|
||||||
|
spacer(8),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
if p.w.Parameter.Type() != tracker.IDParameter {
|
if p.w.Parameter.Type() != tracker.IDParameter {
|
||||||
color := white
|
color := white
|
||||||
@ -387,22 +467,31 @@ func (p ParameterStyle) Layout(gtx C) D {
|
|||||||
}
|
}
|
||||||
return D{}
|
return D{}
|
||||||
}),
|
}),
|
||||||
|
spacer(24),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildUnitLabel(index int, u sointu.Unit) string {
|
func buildUnitLabel(index int, u sointu.Unit) string {
|
||||||
text := u.Type
|
return fmt.Sprintf("%d: %s", index, buildUnitName(u))
|
||||||
if u.Comment != "" {
|
|
||||||
text = fmt.Sprintf("%s \"%s\"", text, u.Comment)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d: %s", index, text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ParameterStyle) tryDerivedParameterInfo() (isSendTarget bool, sendInfo string) {
|
func buildUnitName(u sointu.Unit) string {
|
||||||
|
if u.Comment != "" {
|
||||||
|
return fmt.Sprintf("%s \"%s\"", u.Type, u.Comment)
|
||||||
|
}
|
||||||
|
return u.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ParameterStyle) tryDerivedParameterInfo(unitId int) (isSendTarget bool, sendInfo string) {
|
||||||
param, ok := (p.w.Parameter).(tracker.NamedParameter)
|
param, ok := (p.w.Parameter).(tracker.NamedParameter)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
isSendTarget, sendInfo, _ = p.tracker.ParameterInfo(param.Unit().ID, param.Name())
|
isSendTarget, sendInfo, _ = p.tracker.ParameterInfo(unitId, param.Name())
|
||||||
return isSendTarget, sendInfo
|
return isSendTarget, sendInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pe *UnitEditor) ScrollToUnit(index int) {
|
||||||
|
pe.sliderColumns.List.Position.First = index
|
||||||
|
pe.sliderColumns.List.Position.Offset = 0
|
||||||
|
}
|
||||||
|
167
tracker/list.go
167
tracker/list.go
@ -35,22 +35,12 @@ type (
|
|||||||
unmarshal([]byte) (r Range, err error)
|
unmarshal([]byte) (r Range, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
UnitListItem struct {
|
|
||||||
Type, Comment string
|
|
||||||
Disabled bool
|
|
||||||
StackNeed, StackBefore, StackAfter int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range is used to represent a range [Start,End) of integers
|
// Range is used to represent a range [Start,End) of integers
|
||||||
Range struct {
|
Range struct {
|
||||||
Start, End int
|
Start, End int
|
||||||
}
|
}
|
||||||
|
|
||||||
UnitYieldFunc func(index int, item UnitListItem) (ok bool)
|
|
||||||
UnitSearchYieldFunc func(index int, item string) (ok bool)
|
|
||||||
|
|
||||||
Instruments Model // Instruments is a list of instruments, implementing ListData & MutableListData interfaces
|
Instruments Model // Instruments is a list of instruments, implementing ListData & MutableListData interfaces
|
||||||
Units Model // Units is a list of all the units in the selected instrument, implementing ListData & MutableListData interfaces
|
|
||||||
Tracks Model // Tracks is a list of all the tracks, implementing ListData & MutableListData interfaces
|
Tracks Model // Tracks is a list of all the tracks, implementing ListData & MutableListData interfaces
|
||||||
OrderRows Model // OrderRows is a list of all the order rows, implementing ListData & MutableListData interfaces
|
OrderRows Model // OrderRows is a list of all the order rows, implementing ListData & MutableListData interfaces
|
||||||
NoteRows Model // NoteRows is a list of all the note rows, implementing ListData & MutableListData interfaces
|
NoteRows Model // NoteRows is a list of all the note rows, implementing ListData & MutableListData interfaces
|
||||||
@ -61,7 +51,6 @@ type (
|
|||||||
// Model methods
|
// Model methods
|
||||||
|
|
||||||
func (m *Model) Instruments() *Instruments { return (*Instruments)(m) }
|
func (m *Model) Instruments() *Instruments { return (*Instruments)(m) }
|
||||||
func (m *Model) Units() *Units { return (*Units)(m) }
|
|
||||||
func (m *Model) Tracks() *Tracks { return (*Tracks)(m) }
|
func (m *Model) Tracks() *Tracks { return (*Tracks)(m) }
|
||||||
func (m *Model) OrderRows() *OrderRows { return (*OrderRows)(m) }
|
func (m *Model) OrderRows() *OrderRows { return (*OrderRows)(m) }
|
||||||
func (m *Model) NoteRows() *NoteRows { return (*NoteRows)(m) }
|
func (m *Model) NoteRows() *NoteRows { return (*NoteRows)(m) }
|
||||||
@ -257,162 +246,6 @@ func (m *Instruments) unmarshal(data []byte) (r Range, err error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Units methods
|
|
||||||
|
|
||||||
func (v *Units) List() List {
|
|
||||||
return List{v}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Units) SelectedType() string {
|
|
||||||
if m.d.InstrIndex < 0 ||
|
|
||||||
m.d.InstrIndex >= len(m.d.Song.Patch) ||
|
|
||||||
m.d.UnitIndex < 0 ||
|
|
||||||
m.d.UnitIndex >= len(m.d.Song.Patch[m.d.InstrIndex].Units) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Units) SetSelectedType(t string) {
|
|
||||||
if m.d.InstrIndex < 0 ||
|
|
||||||
m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m.d.UnitIndex < 0 {
|
|
||||||
m.d.UnitIndex = 0
|
|
||||||
}
|
|
||||||
for len(m.d.Song.Patch[m.d.InstrIndex].Units) <= m.d.UnitIndex {
|
|
||||||
m.d.Song.Patch[m.d.InstrIndex].Units = append(m.d.Song.Patch[m.d.InstrIndex].Units, sointu.Unit{})
|
|
||||||
}
|
|
||||||
unit, ok := defaultUnits[t]
|
|
||||||
if !ok { // if the type is invalid, we just set it to empty unit
|
|
||||||
unit = sointu.Unit{Parameters: make(map[string]int)}
|
|
||||||
} else {
|
|
||||||
unit = unit.Copy()
|
|
||||||
}
|
|
||||||
oldUnit := m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex]
|
|
||||||
if oldUnit.Type == unit.Type {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer m.change("SetSelectedType", MajorChange)()
|
|
||||||
m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex] = unit
|
|
||||||
m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].ID = oldUnit.ID // keep the ID of the replaced unit
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) Iterate(yield UnitYieldFunc) {
|
|
||||||
if v.d.InstrIndex < 0 || v.d.InstrIndex >= len(v.d.Song.Patch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stackBefore := 0
|
|
||||||
for i, unit := range v.d.Song.Patch[v.d.InstrIndex].Units {
|
|
||||||
stackAfter := stackBefore + unit.StackChange()
|
|
||||||
if !yield(i, UnitListItem{
|
|
||||||
Type: unit.Type,
|
|
||||||
Comment: unit.Comment,
|
|
||||||
Disabled: unit.Disabled,
|
|
||||||
StackNeed: unit.StackNeed(),
|
|
||||||
StackBefore: stackBefore,
|
|
||||||
StackAfter: stackAfter,
|
|
||||||
}) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
stackBefore = stackAfter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) Selected() int {
|
|
||||||
return max(min(v.d.UnitIndex, v.Count()-1), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) Selected2() int {
|
|
||||||
return max(min(v.d.UnitIndex2, v.Count()-1), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) SetSelected(value int) {
|
|
||||||
m := (*Model)(v)
|
|
||||||
m.d.UnitIndex = max(min(value, v.Count()-1), 0)
|
|
||||||
m.d.ParamIndex = 0
|
|
||||||
m.d.UnitSearching = false
|
|
||||||
m.d.UnitSearchString = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) SetSelected2(value int) {
|
|
||||||
(*Model)(v).d.UnitIndex2 = max(min(value, v.Count()-1), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) Count() int {
|
|
||||||
m := (*Model)(v)
|
|
||||||
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return len(m.d.Song.Patch[(*Model)(v).d.InstrIndex].Units)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) move(r Range, delta int) (ok bool) {
|
|
||||||
m := (*Model)(v)
|
|
||||||
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
units := m.d.Song.Patch[m.d.InstrIndex].Units
|
|
||||||
for i, j := range r.Swaps(delta) {
|
|
||||||
units[i], units[j] = units[j], units[i]
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) delete(r Range) (ok bool) {
|
|
||||||
m := (*Model)(v)
|
|
||||||
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
u := m.d.Song.Patch[m.d.InstrIndex].Units
|
|
||||||
m.d.Song.Patch[m.d.InstrIndex].Units = append(u[:r.Start], u[r.End:]...)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) change(n string, severity ChangeSeverity) func() {
|
|
||||||
return (*Model)(v).change("UnitListView."+n, PatchChange, severity)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) cancel() {
|
|
||||||
(*Model)(v).changeCancel = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) marshal(r Range) ([]byte, error) {
|
|
||||||
m := (*Model)(v)
|
|
||||||
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
||||||
return nil, errors.New("UnitListView.marshal: no instruments")
|
|
||||||
}
|
|
||||||
units := m.d.Song.Patch[m.d.InstrIndex].Units[r.Start:r.End]
|
|
||||||
ret, err := yaml.Marshal(struct{ Units []sointu.Unit }{units})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("UnitListView.marshal: %v", err)
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Units) unmarshal(data []byte) (r Range, err error) {
|
|
||||||
m := (*Model)(v)
|
|
||||||
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
||||||
return Range{}, errors.New("UnitListView.unmarshal: no instruments")
|
|
||||||
}
|
|
||||||
var pastedUnits struct{ Units []sointu.Unit }
|
|
||||||
if err := yaml.Unmarshal(data, &pastedUnits); err != nil {
|
|
||||||
return Range{}, fmt.Errorf("UnitListView.unmarshal: %v", err)
|
|
||||||
}
|
|
||||||
if len(pastedUnits.Units) == 0 {
|
|
||||||
return Range{}, errors.New("UnitListView.unmarshal: no units")
|
|
||||||
}
|
|
||||||
m.assignUnitIDs(pastedUnits.Units)
|
|
||||||
sel := v.Selected()
|
|
||||||
var ok bool
|
|
||||||
m.d.Song.Patch[m.d.InstrIndex].Units, ok = Insert(m.d.Song.Patch[m.d.InstrIndex].Units, sel, pastedUnits.Units...)
|
|
||||||
if !ok {
|
|
||||||
return Range{}, errors.New("UnitListView.unmarshal: insert failed")
|
|
||||||
}
|
|
||||||
return Range{sel, sel + len(pastedUnits.Units)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tracks methods
|
// Tracks methods
|
||||||
|
|
||||||
func (v *Tracks) List() List {
|
func (v *Tracks) List() List {
|
||||||
|
@ -66,7 +66,8 @@ type (
|
|||||||
// when linkInstrTrack is false, editing an instrument does not change
|
// when linkInstrTrack is false, editing an instrument does not change
|
||||||
// the track. when true, editing an instrument changes the tracks (e.g.
|
// the track. when true, editing an instrument changes the tracks (e.g.
|
||||||
// reordering or deleting instrument can delete track)
|
// reordering or deleting instrument can delete track)
|
||||||
linkInstrTrack bool
|
linkInstrTrack bool
|
||||||
|
enableMultiUnits bool
|
||||||
|
|
||||||
voiceLevels [vm.MAX_VOICES]float32
|
voiceLevels [vm.MAX_VOICES]float32
|
||||||
|
|
||||||
|
@ -41,6 +41,11 @@ type (
|
|||||||
|
|
||||||
Params Model
|
Params Model
|
||||||
|
|
||||||
|
ParamsForUnit struct {
|
||||||
|
*Params
|
||||||
|
unitIndex int
|
||||||
|
}
|
||||||
|
|
||||||
ParamYieldFunc func(param Parameter) bool
|
ParamYieldFunc func(param Parameter) bool
|
||||||
|
|
||||||
ParameterType int
|
ParameterType int
|
||||||
@ -61,6 +66,13 @@ const (
|
|||||||
|
|
||||||
func (m *Model) Params() *Params { return (*Params)(m) }
|
func (m *Model) Params() *Params { return (*Params)(m) }
|
||||||
|
|
||||||
|
func (m *Model) ParamsForUnit(u int) *ParamsForUnit {
|
||||||
|
return &ParamsForUnit{
|
||||||
|
Params: m.Params(),
|
||||||
|
unitIndex: u,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parameter methods
|
// parameter methods
|
||||||
|
|
||||||
func (p parameter) change(kind string) func() {
|
func (p parameter) change(kind string) func() {
|
||||||
@ -80,14 +92,22 @@ func (pl *Params) change(n string, severity ChangeSeverity) func() {
|
|||||||
return (*Model)(pl).change("ParamList."+n, PatchChange, severity)
|
return (*Model)(pl).change("ParamList."+n, PatchChange, severity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pl *Params) Count() int {
|
func count(iterator func(yield ParamYieldFunc)) int {
|
||||||
count := 0
|
count := 0
|
||||||
for range pl.Iterate {
|
for _ = range iterator {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pl *Params) Count() int {
|
||||||
|
return count(pl.Iterate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl *Params) CountInUnit(unitIndex int) int {
|
||||||
|
return count(pl.IterateInUnit(unitIndex))
|
||||||
|
}
|
||||||
|
|
||||||
func (pl *Params) SelectedItem() (ret Parameter) {
|
func (pl *Params) SelectedItem() (ret Parameter) {
|
||||||
index := pl.Selected()
|
index := pl.Selected()
|
||||||
for param := range pl.Iterate {
|
for param := range pl.Iterate {
|
||||||
@ -100,55 +120,88 @@ func (pl *Params) SelectedItem() (ret Parameter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pl *Params) Iterate(yield ParamYieldFunc) {
|
func (pl *Params) Iterate(yield ParamYieldFunc) {
|
||||||
if pl.d.InstrIndex < 0 || pl.d.InstrIndex >= len(pl.d.Song.Patch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pl.d.UnitIndex < 0 || pl.d.UnitIndex >= len(pl.d.Song.Patch[pl.d.InstrIndex].Units) {
|
if pl.d.UnitIndex < 0 || pl.d.UnitIndex >= len(pl.d.Song.Patch[pl.d.InstrIndex].Units) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
unit := &pl.d.Song.Patch[pl.d.InstrIndex].Units[pl.d.UnitIndex]
|
pl.IterateInUnit(pl.d.UnitIndex)(yield)
|
||||||
unitType, ok := sointu.UnitTypes[unit.Type]
|
}
|
||||||
if !ok {
|
|
||||||
return
|
func (pl *Params) IterateInUnit(unitIndex int) func(yield ParamYieldFunc) {
|
||||||
}
|
return func(yield ParamYieldFunc) {
|
||||||
for i := range unitType {
|
if pl.d.InstrIndex < 0 || pl.d.InstrIndex >= len(pl.d.Song.Patch) {
|
||||||
if !unitType[i].CanSet {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if unit.Type == "oscillator" && unit.Parameters["type"] != sointu.Sample && i >= 11 {
|
|
||||||
break // don't show the sample related params unless necessary
|
|
||||||
}
|
|
||||||
if !yield(NamedParameter{
|
|
||||||
parameter: parameter{m: (*Model)(pl), unit: unit},
|
|
||||||
up: &unitType[i],
|
|
||||||
}) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
unit := &pl.d.Song.Patch[pl.d.InstrIndex].Units[unitIndex]
|
||||||
if unit.Type == "oscillator" && unit.Parameters["type"] == sointu.Sample {
|
unitType, ok := sointu.UnitTypes[unit.Type]
|
||||||
if !yield(GmDlsEntryParameter{parameter: parameter{m: (*Model)(pl), unit: unit}}) {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
for i := range unitType {
|
||||||
switch {
|
if !unitType[i].CanSet {
|
||||||
case unit.Type == "delay":
|
continue
|
||||||
if unit.Parameters["stereo"] == 1 && len(unit.VarArgs)%2 == 1 {
|
}
|
||||||
unit.VarArgs = append(unit.VarArgs, 1)
|
if unit.Type == "oscillator" && unit.Parameters["type"] != sointu.Sample && i >= 11 {
|
||||||
}
|
break // don't show the sample related params unless necessary
|
||||||
if !yield(ReverbParameter{parameter: parameter{m: (*Model)(pl), unit: unit}}) {
|
}
|
||||||
return
|
if !yield(NamedParameter{
|
||||||
}
|
parameter: parameter{m: (*Model)(pl), unit: unit},
|
||||||
if !yield(DelayLinesParameter{parameter: parameter{m: (*Model)(pl), unit: unit}}) {
|
up: &unitType[i],
|
||||||
return
|
}) {
|
||||||
}
|
|
||||||
for i := range unit.VarArgs {
|
|
||||||
if !yield(DelayTimeParameter{parameter: parameter{m: (*Model)(pl), unit: unit}, index: i}) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if unit.Type == "oscillator" && unit.Parameters["type"] == sointu.Sample {
|
||||||
|
if !yield(GmDlsEntryParameter{parameter: parameter{m: (*Model)(pl), unit: unit}}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case unit.Type == "delay":
|
||||||
|
if unit.Parameters["stereo"] == 1 && len(unit.VarArgs)%2 == 1 {
|
||||||
|
unit.VarArgs = append(unit.VarArgs, 1)
|
||||||
|
}
|
||||||
|
if !yield(ReverbParameter{parameter: parameter{m: (*Model)(pl), unit: unit}}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !yield(DelayLinesParameter{parameter: parameter{m: (*Model)(pl), unit: unit}}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range unit.VarArgs {
|
||||||
|
if !yield(DelayTimeParameter{parameter: parameter{m: (*Model)(pl), unit: unit}, index: i}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParamsForUnit
|
||||||
|
|
||||||
|
func (pu *ParamsForUnit) List() List { return List{pu} }
|
||||||
|
func (pu *ParamsForUnit) Selected2() int { return pu.Selected() }
|
||||||
|
func (pu *ParamsForUnit) SetSelected2(int) {}
|
||||||
|
|
||||||
|
func (pu *ParamsForUnit) Selected() int {
|
||||||
|
if pu.unitIndex != pu.d.UnitIndex {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return pu.d.ParamIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pu *ParamsForUnit) SetSelected(value int) {
|
||||||
|
pu.d.ParamIndex = max(min(value, pu.Count()-1), 0)
|
||||||
|
pu.d.UnitIndex = pu.unitIndex
|
||||||
|
pu.d.UnitIndex2 = pu.unitIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pu *ParamsForUnit) Count() int {
|
||||||
|
return count(pu.Iterate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pu *ParamsForUnit) Iterate(yield ParamYieldFunc) {
|
||||||
|
pu.Params.IterateInUnit(pu.unitIndex)(yield)
|
||||||
|
}
|
||||||
|
|
||||||
// NamedParameter
|
// NamedParameter
|
||||||
|
|
||||||
func (p NamedParameter) Name() string { return p.up.Name }
|
func (p NamedParameter) Name() string { return p.up.Name }
|
||||||
|
191
tracker/units.go
Normal file
191
tracker/units.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package tracker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/vsariola/sointu"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UnitListItem struct {
|
||||||
|
Type, Comment string
|
||||||
|
Disabled bool
|
||||||
|
StackNeed, StackBefore, StackAfter int
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitYieldFunc func(index int, item UnitListItem) (ok bool)
|
||||||
|
UnitSearchYieldFunc func(index int, item string) (ok bool)
|
||||||
|
|
||||||
|
Units Model // Units is a list of all the units in the selected instrument, implementing ListData & MutableListData interfaces
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
// Model methods
|
||||||
|
|
||||||
|
func (m *Model) Units() *Units { return (*Units)(m) }
|
||||||
|
|
||||||
|
// Units methods
|
||||||
|
|
||||||
|
func (ul *Units) List() List {
|
||||||
|
return List{ul}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) SelectedType() string {
|
||||||
|
if ul.d.InstrIndex < 0 ||
|
||||||
|
ul.d.InstrIndex >= len(ul.d.Song.Patch) ||
|
||||||
|
ul.d.UnitIndex < 0 ||
|
||||||
|
ul.d.UnitIndex >= len(ul.d.Song.Patch[ul.d.InstrIndex].Units) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ul.d.Song.Patch[ul.d.InstrIndex].Units[ul.d.UnitIndex].Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) SetSelectedType(t string) {
|
||||||
|
if ul.d.InstrIndex < 0 ||
|
||||||
|
ul.d.InstrIndex >= len(ul.d.Song.Patch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ul.d.UnitIndex < 0 {
|
||||||
|
ul.d.UnitIndex = 0
|
||||||
|
}
|
||||||
|
for len(ul.d.Song.Patch[ul.d.InstrIndex].Units) <= ul.d.UnitIndex {
|
||||||
|
ul.d.Song.Patch[ul.d.InstrIndex].Units = append(ul.d.Song.Patch[ul.d.InstrIndex].Units, sointu.Unit{})
|
||||||
|
}
|
||||||
|
unit, ok := defaultUnits[t]
|
||||||
|
if !ok { // if the type is invalid, we just set it to empty unit
|
||||||
|
unit = sointu.Unit{Parameters: make(map[string]int)}
|
||||||
|
} else {
|
||||||
|
unit = unit.Copy()
|
||||||
|
}
|
||||||
|
oldUnit := ul.d.Song.Patch[ul.d.InstrIndex].Units[ul.d.UnitIndex]
|
||||||
|
if oldUnit.Type == unit.Type {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer ul.change("SetSelectedType", MajorChange)()
|
||||||
|
ul.d.Song.Patch[ul.d.InstrIndex].Units[ul.d.UnitIndex] = unit
|
||||||
|
ul.d.Song.Patch[ul.d.InstrIndex].Units[ul.d.UnitIndex].ID = oldUnit.ID // keep the ID of the replaced unit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) Iterate(yield UnitYieldFunc) {
|
||||||
|
if ul.d.InstrIndex < 0 || ul.d.InstrIndex >= len(ul.d.Song.Patch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stackBefore := 0
|
||||||
|
for i, unit := range ul.d.Song.Patch[ul.d.InstrIndex].Units {
|
||||||
|
stackAfter := stackBefore + unit.StackChange()
|
||||||
|
if !yield(i, UnitListItem{
|
||||||
|
Type: unit.Type,
|
||||||
|
Comment: unit.Comment,
|
||||||
|
Disabled: unit.Disabled,
|
||||||
|
StackNeed: unit.StackNeed(),
|
||||||
|
StackBefore: stackBefore,
|
||||||
|
StackAfter: stackAfter,
|
||||||
|
}) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
stackBefore = stackAfter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) Selected() int {
|
||||||
|
return max(min(ul.d.UnitIndex, ul.Count()-1), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) Selected2() int {
|
||||||
|
return max(min(ul.d.UnitIndex2, ul.Count()-1), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) SetSelected(value int) {
|
||||||
|
m := (*Model)(ul)
|
||||||
|
m.d.UnitIndex = max(min(value, ul.Count()-1), 0)
|
||||||
|
m.d.ParamIndex = 0
|
||||||
|
m.d.UnitSearching = false
|
||||||
|
m.d.UnitSearchString = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) SetSelected2(value int) {
|
||||||
|
(*Model)(ul).d.UnitIndex2 = max(min(value, ul.Count()-1), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) Count() int {
|
||||||
|
m := (*Model)(ul)
|
||||||
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(m.d.Song.Patch[(*Model)(ul).d.InstrIndex].Units)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) move(r Range, delta int) (ok bool) {
|
||||||
|
m := (*Model)(ul)
|
||||||
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
units := m.d.Song.Patch[m.d.InstrIndex].Units
|
||||||
|
for i, j := range r.Swaps(delta) {
|
||||||
|
units[i], units[j] = units[j], units[i]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) delete(r Range) (ok bool) {
|
||||||
|
m := (*Model)(ul)
|
||||||
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
u := m.d.Song.Patch[m.d.InstrIndex].Units
|
||||||
|
m.d.Song.Patch[m.d.InstrIndex].Units = append(u[:r.Start], u[r.End:]...)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) change(n string, severity ChangeSeverity) func() {
|
||||||
|
return (*Model)(ul).change("UnitListView."+n, PatchChange, severity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) cancel() {
|
||||||
|
(*Model)(ul).changeCancel = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) marshal(r Range) ([]byte, error) {
|
||||||
|
m := (*Model)(ul)
|
||||||
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
|
return nil, errors.New("UnitListView.marshal: no instruments")
|
||||||
|
}
|
||||||
|
units := m.d.Song.Patch[m.d.InstrIndex].Units[r.Start:r.End]
|
||||||
|
ret, err := yaml.Marshal(struct{ Units []sointu.Unit }{units})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("UnitListView.marshal: %v", err)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) unmarshal(data []byte) (r Range, err error) {
|
||||||
|
m := (*Model)(ul)
|
||||||
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
|
return Range{}, errors.New("UnitListView.unmarshal: no instruments")
|
||||||
|
}
|
||||||
|
var pastedUnits struct{ Units []sointu.Unit }
|
||||||
|
if err := yaml.Unmarshal(data, &pastedUnits); err != nil {
|
||||||
|
return Range{}, fmt.Errorf("UnitListView.unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if len(pastedUnits.Units) == 0 {
|
||||||
|
return Range{}, errors.New("UnitListView.unmarshal: no units")
|
||||||
|
}
|
||||||
|
m.assignUnitIDs(pastedUnits.Units)
|
||||||
|
sel := ul.Selected()
|
||||||
|
var ok bool
|
||||||
|
m.d.Song.Patch[m.d.InstrIndex].Units, ok = Insert(m.d.Song.Patch[m.d.InstrIndex].Units, sel, pastedUnits.Units...)
|
||||||
|
if !ok {
|
||||||
|
return Range{}, errors.New("UnitListView.unmarshal: insert failed")
|
||||||
|
}
|
||||||
|
return Range{sel, sel + len(pastedUnits.Units)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ul *Units) CurrentInstrumentUnitAt(index int) sointu.Unit {
|
||||||
|
units := ul.d.Song.Patch[ul.d.InstrIndex].Units
|
||||||
|
if index < 0 || index >= len(units) {
|
||||||
|
return sointu.Unit{}
|
||||||
|
}
|
||||||
|
return units[index]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user