feat(tracker): implement edit modes, resembling tab stops

This commit is contained in:
vsariola 2021-02-08 20:15:37 +02:00
parent 7408956f77
commit 38008bdb87
13 changed files with 619 additions and 347 deletions

View File

@ -38,6 +38,10 @@ func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
}
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
s := SampleOffset{Start: uint32(unit.Parameters["samplestart"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
if s.LoopLength == 0 {
// hacky quick fix: looplength 0 causes div by zero so avoid crashing
s.LoopLength = 1
}
index, ok := sampleOffsetMap[s]
if !ok {
index = len(c.SampleOffsets)

View File

@ -255,7 +255,7 @@ var UnitTypes = map[string]([]UnitParameter){
{Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false},
{Name: "samplestart", MinValue: 0, MaxValue: 1720329, CanSet: true, CanModulate: false},
{Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false},
{Name: "looplength", MinValue: 1, MaxValue: 65535, CanSet: true, CanModulate: false}},
{Name: "looplength", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}},
"loadval": []UnitParameter{
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
{Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},

View File

@ -24,7 +24,6 @@ type DragList struct {
type FilledDragListStyle struct {
dragList *DragList
SurfaceColor color.NRGBA
HoverColor color.NRGBA
SelectedColor color.NRGBA
Count int
@ -38,7 +37,6 @@ func FilledDragList(th *material.Theme, dragList *DragList, count int, element f
element: element,
swap: swap,
Count: count,
SurfaceColor: dragListSurfaceColor,
HoverColor: dragListHoverColor,
SelectedColor: dragListSelectedColor,
}
@ -47,7 +45,6 @@ func FilledDragList(th *material.Theme, dragList *DragList, count int, element f
func (s *FilledDragListStyle) Layout(gtx C) D {
swap := 0
paint.FillShape(gtx.Ops, s.SurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
defer op.Save(gtx.Ops).Load()
if s.dragList.List.Axis == layout.Horizontal {

View File

@ -6,8 +6,6 @@ import (
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget"
@ -50,12 +48,6 @@ func (t *Tracker) layoutInstruments(gtx C) D {
}
func (t *Tracker) layoutInstrumentHeader(gtx C) D {
headerBg := func(gtx C) D {
paint.FillShape(gtx.Ops, instrumentSurfaceColor, clip.Rect{
Max: gtx.Constraints.Min,
}.Op())
return layout.Dimensions{Size: gtx.Constraints.Min}
}
header := func(gtx C) D {
deleteInstrumentBtnStyle := material.IconButton(t.Theme, t.DeleteInstrumentBtn, widgetForIcon(icons.ActionDelete))
deleteInstrumentBtnStyle.Background = transparent
@ -87,9 +79,7 @@ func (t *Tracker) layoutInstrumentHeader(gtx C) D {
for t.DeleteInstrumentBtn.Clicked() {
t.DeleteInstrument()
}
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Expanded(headerBg),
layout.Stacked(header))
return Surface{Gray: 37, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, header)
}
func (t *Tracker) layoutInstrumentNames(gtx C) D {
@ -135,10 +125,13 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D {
})
}
color := inactiveLightSurfaceColor
if t.EditMode == EditUnits || t.EditMode == EditParameters {
color = activeLightSurfaceColor
}
instrumentList := FilledDragList(t.Theme, t.InstrumentDragList, len(t.song.Patch.Instruments), element, t.SwapInstruments)
instrumentList.SelectedColor = instrumentSurfaceColor
instrumentList.SelectedColor = color
instrumentList.HoverColor = instrumentHoverColor
instrumentList.SurfaceColor = transparent
t.InstrumentDragList.SelectedItem = t.CurrentInstrument
defer op.Save(gtx.Ops).Load()
@ -174,7 +167,12 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D {
unitList := FilledDragList(t.Theme, t.UnitDragList, len(t.song.Patch.Instruments[t.CurrentInstrument].Units), element, t.SwapUnits)
if t.EditMode == EditUnits {
unitList.SelectedColor = cursorColor
}
t.UnitDragList.SelectedItem = t.CurrentUnit
return Surface{Gray: 30, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
@ -182,6 +180,7 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D {
dims := unitList.Layout(gtx)
if t.CurrentUnit != t.UnitDragList.SelectedItem {
t.CurrentUnit = t.UnitDragList.SelectedItem
t.EditMode = EditUnits
op.InvalidateOp{}.Add(gtx.Ops)
}
return dims
@ -191,4 +190,5 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D {
}))
}),
layout.Rigid(t.layoutUnitEditor))
})
}

View File

@ -1,8 +1,8 @@
package tracker
import (
"os"
"strconv"
"strings"
"gioui.org/io/key"
)
@ -42,6 +42,38 @@ var noteMap = map[string]int{
"P": 16,
}
var unitKeyMap = map[string]string{
"e": "envelope",
"o": "oscillator",
"m": "mulp",
"M": "mul",
"a": "addp",
"A": "add",
"p": "pan",
"S": "push",
"P": "pop",
"O": "out",
"l": "loadnote",
"L": "loadval",
"h": "xch",
"d": "delay",
"D": "distort",
"H": "hold",
"b": "crush",
"g": "gain",
"i": "invgain",
"f": "filter",
"I": "clip",
"E": "speed",
"r": "compressor",
"u": "outaux",
"U": "aux",
"s": "send",
"n": "noise",
"N": "in",
"R": "receive",
}
// KeyEvent handles incoming key events and returns true if repaint is needed.
func (t *Tracker) KeyEvent(e key.Event) bool {
if e.State == key.Press {
@ -59,14 +91,15 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
t.Redo()
return true
}
case "A":
t.SetCurrentNote(0)
return true
case key.NameDeleteForward:
switch t.EditMode {
case EditTracks:
t.DeleteSelection()
return true
case key.NameEscape:
os.Exit(0)
case EditUnits:
t.DeleteUnit()
return true
}
case "Space":
t.TogglePlay()
return true
@ -75,75 +108,161 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
return t.ChangeOctave(1)
}
return t.ChangeOctave(-1)
case key.NameUpArrow:
delta := -1
if e.Modifiers.Contain(key.ModCtrl) {
delta = -t.song.RowsPerPattern
case key.NameTab:
if e.Modifiers.Contain(key.ModShift) {
t.EditMode = (t.EditMode - 1 + 4) % 4
} else {
t.EditMode = (t.EditMode + 1) % 4
}
t.Cursor.Row += delta
t.Cursor.Clamp(t.song)
if !e.Modifiers.Contain(key.ModShift) {
t.SelectionCorner = t.Cursor
return true
case key.NameUpArrow:
switch t.EditMode {
case EditPatterns:
if e.Modifiers.Contain(key.ModCtrl) {
t.Cursor.SongRow = SongRow{}
} else {
t.Cursor.Row -= t.song.RowsPerPattern
}
t.NoteTracking = false
case EditTracks:
if e.Modifiers.Contain(key.ModCtrl) {
t.Cursor.Row -= t.song.RowsPerPattern
} else {
t.Cursor.Row--
}
t.NoteTracking = false
case EditUnits:
t.CurrentUnit--
case EditParameters:
t.CurrentParam--
}
t.ClampPositions()
if !e.Modifiers.Contain(key.ModShift) {
t.Unselect()
}
return true
case key.NameDownArrow:
delta := 1
switch t.EditMode {
case EditPatterns:
if e.Modifiers.Contain(key.ModCtrl) {
delta = t.song.RowsPerPattern
}
t.Cursor.Row += delta
t.Cursor.Clamp(t.song)
if !e.Modifiers.Contain(key.ModShift) {
t.SelectionCorner = t.Cursor
t.Cursor.Row = t.song.TotalRows() - 1
} else {
t.Cursor.Row += t.song.RowsPerPattern
}
t.NoteTracking = false
case EditTracks:
if e.Modifiers.Contain(key.ModCtrl) {
t.Cursor.Row += t.song.RowsPerPattern
} else {
t.Cursor.Row++
}
t.NoteTracking = false
case EditUnits:
t.CurrentUnit++
case EditParameters:
t.CurrentParam++
}
t.ClampPositions()
if !e.Modifiers.Contain(key.ModShift) {
t.Unselect()
}
return true
case key.NameLeftArrow:
switch t.EditMode {
case EditPatterns:
if e.Modifiers.Contain(key.ModCtrl) {
t.Cursor.Track = 0
} else {
t.Cursor.Track--
}
case EditTracks:
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
t.Cursor.Track--
t.Cursor.Clamp(t.song)
if t.TrackShowHex[t.Cursor.Track] {
t.CursorColumn = 1
} else {
t.CursorColumn = 0
}
if !e.Modifiers.Contain(key.ModShift) {
t.SelectionCorner = t.Cursor
}
} else {
t.CursorColumn--
}
case EditUnits:
t.CurrentInstrument--
case EditParameters:
if e.Modifiers.Contain(key.ModShift) {
t.SetUnitParam(t.GetUnitParam() - 16)
} else {
t.SetUnitParam(t.GetUnitParam() - 1)
}
}
t.ClampPositions()
if !e.Modifiers.Contain(key.ModShift) {
t.Unselect()
}
return true
case key.NameRightArrow:
if t.CursorColumn == 1 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
switch t.EditMode {
case EditPatterns:
if e.Modifiers.Contain(key.ModCtrl) {
t.Cursor.Track = len(t.song.Tracks) - 1
} else {
t.Cursor.Track++
t.Cursor.Clamp(t.song)
if !e.Modifiers.Contain(key.ModShift) {
t.SelectionCorner = t.Cursor
}
case EditTracks:
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
t.Cursor.Track++
t.CursorColumn = 0
} else {
t.CursorColumn++
}
case EditUnits:
t.CurrentInstrument++
case EditParameters:
if e.Modifiers.Contain(key.ModShift) {
t.SetUnitParam(t.GetUnitParam() + 16)
} else {
t.SetUnitParam(t.GetUnitParam() + 1)
}
}
t.ClampPositions()
if !e.Modifiers.Contain(key.ModShift) {
t.Unselect()
}
return true
}
if e.Modifiers.Contain(key.ModCtrl) {
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
switch t.EditMode {
case EditPatterns:
if iv, err := strconv.Atoi(e.Name); err == nil {
t.SetCurrentPattern(byte(iv))
return true
}
} else {
if !t.TrackShowHex[t.Cursor.Track] {
if val, ok := noteMap[e.Name]; ok {
t.NotePressed(val)
if b := byte(e.Name[0]) - 'A'; len(e.Name) == 1 && b >= 0 && b < 26 {
t.SetCurrentPattern(b + 10)
return true
}
} else {
case EditTracks:
if t.TrackShowHex[t.Cursor.Track] {
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
t.NumberPressed(byte(iv))
return true
}
} else {
if e.Name == "A" {
t.SetCurrentNote(0)
return true
}
if val, ok := noteMap[e.Name]; ok {
t.NotePressed(val)
return true
}
}
case EditUnits:
name := e.Name
if !e.Modifiers.Contain(key.ModShift) {
name = strings.ToLower(name)
}
if val, ok := unitKeyMap[name]; ok {
if e.Modifiers.Contain(key.ModCtrl) {
t.AddUnit()
}
t.SetUnit(val)
return true
}
}
}

View File

@ -2,16 +2,13 @@ package tracker
import (
"image"
"image/color"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"golang.org/x/exp/shiny/materialdesign/icons"
)
func smallButton(icStyle material.IconButtonStyle) material.IconButtonStyle {
@ -40,156 +37,23 @@ func trackButton(t *material.Theme, w *widget.Clickable, text string, enabled bo
func (t *Tracker) Layout(gtx layout.Context) {
paint.FillShape(gtx.Ops, backgroundColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op())
t.VerticalSplit.Layout(gtx,
t.layoutControls,
t.layoutTracksAndPatterns)
t.layoutTop,
t.layoutBottom)
t.updateInstrumentScroll()
}
func (t *Tracker) layoutTracksAndPatterns(gtx layout.Context) layout.Dimensions {
func (t *Tracker) layoutBottom(gtx layout.Context) layout.Dimensions {
return t.BottomHorizontalSplit.Layout(gtx,
t.layoutPatterns,
t.layoutTracks,
func(gtx C) D {
return Surface{Gray: 24, Focus: t.EditMode == 0}.Layout(gtx, t.layoutPatterns)
},
func(gtx C) D {
return Surface{Gray: 24, Focus: t.EditMode == 1}.Layout(gtx, t.layoutTracker)
},
)
}
func (t *Tracker) layoutTracks(gtx layout.Context) layout.Dimensions {
paint.FillShape(gtx.Ops, trackerSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
flexTracks := make([]layout.FlexChild, len(t.song.Tracks))
t.playRowPatMutex.RLock()
defer t.playRowPatMutex.RUnlock()
playPat := t.PlayPosition.Pattern
if !t.Playing {
playPat = -1
}
rowMarkers := layout.Rigid(t.layoutRowMarkers(
t.song.RowsPerPattern,
len(t.song.Tracks[0].Sequence),
t.Cursor.Row,
t.Cursor.Pattern,
t.CursorColumn,
t.PlayPosition.Row,
playPat,
))
leftInset := layout.Inset{Left: unit.Dp(4)}
for i := range t.song.Tracks {
i2 := i // avoids i being updated in the closure
if len(t.TrackHexCheckBoxes) <= i {
t.TrackHexCheckBoxes = append(t.TrackHexCheckBoxes, new(widget.Bool))
}
if len(t.TrackShowHex) <= i {
t.TrackShowHex = append(t.TrackShowHex, false)
}
flexTracks[i] = layout.Rigid(func(gtx layout.Context) layout.Dimensions {
t.TrackHexCheckBoxes[i2].Value = t.TrackShowHex[i2]
cbStyle := material.CheckBox(t.Theme, t.TrackHexCheckBoxes[i2], "hex")
cbStyle.Color = white
cbStyle.IconColor = t.Theme.Fg
ret := layout.Stack{}.Layout(gtx,
layout.Stacked(func(gtx layout.Context) D {
return leftInset.Layout(gtx, t.layoutTrack(i2))
}),
layout.Stacked(cbStyle.Layout),
)
t.TrackShowHex[i2] = t.TrackHexCheckBoxes[i2].Value
return ret
})
}
menuBg := func(gtx C) D {
paint.FillShape(gtx.Ops, trackMenuSurfaceColor, clip.Rect{
Max: gtx.Constraints.Min,
}.Op())
return layout.Dimensions{Size: gtx.Constraints.Min}
}
for t.AddSemitoneBtn.Clicked() {
t.AdjustSelectionPitch(1)
}
for t.SubtractSemitoneBtn.Clicked() {
t.AdjustSelectionPitch(-1)
}
for t.AddOctaveBtn.Clicked() {
t.AdjustSelectionPitch(12)
}
for t.SubtractOctaveBtn.Clicked() {
t.AdjustSelectionPitch(-12)
}
menu := func(gtx C) D {
addSemitoneBtnStyle := material.Button(t.Theme, t.AddSemitoneBtn, "+1")
addSemitoneBtnStyle.Color = primaryColor
addSemitoneBtnStyle.Background = transparent
addSemitoneBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
subtractSemitoneBtnStyle := material.Button(t.Theme, t.SubtractSemitoneBtn, "-1")
subtractSemitoneBtnStyle.Color = primaryColor
subtractSemitoneBtnStyle.Background = transparent
subtractSemitoneBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
addOctaveBtnStyle := material.Button(t.Theme, t.AddOctaveBtn, "+12")
addOctaveBtnStyle.Color = primaryColor
addOctaveBtnStyle.Background = transparent
addOctaveBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
subtractOctaveBtnStyle := material.Button(t.Theme, t.SubtractOctaveBtn, "-12")
subtractOctaveBtnStyle.Color = primaryColor
subtractOctaveBtnStyle.Background = transparent
subtractOctaveBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
newTrackBtnStyle := material.IconButton(t.Theme, t.NewTrackBtn, widgetForIcon(icons.ContentAdd))
newTrackBtnStyle.Background = transparent
newTrackBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() {
newTrackBtnStyle.Color = primaryColor
} else {
newTrackBtnStyle.Color = disabledTextColor
}
in := layout.UniformInset(unit.Dp(1))
octave := func(gtx C) D {
numStyle := NumericUpDown(t.Theme, t.Octave, 0, 9)
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20))
gtx.Constraints.Min.X = gtx.Px(unit.Dp(70))
return in.Layout(gtx, numStyle.Layout)
}
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(Label("OCT:", white)),
layout.Rigid(octave),
layout.Rigid(Label(" PITCH:", white)),
layout.Rigid(addSemitoneBtnStyle.Layout),
layout.Rigid(subtractSemitoneBtnStyle.Layout),
layout.Rigid(addOctaveBtnStyle.Layout),
layout.Rigid(subtractOctaveBtnStyle.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
layout.Rigid(newTrackBtnStyle.Layout))
}
for t.NewTrackBtn.Clicked() {
t.AddTrack()
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Expanded(menuBg),
layout.Stacked(menu),
)
}),
layout.Flexed(1, func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
rowMarkers,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
defer op.Save(gtx.Ops).Load()
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
dims := layout.Flex{Axis: layout.Horizontal}.Layout(gtx, flexTracks...)
if dims.Size.X > gtx.Constraints.Max.X {
dims.Size.X = gtx.Constraints.Max.X
}
return dims
}))
}),
)
}
func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions {
func (t *Tracker) layoutTop(gtx layout.Context) layout.Dimensions {
for t.NewInstrumentBtn.Clicked() {
t.AddInstrument()
}
@ -200,19 +64,3 @@ func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions {
)
}
func (t *Tracker) line(horizontal bool, color color.NRGBA) layout.Widget {
return func(gtx layout.Context) layout.Dimensions {
if horizontal {
gtx.Constraints.Min.Y = 1
gtx.Constraints.Max.Y = 1
} else {
gtx.Constraints.Min.X = 1
gtx.Constraints.Max.X = 1
}
defer op.Save(gtx.Ops).Load()
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}
}

View File

@ -20,7 +20,6 @@ const patternRowMarkerWidth = 30
func (t *Tracker) layoutPatterns(gtx C) D {
defer op.Save(gtx.Ops).Load()
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
paint.FillShape(gtx.Ops, patternSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
patternRect := SongRect{
Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track},
Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track},
@ -35,15 +34,20 @@ func (t *Tracker) layoutPatterns(gtx C) D {
op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops)
for i, track := range t.song.Tracks {
paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, fmt.Sprintf("%d", track.Sequence[j]))
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(track.Sequence[j]))
point := SongPoint{Track: i, SongRow: SongRow{Pattern: j}}
if t.EditMode == EditPatterns || t.EditMode == EditTracks {
if patternRect.Contains(point) {
color := patternSelectionColor
color := inactiveSelectionColor
if t.EditMode == EditPatterns {
color = selectionColor
if point.Pattern == t.Cursor.Pattern && point.Track == t.Cursor.Track {
color = patternCursorColor
color = cursorColor
}
}
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(patternCellWidth, patternCellHeight)}.Op())
}
}
op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops)
}
stack.Load()

