diff --git a/app/screen_home.go b/app/screen_home.go index d53c50d..7c5dea2 100644 --- a/app/screen_home.go +++ b/app/screen_home.go @@ -43,7 +43,6 @@ type ScreenHome struct { style tcell.Style menuLayout *w.TopMenuLayout - menu *w.Menu openPdsEntry *w.Field @@ -56,11 +55,6 @@ type ScreenHome struct { status *wd.StatusBar stPathBlock *wd.StatusBlock - pdsListingTypes []models.EntryType - pdsNSIDs []syntax.NSID - expandedEntries map[string]interface{} - recordIdsToNSIDs map[string]syntax.NSID - cli *w.Cli doOpen bool @@ -74,9 +68,6 @@ func (s *ScreenHome) Init(a *App) { s.a, s.r = a, a.repo s.style = a.style - s.expandedEntries = make(map[string]interface{}) - s.recordIdsToNSIDs = make(map[string]syntax.NSID) - s.openPdsEntry = w.NewField("home.openpds.field", s.style) s.openPdsEntry.SetLabel("ID") s.openPdsEntry.SetActive(true) @@ -168,11 +159,14 @@ func (s *ScreenHome) HandleKey(ev *tcell.EventKey) bool { s.toggleCli() return true } + m := s.menuLayout.Menu() + if ev.Key() == tcell.KeyEscape { + return s.menuLayout.HandleKey(ev) + } else if m.Active() { + return s.menuLayout.HandleKey(ev) + } + if s.doOpen { - if ev.Key() == tcell.KeyEscape { - s.doOpen = false - return true - } if ev.Key() == tcell.KeyCtrlU { s.openPdsEntry.SetValue("") return true @@ -183,6 +177,7 @@ func (s *ScreenHome) HandleKey(ev *tcell.EventKey) bool { } return s.openPdsEntry.HandleKey(ev) } + return s.menuLayout.HandleKey(ev) } func (s *ScreenHome) HandleTime(ev *tcell.EventTime) { @@ -195,8 +190,9 @@ func (s *ScreenHome) Draw() { s.menuLayout.SetStyle(s.style.Foreground(tcell.ColorGray)) } s.menuLayout.SetStyle(s.style) + m := s.menuLayout.Menu() s.a.DrawWidget(s.menuLayout) - if s.doOpen { + if s.doOpen && !m.Active() { s.a.DrawWidget(s.openPdsEntry) } @@ -214,10 +210,16 @@ func (s *ScreenHome) Log(t string, a ...any) { func (s *ScreenHome) initMenu() { s.menuLayout.SetActive(true) - var vimText = "Enable Vim Mode" + wrk := "[ ]" if viper.GetBool(data.KeyVimMode) { - vimText = "Disable Vim Mode" + wrk = "[X]" } + vimText := fmt.Sprintf("%s Vim Mode", wrk) + wrk = "[ ]" + if viper.GetBool(data.KeyRecNmInfer) { + wrk = "[X]" + } + inferRecNm := fmt.Sprintf("%s Infer Record Names", wrk) s.menuLayout.AddMenuItems( s.menuLayout.CreateMenuItem("file", "File", nil, 'f', s.menuLayout.CreateMenuItem("file.openpds", "Open PDS", func() bool { @@ -246,6 +248,13 @@ func (s *ScreenHome) initMenu() { s.update() return true }, 'v'), + s.menuLayout.CreateMenuItem("settings.inferrecnm", inferRecNm, func() bool { + s.menuLayout.ToggleMenu() + viper.Set(data.KeyRecNmInfer, !viper.GetBool(data.KeyRecNmInfer)) + viper.WriteConfig() + s.update() + return true + }, 'r'), ), ) } @@ -259,11 +268,17 @@ func (s *ScreenHome) update() { s.pdsListing.SetVimMode(viper.GetBool(data.KeyVimMode)) s.jsonContent.SetVimMode(viper.GetBool(data.KeyVimMode)) vimMI := s.menuLayout.FindItem("settings.vimmode") - var vimText = "Enable Vim Mode" + wrk := "[ ]" if viper.GetBool(data.KeyVimMode) { - vimText = "Disable Vim Mode" + wrk = "[X]" } - vimMI.SetLabel(vimText) + vimMI.SetLabel(fmt.Sprintf("%s Vim Mode", wrk)) + recNmInf := s.menuLayout.FindItem("settings.inferrecnm") + wrk = "[ ]" + if viper.GetBool(data.KeyRecNmInfer) { + wrk = "[X]" + } + recNmInf.SetLabel(fmt.Sprintf("%s Infer Record Names", wrk)) miReload := s.menuLayout.FindItem("file.reloadpds") if s.activePds == nil { @@ -323,7 +338,6 @@ func (s *ScreenHome) cliGetPds(args ...string) bool { s.doOpen = false s.cli.Log("Retrieved: %s (%s)", pds.AtId, pds.Did) s.activePds = pds - s.expandedEntries = make(map[string]interface{}) s.updatePdsListing() n, err := s.pdsListing.GetActiveNode() if err == nil && n != nil { @@ -346,7 +360,21 @@ func (s *ScreenHome) updatePdsListing() { nsid := s.activePds.NSIDs[i] rIds := s.activePds.GetRecordIdsFor(nsid) for j := range rIds { - c := wd.NewTreeNode(fmt.Sprintf("%s (%s)", "record", rIds[j]), rIds[j]) + label := rIds[j] + if viper.GetBool(data.KeyRecNmInfer) { + if rec, ok := s.activePds.Records[rIds[j]]; ok { + for k := range rec { + if k == "name" || k == "title" || k == "label" { + if f, ok := rec[k].(string); ok { + label = fmt.Sprintf("%s (%s)", f, rIds[j]) + break + } + } + } + } + } + + c := wd.NewTreeNode(label, rIds[j]) t.AddChild(c) } s.pdsListing.Add(t) @@ -384,63 +412,18 @@ func (s *ScreenHome) updateStatusPathBlock(tn *wd.TreeNode) bool { func (s *ScreenHome) updateJsonView(tn *wd.TreeNode) bool { // TODO: Update JSON View - return true -} - -/* -func (s *ScreenHome) o_selectPdsListingEntry(idx int, nm string) bool { - if !s.o_updateJsonView(idx, nm) { - return false - } - - switch s.pdsListingTypes[idx] { - case models.TypeNSID: - // Expand the NSID - if _, ok := s.expandedEntries[nm]; ok { - delete(s.expandedEntries, nm) - } else { - s.expandedEntries[nm] = new(interface{}) - } - s.updatePdsListing() - return true - - case models.TypeRecord: - // If signed in and we can edit this, activate jsonContent - s.columns.ActivateWidget(s.jsonContent) - return true - } - - return false -} -func (s *ScreenHome) o_updateJsonView(idx int, nm string) bool { - if len(s.pdsListingTypes) < idx { - s.Log("error finding pds listing type (idx: %d >= list length: %d", idx, len(s.pdsListingTypes)) - return false - } - // Update the jsonContent with the list of records - switch s.pdsListingTypes[idx] { - case models.TypeNSID: - nsid, err := syntax.ParseNSID(nm) + if tn.Depth() == 0 { + nsid, err := syntax.ParseNSID(tn.Value()) if err != nil { - s.Log("error parsing NSID from %s: %w", nm, err) + s.Log("error parsing NSID from %s: %w", tn.Value(), err) return false } recordIds := s.activePds.GetRecordIdsFor(nsid) s.jsonContent.SetValue(recordIds) return true - - case models.TypeRecord: - var nsid syntax.NSID - var ok bool - nm, _ := strings.CutPrefix(nm, "• ") - if nsid, ok = s.recordIdsToNSIDs[nm]; !ok { - s.Log("error finding NSID for record %s", nm) - } - rId := fmt.Sprintf("%s/%s", nsid.String(), nm) - s.jsonContent.SetValue(s.activePds.Records[rId]) - return true + } else { + s.jsonContent.SetValue(s.activePds.Records[tn.Value()]) } - return false + return true } -*/ diff --git a/data/config.go b/data/config.go index cf72379..7788888 100644 --- a/data/config.go +++ b/data/config.go @@ -22,9 +22,10 @@ THE SOFTWARE. package data const ( - EnvPrefix = "EXPDS" - KeyConfigDir = "config" - KeyDebug = "debug" - KeyDataDir = "data" - KeyVimMode = "vimMode" + EnvPrefix = "EXPDS" + KeyConfigDir = "config" + KeyDebug = "debug" + KeyDataDir = "data" + KeyVimMode = "vimMode" + KeyRecNmInfer = "inferRecNm" ) diff --git a/data/models/pds.go b/data/models/pds.go index ea8bf38..54d4f9f 100644 --- a/data/models/pds.go +++ b/data/models/pds.go @@ -138,7 +138,7 @@ func (p *Pds) unpack() error { if err != nil { return fmt.Errorf("error unmarshalling cbor (%s/%s): %w", sCol, sRKey, err) } - p.Records[string(k)] = rec + p.Records[sRKey] = rec if !slices.Contains(p.NSIDs, col) { p.NSIDs = append(p.NSIDs, col) diff --git a/go.mod b/go.mod index 3924dc9..44cc1ee 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.bullercodeworks.com/brian/expds go 1.25.1 require ( - git.bullercodeworks.com/brian/tcell-widgets v0.2.3 + git.bullercodeworks.com/brian/tcell-widgets v0.3.2 github.com/bluesky-social/indigo v0.0.0-20260120225912-12d69fa4d209 github.com/gdamore/tcell v1.4.1 github.com/ipfs/go-cid v0.4.1 diff --git a/go.sum b/go.sum index 6daf5b3..74aaec3 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,12 @@ git.bullercodeworks.com/brian/tcell-widgets v0.2.2 h1:DcZYyGMv5U/TGSAj8CpxfrYufP git.bullercodeworks.com/brian/tcell-widgets v0.2.2/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg= git.bullercodeworks.com/brian/tcell-widgets v0.2.3 h1:/57GmrSDru27lhkm13q/sh2xLryayvQcp7MrSpixIKo= git.bullercodeworks.com/brian/tcell-widgets v0.2.3/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg= +git.bullercodeworks.com/brian/tcell-widgets v0.3.0 h1:hvBaFvwgP29PtIushnrI68WIBiPqr3OExpJw65Vuq38= +git.bullercodeworks.com/brian/tcell-widgets v0.3.0/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg= +git.bullercodeworks.com/brian/tcell-widgets v0.3.1 h1:ehHpqSQTQpufVvyvQK5fHhUTd/f+LsrOVzKUJfulTEw= +git.bullercodeworks.com/brian/tcell-widgets v0.3.1/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg= +git.bullercodeworks.com/brian/tcell-widgets v0.3.2 h1:N2WdJmMhbQKXFaB2inbxtK9pjaj/WCY/O8s15uCJtOQ= +git.bullercodeworks.com/brian/tcell-widgets v0.3.2/go.mod h1:3TlKbuGjY8nrKL5Qcp28h+KnEsXBl3iCwACTy79bdPg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/widgets/field_content.go b/widgets/field_content.go new file mode 100644 index 0000000..d236844 --- /dev/null +++ b/widgets/field_content.go @@ -0,0 +1,271 @@ +/* +Copyright © Brian Buller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package widgets + +import ( + "encoding/json" + "fmt" + "strings" + + wd "git.bullercodeworks.com/brian/tcell-widgets" + wh "git.bullercodeworks.com/brian/tcell-widgets/helpers" + "github.com/gdamore/tcell" +) + +type FieldContent struct { + id string + + valueString string + contents []string + style tcell.Style + x, y int + w, h int + + border []rune + visible bool + active bool + keyMap *wd.KeyMap + + editable bool + value any + + activeNode []string + + vimMode bool + cursor int +} + +var _ wd.Widget = (*FieldContent)(nil) + +func NewFieldContent(id string, style tcell.Style) *FieldContent { + ret := &FieldContent{} + ret.Init(id, style) + return ret +} + +func (w *FieldContent) Init(id string, style tcell.Style) { + w.id = id + w.style = style + w.visible = true + w.keyMap = wd.NewKeyMap( + wd.NewKey(wd.BuildEK(tcell.KeyUp), func(_ *tcell.EventKey) bool { return w.MoveUp() }), + wd.NewKey(wd.BuildEK(tcell.KeyDown), func(_ *tcell.EventKey) bool { return w.MoveDown() }), + wd.NewKey(wd.BuildEK(tcell.KeyEnter), func(ev *tcell.EventKey) bool { return false }), + wd.NewKey(wd.BuildEK(tcell.KeyPgDn), func(_ *tcell.EventKey) bool { return w.PageDn() }), + wd.NewKey(wd.BuildEK(tcell.KeyPgUp), func(_ *tcell.EventKey) bool { return w.PageUp() }), + wd.NewKey(wd.BuildEKr('j'), func(ev *tcell.EventKey) bool { + if !w.vimMode { + return false + } + return w.MoveDown() + }), + wd.NewKey(wd.BuildEKr('k'), func(ev *tcell.EventKey) bool { + if !w.vimMode { + return false + } + return w.MoveUp() + }), + wd.NewKey(wd.BuildEKr('b'), func(ev *tcell.EventKey) bool { + if !w.vimMode { + return false + } + if ev.Modifiers()&tcell.ModCtrl != 0 { + return w.PageUp() + } + return false + }), + wd.NewKey(wd.BuildEKr('f'), func(ev *tcell.EventKey) bool { + if !w.vimMode { + return false + } + if ev.Modifiers()&tcell.ModCtrl != 0 { + return w.PageDn() + } + return false + }), + ) +} + +func (w *FieldContent) Id() string { return w.id } +func (w *FieldContent) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() } + +func (w *FieldContent) GetKeyMap() *wd.KeyMap { return w.keyMap } +func (w *FieldContent) SetKeyMap(km *wd.KeyMap) { w.keyMap = km } + +func (w *FieldContent) HandleKey(ev *tcell.EventKey) bool { + return w.keyMap.Handle(ev) +} +func (w *FieldContent) HandleTime(ev *tcell.EventTime) {} +func (w *FieldContent) Draw(screen tcell.Screen) { + if !w.visible { + return + } + x, y := w.x, w.y + brdSz := 0 + if len(w.border) > 0 { + brdSz = 2 + wh.BorderFilled(x, y, x+w.w, y+w.h, w.border, w.style, screen) + } + x, y = x+1, y+1 + h := w.h - brdSz + ln := len(w.contents) + st, ed := 0, ln-1 + if ln == 0 { + return + } + if ln > w.h-2 { + mid := h / 2 + if w.cursor < mid { + // contents need to start at 0 + ed = h + 1 + } else if w.cursor > ln-mid { + // contents need to begin at ln-h + st = ln - h + 1 + } else { + st = w.cursor - mid + ed = st + h + 1 + } + } + // ed cannot be higher than ln-1 + if st < 0 { + st = 0 + } + if ed > ln-1 { + ed = ln - 1 + } + for i := st; i <= ed; i++ { + st := w.style + dim := true + if i == w.cursor { + dim = false + } else { + st = st.Foreground(tcell.ColorGreen) + } + txt := w.contents[i] + if len(txt) > w.w-brdSz && w.w-brdSz >= 0 { + txt = txt[:(w.w - brdSz)] + } + + wh.DrawText(w.x, y, txt, st.Dim(dim).Bold(!dim), screen) + y++ + } +} + +func (w *FieldContent) SetStyle(s tcell.Style) { w.style = s } +func (w *FieldContent) Active() bool { return w.active } +func (w *FieldContent) SetActive(a bool) bool { + w.active = a + return w.active +} +func (w *FieldContent) Visible() bool { return w.visible } +func (w *FieldContent) SetVisible(a bool) { w.visible = a } +func (w *FieldContent) SetX(x int) { w.x = x } +func (w *FieldContent) SetY(y int) { w.y = y } +func (w *FieldContent) GetX() int { return w.x } +func (w *FieldContent) GetY() int { return w.y } +func (w *FieldContent) GetPos() wd.Coord { return wd.Coord{X: w.x, Y: w.y} } +func (w *FieldContent) SetPos(c wd.Coord) { w.x, w.y = c.X, c.Y } +func (w *FieldContent) SetW(x int) { w.w = x } +func (w *FieldContent) SetH(y int) { w.h = y } +func (w *FieldContent) GetW() int { return w.w } +func (w *FieldContent) GetH() int { return w.h } +func (w *FieldContent) WantW() int { return wh.Longest(w.contents) } +func (w *FieldContent) WantH() int { return len(w.contents) } +func (w *FieldContent) SetSize(c wd.Coord) { w.w, w.h = c.X, c.Y } +func (w *FieldContent) MinW() int { return wh.Longest(w.contents) } +func (w *FieldContent) MinH() int { return len(w.contents) } + +func (w *FieldContent) SetValue(v any) error { + w.value = v + // Go ahead and try to build the json for v + bts, err := json.MarshalIndent(v, "", " ") + if err != nil { + return fmt.Errorf("error unmarshalling value: %w", err) + } + w.valueString = string(bts) + w.contents = strings.Split(w.valueString, "\n") + return nil +} +func (w *FieldContent) SetBorder(brd []rune) { + if len(brd) == 0 { + w.border = wh.BRD_SIMPLE + } else { + w.border = wh.ValidateBorder(brd) + } +} + +func (w *FieldContent) SetEditable(v bool) { w.editable = v } + +/* + func (w *FieldContent) SetFieldContent(txt string) { + w.text = txt + if strings.Contains(w.text, "\n") { + w.contents = strings.Split(w.text, "\n") + } else { + w.contents = []string{w.text} + } + } + +func (w *FieldContent) GetFieldContent() string { return w.text } +func (w *FieldContent) GetContents() []string { return w.contents } +*/ + +func (w *FieldContent) SetVimMode(b bool) { w.vimMode = b } +func (w *FieldContent) MoveUp() bool { + w.cursor-- + if w.cursor < 0 { + w.cursor = 0 + } + return true +} +func (w *FieldContent) MoveDown() bool { + w.cursor++ + if w.cursor >= len(w.contents) { + w.cursor = len(w.contents) - 1 + } + return true +} +func (w *FieldContent) PageUp() bool { + w.cursor -= w.h + if w.cursor < 0 { + w.cursor = 0 + } + return true +} +func (w *FieldContent) PageDn() bool { + w.cursor += w.h + if w.cursor > len(w.contents)-1 { + w.cursor = len(w.contents) - 1 + } + return true +} + +func (w *FieldContent) GetSelectedValue() string { + if w.cursor >= len(w.contents) { + w.cursor = len(w.contents) - 1 + } + var ret string + ret = w.contents[w.cursor] + ret = ret[strings.Index(ret, "\": \"")+3:] + ret = ret[1 : len(ret)-1] + return ret +} diff --git a/widgets/json_content.go b/widgets/json_content.go index 5474baa..907bcd7 100644 --- a/widgets/json_content.go +++ b/widgets/json_content.go @@ -40,11 +40,10 @@ type JsonContent struct { x, y int w, h int - border []rune - visible bool - active bool - focusable bool - keyMap *wd.KeyMap + border []rune + visible bool + active bool + keyMap *wd.KeyMap editable bool value any @@ -67,7 +66,6 @@ func (w *JsonContent) Init(id string, style tcell.Style) { w.id = id w.style = style w.visible = true - w.focusable = false w.keyMap = wd.NewKeyMap( wd.NewKey(wd.BuildEK(tcell.KeyUp), func(_ *tcell.EventKey) bool { return w.MoveUp() }), wd.NewKey(wd.BuildEK(tcell.KeyDown), func(_ *tcell.EventKey) bool { return w.MoveDown() }), @@ -174,26 +172,27 @@ func (w *JsonContent) Draw(screen tcell.Screen) { func (w *JsonContent) SetStyle(s tcell.Style) { w.style = s } func (w *JsonContent) Active() bool { return w.active } -func (w *JsonContent) SetActive(a bool) { w.active = a } -func (w *JsonContent) Visible() bool { return w.visible } -func (w *JsonContent) SetVisible(a bool) { w.visible = a } -func (w *JsonContent) SetX(x int) { w.x = x } -func (w *JsonContent) SetY(y int) { w.y = y } -func (w *JsonContent) GetX() int { return w.x } -func (w *JsonContent) GetY() int { return w.y } -func (w *JsonContent) GetPos() wd.Coord { return wd.Coord{X: w.x, Y: w.y} } -func (w *JsonContent) SetPos(c wd.Coord) { w.x, w.y = c.X, c.Y } -func (w *JsonContent) SetW(x int) { w.w = x } -func (w *JsonContent) SetH(y int) { w.h = y } -func (w *JsonContent) GetW() int { return w.w } -func (w *JsonContent) GetH() int { return w.h } -func (w *JsonContent) WantW() int { return wh.Longest(w.contents) } -func (w *JsonContent) WantH() int { return len(w.contents) } -func (w *JsonContent) SetSize(c wd.Coord) { w.w, w.h = c.X, c.Y } -func (w *JsonContent) Focusable() bool { return w.focusable } -func (w *JsonContent) SetFocusable(b bool) { w.focusable = b } -func (w *JsonContent) MinW() int { return wh.Longest(w.contents) } -func (w *JsonContent) MinH() int { return len(w.contents) } +func (w *JsonContent) SetActive(a bool) bool { + w.active = a + return w.active +} +func (w *JsonContent) Visible() bool { return w.visible } +func (w *JsonContent) SetVisible(a bool) { w.visible = a } +func (w *JsonContent) SetX(x int) { w.x = x } +func (w *JsonContent) SetY(y int) { w.y = y } +func (w *JsonContent) GetX() int { return w.x } +func (w *JsonContent) GetY() int { return w.y } +func (w *JsonContent) GetPos() wd.Coord { return wd.Coord{X: w.x, Y: w.y} } +func (w *JsonContent) SetPos(c wd.Coord) { w.x, w.y = c.X, c.Y } +func (w *JsonContent) SetW(x int) { w.w = x } +func (w *JsonContent) SetH(y int) { w.h = y } +func (w *JsonContent) GetW() int { return w.w } +func (w *JsonContent) GetH() int { return w.h } +func (w *JsonContent) WantW() int { return wh.Longest(w.contents) } +func (w *JsonContent) WantH() int { return len(w.contents) } +func (w *JsonContent) SetSize(c wd.Coord) { w.w, w.h = c.X, c.Y } +func (w *JsonContent) MinW() int { return wh.Longest(w.contents) } +func (w *JsonContent) MinH() int { return len(w.contents) } func (w *JsonContent) SetValue(v any) error { w.value = v diff --git a/widgets/status_bar.go b/widgets/status_bar.go index 6dda6a0..d4ca01d 100644 --- a/widgets/status_bar.go +++ b/widgets/status_bar.go @@ -84,7 +84,7 @@ func (w *StatusBar) Draw(screen tcell.Screen) { func (w *StatusBar) SetStyle(s tcell.Style) { w.style = s } func (w *StatusBar) Active() bool { return false } -func (w *StatusBar) SetActive(a bool) {} +func (w *StatusBar) SetActive(a bool) bool { return false } func (w *StatusBar) Visible() bool { return w.visible } func (w *StatusBar) SetVisible(a bool) { w.visible = a } func (w *StatusBar) Focusable() bool { return false } diff --git a/widgets/tree_browser.go b/widgets/tree_browser.go index 4656a07..a6fcdc3 100644 --- a/widgets/tree_browser.go +++ b/widgets/tree_browser.go @@ -29,6 +29,7 @@ type TreeBrowser struct { cursor int cursorWrap bool nodes []*TreeNode + depthIndic string onChange func(*TreeNode) bool onSelect func(*TreeNode) bool @@ -49,6 +50,7 @@ func NewTreeBrowser(id string, s tcell.Style) *TreeBrowser { func (w *TreeBrowser) Init(id string, style tcell.Style) { w.visible = true w.focusable = true + w.depthIndic = "• " w.keyMap = t.NewKeyMap( t.NewKey(t.BuildEK(tcell.KeyUp), func(_ *tcell.EventKey) bool { return w.MoveUp() }), t.NewKey(t.BuildEK(tcell.KeyDown), func(_ *tcell.EventKey) bool { return w.MoveDown() }), @@ -134,33 +136,66 @@ func (w *TreeBrowser) Draw(screen tcell.Screen) { } x, y = x+1, y+1 h := w.h - brdSz - for i := range w.list { - th.DrawText(x, y, w.list[i], w.style.Reverse(i == w.cursor), screen) - y++ - if y > x+h { - break + ln := len(w.list) + st, ed := 0, ln-1 + if ln == 0 { + return + } + if ln > w.h-2 { + mid := h / 2 + if w.cursor < mid { + // Start drawing at 0 + ed = h + } else if w.cursor > ln-mid { + // Start at ln-h+1 + st = ln - h + } else { + st = w.cursor - mid + ed = st + h } } + // ed cannot be higher than ln - 1 + if st < 0 { + st = 0 + } + if ed > ln-1 { + ed = ln - 1 + } + for i := st; i <= ed; i++ { + rev := false + if i == w.cursor { + rev = true + } + txt := w.list[i] + if len(txt) > w.w-brdSz && w.w-brdSz >= 0 { + txt = txt[:(w.w - brdSz)] + } + wh.DrawText(x, y, txt, dS.Reverse(rev), screen) + y += 1 + } } func (w *TreeBrowser) SetStyle(s tcell.Style) { w.style = s } func (w *TreeBrowser) Active() bool { return w.active } -func (w *TreeBrowser) SetActive(a bool) { w.active = a } -func (w *TreeBrowser) Visible() bool { return w.visible } -func (w *TreeBrowser) SetVisible(a bool) { w.visible = a } -func (w *TreeBrowser) Focusable() bool { return w.focusable } -func (w *TreeBrowser) SetFocusable(b bool) { w.focusable = b } -func (w *TreeBrowser) SetX(x int) { w.SetPos(t.Coord{X: x, Y: w.y}) } -func (w *TreeBrowser) SetY(y int) { w.SetPos(t.Coord{X: w.x, Y: y}) } -func (w *TreeBrowser) GetX() int { return w.x } -func (w *TreeBrowser) GetY() int { return w.y } -func (w *TreeBrowser) GetPos() t.Coord { return t.Coord{X: w.x, Y: w.y} } -func (w *TreeBrowser) SetPos(c t.Coord) { w.x, w.y = c.X, c.Y } -func (w *TreeBrowser) GetW() int { return w.w } -func (w *TreeBrowser) GetH() int { return w.h } -func (w *TreeBrowser) SetW(wd int) { w.SetSize(t.Coord{X: wd, Y: w.h}) } -func (w *TreeBrowser) SetH(h int) { w.SetSize(t.Coord{X: w.w, Y: h}) } -func (w *TreeBrowser) SetSize(c t.Coord) { w.w, w.h = c.X, c.Y } +func (w *TreeBrowser) SetActive(a bool) bool { + w.active = a + return w.active +} +func (w *TreeBrowser) Visible() bool { return w.visible } +func (w *TreeBrowser) SetVisible(a bool) { w.visible = a } +func (w *TreeBrowser) Focusable() bool { return w.focusable } +func (w *TreeBrowser) SetFocusable(b bool) { w.focusable = b } +func (w *TreeBrowser) SetX(x int) { w.SetPos(t.Coord{X: x, Y: w.y}) } +func (w *TreeBrowser) SetY(y int) { w.SetPos(t.Coord{X: w.x, Y: y}) } +func (w *TreeBrowser) GetX() int { return w.x } +func (w *TreeBrowser) GetY() int { return w.y } +func (w *TreeBrowser) GetPos() t.Coord { return t.Coord{X: w.x, Y: w.y} } +func (w *TreeBrowser) SetPos(c t.Coord) { w.x, w.y = c.X, c.Y } +func (w *TreeBrowser) GetW() int { return w.w } +func (w *TreeBrowser) GetH() int { return w.h } +func (w *TreeBrowser) SetW(wd int) { w.SetSize(t.Coord{X: wd, Y: w.h}) } +func (w *TreeBrowser) SetH(h int) { w.SetSize(t.Coord{X: w.w, Y: h}) } +func (w *TreeBrowser) SetSize(c t.Coord) { w.w, w.h = c.X, c.Y } func (w *TreeBrowser) WantW() int { var want int for i := range w.list { @@ -226,7 +261,7 @@ func (w *TreeBrowser) MoveUp() bool { return false } func (w *TreeBrowser) MoveDown() bool { - if w.cursor <= len(w.list) { + if w.cursor <= len(w.list)-2 { w.cursor++ if w.onChange != nil { n, err := w.GetActiveNode() @@ -278,6 +313,9 @@ func (w *TreeBrowser) Clear() { w.UpdateList() } func (w *TreeBrowser) Add(n *TreeNode) { + if n.depthIndic == "" { + n.depthIndic = w.depthIndic + } w.nodes = append(w.nodes, n) w.UpdateList() } @@ -301,17 +339,19 @@ func (w *TreeBrowser) UpdateList() { * Tree Node */ type TreeNode struct { - label string - value string - expanded bool - parent *TreeNode - children []*TreeNode + label string + value string + expanded bool + parent *TreeNode + children []*TreeNode + depthIndic string } func NewTreeNode(l, v string) *TreeNode { return &TreeNode{ - label: l, - value: v, + label: l, + value: v, + depthIndic: "• ", } } func (tn *TreeNode) Depth() int { @@ -331,7 +371,7 @@ func (tn *TreeNode) GetLabelPath() []string { return append(path, tn.Label()) } func (tn *TreeNode) getList() []string { - pre := strings.Repeat("-", tn.Depth()) + pre := strings.Repeat(tn.depthIndic, tn.Depth()) ret := []string{fmt.Sprintf("%s%s", pre, tn.label)} if tn.expanded { for i := range tn.children { @@ -353,9 +393,15 @@ func (tn *TreeNode) getVisibleNodeList() []*TreeNode { func (tn *TreeNode) ToggleExpand() { tn.expanded = !tn.expanded } func (tn *TreeNode) AddChild(t *TreeNode, rest ...*TreeNode) { + if t.depthIndic == "" { + t.depthIndic = tn.depthIndic + } t.parent = tn tn.children = append(tn.children, t) for i := range rest { + if rest[i].depthIndic == "" { + rest[i].depthIndic = tn.depthIndic + } rest[i].parent = tn tn.children = append(tn.children, rest[i]) }