diff --git a/go.mod b/go.mod index dcff9de..7d338e7 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,14 @@ module gitea.tyrel.dev/tyrel/itor go 1.19 -require github.com/charmbracelet/bubbletea v0.22.1 +require ( + github.com/charmbracelet/bubbles v0.14.0 + github.com/charmbracelet/bubbletea v0.22.1 +) require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/charmbracelet/lipgloss v0.5.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.16 // indirect diff --git a/go.sum b/go.sum index e37ab32..9364331 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,16 @@ +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og= +github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= +github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= github.com/charmbracelet/bubbletea v0.22.1 h1:z66q0LWdJNOWEH9zadiAIXp2GN1AWrwNXU8obVY9X24= github.com/charmbracelet/bubbletea v0.22.1/go.mod h1:8/7hVvbPN6ZZPkczLiB8YpLkLJ0n7DMho5Wvfd2X1C0= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -9,25 +18,31 @@ github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peK github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= diff --git a/gui/create.go b/gui/create.go index a1a7db0..849ec38 100644 --- a/gui/create.go +++ b/gui/create.go @@ -1,25 +1,76 @@ package gui -import tea "github.com/charmbracelet/bubbletea" +import ( + "fmt" -func (m *model) updateCreate(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { + tea "github.com/charmbracelet/bubbletea" +) - // Is it a key press? - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "q": - return m, tea.Quit - case "esc": - m.switchMode(Select) - } +type errMsg error + +const InputCount = 2 + +func (m *model) nextTabIndex() { + m.createdInputIndex++ + m.createdInputIndex = m.createdInputIndex % InputCount +} + +func (m *model) createCurrent() { + front := m.createInputs[0].Value() + back := m.createInputs[1].Value() + + card := Card{ + Front: front, + Back: back, + } + m.cards = append(m.cards, card) + + // CLEAR + for i, _ := range m.createInputs { + m.createInputs[i].Reset() } - return m, nil + // Back to Select + m.switchMode(Select) +} + +func (m *model) updateCreate(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyCtrlC: + return m, tea.Quit + case tea.KeyEsc: + m.switchMode(Select) + return m, cmd + case tea.KeyEnter: + m.createCurrent() + case tea.KeyTab: + m.createInputs[m.createdInputIndex].Blur() + m.nextTabIndex() + m.createInputs[m.createdInputIndex].Focus() + } + + // We handle errors just like any other message + case errMsg: + m.err = msg + return m, nil + } + for i, _ := range m.createInputs { + m.createInputs[i], cmd = m.createInputs[i].Update(msg) + } + + return m, cmd } func (m *model) viewCreate() string { - s := "Create a card:\n\n" + s := fmt.Sprintf( + "Create a card:\n\nFront: %s\nBack: %s\n\n[Tab] switch field.\n[Enter] creates new card.\n[Escape] for back.", + m.createInputs[0].View(), + m.createInputs[1].View(), + ) return s } diff --git a/gui/draw.go b/gui/draw.go index 4085ea2..6daf76c 100644 --- a/gui/draw.go +++ b/gui/draw.go @@ -21,5 +21,7 @@ func (m *model) viewDraw() string { s += m.cardListToString() + s += "\nPress [s] to go back to select\nPress [q] to quit." + return s } diff --git a/gui/gui.go b/gui/gui.go index 6647109..b4d2353 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -2,9 +2,9 @@ package gui import ( "fmt" - tea "github.com/charmbracelet/bubbletea" "os" - "time" + + tea "github.com/charmbracelet/bubbletea" ) type Mode int64 @@ -15,32 +15,6 @@ const ( Draw ) -type Card struct { - Front string - Back string - LastCorrect time.Time -} - -type model struct { - cards []Card // items on the to-do list - cursor int // which to-do list item our cursor is pointing at - selected map[int]struct{} // which to-do items are selected - mode Mode -} - -func initialModel() model { - return model{ - cards: []Card{ - {Front: "Hello (JP)", Back: "Konnichi wa"}, - {Front: "Hello (SP)", Back: "Hola"}, - {Front: "Hello (DE)", Back: "Guten Tag"}, - }, - - selected: make(map[int]struct{}), - mode: Select, - } -} - func (m *model) Init() tea.Cmd { // Just return `nil`, which means "no I/O right now, please." return nil diff --git a/gui/model.go b/gui/model.go new file mode 100644 index 0000000..057ad8f --- /dev/null +++ b/gui/model.go @@ -0,0 +1,47 @@ +package gui + +import ( + "time" + + "github.com/charmbracelet/bubbles/textinput" +) + +type Card struct { + Front string + Back string + LastCorrect time.Time +} + +type model struct { + cards []Card // items on the to-do list + selectCardIndex int // which to-do list item our selectCardIndex is pointing at + selected map[int]struct{} // which to-do items are selected + createInputs []textinput.Model + createdInputIndex int + mode Mode + err error +} + +func initialModel() model { + front := textinput.New() + front.Placeholder = "Front..." + front.Focus() + front.CharLimit = 256 + front.Width = 80 + + back := textinput.New() + back.Placeholder = "Back..." + back.CharLimit = 256 + back.Width = 80 + + return model{ + cards: []Card{ + {Front: "Hello (JP)", Back: "Konnichi wa"}, + {Front: "Hello (SP)", Back: "Hola"}, + {Front: "Hello (DE)", Back: "Guten Tag"}, + }, + createInputs: []textinput.Model{front, back}, + selected: make(map[int]struct{}), + mode: Select, + } +} diff --git a/gui/select.go b/gui/select.go index ae425fb..65678ba 100644 --- a/gui/select.go +++ b/gui/select.go @@ -14,16 +14,16 @@ func (m *model) updateSelect(msg tea.Msg) (tea.Model, tea.Cmd) { case "ctrl+c", "q": return m, tea.Quit - // The "up" and "k" keys move the cursor up + // The "up" and "k" keys move the selectCardIndex up case "up", "k": - if m.cursor > 0 { - m.cursor-- + if m.selectCardIndex > 0 { + m.selectCardIndex-- } - // The "down" and "j" keys move the cursor down + // The "down" and "j" keys move the selectCardIndex down case "down", "j": - if m.cursor < len(m.cards)-1 { - m.cursor++ + if m.selectCardIndex < len(m.cards)-1 { + m.selectCardIndex++ } // MODES @@ -33,19 +33,17 @@ func (m *model) updateSelect(msg tea.Msg) (tea.Model, tea.Cmd) { m.switchMode(Create) // The "enter" key and the spacebar (a literal space) toggle - // the selected state for the item that the cursor is pointing at. + // the selected state for the item that the selectCardIndex is pointing at. case "enter", " ": - _, ok := m.selected[m.cursor] + _, ok := m.selected[m.selectCardIndex] if ok { - delete(m.selected, m.cursor) + delete(m.selected, m.selectCardIndex) } else { - m.selected[m.cursor] = struct{}{} + m.selected[m.selectCardIndex] = struct{}{} } } } - // Return the updated model to the Bubble Tea runtime for processing. - // Note that we're not returning a command. return m, nil } func (m *model) viewSelect() string { @@ -54,10 +52,10 @@ func (m *model) viewSelect() string { // Iterate over our choices for i, choice := range m.cards { - // Is the cursor pointing at this choice? - cursor := " " // no cursor - if m.cursor == i { - cursor = ">" // cursor! + // Is the selectCardIndex pointing at this choice? + cursor := " " // no selectCardIndex + if m.selectCardIndex == i { + cursor = ">" // selectCardIndex! } // Is this choice selected? @@ -71,7 +69,8 @@ func (m *model) viewSelect() string { } // The footer - s += "\nPress c to Create (_unimplemented_).\nPress d to Draw.\nPress q to quit.\n" + s += "\nPress [up/down/j/k] to Move up and down.\nPress [enter/space] to toggle selection.\n" + s += "\nPress [c] to Create.\nPress [d] to Draw.\nPress [q] to quit.\n" // Send the UI for rendering return s