View File

@ -24,7 +24,7 @@ func (t *Tracker) layoutRowMarkers(patternRows, sequenceLength, cursorRow, curso
}.Op())
defer op.Save(gtx.Ops).Load()
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y/2)-trackRowHeight)).Add(gtx.Ops)
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops)
cursorSongRow := cursorPattern*patternRows + cursorRow
playSongRow := playPattern*patternRows + playRow
op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops)
@ -32,13 +32,13 @@ func (t *Tracker) layoutRowMarkers(patternRows, sequenceLength, cursorRow, curso
for j := 0; j < patternRows; j++ {
songRow := i*patternRows + j
if songRow == playSongRow {
paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight)}.Op())
paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(trackColWidth, trackRowHeight)}.Op())
}
if j == 0 {
paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", i)))
}
if songRow == cursorSongRow {
if t.EditMode == EditTracks && songRow == cursorSongRow {
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
} else {
paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops)

51
tracker/surface.go Normal file
View File

@ -0,0 +1,51 @@
package tracker
import (
"image/color"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
)
type Surface struct {
Gray int
Inset layout.Inset
FitSize bool
Focus bool
}
func (s Surface) Layout(gtx C, widget layout.Widget) D {
bg := func(gtx C) D {
grayInt := s.Gray
if s.Focus {
grayInt += 8
}
var grayUint8 uint8
if grayInt < 0 {
grayUint8 = 0
} else if grayInt > 255 {
grayUint8 = 255
} else {
grayUint8 = uint8(grayInt)
}
color := color.NRGBA{R: grayUint8, G: grayUint8, B: grayUint8, A: 255}
paint.FillShape(gtx.Ops, color, clip.Rect{
Max: gtx.Constraints.Min,
}.Op())
return D{Size: gtx.Constraints.Min}
}
fg := func(gtx C) D {
return s.Inset.Layout(gtx, widget)
}
if s.FitSize {
return layout.Stack{}.Layout(gtx,
layout.Expanded(bg),
layout.Stacked(fg),
)
}
gtxbg := gtx
gtxbg.Constraints.Min = gtxbg.Constraints.Max
bg(gtxbg)
return fg(gtx)
}

View File

@ -56,6 +56,7 @@ var activeTrackColor = focusedContainerColor
var trackSurfaceColor = color.NRGBA{R: 255, G: 255, B: 255, A: 31}
var patternSurfaceColor = color.NRGBA{R: 24, G: 24, B: 24, A: 255}
var patternSurfaceActiveColor = color.NRGBA{R: 30, G: 30, B: 30, A: 255}
var rowMarkerSurfaceColor = color.NRGBA{R: 0, G: 0, B: 0, A: 0}
var rowMarkerPatternTextColor = secondaryColor
@ -101,3 +102,10 @@ var dragListHoverColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
var unitSurfaceColor = color.NRGBA{R: 30, G: 30, B: 30, A: 255}
var unitTypeListHighlightColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
var inactiveLightSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
var activeLightSurfaceColor = color.NRGBA{R: 45, G: 45, B: 45, A: 255}
var cursorColor = color.NRGBA{R: 100, G: 140, B: 255, A: 48}
var selectionColor = color.NRGBA{R: 100, G: 140, B: 255, A: 8}
var inactiveSelectionColor = color.NRGBA{R: 140, G: 140, B: 140, A: 16}

View File

@ -10,54 +10,221 @@ import (
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"golang.org/x/exp/shiny/materialdesign/icons"
)
const trackRowHeight = 16
const trackWidth = 54
const trackColWidth = 54
const patmarkWidth = 16
func (t *Tracker) layoutTrack(trackNo int) layout.Widget {
return func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Min.X = trackWidth
gtx.Constraints.Max.X = trackWidth
func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
t.playRowPatMutex.RLock()
defer t.playRowPatMutex.RUnlock()
playPat := t.PlayPosition.Pattern
if !t.Playing {
playPat = -1
}
rowMarkers := layout.Rigid(t.layoutRowMarkers(
t.song.RowsPerPattern,
len(t.song.Tracks[0].Sequence),
t.Cursor.Row,
t.Cursor.Pattern,
t.CursorColumn,
t.PlayPosition.Row,
playPat,
))
for t.NewTrackBtn.Clicked() {
t.AddTrack()
}
for len(t.TrackHexCheckBoxes) < len(t.song.Tracks) {
t.TrackHexCheckBoxes = append(t.TrackHexCheckBoxes, new(widget.Bool))
}
for len(t.TrackShowHex) < len(t.song.Tracks) {
t.TrackShowHex = append(t.TrackShowHex, false)
}
//t.TrackHexCheckBoxes[i2].Value = t.TrackShowHex[i2]
//cbStyle := material.CheckBox(t.Theme, t.TrackHexCheckBoxes[i2], "hex")
//cbStyle.Color = white
//cbStyle.IconColor = t.Theme.Fg
for t.AddSemitoneBtn.Clicked() {
t.AdjustSelectionPitch(1)
}
for t.SubtractSemitoneBtn.Clicked() {
t.AdjustSelectionPitch(-1)
}
for t.AddOctaveBtn.Clicked() {
t.AdjustSelectionPitch(12)
}
for t.SubtractOctaveBtn.Clicked() {
t.AdjustSelectionPitch(-12)
}
menu := func(gtx C) D {
addSemitoneBtnStyle := material.Button(t.Theme, t.AddSemitoneBtn, "+1")
addSemitoneBtnStyle.Color = primaryColor
addSemitoneBtnStyle.Background = transparent
addSemitoneBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
subtractSemitoneBtnStyle := material.Button(t.Theme, t.SubtractSemitoneBtn, "-1")
subtractSemitoneBtnStyle.Color = primaryColor
subtractSemitoneBtnStyle.Background = transparent
subtractSemitoneBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
addOctaveBtnStyle := material.Button(t.Theme, t.AddOctaveBtn, "+12")
addOctaveBtnStyle.Color = primaryColor
addOctaveBtnStyle.Background = transparent
addOctaveBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
subtractOctaveBtnStyle := material.Button(t.Theme, t.SubtractOctaveBtn, "-12")
subtractOctaveBtnStyle.Color = primaryColor
subtractOctaveBtnStyle.Background = transparent
subtractOctaveBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
newTrackBtnStyle := material.IconButton(t.Theme, t.NewTrackBtn, widgetForIcon(icons.ContentAdd))
newTrackBtnStyle.Background = transparent
newTrackBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() {
newTrackBtnStyle.Color = primaryColor
} else {
newTrackBtnStyle.Color = disabledTextColor
}
in := layout.UniformInset(unit.Dp(1))
octave := func(gtx C) D {
numStyle := NumericUpDown(t.Theme, t.Octave, 0, 9)
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20))
gtx.Constraints.Min.X = gtx.Px(unit.Dp(70))
return in.Layout(gtx, numStyle.Layout)
}
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(Label("OCT:", white)),
layout.Rigid(octave),
layout.Rigid(Label(" PITCH:", white)),
layout.Rigid(addSemitoneBtnStyle.Layout),
layout.Rigid(subtractSemitoneBtnStyle.Layout),
layout.Rigid(addOctaveBtnStyle.Layout),
layout.Rigid(subtractOctaveBtnStyle.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
layout.Rigid(newTrackBtnStyle.Layout))
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return Surface{Gray: 37, Focus: t.EditMode == 1, FitSize: true}.Layout(gtx, menu)
}),
layout.Flexed(1, func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
rowMarkers,
layout.Flexed(1, func(gtx C) D {
return layout.Stack{Alignment: layout.NW}.Layout(gtx,
layout.Stacked(t.layoutTracks),
layout.Stacked(t.layoutTrackTitles),
)
}))
}),
)
}
func (t *Tracker) layoutTrackTitles(gtx C) D {
defer op.Save(gtx.Ops).Load()
hexFlexChildren := make([]layout.FlexChild, len(t.song.Tracks))
for trkIndex := range t.song.Tracks {
trkIndex2 := trkIndex
hexFlexChildren[trkIndex] = layout.Rigid(func(gtx C) D {
t.TrackHexCheckBoxes[trkIndex2].Value = t.TrackShowHex[trkIndex2]
cbStyle := material.CheckBox(t.Theme, t.TrackHexCheckBoxes[trkIndex2], "hex")
dims := cbStyle.Layout(gtx)
t.TrackShowHex[trkIndex2] = t.TrackHexCheckBoxes[trkIndex2].Value
return layout.Dimensions{Size: image.Pt(trackColWidth, dims.Size.Y)}
})
}
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, hexFlexChildren...)
}
func (t *Tracker) layoutTracks(gtx C) D {
defer op.Save(gtx.Ops).Load()
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y/2)-trackRowHeight)).Add(gtx.Ops)
// TODO: this is a time bomb; as soon as one of the patterns is not the same length as rest. Find a solution
// to fix the pattern lengths to a constant value
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops)
cursorSongRow := t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row
op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops)
patternRect := SongRect{
Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track},
Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track},
if t.EditMode == EditPatterns || t.EditMode == EditTracks {
x1, y1 := t.Cursor.Track, t.Cursor.Pattern
x2, y2 := t.SelectionCorner.Track, t.SelectionCorner.Pattern
if x1 > x2 {
x1, x2 = x2, x1
}
pointRect := SongRect{
Corner1: t.Cursor,
Corner2: t.SelectionCorner,
if y1 > y2 {
y1, y2 = y2, y1
}
for i, s := range t.song.Tracks[trackNo].Sequence {
if patternRect.Contains(SongPoint{Track: trackNo, SongRow: SongRow{Pattern: i}}) {
paint.FillShape(gtx.Ops, activeTrackColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight*t.song.RowsPerPattern)}.Op())
x2++
y2++
x1 *= trackColWidth
y1 *= trackRowHeight * t.song.RowsPerPattern
x2 *= trackColWidth
y2 *= trackRowHeight * t.song.RowsPerPattern
paint.FillShape(gtx.Ops, inactiveSelectionColor, clip.Rect{Min: image.Pt(x1, y1), Max: image.Pt(x2, y2)}.Op())
}
for j := 0; j < t.song.RowsPerPattern; j++ {
c := t.song.Tracks[trackNo].Patterns[s][j]
songRow := SongRow{Pattern: i, Row: j}
songPoint := SongPoint{Track: trackNo, SongRow: songRow}
if songRow == t.PlayPosition && t.Playing {
paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight)}.Op())
if t.Playing {
py := trackRowHeight * (t.PlayPosition.Pattern*t.song.RowsPerPattern + t.PlayPosition.Row)
paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Min: image.Pt(0, py), Max: image.Pt(gtx.Constraints.Max.X, py+trackRowHeight)}.Op())
}
if j == 0 {
if t.EditMode == EditTracks {
x1, y1 := t.Cursor.Track, t.Cursor.Pattern*t.song.RowsPerPattern+t.Cursor.Row
x2, y2 := t.SelectionCorner.Track, t.SelectionCorner.Pattern*t.song.RowsPerPattern+t.SelectionCorner.Row
if x1 > x2 {
x1, x2 = x2, x1
}
if y1 > y2 {
y1, y2 = y2, y1
}
x2++
y2++
x1 *= trackColWidth
y1 *= trackRowHeight
x2 *= trackColWidth
y2 *= trackRowHeight
paint.FillShape(gtx.Ops, selectionColor, clip.Rect{Min: image.Pt(x1, y1), Max: image.Pt(x2, y2)}.Op())
cx := t.Cursor.Track * trackColWidth
cy := (t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row) * trackRowHeight
paint.FillShape(gtx.Ops, cursorColor, clip.Rect{Min: image.Pt(cx, cy), Max: image.Pt(cx+trackColWidth, cy+trackRowHeight)}.Op())
}
delta := (gtx.Constraints.Max.Y/2 + trackRowHeight - 1) / trackRowHeight
firstRow := cursorSongRow - delta
lastRow := cursorSongRow + delta
if firstRow < 0 {
firstRow = 0
}
if l := t.song.TotalRows(); lastRow >= l {
lastRow = l - 1
}
op.Offset(f32.Pt(0, float32(trackRowHeight*firstRow))).Add(gtx.Ops)
for trkIndex, trk := range t.song.Tracks {
stack := op.Save(gtx.Ops)
for row := firstRow; row <= lastRow; row++ {
pat := row / t.song.RowsPerPattern
patRow := row % t.song.RowsPerPattern
s := trk.Sequence[pat]
if patRow == 0 {
paint.ColorOp{Color: trackerPatMarker}.Add(gtx.Ops)
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(s))
}
if songRow == t.Cursor.SongRow {
op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops)
if t.EditMode == EditTracks && t.Cursor.SongRow.Row == patRow && t.Cursor.SongRow.Pattern == pat {
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
} else {
paint.ColorOp{Color: trackerInactiveTextColor}.Add(gtx.Ops)
}
op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops)
if t.TrackShowHex[trackNo] {
c := trk.Patterns[s][patRow]
if t.TrackShowHex[trkIndex] {
var text string
switch c {
case 0:
@ -68,28 +235,13 @@ func (t *Tracker) layoutTrack(trackNo int) layout.Widget {
text = fmt.Sprintf("%02x", c)
}
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(text))
if pointRect.Contains(songPoint) {
for col := 0; col < 2; col++ {
color := trackerSelectionColor
if songPoint == t.Cursor && t.CursorColumn == col {
color = trackerCursorColor
}
paint.FillShape(gtx.Ops, color, clip.Rect{Min: image.Pt(col*10, 0), Max: image.Pt(col*10+10, trackRowHeight)}.Op())
}
}
} else {
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, valueAsNote(c))
if pointRect.Contains(songPoint) {
color := trackerSelectionColor
if songPoint == t.Cursor {
color = trackerCursorColor
}
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(30, trackRowHeight)}.Op())
}
}
op.Offset(f32.Pt(-patmarkWidth, trackRowHeight)).Add(gtx.Ops)
}
stack.Load()
op.Offset(f32.Pt(trackColWidth, 0)).Add(gtx.Ops)
}
return layout.Dimensions{Size: gtx.Constraints.Max}
}
}

