mirror of
https://github.com/vsariola/sointu.git
synced 2025-11-12 21:02:52 -05:00
refactor(tracker, gioui): get rid of EditMode, use gio focus instead
This commit is contained in:
@ -4,6 +4,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@ -20,12 +21,16 @@ type DragList struct {
|
|||||||
dragID pointer.ID
|
dragID pointer.ID
|
||||||
tags []bool
|
tags []bool
|
||||||
swapped bool
|
swapped bool
|
||||||
|
focused bool
|
||||||
|
requestFocus bool
|
||||||
|
mainTag bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilledDragListStyle struct {
|
type FilledDragListStyle struct {
|
||||||
dragList *DragList
|
dragList *DragList
|
||||||
HoverColor color.NRGBA
|
HoverColor color.NRGBA
|
||||||
SelectedColor color.NRGBA
|
SelectedColor color.NRGBA
|
||||||
|
CursorColor color.NRGBA
|
||||||
Count int
|
Count int
|
||||||
element func(gtx C, i int) D
|
element func(gtx C, i int) D
|
||||||
swap func(i, j int)
|
swap func(i, j int)
|
||||||
@ -39,9 +44,18 @@ func FilledDragList(th *material.Theme, dragList *DragList, count int, element f
|
|||||||
Count: count,
|
Count: count,
|
||||||
HoverColor: dragListHoverColor,
|
HoverColor: dragListHoverColor,
|
||||||
SelectedColor: dragListSelectedColor,
|
SelectedColor: dragListSelectedColor,
|
||||||
|
CursorColor: cursorColor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DragList) Focus() {
|
||||||
|
d.requestFocus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DragList) Focused() bool {
|
||||||
|
return d.focused
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FilledDragListStyle) Layout(gtx C) D {
|
func (s *FilledDragListStyle) Layout(gtx C) D {
|
||||||
swap := 0
|
swap := 0
|
||||||
|
|
||||||
@ -53,6 +67,40 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
|
|||||||
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.dragList.requestFocus {
|
||||||
|
s.dragList.requestFocus = false
|
||||||
|
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ke := range gtx.Events(&s.dragList.mainTag) {
|
||||||
|
switch ke := ke.(type) {
|
||||||
|
case key.FocusEvent:
|
||||||
|
s.dragList.focused = ke.Focus
|
||||||
|
case key.Event:
|
||||||
|
if !s.dragList.focused || ke.State != key.Press {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delta := 0
|
||||||
|
switch {
|
||||||
|
case s.dragList.List.Axis == layout.Horizontal && ke.Name == key.NameLeftArrow && s.dragList.SelectedItem > 0:
|
||||||
|
delta = -1
|
||||||
|
case s.dragList.List.Axis == layout.Horizontal && ke.Name == key.NameRightArrow && s.dragList.SelectedItem < s.Count-1:
|
||||||
|
delta = 1
|
||||||
|
case s.dragList.List.Axis == layout.Vertical && ke.Name == key.NameUpArrow && s.dragList.SelectedItem > 0:
|
||||||
|
delta = -1
|
||||||
|
case s.dragList.List.Axis == layout.Vertical && ke.Name == key.NameDownArrow && s.dragList.SelectedItem < s.Count-1:
|
||||||
|
delta = 1
|
||||||
|
}
|
||||||
|
if delta != 0 {
|
||||||
|
if ke.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
swap = delta
|
||||||
|
} else {
|
||||||
|
s.dragList.SelectedItem += delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
listElem := func(gtx C, index int) D {
|
listElem := func(gtx C, index int) D {
|
||||||
for len(s.dragList.tags) <= index {
|
for len(s.dragList.tags) <= index {
|
||||||
s.dragList.tags = append(s.dragList.tags, false)
|
s.dragList.tags = append(s.dragList.tags, false)
|
||||||
@ -60,13 +108,18 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
|
|||||||
bg := func(gtx C) D {
|
bg := func(gtx C) D {
|
||||||
var color color.NRGBA
|
var color color.NRGBA
|
||||||
if s.dragList.SelectedItem == index {
|
if s.dragList.SelectedItem == index {
|
||||||
|
if s.dragList.focused {
|
||||||
|
color = s.CursorColor
|
||||||
|
} else {
|
||||||
color = s.SelectedColor
|
color = s.SelectedColor
|
||||||
|
}
|
||||||
} else if s.dragList.HoverItem == index {
|
} else if s.dragList.HoverItem == index {
|
||||||
color = s.HoverColor
|
color = s.HoverColor
|
||||||
}
|
}
|
||||||
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
||||||
return D{Size: gtx.Constraints.Min}
|
return D{Size: gtx.Constraints.Min}
|
||||||
}
|
}
|
||||||
|
|
||||||
inputFg := func(gtx C) D {
|
inputFg := func(gtx C) D {
|
||||||
defer op.Save(gtx.Ops).Load()
|
defer op.Save(gtx.Ops).Load()
|
||||||
for _, ev := range gtx.Events(&s.dragList.tags[index]) {
|
for _, ev := range gtx.Events(&s.dragList.tags[index]) {
|
||||||
@ -86,6 +139,7 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
s.dragList.SelectedItem = index
|
s.dragList.SelectedItem = index
|
||||||
|
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
|
rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
|
||||||
@ -141,6 +195,7 @@ func (s *FilledDragListStyle) Layout(gtx C) D {
|
|||||||
}),
|
}),
|
||||||
layout.Expanded(inputFg))
|
layout.Expanded(inputFg))
|
||||||
}
|
}
|
||||||
|
key.InputOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
|
||||||
dims := s.dragList.List.Layout(gtx, s.Count, listElem)
|
dims := s.dragList.List.Layout(gtx, s.Count, listElem)
|
||||||
if !s.dragList.swapped && swap != 0 && s.dragList.SelectedItem+swap >= 0 && s.dragList.SelectedItem+swap < s.Count {
|
if !s.dragList.swapped && swap != 0 && s.dragList.SelectedItem+swap >= 0 && s.dragList.SelectedItem+swap < s.Count {
|
||||||
s.swap(s.dragList.SelectedItem, s.dragList.SelectedItem+swap)
|
s.swap(s.dragList.SelectedItem, s.dragList.SelectedItem+swap)
|
||||||
|
|||||||
@ -175,7 +175,7 @@ func (t *Tracker) loadInstrument(filename string) bool {
|
|||||||
}
|
}
|
||||||
t.SetInstrument(instrument)
|
t.SetInstrument(instrument)
|
||||||
if t.Instrument().Comment != "" {
|
if t.Instrument().Comment != "" {
|
||||||
t.InstrumentExpanded = true
|
t.InstrumentEditor.ExpandComment()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
449
tracker/gioui/instrumenteditor.go
Normal file
449
tracker/gioui/instrumenteditor.go
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
package gioui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gioui.org/io/clipboard"
|
||||||
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/io/pointer"
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/op"
|
||||||
|
"gioui.org/text"
|
||||||
|
"gioui.org/unit"
|
||||||
|
"gioui.org/widget"
|
||||||
|
"gioui.org/widget/material"
|
||||||
|
"gioui.org/x/eventx"
|
||||||
|
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InstrumentEditor struct {
|
||||||
|
newInstrumentBtn *widget.Clickable
|
||||||
|
deleteInstrumentBtn *widget.Clickable
|
||||||
|
copyInstrumentBtn *widget.Clickable
|
||||||
|
saveInstrumentBtn *widget.Clickable
|
||||||
|
loadInstrumentBtn *widget.Clickable
|
||||||
|
addUnitBtn *widget.Clickable
|
||||||
|
commentExpandBtn *widget.Clickable
|
||||||
|
commentEditor *widget.Editor
|
||||||
|
nameEditor *widget.Editor
|
||||||
|
instrumentDragList *DragList
|
||||||
|
instrumentScrollBar *ScrollBar
|
||||||
|
unitDragList *DragList
|
||||||
|
unitScrollBar *ScrollBar
|
||||||
|
confirmInstrDelete *Dialog
|
||||||
|
paramEditor *ParamEditor
|
||||||
|
stackUse []int
|
||||||
|
tag bool
|
||||||
|
wasFocused bool
|
||||||
|
commentExpanded bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInstrumentEditor() *InstrumentEditor {
|
||||||
|
return &InstrumentEditor{
|
||||||
|
newInstrumentBtn: new(widget.Clickable),
|
||||||
|
deleteInstrumentBtn: new(widget.Clickable),
|
||||||
|
copyInstrumentBtn: new(widget.Clickable),
|
||||||
|
saveInstrumentBtn: new(widget.Clickable),
|
||||||
|
loadInstrumentBtn: new(widget.Clickable),
|
||||||
|
addUnitBtn: new(widget.Clickable),
|
||||||
|
commentExpandBtn: new(widget.Clickable),
|
||||||
|
commentEditor: new(widget.Editor),
|
||||||
|
nameEditor: &widget.Editor{SingleLine: true, Submit: true, Alignment: text.Middle},
|
||||||
|
instrumentDragList: &DragList{List: &layout.List{Axis: layout.Horizontal}, HoverItem: -1},
|
||||||
|
instrumentScrollBar: &ScrollBar{Axis: layout.Horizontal},
|
||||||
|
unitDragList: &DragList{List: &layout.List{Axis: layout.Vertical}, HoverItem: -1},
|
||||||
|
unitScrollBar: &ScrollBar{Axis: layout.Vertical},
|
||||||
|
confirmInstrDelete: new(Dialog),
|
||||||
|
paramEditor: NewParamEditor(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *InstrumentEditor) ExpandComment() {
|
||||||
|
t.commentExpanded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ie *InstrumentEditor) Focus() {
|
||||||
|
ie.unitDragList.Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ie *InstrumentEditor) Focused() bool {
|
||||||
|
return ie.unitDragList.focused
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ie *InstrumentEditor) ChildFocused() bool {
|
||||||
|
return ie.paramEditor.Focused() || ie.instrumentDragList.Focused() || ie.commentEditor.Focused() || ie.nameEditor.Focused()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
|
||||||
|
ie.wasFocused = ie.Focused() || ie.ChildFocused()
|
||||||
|
for _, e := range gtx.Events(&ie.tag) {
|
||||||
|
switch e.(type) {
|
||||||
|
case pointer.Event:
|
||||||
|
ie.unitDragList.Focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
||||||
|
pointer.Rect(rect).Add(gtx.Ops)
|
||||||
|
pointer.InputOp{Tag: &ie.tag,
|
||||||
|
Types: pointer.Press,
|
||||||
|
}.Add(gtx.Ops)
|
||||||
|
for ie.newInstrumentBtn.Clicked() {
|
||||||
|
t.AddInstrument(true)
|
||||||
|
}
|
||||||
|
btnStyle := IconButton(t.Theme, ie.newInstrumentBtn, icons.ContentAdd, t.CanAddInstrument())
|
||||||
|
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx C) D {
|
||||||
|
return layout.Flex{}.Layout(
|
||||||
|
gtx,
|
||||||
|
layout.Flexed(1, func(gtx C) D {
|
||||||
|
return layout.Stack{}.Layout(gtx,
|
||||||
|
layout.Stacked(func(gtx C) D {
|
||||||
|
return ie.layoutInstrumentNames(gtx, t)
|
||||||
|
}),
|
||||||
|
layout.Expanded(func(gtx C) D {
|
||||||
|
return ie.instrumentScrollBar.Layout(gtx, unit.Dp(6), len(t.Song().Patch), &ie.instrumentDragList.List.Position)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
layout.Rigid(func(gtx C) D {
|
||||||
|
return layout.E.Layout(gtx, btnStyle.Layout)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
layout.Rigid(func(gtx C) D {
|
||||||
|
return ie.layoutInstrumentHeader(gtx, t)
|
||||||
|
}),
|
||||||
|
layout.Flexed(1, func(gtx C) D {
|
||||||
|
return ie.layoutInstrumentEditor(gtx, t)
|
||||||
|
}))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
|
||||||
|
header := func(gtx C) D {
|
||||||
|
collapseIcon := icons.NavigationExpandLess
|
||||||
|
if ie.commentExpanded {
|
||||||
|
collapseIcon = icons.NavigationExpandMore
|
||||||
|
}
|
||||||
|
|
||||||
|
commentExpandBtnStyle := IconButton(t.Theme, ie.commentExpandBtn, collapseIcon, true)
|
||||||
|
copyInstrumentBtnStyle := IconButton(t.Theme, ie.copyInstrumentBtn, icons.ContentContentCopy, true)
|
||||||
|
saveInstrumentBtnStyle := IconButton(t.Theme, ie.saveInstrumentBtn, icons.ContentSave, true)
|
||||||
|
loadInstrumentBtnStyle := IconButton(t.Theme, ie.loadInstrumentBtn, icons.FileFolderOpen, true)
|
||||||
|
deleteInstrumentBtnStyle := IconButton(t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, t.CanDeleteInstrument())
|
||||||
|
|
||||||
|
header := func(gtx C) D {
|
||||||
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Rigid(Label("Voices: ", white)),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
maxRemain := t.MaxInstrumentVoices()
|
||||||
|
t.InstrumentVoices.Value = t.Instrument().NumVoices
|
||||||
|
numStyle := NumericUpDown(t.Theme, t.InstrumentVoices, 0, maxRemain)
|
||||||
|
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20))
|
||||||
|
gtx.Constraints.Min.X = gtx.Px(unit.Dp(70))
|
||||||
|
dims := numStyle.Layout(gtx)
|
||||||
|
t.SetInstrumentVoices(t.InstrumentVoices.Value)
|
||||||
|
return dims
|
||||||
|
}),
|
||||||
|
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||||
|
layout.Rigid(commentExpandBtnStyle.Layout),
|
||||||
|
layout.Rigid(saveInstrumentBtnStyle.Layout),
|
||||||
|
layout.Rigid(loadInstrumentBtnStyle.Layout),
|
||||||
|
layout.Rigid(copyInstrumentBtnStyle.Layout),
|
||||||
|
layout.Rigid(deleteInstrumentBtnStyle.Layout))
|
||||||
|
}
|
||||||
|
for ie.commentExpandBtn.Clicked() {
|
||||||
|
ie.commentExpanded = !ie.commentExpanded
|
||||||
|
if !ie.commentExpanded {
|
||||||
|
key.FocusOp{Tag: &ie.tag}.Add(gtx.Ops) // clear focus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ie.commentExpanded || ie.commentEditor.Focused() { // we draw once the widget after it manages to lose focus
|
||||||
|
if ie.commentEditor.Text() != t.Instrument().Comment {
|
||||||
|
ie.commentEditor.SetText(t.Instrument().Comment)
|
||||||
|
}
|
||||||
|
editorStyle := material.Editor(t.Theme, ie.commentEditor, "Comment")
|
||||||
|
editorStyle.Color = highEmphasisTextColor
|
||||||
|
|
||||||
|
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(header),
|
||||||
|
layout.Rigid(func(gtx C) D {
|
||||||
|
spy, spiedGtx := eventx.Enspy(gtx)
|
||||||
|
ret := layout.UniformInset(unit.Dp(6)).Layout(spiedGtx, editorStyle.Layout)
|
||||||
|
for _, group := range spy.AllEvents() {
|
||||||
|
for _, event := range group.Items {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case key.Event:
|
||||||
|
if e.Name == key.NameEscape {
|
||||||
|
ie.instrumentDragList.Focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
t.SetInstrumentComment(ie.commentEditor.Text())
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return header(gtx)
|
||||||
|
}
|
||||||
|
for ie.copyInstrumentBtn.Clicked() {
|
||||||
|
contents, err := yaml.Marshal(t.Instrument())
|
||||||
|
if err == nil {
|
||||||
|
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
|
||||||
|
t.Alert.Update("Instrument copied to clipboard", Notify, time.Second*3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ie.deleteInstrumentBtn.Clicked() && t.ModalDialog == nil {
|
||||||
|
dialogStyle := ConfirmDialog(t.Theme, ie.confirmInstrDelete, "Are you sure you want to delete this instrument?")
|
||||||
|
ie.confirmInstrDelete.Visible = true
|
||||||
|
t.ModalDialog = dialogStyle.Layout
|
||||||
|
}
|
||||||
|
for ie.confirmInstrDelete.BtnOk.Clicked() {
|
||||||
|
t.DeleteInstrument(false)
|
||||||
|
t.ModalDialog = nil
|
||||||
|
}
|
||||||
|
for ie.confirmInstrDelete.BtnCancel.Clicked() {
|
||||||
|
t.ModalDialog = nil
|
||||||
|
}
|
||||||
|
for ie.saveInstrumentBtn.Clicked() {
|
||||||
|
t.SaveInstrument()
|
||||||
|
}
|
||||||
|
|
||||||
|
for ie.loadInstrumentBtn.Clicked() {
|
||||||
|
t.LoadInstrument()
|
||||||
|
}
|
||||||
|
return Surface{Gray: 37, Focus: ie.wasFocused}.Layout(gtx, header)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ie *InstrumentEditor) layoutInstrumentNames(gtx C, t *Tracker) D {
|
||||||
|
element := func(gtx C, i int) D {
|
||||||
|
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(36))
|
||||||
|
gtx.Constraints.Min.X = gtx.Px(unit.Dp(30))
|
||||||
|
grabhandle := LabelStyle{Text: "", ShadeColor: black, Color: white, FontSize: unit.Sp(10), Alignment: layout.Center}
|
||||||
|
if i == t.InstrIndex() {
|
||||||
|
grabhandle.Text = ":::"
|
||||||
|
}
|
||||||
|
label := func(gtx C) D {
|
||||||
|
c := 0.0
|
||||||
|
voice := t.Song().Patch.FirstVoiceForInstrument(i)
|
||||||
|
for j := 0; j < t.Song().Patch[i].NumVoices; j++ {
|
||||||
|
released, event := t.player.VoiceState(voice)
|
||||||
|
vc := math.Exp(-float64(event)/15000) * .5
|
||||||
|
if !released {
|
||||||
|
vc += .5
|
||||||
|
}
|
||||||
|
if c < vc {
|
||||||
|
c = vc
|
||||||
|
}
|
||||||
|
voice++
|
||||||
|
}
|
||||||
|
k := byte(255 - c*127)
|
||||||
|
color := color.NRGBA{R: 255, G: k, B: 255, A: 255}
|
||||||
|
if i == t.InstrIndex() {
|
||||||
|
for _, ev := range ie.nameEditor.Events() {
|
||||||
|
_, ok := ev.(widget.SubmitEvent)
|
||||||
|
if ok {
|
||||||
|
ie.instrumentDragList.Focus()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n := t.Instrument().Name; n != ie.nameEditor.Text() {
|
||||||
|
ie.nameEditor.SetText(n)
|
||||||
|
}
|
||||||
|
editor := material.Editor(t.Theme, ie.nameEditor, "Instr")
|
||||||
|
editor.Color = color
|
||||||
|
editor.HintColor = instrumentNameHintColor
|
||||||
|
editor.TextSize = unit.Dp(12)
|
||||||
|
dims := layout.Center.Layout(gtx, editor.Layout)
|
||||||
|
t.SetInstrumentName(ie.nameEditor.Text())
|
||||||
|
return dims
|
||||||
|
}
|
||||||
|
text := t.Song().Patch[i].Name
|
||||||
|
if text == "" {
|
||||||
|
text = "Instr"
|
||||||
|
}
|
||||||
|
labelStyle := LabelStyle{Text: text, ShadeColor: black, Color: color, FontSize: unit.Sp(12)}
|
||||||
|
return layout.Center.Layout(gtx, labelStyle.Layout)
|
||||||
|
}
|
||||||
|
return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6)}.Layout(gtx, func(gtx C) D {
|
||||||
|
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Rigid(grabhandle.Layout),
|
||||||
|
layout.Rigid(label),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
color := inactiveLightSurfaceColor
|
||||||
|
if ie.wasFocused {
|
||||||
|
color = activeLightSurfaceColor
|
||||||
|
}
|
||||||
|
instrumentList := FilledDragList(t.Theme, ie.instrumentDragList, len(t.Song().Patch), element, t.SwapInstruments)
|
||||||
|
instrumentList.SelectedColor = color
|
||||||
|
instrumentList.HoverColor = instrumentHoverColor
|
||||||
|
|
||||||
|
ie.instrumentDragList.SelectedItem = t.InstrIndex()
|
||||||
|
defer op.Save(gtx.Ops).Load()
|
||||||
|
pointer.PassOp{Pass: true}.Add(gtx.Ops)
|
||||||
|
spy, spiedGtx := eventx.Enspy(gtx)
|
||||||
|
dims := instrumentList.Layout(spiedGtx)
|
||||||
|
for _, group := range spy.AllEvents() {
|
||||||
|
for _, event := range group.Items {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case key.Event:
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !ie.nameEditor.Focused() {
|
||||||
|
switch e.State {
|
||||||
|
case key.Press:
|
||||||
|
switch e.Name {
|
||||||
|
case key.NameDownArrow:
|
||||||
|
ie.unitDragList.Focus()
|
||||||
|
case key.NameReturn, key.NameEnter:
|
||||||
|
ie.nameEditor.Focus()
|
||||||
|
}
|
||||||
|
t.JammingPressed(e)
|
||||||
|
case key.Release:
|
||||||
|
t.JammingReleased(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.InstrIndex() != ie.instrumentDragList.SelectedItem {
|
||||||
|
t.SetInstrIndex(ie.instrumentDragList.SelectedItem)
|
||||||
|
op.InvalidateOp{}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
return dims
|
||||||
|
}
|
||||||
|
func (ie *InstrumentEditor) layoutInstrumentEditor(gtx C, t *Tracker) D {
|
||||||
|
for ie.addUnitBtn.Clicked() {
|
||||||
|
t.AddUnit(true)
|
||||||
|
ie.unitDragList.Focus()
|
||||||
|
}
|
||||||
|
addUnitBtnStyle := material.IconButton(t.Theme, ie.addUnitBtn, widgetForIcon(icons.ContentAdd))
|
||||||
|
addUnitBtnStyle.Color = t.Theme.ContrastFg
|
||||||
|
addUnitBtnStyle.Background = t.Theme.Fg
|
||||||
|
addUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(4))
|
||||||
|
|
||||||
|
units := t.Instrument().Units
|
||||||
|
for len(ie.stackUse) < len(units) {
|
||||||
|
ie.stackUse = append(ie.stackUse, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
stackHeight := 0
|
||||||
|
for i, u := range units {
|
||||||
|
stackHeight += u.StackChange()
|
||||||
|
ie.stackUse[i] = stackHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
element := func(gtx C, i int) D {
|
||||||
|
gtx.Constraints = layout.Exact(image.Pt(gtx.Px(unit.Dp(120)), gtx.Px(unit.Dp(20))))
|
||||||
|
u := units[i]
|
||||||
|
unitNameLabel := LabelStyle{Text: u.Type, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
||||||
|
if unitNameLabel.Text == "" {
|
||||||
|
unitNameLabel.Text = "---"
|
||||||
|
unitNameLabel.Alignment = layout.Center
|
||||||
|
}
|
||||||
|
var stackText string
|
||||||
|
if i < len(ie.stackUse) {
|
||||||
|
stackText = strconv.FormatInt(int64(ie.stackUse[i]), 10)
|
||||||
|
var prevStackUse int
|
||||||
|
if i > 0 {
|
||||||
|
prevStackUse = ie.stackUse[i-1]
|
||||||
|
}
|
||||||
|
if stackNeed := u.StackNeed(); stackNeed > prevStackUse {
|
||||||
|
unitNameLabel.Color = errorColor
|
||||||
|
typeString := u.Type
|
||||||
|
if u.Parameters["stereo"] == 1 {
|
||||||
|
typeString += " (stereo)"
|
||||||
|
}
|
||||||
|
t.Alert.Update(fmt.Sprintf("%v needs at least %v input signals, got %v", typeString, stackNeed, prevStackUse), Error, 0)
|
||||||
|
} else if i == len(units)-1 && ie.stackUse[i] != 0 {
|
||||||
|
unitNameLabel.Color = warningColor
|
||||||
|
t.Alert.Update(fmt.Sprintf("Instrument leaves %v signal(s) on the stack", ie.stackUse[i]), Warning, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stackLabel := LabelStyle{Text: stackText, ShadeColor: black, Color: mediumEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
||||||
|
rightMargin := layout.Inset{Right: unit.Dp(10)}
|
||||||
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
|
layout.Flexed(1, unitNameLabel.Layout),
|
||||||
|
layout.Rigid(func(gtx C) D {
|
||||||
|
return rightMargin.Layout(gtx, stackLabel.Layout)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
unitList := FilledDragList(t.Theme, ie.unitDragList, len(units), element, t.SwapUnits)
|
||||||
|
ie.unitDragList.SelectedItem = t.UnitIndex()
|
||||||
|
return Surface{Gray: 30, Focus: ie.wasFocused}.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,
|
||||||
|
layout.Expanded(func(gtx C) D {
|
||||||
|
spy, spiedGtx := eventx.Enspy(gtx)
|
||||||
|
dims := unitList.Layout(spiedGtx)
|
||||||
|
prevUnitIndex := t.UnitIndex()
|
||||||
|
if t.UnitIndex() != ie.unitDragList.SelectedItem {
|
||||||
|
t.SetUnitIndex(ie.unitDragList.SelectedItem)
|
||||||
|
}
|
||||||
|
for _, group := range spy.AllEvents() {
|
||||||
|
for _, event := range group.Items {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case key.Event:
|
||||||
|
switch e.State {
|
||||||
|
case key.Press:
|
||||||
|
switch e.Name {
|
||||||
|
case key.NameUpArrow:
|
||||||
|
if prevUnitIndex == 0 {
|
||||||
|
ie.instrumentDragList.Focus()
|
||||||
|
}
|
||||||
|
case key.NameRightArrow:
|
||||||
|
ie.paramEditor.Focus()
|
||||||
|
case key.NameDeleteForward, key.NameDeleteBackward:
|
||||||
|
t.DeleteUnit(e.Name == key.NameDeleteForward)
|
||||||
|
case key.NameReturn:
|
||||||
|
t.AddUnit(!e.Modifiers.Contain(key.ModShortcut))
|
||||||
|
}
|
||||||
|
name := e.Name
|
||||||
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
}
|
||||||
|
if val, ok := unitKeyMap[name]; ok {
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
t.SetUnitType(val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.JammingPressed(e)
|
||||||
|
case key.Release:
|
||||||
|
t.JammingReleased(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dims
|
||||||
|
}),
|
||||||
|
layout.Expanded(func(gtx C) D {
|
||||||
|
return ie.unitScrollBar.Layout(gtx, unit.Dp(10), len(t.Instrument().Units), &ie.unitDragList.List.Position)
|
||||||
|
}),
|
||||||
|
layout.Stacked(func(gtx C) D {
|
||||||
|
margin := layout.Inset{Right: unit.Dp(20), Bottom: unit.Dp(1)}
|
||||||
|
return margin.Layout(gtx, addUnitBtnStyle.Layout)
|
||||||
|
}))
|
||||||
|
}),
|
||||||
|
layout.Rigid(ie.paramEditor.Bind(t)))
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,338 +0,0 @@
|
|||||||
package gioui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gioui.org/io/clipboard"
|
|
||||||
"gioui.org/io/key"
|
|
||||||
"gioui.org/io/pointer"
|
|
||||||
"gioui.org/layout"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/text"
|
|
||||||
"gioui.org/unit"
|
|
||||||
"gioui.org/widget"
|
|
||||||
"gioui.org/widget/material"
|
|
||||||
"gioui.org/x/eventx"
|
|
||||||
"github.com/vsariola/sointu/tracker"
|
|
||||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var instrumentPointerTag = false
|
|
||||||
|
|
||||||
func (t *Tracker) layoutInstruments(gtx C) D {
|
|
||||||
for _, ev := range gtx.Events(&instrumentPointerTag) {
|
|
||||||
e, ok := ev.(pointer.Event)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if e.Type == pointer.Press && (t.EditMode() != tracker.EditUnits && t.EditMode() != tracker.EditParameters) {
|
|
||||||
t.SetEditMode(tracker.EditUnits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
|
||||||
pointer.Rect(rect).Add(gtx.Ops)
|
|
||||||
pointer.InputOp{Tag: &instrumentPointerTag,
|
|
||||||
Types: pointer.Press,
|
|
||||||
}.Add(gtx.Ops)
|
|
||||||
for t.NewInstrumentBtn.Clicked() {
|
|
||||||
t.AddInstrument(true)
|
|
||||||
}
|
|
||||||
btnStyle := IconButton(t.Theme, t.NewInstrumentBtn, icons.ContentAdd, t.CanAddInstrument())
|
|
||||||
spy, spiedGtx := eventx.Enspy(gtx)
|
|
||||||
ret := layout.Flex{Axis: layout.Vertical}.Layout(spiedGtx,
|
|
||||||
layout.Rigid(func(gtx C) D {
|
|
||||||
return layout.Flex{}.Layout(
|
|
||||||
gtx,
|
|
||||||
layout.Flexed(1, func(gtx C) D {
|
|
||||||
return layout.Stack{}.Layout(gtx,
|
|
||||||
layout.Stacked(t.layoutInstrumentNames),
|
|
||||||
layout.Expanded(func(gtx C) D {
|
|
||||||
return t.InstrumentScrollBar.Layout(gtx, unit.Dp(6), len(t.Song().Patch), &t.InstrumentDragList.List.Position)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
layout.Rigid(func(gtx C) D {
|
|
||||||
return layout.E.Layout(gtx, btnStyle.Layout)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
layout.Rigid(t.layoutInstrumentHeader),
|
|
||||||
layout.Flexed(1, t.layoutInstrumentEditor))
|
|
||||||
for _, group := range spy.AllEvents() {
|
|
||||||
for _, event := range group.Items {
|
|
||||||
switch e := event.(type) {
|
|
||||||
case key.Event:
|
|
||||||
if e.Name == key.NameEscape {
|
|
||||||
key.FocusOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tracker) layoutInstrumentHeader(gtx C) D {
|
|
||||||
header := func(gtx C) D {
|
|
||||||
collapseIcon := icons.NavigationExpandLess
|
|
||||||
if t.InstrumentExpanded {
|
|
||||||
collapseIcon = icons.NavigationExpandMore
|
|
||||||
}
|
|
||||||
|
|
||||||
instrumentExpandBtnStyle := IconButton(t.Theme, t.InstrumentExpandBtn, collapseIcon, true)
|
|
||||||
copyInstrumentBtnStyle := IconButton(t.Theme, t.CopyInstrumentBtn, icons.ContentContentCopy, true)
|
|
||||||
saveInstrumentBtnStyle := IconButton(t.Theme, t.SaveInstrumentBtn, icons.ContentSave, true)
|
|
||||||
loadInstrumentBtnStyle := IconButton(t.Theme, t.LoadInstrumentBtn, icons.FileFolderOpen, true)
|
|
||||||
deleteInstrumentBtnStyle := IconButton(t.Theme, t.DeleteInstrumentBtn, icons.ActionDelete, t.CanDeleteInstrument())
|
|
||||||
|
|
||||||
header := func(gtx C) D {
|
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
|
||||||
layout.Rigid(Label("Voices: ", white)),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
maxRemain := t.MaxInstrumentVoices()
|
|
||||||
t.InstrumentVoices.Value = t.Instrument().NumVoices
|
|
||||||
numStyle := NumericUpDown(t.Theme, t.InstrumentVoices, 0, maxRemain)
|
|
||||||
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20))
|
|
||||||
gtx.Constraints.Min.X = gtx.Px(unit.Dp(70))
|
|
||||||
dims := numStyle.Layout(gtx)
|
|
||||||
t.SetInstrumentVoices(t.InstrumentVoices.Value)
|
|
||||||
return dims
|
|
||||||
}),
|
|
||||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
|
||||||
layout.Rigid(instrumentExpandBtnStyle.Layout),
|
|
||||||
layout.Rigid(saveInstrumentBtnStyle.Layout),
|
|
||||||
layout.Rigid(loadInstrumentBtnStyle.Layout),
|
|
||||||
layout.Rigid(copyInstrumentBtnStyle.Layout),
|
|
||||||
layout.Rigid(deleteInstrumentBtnStyle.Layout))
|
|
||||||
}
|
|
||||||
for t.InstrumentExpandBtn.Clicked() {
|
|
||||||
t.InstrumentExpanded = !t.InstrumentExpanded
|
|
||||||
if !t.InstrumentExpanded {
|
|
||||||
key.FocusOp{Tag: nil}.Add(gtx.Ops) // clear focus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.InstrumentExpanded || t.InstrumentCommentEditor.Focused() { // we draw once the widget after it manages to lose focus
|
|
||||||
if t.InstrumentCommentEditor.Text() != t.Instrument().Comment {
|
|
||||||
t.InstrumentCommentEditor.SetText(t.Instrument().Comment)
|
|
||||||
}
|
|
||||||
editorStyle := material.Editor(t.Theme, t.InstrumentCommentEditor, "Comment")
|
|
||||||
editorStyle.Color = highEmphasisTextColor
|
|
||||||
|
|
||||||
spy, spiedGtx := eventx.Enspy(gtx)
|
|
||||||
ret := layout.Flex{Axis: layout.Vertical}.Layout(spiedGtx,
|
|
||||||
layout.Rigid(header),
|
|
||||||
layout.Rigid(func(gtx C) D {
|
|
||||||
return layout.UniformInset(unit.Dp(6)).Layout(gtx, editorStyle.Layout)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
for _, group := range spy.AllEvents() {
|
|
||||||
for _, event := range group.Items {
|
|
||||||
switch e := event.(type) {
|
|
||||||
case key.Event:
|
|
||||||
if e.Name == key.NameEscape {
|
|
||||||
key.FocusOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.SetInstrumentComment(t.InstrumentCommentEditor.Text())
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
return header(gtx)
|
|
||||||
}
|
|
||||||
for t.CopyInstrumentBtn.Clicked() {
|
|
||||||
contents, err := yaml.Marshal(t.Instrument())
|
|
||||||
if err == nil {
|
|
||||||
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
|
|
||||||
t.Alert.Update("Instrument copied to clipboard", Notify, time.Second*3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for t.DeleteInstrumentBtn.Clicked() {
|
|
||||||
t.ConfirmInstrDelete.Visible = true
|
|
||||||
}
|
|
||||||
for t.ConfirmInstrDelete.BtnOk.Clicked() {
|
|
||||||
t.DeleteInstrument(false)
|
|
||||||
t.ConfirmInstrDelete.Visible = false
|
|
||||||
}
|
|
||||||
for t.ConfirmInstrDelete.BtnCancel.Clicked() {
|
|
||||||
t.ConfirmInstrDelete.Visible = false
|
|
||||||
}
|
|
||||||
for t.SaveInstrumentBtn.Clicked() {
|
|
||||||
t.SaveInstrument()
|
|
||||||
}
|
|
||||||
|
|
||||||
for t.LoadInstrumentBtn.Clicked() {
|
|
||||||
t.LoadInstrument()
|
|
||||||
}
|
|
||||||
return Surface{Gray: 37, Focus: t.EditMode() == tracker.EditUnits || t.EditMode() == tracker.EditParameters}.Layout(gtx, header)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tracker) layoutInstrumentNames(gtx C) D {
|
|
||||||
element := func(gtx C, i int) D {
|
|
||||||
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(36))
|
|
||||||
gtx.Constraints.Min.X = gtx.Px(unit.Dp(30))
|
|
||||||
grabhandle := LabelStyle{Text: "", ShadeColor: black, Color: white, FontSize: unit.Sp(10), Alignment: layout.Center}
|
|
||||||
if i == t.InstrIndex() {
|
|
||||||
grabhandle.Text = ":::"
|
|
||||||
}
|
|
||||||
label := func(gtx C) D {
|
|
||||||
c := 0.0
|
|
||||||
voice := t.Song().Patch.FirstVoiceForInstrument(i)
|
|
||||||
for j := 0; j < t.Song().Patch[i].NumVoices; j++ {
|
|
||||||
released, event := t.player.VoiceState(voice)
|
|
||||||
vc := math.Exp(-float64(event)/15000) * .5
|
|
||||||
if !released {
|
|
||||||
vc += .5
|
|
||||||
}
|
|
||||||
if c < vc {
|
|
||||||
c = vc
|
|
||||||
}
|
|
||||||
voice++
|
|
||||||
}
|
|
||||||
k := byte(255 - c*127)
|
|
||||||
color := color.NRGBA{R: 255, G: k, B: 255, A: 255}
|
|
||||||
if i == t.InstrIndex() {
|
|
||||||
for _, ev := range t.InstrumentNameEditor.Events() {
|
|
||||||
_, ok := ev.(widget.SubmitEvent)
|
|
||||||
if ok {
|
|
||||||
t.InstrumentNameEditor = &widget.Editor{SingleLine: true, Submit: true, Alignment: text.Middle} // TODO: is there any other way to defocus the editor
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if n := t.Instrument().Name; n != t.InstrumentNameEditor.Text() {
|
|
||||||
t.InstrumentNameEditor.SetText(n)
|
|
||||||
}
|
|
||||||
editor := material.Editor(t.Theme, t.InstrumentNameEditor, "Instr")
|
|
||||||
editor.Color = color
|
|
||||||
editor.HintColor = instrumentNameHintColor
|
|
||||||
editor.TextSize = unit.Dp(12)
|
|
||||||
dims := layout.Center.Layout(gtx, editor.Layout)
|
|
||||||
t.SetInstrumentName(t.InstrumentNameEditor.Text())
|
|
||||||
return dims
|
|
||||||
}
|
|
||||||
text := t.Song().Patch[i].Name
|
|
||||||
if text == "" {
|
|
||||||
text = "Instr"
|
|
||||||
}
|
|
||||||
labelStyle := LabelStyle{Text: text, ShadeColor: black, Color: color, FontSize: unit.Sp(12)}
|
|
||||||
return layout.Center.Layout(gtx, labelStyle.Layout)
|
|
||||||
}
|
|
||||||
return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6)}.Layout(gtx, func(gtx C) D {
|
|
||||||
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
|
|
||||||
layout.Rigid(grabhandle.Layout),
|
|
||||||
layout.Rigid(label),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
color := inactiveLightSurfaceColor
|
|
||||||
if t.EditMode() == tracker.EditUnits || t.EditMode() == tracker.EditParameters {
|
|
||||||
color = activeLightSurfaceColor
|
|
||||||
}
|
|
||||||
instrumentList := FilledDragList(t.Theme, t.InstrumentDragList, len(t.Song().Patch), element, t.SwapInstruments)
|
|
||||||
instrumentList.SelectedColor = color
|
|
||||||
instrumentList.HoverColor = instrumentHoverColor
|
|
||||||
|
|
||||||
t.InstrumentDragList.SelectedItem = t.InstrIndex()
|
|
||||||
defer op.Save(gtx.Ops).Load()
|
|
||||||
pointer.PassOp{Pass: true}.Add(gtx.Ops)
|
|
||||||
dims := instrumentList.Layout(gtx)
|
|
||||||
if t.InstrIndex() != t.InstrumentDragList.SelectedItem {
|
|
||||||
t.SetInstrIndex(t.InstrumentDragList.SelectedItem)
|
|
||||||
op.InvalidateOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
return dims
|
|
||||||
}
|
|
||||||
func (t *Tracker) layoutInstrumentEditor(gtx C) D {
|
|
||||||
for t.AddUnitBtn.Clicked() {
|
|
||||||
t.AddUnit(true)
|
|
||||||
}
|
|
||||||
addUnitBtnStyle := material.IconButton(t.Theme, t.AddUnitBtn, widgetForIcon(icons.ContentAdd))
|
|
||||||
addUnitBtnStyle.Color = t.Theme.ContrastFg
|
|
||||||
addUnitBtnStyle.Background = t.Theme.Fg
|
|
||||||
addUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(4))
|
|
||||||
|
|
||||||
units := t.Instrument().Units
|
|
||||||
for len(t.StackUse) < len(units) {
|
|
||||||
t.StackUse = append(t.StackUse, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
stackHeight := 0
|
|
||||||
for i, u := range units {
|
|
||||||
stackHeight += u.StackChange()
|
|
||||||
t.StackUse[i] = stackHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
element := func(gtx C, i int) D {
|
|
||||||
gtx.Constraints = layout.Exact(image.Pt(gtx.Px(unit.Dp(120)), gtx.Px(unit.Dp(20))))
|
|
||||||
u := units[i]
|
|
||||||
unitNameLabel := LabelStyle{Text: u.Type, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
|
||||||
if unitNameLabel.Text == "" {
|
|
||||||
unitNameLabel.Text = "---"
|
|
||||||
unitNameLabel.Alignment = layout.Center
|
|
||||||
}
|
|
||||||
var stackText string
|
|
||||||
if i < len(t.StackUse) {
|
|
||||||
stackText = strconv.FormatInt(int64(t.StackUse[i]), 10)
|
|
||||||
var prevStackUse int
|
|
||||||
if i > 0 {
|
|
||||||
prevStackUse = t.StackUse[i-1]
|
|
||||||
}
|
|
||||||
if stackNeed := u.StackNeed(); stackNeed > prevStackUse {
|
|
||||||
unitNameLabel.Color = errorColor
|
|
||||||
typeString := u.Type
|
|
||||||
if u.Parameters["stereo"] == 1 {
|
|
||||||
typeString += " (stereo)"
|
|
||||||
}
|
|
||||||
t.Alert.Update(fmt.Sprintf("%v needs at least %v input signals, got %v", typeString, stackNeed, prevStackUse), Error, 0)
|
|
||||||
} else if i == len(units)-1 && t.StackUse[i] != 0 {
|
|
||||||
unitNameLabel.Color = warningColor
|
|
||||||
t.Alert.Update(fmt.Sprintf("Instrument leaves %v signal(s) on the stack", t.StackUse[i]), Warning, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stackLabel := LabelStyle{Text: stackText, ShadeColor: black, Color: mediumEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
|
||||||
rightMargin := layout.Inset{Right: unit.Dp(10)}
|
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
|
||||||
layout.Flexed(1, unitNameLabel.Layout),
|
|
||||||
layout.Rigid(func(gtx C) D {
|
|
||||||
return rightMargin.Layout(gtx, stackLabel.Layout)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
unitList := FilledDragList(t.Theme, t.UnitDragList, len(units), element, t.SwapUnits)
|
|
||||||
|
|
||||||
if t.EditMode() == tracker.EditUnits {
|
|
||||||
unitList.SelectedColor = cursorColor
|
|
||||||
}
|
|
||||||
|
|
||||||
t.UnitDragList.SelectedItem = t.UnitIndex()
|
|
||||||
return Surface{Gray: 30, Focus: t.EditMode() == tracker.EditUnits || t.EditMode() == tracker.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,
|
|
||||||
layout.Expanded(func(gtx C) D {
|
|
||||||
dims := unitList.Layout(gtx)
|
|
||||||
if t.UnitIndex() != t.UnitDragList.SelectedItem {
|
|
||||||
t.SetUnitIndex(t.UnitDragList.SelectedItem)
|
|
||||||
t.SetEditMode(tracker.EditUnits)
|
|
||||||
op.InvalidateOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
return dims
|
|
||||||
}),
|
|
||||||
layout.Expanded(func(gtx C) D {
|
|
||||||
return t.UnitScrollBar.Layout(gtx, unit.Dp(10), len(t.Instrument().Units), &t.UnitDragList.List.Position)
|
|
||||||
}),
|
|
||||||
layout.Stacked(func(gtx C) D {
|
|
||||||
margin := layout.Inset{Right: unit.Dp(20), Bottom: unit.Dp(1)}
|
|
||||||
return margin.Layout(gtx, addUnitBtnStyle.Layout)
|
|
||||||
}))
|
|
||||||
}),
|
|
||||||
layout.Rigid(t.layoutUnitEditor))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,11 +1,8 @@
|
|||||||
package gioui
|
package gioui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gioui.org/app"
|
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"github.com/vsariola/sointu/tracker"
|
"github.com/vsariola/sointu/tracker"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@ -79,11 +76,9 @@ var unitKeyMap = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
||||||
func (t *Tracker) KeyEvent(w *app.Window, e key.Event) bool {
|
func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||||
if e.State == key.Press {
|
if e.State == key.Press {
|
||||||
if t.InstrumentNameEditor.Focused() ||
|
if t.OpenSongDialog.Visible ||
|
||||||
t.InstrumentCommentEditor.Focused() ||
|
|
||||||
t.OpenSongDialog.Visible ||
|
|
||||||
t.SaveSongDialog.Visible ||
|
t.SaveSongDialog.Visible ||
|
||||||
t.SaveInstrumentDialog.Visible ||
|
t.SaveInstrumentDialog.Visible ||
|
||||||
t.OpenInstrumentDialog.Visible ||
|
t.OpenInstrumentDialog.Visible ||
|
||||||
@ -95,14 +90,14 @@ func (t *Tracker) KeyEvent(w *app.Window, e key.Event) bool {
|
|||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
contents, err := yaml.Marshal(t.Song())
|
contents, err := yaml.Marshal(t.Song())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
w.WriteClipboard(string(contents))
|
t.window.WriteClipboard(string(contents))
|
||||||
t.Alert.Update("Song copied to clipboard", Notify, time.Second*3)
|
t.Alert.Update("Song copied to clipboard", Notify, time.Second*3)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case "V":
|
case "V":
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
w.ReadClipboard()
|
t.window.ReadClipboard()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case "Z":
|
case "Z":
|
||||||
@ -131,21 +126,21 @@ func (t *Tracker) KeyEvent(w *app.Window, e key.Event) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case "F1":
|
case "F1":
|
||||||
t.SetEditMode(tracker.EditPatterns)
|
t.OrderEditor.Focus()
|
||||||
return true
|
return true
|
||||||
case "F2":
|
case "F2":
|
||||||
t.SetEditMode(tracker.EditTracks)
|
t.TrackEditor.Focus()
|
||||||
return true
|
return true
|
||||||
case "F3":
|
case "F3":
|
||||||
t.SetEditMode(tracker.EditUnits)
|
t.InstrumentEditor.Focus()
|
||||||
return true
|
return true
|
||||||
case "F4":
|
case "F4":
|
||||||
t.SetEditMode(tracker.EditParameters)
|
t.TrackEditor.Focus()
|
||||||
return true
|
return true
|
||||||
case "F5":
|
case "F5":
|
||||||
t.SetNoteTracking(true)
|
t.SetNoteTracking(true)
|
||||||
startRow := t.Cursor().SongRow
|
startRow := t.Cursor().SongRow
|
||||||
if t.EditMode() == tracker.EditPatterns {
|
if t.OrderEditor.Focused() {
|
||||||
startRow.Row = 0
|
startRow.Row = 0
|
||||||
}
|
}
|
||||||
t.player.Play(startRow)
|
t.player.Play(startRow)
|
||||||
@ -153,7 +148,7 @@ func (t *Tracker) KeyEvent(w *app.Window, e key.Event) bool {
|
|||||||
case "F6":
|
case "F6":
|
||||||
t.SetNoteTracking(false)
|
t.SetNoteTracking(false)
|
||||||
startRow := t.Cursor().SongRow
|
startRow := t.Cursor().SongRow
|
||||||
if t.EditMode() == tracker.EditPatterns {
|
if t.OrderEditor.Focused() {
|
||||||
startRow.Row = 0
|
startRow.Row = 0
|
||||||
}
|
}
|
||||||
t.player.Play(startRow)
|
t.player.Play(startRow)
|
||||||
@ -161,299 +156,48 @@ func (t *Tracker) KeyEvent(w *app.Window, e key.Event) bool {
|
|||||||
case "F8":
|
case "F8":
|
||||||
t.player.Stop()
|
t.player.Stop()
|
||||||
return true
|
return true
|
||||||
case key.NameDeleteForward, key.NameDeleteBackward:
|
|
||||||
switch t.EditMode() {
|
|
||||||
case tracker.EditPatterns:
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
t.DeleteOrderRow(e.Name == key.NameDeleteForward)
|
|
||||||
} else {
|
|
||||||
t.DeletePatternSelection()
|
|
||||||
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
|
||||||
t.SetCursor(t.Cursor().AddPatterns(1))
|
|
||||||
t.SetSelectionCorner(t.Cursor())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case tracker.EditTracks:
|
|
||||||
t.DeleteSelection()
|
|
||||||
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
|
||||||
t.SetCursor(t.Cursor().AddRows(t.Step.Value))
|
|
||||||
t.SetSelectionCorner(t.Cursor())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case tracker.EditUnits:
|
|
||||||
t.DeleteUnit(e.Name == key.NameDeleteForward)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case "Space":
|
case "Space":
|
||||||
_, playing := t.player.Position()
|
_, playing := t.player.Position()
|
||||||
if !playing {
|
if !playing {
|
||||||
t.SetNoteTracking(!e.Modifiers.Contain(key.ModShortcut))
|
t.SetNoteTracking(!e.Modifiers.Contain(key.ModShortcut))
|
||||||
startRow := t.Cursor().SongRow
|
startRow := t.Cursor().SongRow
|
||||||
if t.EditMode() == tracker.EditPatterns {
|
|
||||||
startRow.Row = 0
|
|
||||||
}
|
|
||||||
t.player.Play(startRow)
|
t.player.Play(startRow)
|
||||||
} else {
|
} else {
|
||||||
t.player.Stop()
|
t.player.Stop()
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
case `\`, `<`, `>`:
|
case `\`, `<`, `>`:
|
||||||
if e.Modifiers.Contain(key.ModShift) {
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
return t.SetOctave(t.Octave() + 1)
|
t.SetOctave(t.Octave() + 1)
|
||||||
|
} else {
|
||||||
|
t.SetOctave(t.Octave() - 1)
|
||||||
}
|
}
|
||||||
return t.SetOctave(t.Octave() - 1)
|
|
||||||
case key.NameTab:
|
case key.NameTab:
|
||||||
if e.Modifiers.Contain(key.ModShift) {
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
t.SetEditMode((t.EditMode() - 1 + 4) % 4)
|
switch {
|
||||||
} else {
|
case t.OrderEditor.Focused():
|
||||||
t.SetEditMode((t.EditMode() + 1) % 4)
|
t.InstrumentEditor.paramEditor.Focus()
|
||||||
}
|
case t.TrackEditor.Focused():
|
||||||
return true
|
t.OrderEditor.Focus()
|
||||||
case key.NameReturn:
|
case t.InstrumentEditor.Focused():
|
||||||
switch t.EditMode() {
|
t.TrackEditor.Focus()
|
||||||
case tracker.EditPatterns:
|
default:
|
||||||
t.AddOrderRow(!e.Modifiers.Contain(key.ModShortcut))
|
t.InstrumentEditor.Focus()
|
||||||
case tracker.EditUnits:
|
|
||||||
t.AddUnit(!e.Modifiers.Contain(key.ModShortcut))
|
|
||||||
}
|
|
||||||
case key.NameUpArrow:
|
|
||||||
cursor := t.Cursor()
|
|
||||||
switch t.EditMode() {
|
|
||||||
case tracker.EditPatterns:
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
cursor.SongRow = tracker.SongRow{}
|
|
||||||
} else {
|
|
||||||
cursor.Row -= t.Song().Score.RowsPerPattern
|
|
||||||
}
|
|
||||||
t.SetNoteTracking(false)
|
|
||||||
case tracker.EditTracks:
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
cursor.Row -= t.Song().Score.RowsPerPattern
|
|
||||||
} else {
|
|
||||||
if t.Step.Value > 0 {
|
|
||||||
cursor.Row -= t.Step.Value
|
|
||||||
} else {
|
|
||||||
cursor.Row--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.SetNoteTracking(false)
|
|
||||||
case tracker.EditUnits:
|
|
||||||
t.SetUnitIndex(t.UnitIndex() - 1)
|
|
||||||
case tracker.EditParameters:
|
|
||||||
t.SetParamIndex(t.ParamIndex() - 1)
|
|
||||||
}
|
|
||||||
t.SetCursor(cursor)
|
|
||||||
if !e.Modifiers.Contain(key.ModShift) {
|
|
||||||
t.SetSelectionCorner(t.Cursor())
|
|
||||||
}
|
|
||||||
scrollToView(t.PatternOrderList, t.Cursor().Pattern, t.Song().Score.Length)
|
|
||||||
return true
|
|
||||||
case key.NameDownArrow:
|
|
||||||
cursor := t.Cursor()
|
|
||||||
switch t.EditMode() {
|
|
||||||
case tracker.EditPatterns:
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
cursor.Row = t.Song().Score.LengthInRows() - 1
|
|
||||||
} else {
|
|
||||||
cursor.Row += t.Song().Score.RowsPerPattern
|
|
||||||
}
|
|
||||||
t.SetNoteTracking(false)
|
|
||||||
case tracker.EditTracks:
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
cursor.Row += t.Song().Score.RowsPerPattern
|
|
||||||
} else {
|
|
||||||
if t.Step.Value > 0 {
|
|
||||||
cursor.Row += t.Step.Value
|
|
||||||
} else {
|
|
||||||
cursor.Row++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.SetNoteTracking(false)
|
|
||||||
case tracker.EditUnits:
|
|
||||||
t.SetUnitIndex(t.UnitIndex() + 1)
|
|
||||||
case tracker.EditParameters:
|
|
||||||
t.SetParamIndex(t.ParamIndex() + 1)
|
|
||||||
}
|
|
||||||
t.SetCursor(cursor)
|
|
||||||
if !e.Modifiers.Contain(key.ModShift) {
|
|
||||||
t.SetSelectionCorner(t.Cursor())
|
|
||||||
}
|
|
||||||
scrollToView(t.PatternOrderList, t.Cursor().Pattern, t.Song().Score.Length)
|
|
||||||
return true
|
|
||||||
case key.NameLeftArrow:
|
|
||||||
cursor := t.Cursor()
|
|
||||||
switch t.EditMode() {
|
|
||||||
case tracker.EditPatterns:
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
cursor.Track = 0
|
|
||||||
} else {
|
|
||||||
cursor.Track--
|
|
||||||
}
|
|
||||||
case tracker.EditTracks:
|
|
||||||
if !t.LowNibble() || !t.Song().Score.Tracks[t.Cursor().Track].Effect || e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
cursor.Track--
|
|
||||||
t.SetLowNibble(true)
|
|
||||||
} else {
|
|
||||||
t.SetLowNibble(false)
|
|
||||||
}
|
|
||||||
case tracker.EditUnits:
|
|
||||||
t.SetInstrIndex(t.InstrIndex() - 1)
|
|
||||||
case tracker.EditParameters:
|
|
||||||
param, _ := t.Param(t.ParamIndex())
|
|
||||||
if e.Modifiers.Contain(key.ModShift) {
|
|
||||||
p, err := t.Param(t.ParamIndex())
|
|
||||||
if err == nil {
|
|
||||||
t.SetParam(param.Value - p.LargeStep)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.SetParam(param.Value - 1)
|
switch {
|
||||||
}
|
case t.OrderEditor.Focused():
|
||||||
}
|
t.TrackEditor.Focus()
|
||||||
t.SetCursor(cursor)
|
case t.TrackEditor.Focused():
|
||||||
if !e.Modifiers.Contain(key.ModShift) {
|
t.InstrumentEditor.Focus()
|
||||||
t.SetSelectionCorner(t.Cursor())
|
case t.InstrumentEditor.Focused():
|
||||||
}
|
t.InstrumentEditor.paramEditor.Focus()
|
||||||
return true
|
default:
|
||||||
case key.NameRightArrow:
|
t.OrderEditor.Focus()
|
||||||
switch t.EditMode() {
|
|
||||||
case tracker.EditPatterns:
|
|
||||||
cursor := t.Cursor()
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
cursor.Track = len(t.Song().Score.Tracks) - 1
|
|
||||||
} else {
|
|
||||||
cursor.Track++
|
|
||||||
}
|
|
||||||
t.SetCursor(cursor)
|
|
||||||
case tracker.EditTracks:
|
|
||||||
if t.LowNibble() || !t.Song().Score.Tracks[t.Cursor().Track].Effect || e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
cursor := t.Cursor()
|
|
||||||
cursor.Track++
|
|
||||||
t.SetCursor(cursor)
|
|
||||||
t.SetLowNibble(false)
|
|
||||||
} else {
|
|
||||||
t.SetLowNibble(true)
|
|
||||||
}
|
|
||||||
case tracker.EditUnits:
|
|
||||||
t.SetInstrIndex(t.InstrIndex() + 1)
|
|
||||||
case tracker.EditParameters:
|
|
||||||
param, _ := t.Param(t.ParamIndex())
|
|
||||||
if e.Modifiers.Contain(key.ModShift) {
|
|
||||||
p, err := t.Param(t.ParamIndex())
|
|
||||||
if err == nil {
|
|
||||||
t.SetParam(param.Value + p.LargeStep)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.SetParam(param.Value + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !e.Modifiers.Contain(key.ModShift) {
|
|
||||||
t.SetSelectionCorner(t.Cursor())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case "+":
|
|
||||||
switch t.EditMode() {
|
|
||||||
case tracker.EditTracks:
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
t.AdjustSelectionPitch(12)
|
|
||||||
} else {
|
|
||||||
t.AdjustSelectionPitch(1)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case "-":
|
|
||||||
switch t.EditMode() {
|
|
||||||
case tracker.EditTracks:
|
|
||||||
if e.Modifiers.Contain(key.ModShortcut) {
|
|
||||||
t.AdjustSelectionPitch(-12)
|
|
||||||
} else {
|
|
||||||
t.AdjustSelectionPitch(-1)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch t.EditMode() {
|
|
||||||
case tracker.EditPatterns:
|
|
||||||
if iv, err := strconv.Atoi(e.Name); err == nil {
|
|
||||||
t.SetCurrentPattern(iv)
|
|
||||||
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
|
||||||
t.SetCursor(t.Cursor().AddPatterns(1))
|
|
||||||
t.SetSelectionCorner(t.Cursor())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if b := int(e.Name[0]) - 'A'; len(e.Name) == 1 && b >= 0 && b < 26 {
|
|
||||||
t.SetCurrentPattern(b + 10)
|
|
||||||
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
|
||||||
t.SetCursor(t.Cursor().AddPatterns(1))
|
|
||||||
t.SetSelectionCorner(t.Cursor())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case tracker.EditTracks:
|
|
||||||
step := false
|
|
||||||
if t.Song().Score.Tracks[t.Cursor().Track].Effect {
|
|
||||||
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
|
|
||||||
t.NumberPressed(byte(iv))
|
|
||||||
step = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if e.Name == "A" || e.Name == "1" {
|
|
||||||
t.SetNote(0)
|
|
||||||
step = true
|
|
||||||
} else {
|
|
||||||
if val, ok := noteMap[e.Name]; ok {
|
|
||||||
if _, ok := t.KeyPlaying[e.Name]; !ok {
|
|
||||||
n := tracker.NoteAsValue(t.OctaveNumberInput.Value, val)
|
|
||||||
t.SetNote(n)
|
|
||||||
step = true
|
|
||||||
trk := t.Cursor().Track
|
|
||||||
start := t.Song().Score.FirstVoiceForTrack(trk)
|
|
||||||
end := start + t.Song().Score.Tracks[trk].NumVoices
|
|
||||||
t.KeyPlaying[e.Name] = t.player.Trigger(start, end, n)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if step && !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
|
||||||
t.SetCursor(t.Cursor().AddRows(t.Step.Value))
|
|
||||||
t.SetSelectionCorner(t.Cursor())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case tracker.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.ModShortcut) {
|
|
||||||
t.SetUnitType(val)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case tracker.EditParameters:
|
|
||||||
if val, ok := noteMap[e.Name]; ok {
|
|
||||||
if _, ok := t.KeyPlaying[e.Name]; !ok {
|
|
||||||
n := tracker.NoteAsValue(t.OctaveNumberInput.Value, val)
|
|
||||||
instr := t.InstrIndex()
|
|
||||||
start := t.Song().Patch.FirstVoiceForInstrument(instr)
|
|
||||||
end := start + t.Instrument().NumVoices
|
|
||||||
t.KeyPlaying[e.Name] = t.player.Trigger(start, end, n)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if e.State == key.Release {
|
|
||||||
if ID, ok := t.KeyPlaying[e.Name]; ok {
|
|
||||||
t.player.Release(ID)
|
|
||||||
delete(t.KeyPlaying, e.Name)
|
|
||||||
if _, playing := t.player.Position(); t.EditMode() == tracker.EditTracks && playing && t.Note() == 1 && t.NoteTracking() {
|
|
||||||
t.SetNote(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,3 +214,25 @@ func (t *Tracker) NumberPressed(iv byte) {
|
|||||||
}
|
}
|
||||||
t.SetNote(val)
|
t.SetNote(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tracker) JammingPressed(e key.Event) {
|
||||||
|
if val, ok := noteMap[e.Name]; ok {
|
||||||
|
if _, ok := t.KeyPlaying[e.Name]; !ok {
|
||||||
|
n := tracker.NoteAsValue(t.OctaveNumberInput.Value, val)
|
||||||
|
instr := t.InstrIndex()
|
||||||
|
start := t.Song().Patch.FirstVoiceForInstrument(instr)
|
||||||
|
end := start + t.Instrument().NumVoices
|
||||||
|
t.KeyPlaying[e.Name] = t.player.Trigger(start, end, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracker) JammingReleased(e key.Event) {
|
||||||
|
if ID, ok := t.KeyPlaying[e.Name]; ok {
|
||||||
|
t.player.Release(ID)
|
||||||
|
delete(t.KeyPlaying, e.Name)
|
||||||
|
if _, playing := t.player.Position(); t.TrackEditor.focused && playing && t.Note() == 1 && t.NoteTracking() {
|
||||||
|
t.SetNote(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
"gioui.org/op/paint"
|
"gioui.org/op/paint"
|
||||||
"github.com/vsariola/sointu/tracker"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type C = layout.Context
|
type C = layout.Context
|
||||||
@ -20,9 +19,7 @@ func (t *Tracker) Layout(gtx layout.Context) {
|
|||||||
t.layoutTop,
|
t.layoutTop,
|
||||||
t.layoutBottom)
|
t.layoutBottom)
|
||||||
t.Alert.Layout(gtx)
|
t.Alert.Layout(gtx)
|
||||||
dstyle := ConfirmDialog(t.Theme, t.ConfirmInstrDelete, "Are you sure you want to delete this instrument?")
|
dstyle := ConfirmDialog(t.Theme, t.ConfirmSongDialog, "Do you want to save your changes to the song? Your changes will be lost if you don't save them.")
|
||||||
dstyle.Layout(gtx)
|
|
||||||
dstyle = ConfirmDialog(t.Theme, t.ConfirmSongDialog, "Do you want to save your changes to the song? Your changes will be lost if you don't save them.")
|
|
||||||
dstyle.ShowAlt = true
|
dstyle.ShowAlt = true
|
||||||
dstyle.OkStyle.Text = "Save"
|
dstyle.OkStyle.Text = "Save"
|
||||||
dstyle.AltStyle.Text = "Don't save"
|
dstyle.AltStyle.Text = "Don't save"
|
||||||
@ -93,6 +90,9 @@ func (t *Tracker) Layout(gtx layout.Context) {
|
|||||||
t.loadInstrument(file)
|
t.loadInstrument(file)
|
||||||
}
|
}
|
||||||
fstyle.Layout(gtx)
|
fstyle.Layout(gtx)
|
||||||
|
if t.ModalDialog != nil {
|
||||||
|
t.ModalDialog(gtx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) confirmedSongAction() {
|
func (t *Tracker) confirmedSongAction() {
|
||||||
@ -122,10 +122,10 @@ func (t *Tracker) NewSong(forced bool) {
|
|||||||
func (t *Tracker) layoutBottom(gtx layout.Context) layout.Dimensions {
|
func (t *Tracker) layoutBottom(gtx layout.Context) layout.Dimensions {
|
||||||
return t.BottomHorizontalSplit.Layout(gtx,
|
return t.BottomHorizontalSplit.Layout(gtx,
|
||||||
func(gtx C) D {
|
func(gtx C) D {
|
||||||
return Surface{Gray: 24, Focus: t.EditMode() == tracker.EditPatterns}.Layout(gtx, t.layoutPatterns)
|
return t.OrderEditor.Layout(gtx, t)
|
||||||
},
|
},
|
||||||
func(gtx C) D {
|
func(gtx C) D {
|
||||||
return Surface{Gray: 24, Focus: t.EditMode() == tracker.EditTracks}.Layout(gtx, t.layoutTracker)
|
return t.TrackEditor.Layout(gtx, t)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -133,6 +133,8 @@ func (t *Tracker) layoutBottom(gtx layout.Context) layout.Dimensions {
|
|||||||
func (t *Tracker) layoutTop(gtx layout.Context) layout.Dimensions {
|
func (t *Tracker) layoutTop(gtx layout.Context) layout.Dimensions {
|
||||||
return t.TopHorizontalSplit.Layout(gtx,
|
return t.TopHorizontalSplit.Layout(gtx,
|
||||||
t.layoutSongPanel,
|
t.layoutSongPanel,
|
||||||
t.layoutInstruments,
|
func(gtx C) D {
|
||||||
|
return t.InstrumentEditor.Layout(gtx, t)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
247
tracker/gioui/ordereditor.go
Normal file
247
tracker/gioui/ordereditor.go
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
package gioui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/key"
|
||||||
|
"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"
|
||||||
|
"github.com/vsariola/sointu/tracker"
|
||||||
|
)
|
||||||
|
|
||||||
|
const patternCellHeight = 16
|
||||||
|
const patternCellWidth = 16
|
||||||
|
const patternRowMarkerWidth = 30
|
||||||
|
|
||||||
|
type OrderEditor struct {
|
||||||
|
list *layout.List
|
||||||
|
scrollBar *ScrollBar
|
||||||
|
tag bool
|
||||||
|
focused bool
|
||||||
|
requestFocus bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrderEditor() *OrderEditor {
|
||||||
|
return &OrderEditor{
|
||||||
|
list: &layout.List{Axis: layout.Vertical},
|
||||||
|
scrollBar: &ScrollBar{Axis: layout.Vertical},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oe *OrderEditor) Focus() {
|
||||||
|
oe.requestFocus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oe *OrderEditor) Focused() bool {
|
||||||
|
return oe.focused
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
|
||||||
|
return Surface{Gray: 24, Focus: oe.focused}.Layout(gtx, func(gtx C) D {
|
||||||
|
return oe.doLayout(gtx, t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oe *OrderEditor) doLayout(gtx C, t *Tracker) D {
|
||||||
|
for _, e := range gtx.Events(&oe.tag) {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case key.FocusEvent:
|
||||||
|
oe.focused = e.Focus
|
||||||
|
case pointer.Event:
|
||||||
|
if e.Type == pointer.Press {
|
||||||
|
key.FocusOp{Tag: &oe.tag}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
case key.Event:
|
||||||
|
if !oe.focused || e.State != key.Press {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch e.Name {
|
||||||
|
case key.NameDeleteForward, key.NameDeleteBackward:
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
t.DeleteOrderRow(e.Name == key.NameDeleteForward)
|
||||||
|
} else {
|
||||||
|
t.DeletePatternSelection()
|
||||||
|
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
||||||
|
t.SetCursor(t.Cursor().AddPatterns(1))
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Space":
|
||||||
|
_, playing := t.player.Position()
|
||||||
|
if !playing {
|
||||||
|
t.SetNoteTracking(!e.Modifiers.Contain(key.ModShortcut))
|
||||||
|
startRow := t.Cursor().SongRow
|
||||||
|
startRow.Row = 0
|
||||||
|
t.player.Play(startRow)
|
||||||
|
} else {
|
||||||
|
t.player.Stop()
|
||||||
|
}
|
||||||
|
case key.NameReturn:
|
||||||
|
t.AddOrderRow(!e.Modifiers.Contain(key.ModShortcut))
|
||||||
|
case key.NameUpArrow:
|
||||||
|
cursor := t.Cursor()
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
cursor.SongRow = tracker.SongRow{}
|
||||||
|
} else {
|
||||||
|
cursor.Row -= t.Song().Score.RowsPerPattern
|
||||||
|
}
|
||||||
|
t.SetNoteTracking(false)
|
||||||
|
t.SetCursor(cursor)
|
||||||
|
case key.NameDownArrow:
|
||||||
|
cursor := t.Cursor()
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
cursor.Row = t.Song().Score.LengthInRows() - 1
|
||||||
|
} else {
|
||||||
|
cursor.Row += t.Song().Score.RowsPerPattern
|
||||||
|
}
|
||||||
|
t.SetNoteTracking(false)
|
||||||
|
t.SetCursor(cursor)
|
||||||
|
case key.NameLeftArrow:
|
||||||
|
cursor := t.Cursor()
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
cursor.Track = 0
|
||||||
|
} else {
|
||||||
|
cursor.Track--
|
||||||
|
}
|
||||||
|
t.SetCursor(cursor)
|
||||||
|
case key.NameRightArrow:
|
||||||
|
cursor := t.Cursor()
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
cursor.Track = len(t.Song().Score.Tracks) - 1
|
||||||
|
} else {
|
||||||
|
cursor.Track++
|
||||||
|
}
|
||||||
|
t.SetCursor(cursor)
|
||||||
|
}
|
||||||
|
if (e.Name != key.NameLeftArrow &&
|
||||||
|
e.Name != key.NameRightArrow &&
|
||||||
|
e.Name != key.NameUpArrow &&
|
||||||
|
e.Name != key.NameDownArrow) ||
|
||||||
|
!e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if iv, err := strconv.Atoi(e.Name); err == nil {
|
||||||
|
t.SetCurrentPattern(iv)
|
||||||
|
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
||||||
|
t.SetCursor(t.Cursor().AddPatterns(1))
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b := int(e.Name[0]) - 'A'; len(e.Name) == 1 && b >= 0 && b < 26 {
|
||||||
|
t.SetCurrentPattern(b + 10)
|
||||||
|
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
||||||
|
t.SetCursor(t.Cursor().AddPatterns(1))
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer op.Save(gtx.Ops).Load()
|
||||||
|
if oe.requestFocus {
|
||||||
|
oe.requestFocus = false
|
||||||
|
key.FocusOp{Tag: &oe.tag}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
||||||
|
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
||||||
|
pointer.Rect(rect).Add(gtx.Ops)
|
||||||
|
pointer.InputOp{Tag: &oe.tag,
|
||||||
|
Types: pointer.Press,
|
||||||
|
}.Add(gtx.Ops)
|
||||||
|
key.InputOp{Tag: &oe.tag}.Add(gtx.Ops)
|
||||||
|
patternRect := tracker.SongRect{
|
||||||
|
Corner1: tracker.SongPoint{SongRow: tracker.SongRow{Pattern: t.Cursor().Pattern}, Track: t.Cursor().Track},
|
||||||
|
Corner2: tracker.SongPoint{SongRow: tracker.SongRow{Pattern: t.SelectionCorner().Pattern}, Track: t.SelectionCorner().Track},
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the single letter titles for tracks
|
||||||
|
{
|
||||||
|
gtx := gtx
|
||||||
|
curVoice := 0
|
||||||
|
stack := op.Save(gtx.Ops)
|
||||||
|
op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops)
|
||||||
|
gtx.Constraints = layout.Exact(image.Pt(patternCellWidth, patternCellHeight))
|
||||||
|
for _, track := range t.Song().Score.Tracks {
|
||||||
|
instr, err := t.Song().Patch.InstrumentForVoice(curVoice)
|
||||||
|
var title string
|
||||||
|
if err == nil && len(t.Song().Patch[instr].Name) > 0 {
|
||||||
|
title = string(t.Song().Patch[instr].Name[0])
|
||||||
|
} else {
|
||||||
|
title = "I"
|
||||||
|
}
|
||||||
|
LabelStyle{Alignment: layout.N, Text: title, FontSize: unit.Dp(12), Color: mediumEmphasisTextColor}.Layout(gtx)
|
||||||
|
op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops)
|
||||||
|
curVoice += track.NumVoices
|
||||||
|
}
|
||||||
|
stack.Load()
|
||||||
|
}
|
||||||
|
op.Offset(f32.Pt(0, patternCellHeight)).Add(gtx.Ops)
|
||||||
|
gtx.Constraints.Max.Y -= patternCellHeight
|
||||||
|
gtx.Constraints.Min.Y -= patternCellHeight
|
||||||
|
element := func(gtx C, j int) D {
|
||||||
|
if playPos, ok := t.player.Position(); ok && j == playPos.Pattern {
|
||||||
|
paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}.Op())
|
||||||
|
}
|
||||||
|
paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
|
||||||
|
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j)))
|
||||||
|
stack := op.Save(gtx.Ops)
|
||||||
|
op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops)
|
||||||
|
for i, track := range t.Song().Score.Tracks {
|
||||||
|
paint.FillShape(gtx.Ops, patternCellColor, clip.Rect{Min: image.Pt(1, 1), Max: image.Pt(patternCellWidth-1, patternCellHeight-1)}.Op())
|
||||||
|
paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
|
||||||
|
if j >= 0 && j < len(track.Order) && track.Order[j] >= 0 {
|
||||||
|
gtx := gtx
|
||||||
|
gtx.Constraints.Max.X = patternCellWidth
|
||||||
|
op.Offset(f32.Pt(0, -2)).Add(gtx.Ops)
|
||||||
|
widget.Label{Alignment: text.Middle}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(track.Order[j]))
|
||||||
|
op.Offset(f32.Pt(0, 2)).Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
point := tracker.SongPoint{Track: i, SongRow: tracker.SongRow{Pattern: j}}
|
||||||
|
if oe.focused || t.TrackEditor.Focused() {
|
||||||
|
if patternRect.Contains(point) {
|
||||||
|
color := inactiveSelectionColor
|
||||||
|
if oe.focused {
|
||||||
|
color = selectionColor
|
||||||
|
if point.Pattern == t.Cursor().Pattern && point.Track == t.Cursor().Track {
|
||||||
|
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()
|
||||||
|
return D{Size: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout.Stack{Alignment: layout.NE}.Layout(gtx,
|
||||||
|
layout.Expanded(func(gtx C) D {
|
||||||
|
return oe.list.Layout(gtx, t.Song().Score.Length, element)
|
||||||
|
}),
|
||||||
|
layout.Expanded(func(gtx C) D {
|
||||||
|
return oe.scrollBar.Layout(gtx, unit.Dp(10), t.Song().Score.Length, &oe.list.Position)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func patternIndexToString(index int) string {
|
||||||
|
if index < 0 {
|
||||||
|
return ""
|
||||||
|
} else if index < 10 {
|
||||||
|
return string('0' + byte(index))
|
||||||
|
}
|
||||||
|
return string('A' + byte(index-10))
|
||||||
|
}
|
||||||
240
tracker/gioui/parameditor.go
Normal file
240
tracker/gioui/parameditor.go
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
package gioui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/io/pointer"
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/op"
|
||||||
|
"gioui.org/op/clip"
|
||||||
|
"gioui.org/op/paint"
|
||||||
|
"gioui.org/unit"
|
||||||
|
"gioui.org/widget"
|
||||||
|
"github.com/vsariola/sointu/tracker"
|
||||||
|
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParamEditor struct {
|
||||||
|
list *layout.List
|
||||||
|
scrollBar *ScrollBar
|
||||||
|
Parameters []*ParameterWidget
|
||||||
|
DeleteUnitBtn *widget.Clickable
|
||||||
|
ClearUnitBtn *widget.Clickable
|
||||||
|
ChooseUnitTypeBtns []*widget.Clickable
|
||||||
|
tag bool
|
||||||
|
focused bool
|
||||||
|
requestFocus bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *ParamEditor) Focus() {
|
||||||
|
pe.requestFocus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *ParamEditor) Focused() bool {
|
||||||
|
return pe.focused
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParamEditor() *ParamEditor {
|
||||||
|
ret := &ParamEditor{
|
||||||
|
DeleteUnitBtn: new(widget.Clickable),
|
||||||
|
ClearUnitBtn: new(widget.Clickable),
|
||||||
|
list: &layout.List{Axis: layout.Vertical},
|
||||||
|
scrollBar: &ScrollBar{Axis: layout.Vertical},
|
||||||
|
}
|
||||||
|
for range tracker.UnitTypeNames {
|
||||||
|
ret.ChooseUnitTypeBtns = append(ret.ChooseUnitTypeBtns, new(widget.Clickable))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *ParamEditor) Bind(t *Tracker) layout.Widget {
|
||||||
|
return func(gtx C) D {
|
||||||
|
for _, e := range gtx.Events(&pe.tag) {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case key.FocusEvent:
|
||||||
|
pe.focused = e.Focus
|
||||||
|
case pointer.Event:
|
||||||
|
if e.Type == pointer.Press {
|
||||||
|
key.FocusOp{Tag: &pe.tag}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
case key.Event:
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch e.State {
|
||||||
|
case key.Press:
|
||||||
|
switch e.Name {
|
||||||
|
case key.NameUpArrow:
|
||||||
|
t.SetParamIndex(t.ParamIndex() - 1)
|
||||||
|
case key.NameDownArrow:
|
||||||
|
t.SetParamIndex(t.ParamIndex() + 1)
|
||||||
|
case key.NameLeftArrow:
|
||||||
|
p, err := t.Param(t.ParamIndex())
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.SetParam(p.Value - p.LargeStep)
|
||||||
|
} else {
|
||||||
|
t.SetParam(p.Value - 1)
|
||||||
|
}
|
||||||
|
case key.NameRightArrow:
|
||||||
|
p, err := t.Param(t.ParamIndex())
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.SetParam(p.Value + p.LargeStep)
|
||||||
|
} else {
|
||||||
|
t.SetParam(p.Value + 1)
|
||||||
|
}
|
||||||
|
case key.NameEscape:
|
||||||
|
t.InstrumentEditor.unitDragList.Focus()
|
||||||
|
}
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.JammingPressed(e)
|
||||||
|
case key.Release:
|
||||||
|
t.JammingReleased(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pe.requestFocus {
|
||||||
|
pe.requestFocus = false
|
||||||
|
key.FocusOp{Tag: &pe.tag}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
editorFunc := pe.layoutUnitSliders
|
||||||
|
if t.Unit().Type == "" {
|
||||||
|
editorFunc = pe.layoutUnitTypeChooser
|
||||||
|
}
|
||||||
|
return Surface{Gray: 24, Focus: t.InstrumentEditor.wasFocused}.Layout(gtx, func(gtx C) D {
|
||||||
|
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Flexed(1, func(gtx C) D {
|
||||||
|
return editorFunc(gtx, t)
|
||||||
|
}),
|
||||||
|
layout.Rigid(pe.layoutUnitFooter(t)))
|
||||||
|
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
||||||
|
pointer.PassOp{Pass: true}.Add(gtx.Ops)
|
||||||
|
pointer.Rect(rect).Add(gtx.Ops)
|
||||||
|
pointer.InputOp{Tag: &pe.tag,
|
||||||
|
Types: pointer.Press,
|
||||||
|
}.Add(gtx.Ops)
|
||||||
|
key.InputOp{Tag: &pe.tag}.Add(gtx.Ops)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *ParamEditor) layoutUnitSliders(gtx C, t *Tracker) D {
|
||||||
|
numItems := t.NumParams()
|
||||||
|
|
||||||
|
for len(pe.Parameters) <= numItems {
|
||||||
|
pe.Parameters = append(pe.Parameters, new(ParameterWidget))
|
||||||
|
}
|
||||||
|
|
||||||
|
listItem := func(gtx C, index int) D {
|
||||||
|
for pe.Parameters[index].Clicked() {
|
||||||
|
if !pe.focused || t.ParamIndex() != index {
|
||||||
|
pe.Focus()
|
||||||
|
t.SetParamIndex(index)
|
||||||
|
} else {
|
||||||
|
t.ResetParam()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
param, err := t.Param(index)
|
||||||
|
if err != nil {
|
||||||
|
return D{}
|
||||||
|
}
|
||||||
|
oldVal := param.Value
|
||||||
|
paramStyle := t.ParamStyle(t.Theme, ¶m, pe.Parameters[index])
|
||||||
|
paramStyle.Focus = pe.focused && t.ParamIndex() == index
|
||||||
|
dims := paramStyle.Layout(gtx)
|
||||||
|
if oldVal != param.Value {
|
||||||
|
pe.Focus()
|
||||||
|
t.SetParamIndex(index)
|
||||||
|
t.SetParam(param.Value)
|
||||||
|
}
|
||||||
|
return dims
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout.Stack{}.Layout(gtx,
|
||||||
|
layout.Stacked(func(gtx C) D {
|
||||||
|
return pe.list.Layout(gtx, numItems, listItem)
|
||||||
|
}),
|
||||||
|
layout.Stacked(func(gtx C) D {
|
||||||
|
gtx.Constraints.Min = gtx.Constraints.Max
|
||||||
|
return pe.scrollBar.Layout(gtx, unit.Dp(10), numItems, &pe.list.Position)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *ParamEditor) layoutUnitFooter(t *Tracker) layout.Widget {
|
||||||
|
return func(gtx C) D {
|
||||||
|
for pe.ClearUnitBtn.Clicked() {
|
||||||
|
t.SetUnitType("")
|
||||||
|
op.InvalidateOp{}.Add(gtx.Ops)
|
||||||
|
t.InstrumentEditor.unitDragList.Focus()
|
||||||
|
}
|
||||||
|
for pe.DeleteUnitBtn.Clicked() {
|
||||||
|
t.DeleteUnit(false)
|
||||||
|
op.InvalidateOp{}.Add(gtx.Ops)
|
||||||
|
t.InstrumentEditor.unitDragList.Focus()
|
||||||
|
}
|
||||||
|
deleteUnitBtnStyle := IconButton(t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, t.CanDeleteUnit())
|
||||||
|
text := t.Unit().Type
|
||||||
|
if text == "" {
|
||||||
|
text = "Choose unit type"
|
||||||
|
} else {
|
||||||
|
text = strings.Title(text)
|
||||||
|
}
|
||||||
|
hintText := Label(text, white)
|
||||||
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Rigid(deleteUnitBtnStyle.Layout),
|
||||||
|
layout.Rigid(func(gtx C) D {
|
||||||
|
var dims D
|
||||||
|
if t.Unit().Type != "" {
|
||||||
|
clearUnitBtnStyle := IconButton(t.Theme, pe.ClearUnitBtn, icons.ContentClear, true)
|
||||||
|
dims = clearUnitBtnStyle.Layout(gtx)
|
||||||
|
}
|
||||||
|
return D{Size: image.Pt(gtx.Px(unit.Dp(48)), dims.Size.Y)}
|
||||||
|
}),
|
||||||
|
layout.Flexed(1, hintText),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *ParamEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D {
|
||||||
|
listElem := func(gtx C, i int) D {
|
||||||
|
for pe.ChooseUnitTypeBtns[i].Clicked() {
|
||||||
|
t.SetUnitType(tracker.UnitTypeNames[i])
|
||||||
|
}
|
||||||
|
labelStyle := LabelStyle{Text: tracker.UnitTypeNames[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
||||||
|
bg := func(gtx C) D {
|
||||||
|
gtx.Constraints = layout.Exact(image.Pt(gtx.Constraints.Max.X, 20))
|
||||||
|
var color color.NRGBA
|
||||||
|
if pe.ChooseUnitTypeBtns[i].Hovered() {
|
||||||
|
color = unitTypeListHighlightColor
|
||||||
|
}
|
||||||
|
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
||||||
|
return D{Size: gtx.Constraints.Min}
|
||||||
|
}
|
||||||
|
leftMargin := layout.Inset{Left: unit.Dp(10)}
|
||||||
|
return layout.Stack{Alignment: layout.W}.Layout(gtx,
|
||||||
|
layout.Stacked(bg),
|
||||||
|
layout.Expanded(func(gtx C) D {
|
||||||
|
return leftMargin.Layout(gtx, labelStyle.Layout)
|
||||||
|
}),
|
||||||
|
layout.Expanded(pe.ChooseUnitTypeBtns[i].Layout))
|
||||||
|
}
|
||||||
|
return layout.Stack{}.Layout(gtx,
|
||||||
|
layout.Stacked(func(gtx C) D {
|
||||||
|
return pe.list.Layout(gtx, len(tracker.UnitTypeNames), listElem)
|
||||||
|
}),
|
||||||
|
layout.Expanded(func(gtx C) D {
|
||||||
|
return pe.scrollBar.Layout(gtx, unit.Dp(10), len(tracker.UnitTypeNames), &pe.list.Position)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,125 +0,0 @@
|
|||||||
package gioui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gioui.org/f32"
|
|
||||||
"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"
|
|
||||||
"github.com/vsariola/sointu/tracker"
|
|
||||||
)
|
|
||||||
|
|
||||||
const patternCellHeight = 16
|
|
||||||
const patternCellWidth = 16
|
|
||||||
const patternRowMarkerWidth = 30
|
|
||||||
|
|
||||||
var patternPointerTag = false
|
|
||||||
|
|
||||||
func (t *Tracker) layoutPatterns(gtx C) D {
|
|
||||||
defer op.Save(gtx.Ops).Load()
|
|
||||||
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
|
||||||
for _, ev := range gtx.Events(&patternPointerTag) {
|
|
||||||
e, ok := ev.(pointer.Event)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if e.Type == pointer.Press {
|
|
||||||
t.SetEditMode(tracker.EditPatterns)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
|
||||||
pointer.Rect(rect).Add(gtx.Ops)
|
|
||||||
pointer.InputOp{Tag: &patternPointerTag,
|
|
||||||
Types: pointer.Press,
|
|
||||||
}.Add(gtx.Ops)
|
|
||||||
patternRect := tracker.SongRect{
|
|
||||||
Corner1: tracker.SongPoint{SongRow: tracker.SongRow{Pattern: t.Cursor().Pattern}, Track: t.Cursor().Track},
|
|
||||||
Corner2: tracker.SongPoint{SongRow: tracker.SongRow{Pattern: t.SelectionCorner().Pattern}, Track: t.SelectionCorner().Track},
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the single letter titles for tracks
|
|
||||||
{
|
|
||||||
gtx := gtx
|
|
||||||
curVoice := 0
|
|
||||||
stack := op.Save(gtx.Ops)
|
|
||||||
op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops)
|
|
||||||
gtx.Constraints = layout.Exact(image.Pt(patternCellWidth, patternCellHeight))
|
|
||||||
for _, track := range t.Song().Score.Tracks {
|
|
||||||
instr, err := t.Song().Patch.InstrumentForVoice(curVoice)
|
|
||||||
var title string
|
|
||||||
if err == nil && len(t.Song().Patch[instr].Name) > 0 {
|
|
||||||
title = string(t.Song().Patch[instr].Name[0])
|
|
||||||
} else {
|
|
||||||
title = "I"
|
|
||||||
}
|
|
||||||
LabelStyle{Alignment: layout.N, Text: title, FontSize: unit.Dp(12), Color: mediumEmphasisTextColor}.Layout(gtx)
|
|
||||||
op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops)
|
|
||||||
curVoice += track.NumVoices
|
|
||||||
}
|
|
||||||
stack.Load()
|
|
||||||
}
|
|
||||||
op.Offset(f32.Pt(0, patternCellHeight)).Add(gtx.Ops)
|
|
||||||
gtx.Constraints.Max.Y -= patternCellHeight
|
|
||||||
gtx.Constraints.Min.Y -= patternCellHeight
|
|
||||||
element := func(gtx C, j int) D {
|
|
||||||
if playPos, ok := t.player.Position(); ok && j == playPos.Pattern {
|
|
||||||
paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}.Op())
|
|
||||||
}
|
|
||||||
paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
|
|
||||||
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j)))
|
|
||||||
stack := op.Save(gtx.Ops)
|
|
||||||
op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops)
|
|
||||||
for i, track := range t.Song().Score.Tracks {
|
|
||||||
paint.FillShape(gtx.Ops, patternCellColor, clip.Rect{Min: image.Pt(1, 1), Max: image.Pt(patternCellWidth-1, patternCellHeight-1)}.Op())
|
|
||||||
paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
|
|
||||||
if j >= 0 && j < len(track.Order) && track.Order[j] >= 0 {
|
|
||||||
gtx := gtx
|
|
||||||
gtx.Constraints.Max.X = patternCellWidth
|
|
||||||
op.Offset(f32.Pt(0, -2)).Add(gtx.Ops)
|
|
||||||
widget.Label{Alignment: text.Middle}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(track.Order[j]))
|
|
||||||
op.Offset(f32.Pt(0, 2)).Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
point := tracker.SongPoint{Track: i, SongRow: tracker.SongRow{Pattern: j}}
|
|
||||||
if t.EditMode() == tracker.EditPatterns || t.EditMode() == tracker.EditTracks {
|
|
||||||
if patternRect.Contains(point) {
|
|
||||||
color := inactiveSelectionColor
|
|
||||||
if t.EditMode() == tracker.EditPatterns {
|
|
||||||
color = selectionColor
|
|
||||||
if point.Pattern == t.Cursor().Pattern && point.Track == t.Cursor().Track {
|
|
||||||
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()
|
|
||||||
return D{Size: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}
|
|
||||||
}
|
|
||||||
return layout.Stack{Alignment: layout.NE}.Layout(gtx,
|
|
||||||
layout.Expanded(func(gtx C) D {
|
|
||||||
return t.PatternOrderList.Layout(gtx, t.Song().Score.Length, element)
|
|
||||||
}),
|
|
||||||
layout.Expanded(func(gtx C) D {
|
|
||||||
return t.PatternOrderScrollBar.Layout(gtx, unit.Dp(10), t.Song().Score.Length, &t.PatternOrderList.Position)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func patternIndexToString(index int) string {
|
|
||||||
if index < 0 {
|
|
||||||
return ""
|
|
||||||
} else if index < 10 {
|
|
||||||
return string('0' + byte(index))
|
|
||||||
}
|
|
||||||
return string('A' + byte(index-10))
|
|
||||||
}
|
|
||||||
@ -11,7 +11,6 @@ import (
|
|||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
"gioui.org/op/paint"
|
"gioui.org/op/paint"
|
||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
"github.com/vsariola/sointu/tracker"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const rowMarkerWidth = 50
|
const rowMarkerWidth = 50
|
||||||
@ -47,7 +46,7 @@ func (t *Tracker) layoutRowMarkers(gtx C) D {
|
|||||||
paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
|
||||||
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", i)))
|
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", i)))
|
||||||
}
|
}
|
||||||
if t.EditMode() == tracker.EditTracks && songRow == cursorSongRow {
|
if t.TrackEditor.Focused() && songRow == cursorSongRow {
|
||||||
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
|
||||||
} else {
|
} else {
|
||||||
paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops)
|
||||||
|
|||||||
@ -44,7 +44,7 @@ func (t *Tracker) Run(w *app.Window) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
case key.Event:
|
case key.Event:
|
||||||
if t.KeyEvent(w, e) {
|
if t.KeyEvent(e) {
|
||||||
w.Invalidate()
|
w.Invalidate()
|
||||||
}
|
}
|
||||||
case clipboard.Event:
|
case clipboard.Event:
|
||||||
|
|||||||
@ -17,6 +17,7 @@ type ScrollBar struct {
|
|||||||
dragStart float32
|
dragStart float32
|
||||||
hovering bool
|
hovering bool
|
||||||
dragging bool
|
dragging bool
|
||||||
|
tag bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScrollBar) Layout(gtx C, width unit.Value, numItems int, pos *layout.Position) D {
|
func (s *ScrollBar) Layout(gtx C, width unit.Value, numItems int, pos *layout.Position) D {
|
||||||
@ -107,11 +108,11 @@ func (s *ScrollBar) Layout(gtx C, width unit.Value, numItems int, pos *layout.Po
|
|||||||
pointer.PassOp{Pass: true}.Add(gtx.Ops)
|
pointer.PassOp{Pass: true}.Add(gtx.Ops)
|
||||||
rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
|
rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
|
||||||
pointer.Rect(rect).Add(gtx.Ops)
|
pointer.Rect(rect).Add(gtx.Ops)
|
||||||
pointer.InputOp{Tag: s,
|
pointer.InputOp{Tag: &s.tag,
|
||||||
Types: pointer.Enter | pointer.Leave,
|
Types: pointer.Enter | pointer.Leave,
|
||||||
}.Add(gtx.Ops)
|
}.Add(gtx.Ops)
|
||||||
|
|
||||||
for _, ev := range gtx.Events(s) {
|
for _, ev := range gtx.Events(&s.tag) {
|
||||||
e, ok := ev.(pointer.Event)
|
e, ok := ev.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -3,9 +3,11 @@ package gioui
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@ -22,17 +24,172 @@ const trackRowHeight = 16
|
|||||||
const trackColWidth = 54
|
const trackColWidth = 54
|
||||||
const patmarkWidth = 16
|
const patmarkWidth = 16
|
||||||
|
|
||||||
var trackPointerTag bool
|
type TrackEditor struct {
|
||||||
var trackJumpPointerTag bool
|
TrackVoices *NumberInput
|
||||||
|
NewTrackBtn *widget.Clickable
|
||||||
|
DeleteTrackBtn *widget.Clickable
|
||||||
|
AddSemitoneBtn *widget.Clickable
|
||||||
|
SubtractSemitoneBtn *widget.Clickable
|
||||||
|
AddOctaveBtn *widget.Clickable
|
||||||
|
SubtractOctaveBtn *widget.Clickable
|
||||||
|
NoteOffBtn *widget.Clickable
|
||||||
|
trackPointerTag bool
|
||||||
|
trackJumpPointerTag bool
|
||||||
|
tag bool
|
||||||
|
focused bool
|
||||||
|
requestFocus bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrackEditor() *TrackEditor {
|
||||||
|
return &TrackEditor{
|
||||||
|
TrackVoices: new(NumberInput),
|
||||||
|
NewTrackBtn: new(widget.Clickable),
|
||||||
|
DeleteTrackBtn: new(widget.Clickable),
|
||||||
|
AddSemitoneBtn: new(widget.Clickable),
|
||||||
|
SubtractSemitoneBtn: new(widget.Clickable),
|
||||||
|
AddOctaveBtn: new(widget.Clickable),
|
||||||
|
SubtractOctaveBtn: new(widget.Clickable),
|
||||||
|
NoteOffBtn: new(widget.Clickable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (te *TrackEditor) Focus() {
|
||||||
|
te.requestFocus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (te *TrackEditor) Focused() bool {
|
||||||
|
return te.focused
|
||||||
|
}
|
||||||
|
|
||||||
|
func (te *TrackEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
|
||||||
|
for _, e := range gtx.Events(&te.tag) {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case key.FocusEvent:
|
||||||
|
te.focused = e.Focus
|
||||||
|
case pointer.Event:
|
||||||
|
if e.Type == pointer.Press {
|
||||||
|
key.FocusOp{Tag: &te.tag}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
case key.Event:
|
||||||
|
switch e.State {
|
||||||
|
case key.Press:
|
||||||
|
switch e.Name {
|
||||||
|
case key.NameDeleteForward, key.NameDeleteBackward:
|
||||||
|
t.DeleteSelection()
|
||||||
|
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
||||||
|
t.SetCursor(t.Cursor().AddRows(t.Step.Value))
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
case key.NameUpArrow, key.NameDownArrow:
|
||||||
|
sign := -1
|
||||||
|
if e.Name == key.NameDownArrow {
|
||||||
|
sign = 1
|
||||||
|
}
|
||||||
|
cursor := t.Cursor()
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
cursor.Row += t.Song().Score.RowsPerPattern * sign
|
||||||
|
} else {
|
||||||
|
if t.Step.Value > 0 {
|
||||||
|
cursor.Row += t.Step.Value * sign
|
||||||
|
} else {
|
||||||
|
cursor.Row += sign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.SetNoteTracking(false)
|
||||||
|
t.SetCursor(cursor)
|
||||||
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
//scrollToView(t.PatternOrderList, t.Cursor().Pattern, t.Song().Score.Length)
|
||||||
|
case key.NameLeftArrow:
|
||||||
|
cursor := t.Cursor()
|
||||||
|
if !t.LowNibble() || !t.Song().Score.Tracks[t.Cursor().Track].Effect || e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
cursor.Track--
|
||||||
|
t.SetLowNibble(true)
|
||||||
|
} else {
|
||||||
|
t.SetLowNibble(false)
|
||||||
|
}
|
||||||
|
t.SetCursor(cursor)
|
||||||
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
case key.NameRightArrow:
|
||||||
|
if t.LowNibble() || !t.Song().Score.Tracks[t.Cursor().Track].Effect || e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
cursor := t.Cursor()
|
||||||
|
cursor.Track++
|
||||||
|
t.SetCursor(cursor)
|
||||||
|
t.SetLowNibble(false)
|
||||||
|
} else {
|
||||||
|
t.SetLowNibble(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
case "+":
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
t.AdjustSelectionPitch(12)
|
||||||
|
} else {
|
||||||
|
t.AdjustSelectionPitch(1)
|
||||||
|
}
|
||||||
|
case "-":
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
t.AdjustSelectionPitch(-12)
|
||||||
|
} else {
|
||||||
|
t.AdjustSelectionPitch(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.Modifiers.Contain(key.ModShortcut) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
step := false
|
||||||
|
if t.Song().Score.Tracks[t.Cursor().Track].Effect {
|
||||||
|
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
|
||||||
|
t.NumberPressed(byte(iv))
|
||||||
|
step = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if e.Name == "A" || e.Name == "1" {
|
||||||
|
t.SetNote(0)
|
||||||
|
step = true
|
||||||
|
} else {
|
||||||
|
if val, ok := noteMap[e.Name]; ok {
|
||||||
|
if _, ok := t.KeyPlaying[e.Name]; !ok {
|
||||||
|
n := tracker.NoteAsValue(t.OctaveNumberInput.Value, val)
|
||||||
|
t.SetNote(n)
|
||||||
|
step = true
|
||||||
|
trk := t.Cursor().Track
|
||||||
|
start := t.Song().Score.FirstVoiceForTrack(trk)
|
||||||
|
end := start + t.Song().Score.Tracks[trk].NumVoices
|
||||||
|
t.KeyPlaying[e.Name] = t.player.Trigger(start, end, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if step && !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
||||||
|
t.SetCursor(t.Cursor().AddRows(t.Step.Value))
|
||||||
|
t.SetSelectionCorner(t.Cursor())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.JammingPressed(e)
|
||||||
|
case key.Release:
|
||||||
|
t.JammingReleased(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if te.requestFocus {
|
||||||
|
te.requestFocus = false
|
||||||
|
key.FocusOp{Tag: &te.tag}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
|
|
||||||
rowMarkers := layout.Rigid(t.layoutRowMarkers)
|
rowMarkers := layout.Rigid(t.layoutRowMarkers)
|
||||||
|
|
||||||
for t.NewTrackBtn.Clicked() {
|
for te.NewTrackBtn.Clicked() {
|
||||||
t.AddTrack(true)
|
t.AddTrack(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
for t.DeleteTrackBtn.Clicked() {
|
for te.DeleteTrackBtn.Clicked() {
|
||||||
t.DeleteTrack(false)
|
t.DeleteTrack(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,15 +198,15 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
|
|||||||
//cbStyle.Color = white
|
//cbStyle.Color = white
|
||||||
//cbStyle.IconColor = t.Theme.Fg
|
//cbStyle.IconColor = t.Theme.Fg
|
||||||
|
|
||||||
for t.AddSemitoneBtn.Clicked() {
|
for te.AddSemitoneBtn.Clicked() {
|
||||||
t.AdjustSelectionPitch(1)
|
t.AdjustSelectionPitch(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for t.SubtractSemitoneBtn.Clicked() {
|
for te.SubtractSemitoneBtn.Clicked() {
|
||||||
t.AdjustSelectionPitch(-1)
|
t.AdjustSelectionPitch(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for t.NoteOffBtn.Clicked() {
|
for te.NoteOffBtn.Clicked() {
|
||||||
t.SetNote(0)
|
t.SetNote(0)
|
||||||
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 {
|
||||||
t.SetCursor(t.Cursor().AddRows(t.Step.Value))
|
t.SetCursor(t.Cursor().AddRows(t.Step.Value))
|
||||||
@ -57,22 +214,22 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for t.AddOctaveBtn.Clicked() {
|
for te.AddOctaveBtn.Clicked() {
|
||||||
t.AdjustSelectionPitch(12)
|
t.AdjustSelectionPitch(12)
|
||||||
}
|
}
|
||||||
|
|
||||||
for t.SubtractOctaveBtn.Clicked() {
|
for te.SubtractOctaveBtn.Clicked() {
|
||||||
t.AdjustSelectionPitch(-12)
|
t.AdjustSelectionPitch(-12)
|
||||||
}
|
}
|
||||||
|
|
||||||
menu := func(gtx C) D {
|
menu := func(gtx C) D {
|
||||||
addSemitoneBtnStyle := LowEmphasisButton(t.Theme, t.AddSemitoneBtn, "+1")
|
addSemitoneBtnStyle := LowEmphasisButton(t.Theme, te.AddSemitoneBtn, "+1")
|
||||||
subtractSemitoneBtnStyle := LowEmphasisButton(t.Theme, t.SubtractSemitoneBtn, "-1")
|
subtractSemitoneBtnStyle := LowEmphasisButton(t.Theme, te.SubtractSemitoneBtn, "-1")
|
||||||
addOctaveBtnStyle := LowEmphasisButton(t.Theme, t.AddOctaveBtn, "+12")
|
addOctaveBtnStyle := LowEmphasisButton(t.Theme, te.AddOctaveBtn, "+12")
|
||||||
subtractOctaveBtnStyle := LowEmphasisButton(t.Theme, t.SubtractOctaveBtn, "-12")
|
subtractOctaveBtnStyle := LowEmphasisButton(t.Theme, te.SubtractOctaveBtn, "-12")
|
||||||
noteOffBtnStyle := LowEmphasisButton(t.Theme, t.NoteOffBtn, "Note Off")
|
noteOffBtnStyle := LowEmphasisButton(t.Theme, te.NoteOffBtn, "Note Off")
|
||||||
deleteTrackBtnStyle := IconButton(t.Theme, t.DeleteTrackBtn, icons.ActionDelete, t.CanDeleteTrack())
|
deleteTrackBtnStyle := IconButton(t.Theme, te.DeleteTrackBtn, icons.ActionDelete, t.CanDeleteTrack())
|
||||||
newTrackBtnStyle := IconButton(t.Theme, t.NewTrackBtn, icons.ContentAdd, t.CanAddTrack())
|
newTrackBtnStyle := IconButton(t.Theme, te.NewTrackBtn, icons.ContentAdd, t.CanAddTrack())
|
||||||
in := layout.UniformInset(unit.Dp(1))
|
in := layout.UniformInset(unit.Dp(1))
|
||||||
octave := func(gtx C) D {
|
octave := func(gtx C) D {
|
||||||
t.OctaveNumberInput.Value = t.Octave()
|
t.OctaveNumberInput.Value = t.Octave()
|
||||||
@ -84,9 +241,9 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
|
|||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
n := t.Song().Score.Tracks[t.Cursor().Track].NumVoices
|
n := t.Song().Score.Tracks[t.Cursor().Track].NumVoices
|
||||||
t.TrackVoices.Value = n
|
te.TrackVoices.Value = n
|
||||||
voiceUpDown := func(gtx C) D {
|
voiceUpDown := func(gtx C) D {
|
||||||
numStyle := NumericUpDown(t.Theme, t.TrackVoices, 1, t.MaxTrackVoices())
|
numStyle := NumericUpDown(t.Theme, te.TrackVoices, 1, t.MaxTrackVoices())
|
||||||
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20))
|
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20))
|
||||||
gtx.Constraints.Min.X = gtx.Px(unit.Dp(70))
|
gtx.Constraints.Min.X = gtx.Px(unit.Dp(70))
|
||||||
return in.Layout(gtx, numStyle.Layout)
|
return in.Layout(gtx, numStyle.Layout)
|
||||||
@ -109,48 +266,44 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(deleteTrackBtnStyle.Layout),
|
layout.Rigid(deleteTrackBtnStyle.Layout),
|
||||||
layout.Rigid(newTrackBtnStyle.Layout))
|
layout.Rigid(newTrackBtnStyle.Layout))
|
||||||
t.Song().Score.Tracks[t.Cursor().Track].Effect = t.TrackHexCheckBox.Value // TODO: we should not modify the model, but how should this be done
|
t.Song().Score.Tracks[t.Cursor().Track].Effect = t.TrackHexCheckBox.Value // TODO: we should not modify the model, but how should this be done
|
||||||
t.SetTrackVoices(t.TrackVoices.Value)
|
t.SetTrackVoices(te.TrackVoices.Value)
|
||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ev := range gtx.Events(&trackPointerTag) {
|
|
||||||
e, ok := ev.(pointer.Event)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if e.Type == pointer.Press {
|
|
||||||
t.SetEditMode(tracker.EditTracks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
||||||
pointer.Rect(rect).Add(gtx.Ops)
|
pointer.Rect(rect).Add(gtx.Ops)
|
||||||
pointer.InputOp{Tag: &trackPointerTag,
|
pointer.InputOp{Tag: &te.tag,
|
||||||
Types: pointer.Press,
|
Types: pointer.Press,
|
||||||
}.Add(gtx.Ops)
|
}.Add(gtx.Ops)
|
||||||
|
key.InputOp{Tag: &te.tag}.Add(gtx.Ops)
|
||||||
|
|
||||||
|
return Surface{Gray: 24, Focus: te.focused}.Layout(gtx, func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return Surface{Gray: 37, Focus: t.EditMode() == tracker.EditTracks, FitSize: true}.Layout(gtx, menu)
|
return Surface{Gray: 37, Focus: te.focused, FitSize: true}.Layout(gtx, menu)
|
||||||
}),
|
}),
|
||||||
layout.Flexed(1, func(gtx C) D {
|
layout.Flexed(1, func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
rowMarkers,
|
rowMarkers,
|
||||||
layout.Flexed(1, t.layoutTracks))
|
layout.Flexed(1, func(gtx C) D {
|
||||||
|
return te.layoutTracks(gtx, t)
|
||||||
|
}))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) layoutTracks(gtx C) D {
|
func (te *TrackEditor) layoutTracks(gtx C, t *Tracker) D {
|
||||||
defer op.Save(gtx.Ops).Load()
|
defer op.Save(gtx.Ops).Load()
|
||||||
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
||||||
cursorSongRow := t.Cursor().Pattern*t.Song().Score.RowsPerPattern + t.Cursor().Row
|
cursorSongRow := t.Cursor().Pattern*t.Song().Score.RowsPerPattern + t.Cursor().Row
|
||||||
for _, ev := range gtx.Events(&trackJumpPointerTag) {
|
for _, ev := range gtx.Events(&te.trackJumpPointerTag) {
|
||||||
e, ok := ev.(pointer.Event)
|
e, ok := ev.(pointer.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if e.Type == pointer.Press {
|
if e.Type == pointer.Press {
|
||||||
t.SetEditMode(tracker.EditTracks)
|
te.Focus()
|
||||||
track := int(e.Position.X) / trackColWidth
|
track := int(e.Position.X) / trackColWidth
|
||||||
row := int((e.Position.Y-float32(gtx.Constraints.Max.Y-trackRowHeight)/2)/trackRowHeight + float32(cursorSongRow))
|
row := int((e.Position.Y-float32(gtx.Constraints.Max.Y-trackRowHeight)/2)/trackRowHeight + float32(cursorSongRow))
|
||||||
cursor := tracker.SongPoint{Track: track, SongRow: tracker.SongRow{Row: row}}.Clamp(t.Song().Score)
|
cursor := tracker.SongPoint{Track: track, SongRow: tracker.SongRow{Row: row}}.Clamp(t.Song().Score)
|
||||||
@ -161,7 +314,7 @@ func (t *Tracker) layoutTracks(gtx C) D {
|
|||||||
}
|
}
|
||||||
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
|
||||||
pointer.Rect(rect).Add(gtx.Ops)
|
pointer.Rect(rect).Add(gtx.Ops)
|
||||||
pointer.InputOp{Tag: &trackJumpPointerTag,
|
pointer.InputOp{Tag: &te.trackJumpPointerTag,
|
||||||
Types: pointer.Press,
|
Types: pointer.Press,
|
||||||
}.Add(gtx.Ops)
|
}.Add(gtx.Ops)
|
||||||
stack := op.Save(gtx.Ops)
|
stack := op.Save(gtx.Ops)
|
||||||
@ -192,7 +345,7 @@ func (t *Tracker) layoutTracks(gtx C) D {
|
|||||||
stack.Load()
|
stack.Load()
|
||||||
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops)
|
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops)
|
||||||
op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops)
|
op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops)
|
||||||
if t.EditMode() == tracker.EditPatterns || t.EditMode() == tracker.EditTracks {
|
if te.focused || t.OrderEditor.Focused() {
|
||||||
x1, y1 := t.Cursor().Track, t.Cursor().Pattern
|
x1, y1 := t.Cursor().Track, t.Cursor().Pattern
|
||||||
x2, y2 := t.SelectionCorner().Track, t.SelectionCorner().Pattern
|
x2, y2 := t.SelectionCorner().Track, t.SelectionCorner().Pattern
|
||||||
if x1 > x2 {
|
if x1 > x2 {
|
||||||
@ -209,7 +362,7 @@ func (t *Tracker) layoutTracks(gtx C) D {
|
|||||||
y2 *= trackRowHeight * t.Song().Score.RowsPerPattern
|
y2 *= trackRowHeight * t.Song().Score.RowsPerPattern
|
||||||
paint.FillShape(gtx.Ops, inactiveSelectionColor, clip.Rect{Min: image.Pt(x1, y1), Max: image.Pt(x2, y2)}.Op())
|
paint.FillShape(gtx.Ops, inactiveSelectionColor, clip.Rect{Min: image.Pt(x1, y1), Max: image.Pt(x2, y2)}.Op())
|
||||||
}
|
}
|
||||||
if t.EditMode() == tracker.EditTracks {
|
if te.focused {
|
||||||
x1, y1 := t.Cursor().Track, t.Cursor().Pattern*t.Song().Score.RowsPerPattern+t.Cursor().Row
|
x1, y1 := t.Cursor().Track, t.Cursor().Pattern*t.Song().Score.RowsPerPattern+t.Cursor().Row
|
||||||
x2, y2 := t.SelectionCorner().Track, t.SelectionCorner().Pattern*t.Song().Score.RowsPerPattern+t.SelectionCorner().Row
|
x2, y2 := t.SelectionCorner().Track, t.SelectionCorner().Pattern*t.Song().Score.RowsPerPattern+t.SelectionCorner().Row
|
||||||
if x1 > x2 {
|
if x1 > x2 {
|
||||||
@ -268,7 +421,7 @@ func (t *Tracker) layoutTracks(gtx C) D {
|
|||||||
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, "*")
|
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, "*")
|
||||||
}
|
}
|
||||||
op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops)
|
op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops)
|
||||||
if t.EditMode() == tracker.EditTracks && t.Cursor().Row == patRow && t.Cursor().Pattern == pat {
|
if te.focused && t.Cursor().Row == patRow && t.Cursor().Pattern == pat {
|
||||||
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
|
||||||
} else {
|
} else {
|
||||||
paint.ColorOp{Color: trackerInactiveTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: trackerInactiveTextColor}.Add(gtx.Ops)
|
||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"gioui.org/font/gofont"
|
"gioui.org/font/gofont"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/text"
|
|
||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
@ -32,45 +31,15 @@ type Tracker struct {
|
|||||||
RowsPerBeat *NumberInput
|
RowsPerBeat *NumberInput
|
||||||
Step *NumberInput
|
Step *NumberInput
|
||||||
InstrumentVoices *NumberInput
|
InstrumentVoices *NumberInput
|
||||||
TrackVoices *NumberInput
|
|
||||||
InstrumentNameEditor *widget.Editor
|
|
||||||
NewTrackBtn *widget.Clickable
|
|
||||||
DeleteTrackBtn *widget.Clickable
|
|
||||||
NewInstrumentBtn *widget.Clickable
|
|
||||||
DeleteInstrumentBtn *widget.Clickable
|
|
||||||
AddSemitoneBtn *widget.Clickable
|
|
||||||
SubtractSemitoneBtn *widget.Clickable
|
|
||||||
AddOctaveBtn *widget.Clickable
|
|
||||||
SubtractOctaveBtn *widget.Clickable
|
|
||||||
NoteOffBtn *widget.Clickable
|
|
||||||
SongLength *NumberInput
|
SongLength *NumberInput
|
||||||
PanicBtn *widget.Clickable
|
PanicBtn *widget.Clickable
|
||||||
CopyInstrumentBtn *widget.Clickable
|
|
||||||
SaveInstrumentBtn *widget.Clickable
|
|
||||||
LoadInstrumentBtn *widget.Clickable
|
|
||||||
ParameterList *layout.List
|
|
||||||
ParameterScrollBar *ScrollBar
|
|
||||||
Parameters []*ParameterWidget
|
|
||||||
UnitDragList *DragList
|
|
||||||
UnitScrollBar *ScrollBar
|
|
||||||
DeleteUnitBtn *widget.Clickable
|
|
||||||
ClearUnitBtn *widget.Clickable
|
|
||||||
ChooseUnitTypeList *layout.List
|
|
||||||
ChooseUnitScrollBar *ScrollBar
|
|
||||||
ChooseUnitTypeBtns []*widget.Clickable
|
|
||||||
AddUnitBtn *widget.Clickable
|
AddUnitBtn *widget.Clickable
|
||||||
InstrumentDragList *DragList
|
|
||||||
InstrumentScrollBar *ScrollBar
|
|
||||||
TrackHexCheckBox *widget.Bool
|
TrackHexCheckBox *widget.Bool
|
||||||
TopHorizontalSplit *Split
|
TopHorizontalSplit *Split
|
||||||
BottomHorizontalSplit *Split
|
BottomHorizontalSplit *Split
|
||||||
VerticalSplit *Split
|
VerticalSplit *Split
|
||||||
StackUse []int
|
|
||||||
KeyPlaying map[string]uint32
|
KeyPlaying map[string]uint32
|
||||||
Alert Alert
|
Alert Alert
|
||||||
PatternOrderList *layout.List
|
|
||||||
PatternOrderScrollBar *ScrollBar
|
|
||||||
ConfirmInstrDelete *Dialog
|
|
||||||
ConfirmSongDialog *Dialog
|
ConfirmSongDialog *Dialog
|
||||||
WaveTypeDialog *Dialog
|
WaveTypeDialog *Dialog
|
||||||
OpenSongDialog *FileDialog
|
OpenSongDialog *FileDialog
|
||||||
@ -78,11 +47,12 @@ type Tracker struct {
|
|||||||
OpenInstrumentDialog *FileDialog
|
OpenInstrumentDialog *FileDialog
|
||||||
SaveInstrumentDialog *FileDialog
|
SaveInstrumentDialog *FileDialog
|
||||||
ExportWavDialog *FileDialog
|
ExportWavDialog *FileDialog
|
||||||
InstrumentCommentEditor *widget.Editor
|
|
||||||
InstrumentExpandBtn *widget.Clickable
|
|
||||||
InstrumentExpanded bool
|
|
||||||
ConfirmSongActionType int
|
ConfirmSongActionType int
|
||||||
window *app.Window
|
window *app.Window
|
||||||
|
ModalDialog layout.Widget
|
||||||
|
InstrumentEditor *InstrumentEditor
|
||||||
|
OrderEditor *OrderEditor
|
||||||
|
TrackEditor *TrackEditor
|
||||||
|
|
||||||
lastVolume tracker.Volume
|
lastVolume tracker.Volume
|
||||||
volumeChan chan tracker.Volume
|
volumeChan chan tracker.Volume
|
||||||
@ -140,53 +110,29 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService, syn
|
|||||||
RowsPerBeat: new(NumberInput),
|
RowsPerBeat: new(NumberInput),
|
||||||
Step: &NumberInput{Value: 1},
|
Step: &NumberInput{Value: 1},
|
||||||
InstrumentVoices: new(NumberInput),
|
InstrumentVoices: new(NumberInput),
|
||||||
TrackVoices: new(NumberInput),
|
|
||||||
InstrumentNameEditor: &widget.Editor{SingleLine: true, Submit: true, Alignment: text.Middle},
|
|
||||||
NewTrackBtn: new(widget.Clickable),
|
|
||||||
DeleteTrackBtn: new(widget.Clickable),
|
|
||||||
NewInstrumentBtn: new(widget.Clickable),
|
|
||||||
DeleteInstrumentBtn: new(widget.Clickable),
|
|
||||||
AddSemitoneBtn: new(widget.Clickable),
|
|
||||||
SubtractSemitoneBtn: new(widget.Clickable),
|
|
||||||
AddOctaveBtn: new(widget.Clickable),
|
|
||||||
SubtractOctaveBtn: new(widget.Clickable),
|
|
||||||
NoteOffBtn: new(widget.Clickable),
|
|
||||||
AddUnitBtn: new(widget.Clickable),
|
|
||||||
DeleteUnitBtn: new(widget.Clickable),
|
|
||||||
ClearUnitBtn: new(widget.Clickable),
|
|
||||||
PanicBtn: new(widget.Clickable),
|
PanicBtn: new(widget.Clickable),
|
||||||
CopyInstrumentBtn: new(widget.Clickable),
|
|
||||||
SaveInstrumentBtn: new(widget.Clickable),
|
|
||||||
LoadInstrumentBtn: new(widget.Clickable),
|
|
||||||
TrackHexCheckBox: new(widget.Bool),
|
TrackHexCheckBox: new(widget.Bool),
|
||||||
Menus: make([]Menu, 2),
|
Menus: make([]Menu, 2),
|
||||||
MenuBar: make([]widget.Clickable, 2),
|
MenuBar: make([]widget.Clickable, 2),
|
||||||
UnitDragList: &DragList{List: &layout.List{Axis: layout.Vertical}, HoverItem: -1},
|
|
||||||
UnitScrollBar: &ScrollBar{Axis: layout.Vertical},
|
|
||||||
refresh: make(chan struct{}, 1), // use non-blocking sends; no need to queue extra ticks if one is queued already
|
refresh: make(chan struct{}, 1), // use non-blocking sends; no need to queue extra ticks if one is queued already
|
||||||
InstrumentDragList: &DragList{List: &layout.List{Axis: layout.Horizontal}, HoverItem: -1},
|
|
||||||
InstrumentScrollBar: &ScrollBar{Axis: layout.Horizontal},
|
|
||||||
ParameterList: &layout.List{Axis: layout.Vertical},
|
|
||||||
ParameterScrollBar: &ScrollBar{Axis: layout.Vertical},
|
|
||||||
TopHorizontalSplit: &Split{Ratio: -.6},
|
TopHorizontalSplit: &Split{Ratio: -.6},
|
||||||
BottomHorizontalSplit: &Split{Ratio: -.6},
|
BottomHorizontalSplit: &Split{Ratio: -.6},
|
||||||
VerticalSplit: &Split{Axis: layout.Vertical},
|
VerticalSplit: &Split{Axis: layout.Vertical},
|
||||||
ChooseUnitTypeList: &layout.List{Axis: layout.Vertical},
|
|
||||||
ChooseUnitScrollBar: &ScrollBar{Axis: layout.Vertical},
|
|
||||||
KeyPlaying: make(map[string]uint32),
|
KeyPlaying: make(map[string]uint32),
|
||||||
volumeChan: make(chan tracker.Volume, 1),
|
volumeChan: make(chan tracker.Volume, 1),
|
||||||
playerCloser: make(chan struct{}),
|
playerCloser: make(chan struct{}),
|
||||||
PatternOrderList: &layout.List{Axis: layout.Vertical},
|
|
||||||
PatternOrderScrollBar: &ScrollBar{Axis: layout.Vertical},
|
|
||||||
ConfirmInstrDelete: new(Dialog),
|
|
||||||
ConfirmSongDialog: new(Dialog),
|
ConfirmSongDialog: new(Dialog),
|
||||||
WaveTypeDialog: new(Dialog),
|
WaveTypeDialog: new(Dialog),
|
||||||
OpenSongDialog: NewFileDialog(),
|
OpenSongDialog: NewFileDialog(),
|
||||||
SaveSongDialog: NewFileDialog(),
|
SaveSongDialog: NewFileDialog(),
|
||||||
OpenInstrumentDialog: NewFileDialog(),
|
OpenInstrumentDialog: NewFileDialog(),
|
||||||
SaveInstrumentDialog: NewFileDialog(),
|
SaveInstrumentDialog: NewFileDialog(),
|
||||||
InstrumentCommentEditor: new(widget.Editor),
|
InstrumentEditor: NewInstrumentEditor(),
|
||||||
InstrumentExpandBtn: new(widget.Clickable),
|
OrderEditor: NewOrderEditor(),
|
||||||
|
TrackEditor: NewTrackEditor(),
|
||||||
|
|
||||||
ExportWavDialog: NewFileDialog(),
|
ExportWavDialog: NewFileDialog(),
|
||||||
errorChannel: make(chan error, 32),
|
errorChannel: make(chan error, 32),
|
||||||
@ -198,10 +144,7 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService, syn
|
|||||||
go tracker.VuAnalyzer(0.3, 1e-4, 1, -100, 20, vuBufferObserver, t.volumeChan, t.errorChannel)
|
go tracker.VuAnalyzer(0.3, 1e-4, 1, -100, 20, vuBufferObserver, t.volumeChan, t.errorChannel)
|
||||||
t.Theme.Palette.Fg = primaryColor
|
t.Theme.Palette.Fg = primaryColor
|
||||||
t.Theme.Palette.ContrastFg = black
|
t.Theme.Palette.ContrastFg = black
|
||||||
t.SetEditMode(tracker.EditTracks)
|
t.TrackEditor.Focus()
|
||||||
for range tracker.UnitTypeNames {
|
|
||||||
t.ChooseUnitTypeBtns = append(t.ChooseUnitTypeBtns, new(widget.Clickable))
|
|
||||||
}
|
|
||||||
t.SetOctave(4)
|
t.SetOctave(4)
|
||||||
patchObserver := make(chan sointu.Patch, 16)
|
patchObserver := make(chan sointu.Patch, 16)
|
||||||
t.AddPatchObserver(patchObserver)
|
t.AddPatchObserver(patchObserver)
|
||||||
|
|||||||
@ -1,135 +0,0 @@
|
|||||||
package gioui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gioui.org/layout"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/op/clip"
|
|
||||||
"gioui.org/op/paint"
|
|
||||||
"gioui.org/unit"
|
|
||||||
"github.com/vsariola/sointu/tracker"
|
|
||||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *Tracker) layoutUnitEditor(gtx C) D {
|
|
||||||
editorFunc := t.layoutUnitSliders
|
|
||||||
if t.Unit().Type == "" {
|
|
||||||
editorFunc = t.layoutUnitTypeChooser
|
|
||||||
}
|
|
||||||
return Surface{Gray: 24, Focus: t.EditMode() == tracker.EditUnits || t.EditMode() == tracker.EditParameters}.Layout(gtx, func(gtx C) D {
|
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
|
||||||
layout.Flexed(1, editorFunc),
|
|
||||||
layout.Rigid(t.layoutUnitFooter()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tracker) layoutUnitSliders(gtx C) D {
|
|
||||||
numItems := t.NumParams()
|
|
||||||
|
|
||||||
for len(t.Parameters) <= numItems {
|
|
||||||
t.Parameters = append(t.Parameters, new(ParameterWidget))
|
|
||||||
}
|
|
||||||
|
|
||||||
listItem := func(gtx C, index int) D {
|
|
||||||
for t.Parameters[index].Clicked() {
|
|
||||||
if t.EditMode() != tracker.EditParameters || t.ParamIndex() != index {
|
|
||||||
t.SetEditMode(tracker.EditParameters)
|
|
||||||
t.SetParamIndex(index)
|
|
||||||
} else {
|
|
||||||
t.ResetParam()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
param, err := t.Param(index)
|
|
||||||
if err != nil {
|
|
||||||
return D{}
|
|
||||||
}
|
|
||||||
oldVal := param.Value
|
|
||||||
paramStyle := t.ParamStyle(t.Theme, ¶m, t.Parameters[index])
|
|
||||||
paramStyle.Focus = t.EditMode() == tracker.EditParameters && t.ParamIndex() == index
|
|
||||||
dims := paramStyle.Layout(gtx)
|
|
||||||
if oldVal != param.Value {
|
|
||||||
t.SetEditMode(tracker.EditParameters)
|
|
||||||
t.SetParamIndex(index)
|
|
||||||
t.SetParam(param.Value)
|
|
||||||
}
|
|
||||||
return dims
|
|
||||||
}
|
|
||||||
|
|
||||||
return layout.Stack{}.Layout(gtx,
|
|
||||||
layout.Stacked(func(gtx C) D {
|
|
||||||
return t.ParameterList.Layout(gtx, numItems, listItem)
|
|
||||||
}),
|
|
||||||
layout.Stacked(func(gtx C) D {
|
|
||||||
gtx.Constraints.Min = gtx.Constraints.Max
|
|
||||||
return t.ParameterScrollBar.Layout(gtx, unit.Dp(10), numItems, &t.ParameterList.Position)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tracker) layoutUnitFooter() layout.Widget {
|
|
||||||
return func(gtx C) D {
|
|
||||||
for t.ClearUnitBtn.Clicked() {
|
|
||||||
t.SetUnitType("")
|
|
||||||
op.InvalidateOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
for t.DeleteUnitBtn.Clicked() {
|
|
||||||
t.DeleteUnit(false)
|
|
||||||
op.InvalidateOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
deleteUnitBtnStyle := IconButton(t.Theme, t.DeleteUnitBtn, icons.ActionDelete, t.CanDeleteUnit())
|
|
||||||
text := t.Unit().Type
|
|
||||||
if text == "" {
|
|
||||||
text = "Choose unit type"
|
|
||||||
} else {
|
|
||||||
text = strings.Title(text)
|
|
||||||
}
|
|
||||||
hintText := Label(text, white)
|
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
|
||||||
layout.Rigid(deleteUnitBtnStyle.Layout),
|
|
||||||
layout.Rigid(func(gtx C) D {
|
|
||||||
var dims D
|
|
||||||
if t.Unit().Type != "" {
|
|
||||||
clearUnitBtnStyle := IconButton(t.Theme, t.ClearUnitBtn, icons.ContentClear, true)
|
|
||||||
dims = clearUnitBtnStyle.Layout(gtx)
|
|
||||||
}
|
|
||||||
return D{Size: image.Pt(gtx.Px(unit.Dp(48)), dims.Size.Y)}
|
|
||||||
}),
|
|
||||||
layout.Flexed(1, hintText),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tracker) layoutUnitTypeChooser(gtx C) D {
|
|
||||||
listElem := func(gtx C, i int) D {
|
|
||||||
for t.ChooseUnitTypeBtns[i].Clicked() {
|
|
||||||
t.SetUnitType(tracker.UnitTypeNames[i])
|
|
||||||
}
|
|
||||||
labelStyle := LabelStyle{Text: tracker.UnitTypeNames[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
|
||||||
bg := func(gtx C) D {
|
|
||||||
gtx.Constraints = layout.Exact(image.Pt(gtx.Constraints.Max.X, 20))
|
|
||||||
var color color.NRGBA
|
|
||||||
if t.ChooseUnitTypeBtns[i].Hovered() {
|
|
||||||
color = unitTypeListHighlightColor
|
|
||||||
}
|
|
||||||
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
|
||||||
return D{Size: gtx.Constraints.Min}
|
|
||||||
}
|
|
||||||
leftMargin := layout.Inset{Left: unit.Dp(10)}
|
|
||||||
return layout.Stack{Alignment: layout.W}.Layout(gtx,
|
|
||||||
layout.Stacked(bg),
|
|
||||||
layout.Expanded(func(gtx C) D {
|
|
||||||
return leftMargin.Layout(gtx, labelStyle.Layout)
|
|
||||||
}),
|
|
||||||
layout.Expanded(t.ChooseUnitTypeBtns[i].Layout))
|
|
||||||
}
|
|
||||||
return layout.Stack{}.Layout(gtx,
|
|
||||||
layout.Stacked(func(gtx C) D {
|
|
||||||
return t.ChooseUnitTypeList.Layout(gtx, len(tracker.UnitTypeNames), listElem)
|
|
||||||
}),
|
|
||||||
layout.Expanded(func(gtx C) D {
|
|
||||||
return t.ChooseUnitScrollBar.Layout(gtx, unit.Dp(10), len(tracker.UnitTypeNames), &t.ChooseUnitTypeList.Position)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -18,7 +18,6 @@ import (
|
|||||||
// protected.
|
// protected.
|
||||||
type Model struct {
|
type Model struct {
|
||||||
song sointu.Song
|
song sointu.Song
|
||||||
editMode EditMode
|
|
||||||
selectionCorner SongPoint
|
selectionCorner SongPoint
|
||||||
cursor SongPoint
|
cursor SongPoint
|
||||||
lowNibble bool
|
lowNibble bool
|
||||||
@ -54,17 +53,8 @@ type Parameter struct {
|
|||||||
LargeStep int
|
LargeStep int
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditMode int
|
|
||||||
|
|
||||||
type ParameterType int
|
type ParameterType int
|
||||||
|
|
||||||
const (
|
|
||||||
EditPatterns EditMode = iota
|
|
||||||
EditTracks
|
|
||||||
EditUnits
|
|
||||||
EditParameters
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IntegerParameter ParameterType = iota
|
IntegerParameter ParameterType = iota
|
||||||
BoolParameter
|
BoolParameter
|
||||||
@ -697,10 +687,6 @@ func (m *Model) DeletePatternSelection() {
|
|||||||
m.notifyScoreChange()
|
m.notifyScoreChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetEditMode(value EditMode) {
|
|
||||||
m.editMode = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) Undo() {
|
func (m *Model) Undo() {
|
||||||
if !m.CanUndo() {
|
if !m.CanUndo() {
|
||||||
return
|
return
|
||||||
@ -758,10 +744,6 @@ func (m *Model) Song() sointu.Song {
|
|||||||
return m.song
|
return m.song
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) EditMode() EditMode {
|
|
||||||
return m.editMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) SelectionCorner() SongPoint {
|
func (m *Model) SelectionCorner() SongPoint {
|
||||||
return m.selectionCorner
|
return m.selectionCorner
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user