diff --git a/app/ui/manga_page.go b/app/ui/manga_page.go index 592a13f..309397c 100644 --- a/app/ui/manga_page.go +++ b/app/ui/manga_page.go @@ -108,7 +108,8 @@ func newMangaPage(manga *mangodex.Manga) *MangaPage { Info: info, Table: table, sWrap: &utils.SelectorWrapper{ - Selection: map[int]struct{}{}, + Selection: map[int]struct{}{}, + VisualStart: -1, }, cWrap: &utils.ContextWrapper{ Ctx: ctx, diff --git a/app/ui/page_inputs.go b/app/ui/page_inputs.go index 4d9d4e4..7fd5b29 100644 --- a/app/ui/page_inputs.go +++ b/app/ui/page_inputs.go @@ -2,10 +2,11 @@ package ui import ( "context" - "github.com/darylhjd/mangadesk/app/ui/utils" "log" "math" + "github.com/darylhjd/mangadesk/app/ui/utils" + "github.com/darylhjd/mangodex" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" @@ -188,6 +189,12 @@ func (p *MangaPage) setHandlers(cancel context.CancelFunc) { return event }) + p.Table.SetSelectionChangedFunc(func(row, _column int) { + if p.sWrap.IsInVisualMode() { + p.selectRange(min(row, p.sWrap.VisualStart), max(row, p.sWrap.VisualStart)) + } + }) + // Set table selected function. p.Table.SetSelectedFunc(func(row, _ int) { log.Println("Creating and showing confirm download modal...") @@ -202,16 +209,26 @@ func (p *MangaPage) setHandlers(cancel context.CancelFunc) { // Set table input captures. p.Table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { case tcell.KeyCtrlE: // User selects this manga row. p.ctrlEInput() + return event case tcell.KeyCtrlA: // User wants to toggle select All. p.ctrlAInput() + return event case tcell.KeyCtrlR: // User wants to toggle read status for Selection. p.ctrlRInput() + return event case tcell.KeyCtrlQ: p.ctrlQInput() + return event } + + if event.Rune() == 'v' || event.Rune() == 'V' { + p.shiftVInput() + } + return event }) } @@ -233,6 +250,51 @@ func (p *MangaPage) ctrlAInput() { p.markAll() } +func (p *MangaPage) selectRange(from, to int) { + start := min(from, to) + end := max(from, to) + + for row := 1; row < p.Table.GetRowCount(); row++ { + if row < start || row > end { + if p.sWrap.HasSelection(row) { + p.markUnselected(row) + } + } else { + if !p.sWrap.HasSelection(row) { + p.markSelected(row) + } + } + } +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func (p *MangaPage) shiftVInput() { + if p.sWrap.IsInVisualMode() { + p.sWrap.StopVisualSelection() + for row := 1; row < p.Table.GetRowCount(); row++ { + if p.sWrap.HasSelection(row) { + p.markUnselected(row) + } + } + } else { + row, _ := p.Table.GetSelection() + p.sWrap.StartVisualSelection(row) + } +} + // ctrlRInput : Allows user to toggle read status for a chapter. func (p *MangaPage) ctrlRInput() { modal := confirmModal(utils.ToggleReadChapterModalID, diff --git a/app/ui/utils/selector.go b/app/ui/utils/selector.go index ea40158..c2939e2 100644 --- a/app/ui/utils/selector.go +++ b/app/ui/utils/selector.go @@ -3,8 +3,9 @@ package utils // SelectorWrapper : A wrapper to store selections. Used by the manga page to // keep track of selections. type SelectorWrapper struct { - Selection map[int]struct{} // Keep track of which chapters have been selected by user. - All bool // Keep track of whether user has selected All or not. + Selection map[int]struct{} // Keep track of which chapters have been selected by user. + All bool // Keep track of whether user has selected All or not. + VisualStart int // Keeps track of the start of the visual selection. -1 If none. } // HasSelections : Checks whether there are currently selections. @@ -24,7 +25,6 @@ func (s *SelectorWrapper) CopySelection(row int) map[int]struct{} { if !s.HasSelections() { s.AddSelection(row) } - selection := map[int]struct{}{} for se := range s.Selection { selection[se] = struct{}{} @@ -47,3 +47,15 @@ func (s *SelectorWrapper) AddSelection(row int) { func (s *SelectorWrapper) RemoveSelection(row int) { delete(s.Selection, row) } + +func (s *SelectorWrapper) IsInVisualMode() bool { + return s.VisualStart != -1 +} + +func (s *SelectorWrapper) StartVisualSelection(row int) { + s.VisualStart = row +} + +func (s *SelectorWrapper) StopVisualSelection() { + s.VisualStart = -1 +}