View File

@ -13,6 +13,15 @@ import (
"github.com/vsariola/sointu"
)
type EditMode int
const (
EditPatterns EditMode = iota
EditTracks
EditUnits
EditParameters
)
type Tracker struct {
QuitButton *widget.Clickable
songPlayMutex sync.RWMutex // protects song and playing
@ -21,11 +30,13 @@ type Tracker struct {
// protects PlayPattern and PlayRow
playRowPatMutex sync.RWMutex // protects song and playing
PlayPosition SongRow
EditMode EditMode
SelectionCorner SongPoint
Cursor SongPoint
CursorColumn int
CurrentInstrument int
CurrentUnit int
CurrentParam int
UnitGroupMenuVisible bool
UnitGroupMenuIndex int
UnitSubMenuIndex int
@ -85,12 +96,6 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
defer t.songPlayMutex.Unlock()
t.song = song
t.ClampPositions()
if l := len(t.song.Patch.Instruments); t.CurrentInstrument >= l {
t.CurrentInstrument = l - 1
}
if l := len(t.song.Patch.Instruments[t.CurrentInstrument].Units); t.CurrentUnit >= l {
t.CurrentUnit = l - 1
}
if t.sequencer != nil {
t.sequencer.SetPatch(song.Patch)
t.sequencer.SetRowLength(song.SamplesPerRow())
@ -98,6 +103,16 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
return nil
}
func clamp(a, min, max int) int {
if a < min {
return min
}
if a >= max {
return max - 1
}
return a
}
func (t *Tracker) Close() {
t.audioContext.Close()
t.closer <- struct{}{}
@ -232,6 +247,7 @@ func (t *Tracker) AddInstrument() {
copy(instr[t.CurrentInstrument+2:], t.song.Patch.Instruments[t.CurrentInstrument+1:])
t.song.Patch.Instruments = instr
t.CurrentInstrument++
t.ClampPositions()
t.sequencer.SetPatch(t.song.Patch)
}
@ -242,6 +258,7 @@ func (t *Tracker) SwapInstruments(i, j int) {
t.SaveUndo()
instruments := t.song.Patch.Instruments
instruments[i], instruments[j] = instruments[j], instruments[i]
t.ClampPositions()
t.sequencer.SetPatch(t.song.Patch)
}
@ -254,6 +271,7 @@ func (t *Tracker) DeleteInstrument() {
if t.CurrentInstrument >= len(t.song.Patch.Instruments) {
t.CurrentInstrument = len(t.song.Patch.Instruments) - 1
}
t.ClampPositions()
t.sequencer.SetPatch(t.song.Patch)
}
@ -343,6 +361,7 @@ func (t *Tracker) AddUnit() {
copy(units[t.CurrentUnit+2:], t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit+1:])
t.song.Patch.Instruments[t.CurrentInstrument].Units = units
t.CurrentUnit++
t.ClampPositions()
t.sequencer.SetPatch(t.song.Patch)
}
@ -350,6 +369,7 @@ func (t *Tracker) ClearUnit() {
t.SaveUndo()
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type = ""
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters = make(map[string]int)
t.ClampPositions()
t.sequencer.SetPatch(t.song.Patch)
}
@ -365,6 +385,30 @@ func (t *Tracker) DeleteUnit() {
if t.CurrentUnit > 0 {
t.CurrentUnit--
}
t.ClampPositions()
t.sequencer.SetPatch(t.song.Patch)
}
func (t *Tracker) GetUnitParam() int {
unit := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit]
paramtype := sointu.UnitTypes[unit.Type][t.CurrentParam]
return unit.Parameters[paramtype.Name]
}
func (t *Tracker) SetUnitParam(value int) {
unit := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit]
unittype := sointu.UnitTypes[unit.Type][t.CurrentParam]
if value < unittype.MinValue {
value = unittype.MinValue
} else if value > unittype.MaxValue {
value = unittype.MaxValue
}
if unit.Parameters[unittype.Name] == value {
return
}
t.SaveUndo()
unit.Parameters[unittype.Name] = value
t.ClampPositions()
t.sequencer.SetPatch(t.song.Patch)
}
@ -375,6 +419,7 @@ func (t *Tracker) SwapUnits(i, j int) {
t.SaveUndo()
units := t.song.Patch.Instruments[t.CurrentInstrument].Units
units[i], units[j] = units[j], units[i]
t.ClampPositions()
t.sequencer.SetPatch(t.song.Patch)
}
@ -382,6 +427,38 @@ func (t *Tracker) ClampPositions() {
t.PlayPosition.Clamp(t.song)
t.Cursor.Clamp(t.song)
t.SelectionCorner.Clamp(t.song)
if t.Cursor.Track >= len(t.TrackShowHex) || !t.TrackShowHex[t.Cursor.Track] {
t.CursorColumn = 0
}
t.CurrentInstrument = clamp(t.CurrentInstrument, 0, len(t.song.Patch.Instruments))
t.CurrentUnit = clamp(t.CurrentUnit, 0, len(t.song.Patch.Instruments[t.CurrentInstrument].Units))
numSettableParams := 0
for _, t := range sointu.UnitTypes[t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type] {
if t.CanSet {
numSettableParams++
}
}
if t.CurrentParam < 0 && t.CurrentUnit > 0 {
t.CurrentUnit--
numSettableParams = 0
for _, t := range sointu.UnitTypes[t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type] {
if t.CanSet {
numSettableParams++
}
}
t.CurrentParam = numSettableParams - 1
}
if t.CurrentParam >= numSettableParams && t.CurrentUnit < len(t.song.Patch.Instruments[t.CurrentInstrument].Units)-1 {
t.CurrentUnit++
numSettableParams = 0
for _, t := range sointu.UnitTypes[t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type] {
if t.CanSet {
numSettableParams++
}
}
t.CurrentParam = 0
}
t.CurrentParam = clamp(t.CurrentParam, 0, numSettableParams)
}
func (t *Tracker) getSelectionRange() (int, int, int, int) {
@ -443,6 +520,10 @@ func (t *Tracker) DeleteSelection() {
}
}
func (t *Tracker) Unselect() {
t.SelectionCorner = t.Cursor
}
func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tracker {
t := &Tracker{
Theme: material.NewTheme(gofont.Collection()),
@ -484,6 +565,8 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tr
VerticalSplit: new(Split),
ChooseUnitTypeList: &layout.List{Axis: layout.Vertical},
}
t.UnitDragList.HoverItem = -1
t.InstrumentDragList.HoverItem = -1
t.Octave.Value = 4
t.VerticalSplit.Axis = layout.Vertical
t.BottomHorizontalSplit.Ratio = -.5

View File

@ -21,11 +21,12 @@ func (t *Tracker) layoutUnitEditor(gtx C) D {
if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type == "" {
editorFunc = t.layoutUnitTypeChooser
}
paint.FillShape(gtx.Ops, unitSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
return Surface{Gray: 24, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Flexed(1, editorFunc),
layout.Rigid(t.layoutUnitFooter()),
)
layout.Rigid(t.layoutUnitFooter()))
})
}
func (t *Tracker) layoutUnitSliders(gtx C) D {
@ -41,12 +42,6 @@ func (t *Tracker) layoutUnitSliders(gtx C) D {
t.ParameterSliders = append(t.ParameterSliders, new(widget.Float))
}
params := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters
for t.ParameterSliders[index].Changed() {
params[ut[index].Name] = int(t.ParameterSliders[index].Value)
// TODO: tracker should have functions to update parameters and
// to do this efficiently i.e. not compile the whole patch again
t.LoadSong(t.song)
}
t.ParameterSliders[index].Value = float32(params[ut[index].Name])
sliderStyle := material.Slider(t.Theme, t.ParameterSliders[index], float32(ut[index].MinValue), float32(ut[index].MaxValue))
sliderStyle.Color = t.Theme.Fg
@ -65,7 +60,19 @@ func (t *Tracker) layoutUnitSliders(gtx C) D {
}),
layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Px(unit.Dp(200))
return sliderStyle.Layout(gtx)
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40))
if t.EditMode == EditParameters && t.CurrentParam == index {
paint.FillShape(gtx.Ops, cursorColor, clip.Rect{
Max: gtx.Constraints.Min,
}.Op())
}
dims := sliderStyle.Layout(gtx)
for sliderStyle.Float.Changed() {
t.EditMode = EditParameters
t.CurrentParam = index
t.SetUnitParam(int(t.ParameterSliders[index].Value))
}
return dims
}),
layout.Rigid(Label(valueText, white)),
)
@ -108,7 +115,6 @@ func (t *Tracker) layoutUnitFooter() layout.Widget {
}
func (t *Tracker) layoutUnitTypeChooser(gtx C) D {
paint.FillShape(gtx.Ops, unitSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
listElem := func(gtx C, i int) D {
for t.ChooseUnitTypeBtns[i].Clicked() {
t.SetUnit(allUnits[i])