go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

menu.go (5906B)


      1 package render
      2 
      3 import (
      4 	"context"
      5 	"fmt"
      6 
      7 	"git.defalsify.org/vise.git/resource"
      8 )
      9 
     10 // BrowseError is raised when browsing outside the page range of a rendered node.
     11 type BrowseError struct {
     12 	Idx uint16
     13 	PageCount uint16
     14 }
     15 
     16 // Error implements the Error interface.
     17 func(err *BrowseError) Error() string {
     18 	return fmt.Sprintf("index is out of bounds: %v", err.Idx)
     19 }
     20 
     21 // BrowseConfig defines the availability and display parameters for page browsing.
     22 type BrowseConfig struct {
     23 	NextAvailable bool
     24 	NextSelector string
     25 	NextTitle string
     26 	PreviousAvailable bool
     27 	PreviousSelector string
     28 	PreviousTitle string
     29 }
     30 
     31 // Default browse settings for convenience.
     32 func DefaultBrowseConfig() BrowseConfig {
     33 	return BrowseConfig{
     34 		NextAvailable: true,
     35 		NextSelector: "11",
     36 		NextTitle: "next",
     37 		PreviousAvailable: true,
     38 		PreviousSelector: "22",
     39 		PreviousTitle: "previous",
     40 	}
     41 }
     42 
     43 // Menu renders menus. May be included in a Page object to render menus for pages.
     44 type Menu struct {
     45 	rs resource.Resource
     46 	menu [][2]string // selector and title for menu items.
     47 	browse BrowseConfig // browse definitions.
     48 	pageCount uint16 // number of pages the menu should represent.
     49 	canNext bool // availability flag for the "next" browse option.
     50 	canPrevious bool // availability flag for the "previous" browse option.
     51 	//outputSize uint16 // maximum size constraint for the menu.
     52 	sink bool
     53 	keep bool
     54 }
     55 
     56 func(m Menu) String() string {
     57 	return fmt.Sprintf("pagecount: %v menusink: %v next: %v prev: %v", m.pageCount, m.sink, m.canNext, m.canPrevious)
     58 }
     59 
     60 // NewMenu creates a new Menu with an explicit page count.
     61 func NewMenu() *Menu {
     62 	return &Menu{
     63 		keep: true,
     64 	}
     65 }
     66 
     67 // WithBrowseConfig defines the criteria for page browsing.
     68 func(m *Menu) WithPageCount(pageCount uint16) *Menu {
     69 	m.pageCount = pageCount
     70 	return m
     71 }
     72 
     73 func(m *Menu) WithPages() *Menu {
     74 	if m.pageCount == 0 {
     75 		m.pageCount = 1
     76 	}
     77 	return m
     78 }
     79 
     80 func(m *Menu) WithSink() *Menu {
     81 	m.sink = true
     82 	return m
     83 }
     84 
     85 func(m *Menu) WithDispose() *Menu {
     86 	m.keep = false
     87 	return m
     88 }
     89 
     90 func(m *Menu) WithResource(rs resource.Resource) *Menu {
     91 	m.rs = rs
     92 	return m
     93 }
     94 
     95 func(m Menu) IsSink() bool {
     96 	return m.sink
     97 }
     98 
     99 // WithSize defines the maximum byte size of the rendered menu.
    100 //func(m *Menu) WithOutputSize(outputSize uint16) *Menu {
    101 //	m.outputSize = outputSize
    102 //	return m
    103 //}
    104 
    105 // GetOutputSize returns the defined heuristic menu size.
    106 //func(m *Menu) GetOutputSize() uint32 {
    107 //	return uint32(m.outputSize)
    108 //}
    109 
    110 // WithBrowseConfig defines the criteria for page browsing.
    111 func(m *Menu) WithBrowseConfig(cfg BrowseConfig) *Menu {
    112 	m.browse = cfg
    113 	return m
    114 }
    115 
    116 // GetBrowseConfig returns a copy of the current state of the browse configuration.
    117 func(m *Menu) GetBrowseConfig() BrowseConfig {
    118 	return m.browse
    119 }
    120 
    121 // Put adds a menu option to the menu rendering.
    122 func(m *Menu) Put(selector string, title string) error {
    123 	m.menu = append(m.menu, [2]string{selector, title})
    124 	return nil
    125 }
    126 
    127 // ReservedSize returns the maximum render byte size of the menu.
    128 //func(m *Menu) ReservedSize() uint16 {
    129 //	return m.outputSize
    130 //}
    131 
    132  // mainSize, prevsize, nextsize, nextsize+prevsize
    133 func(m *Menu) Sizes(ctx context.Context) ([4]uint32, error) {
    134 	var menuSizes [4]uint32
    135 	cfg := m.GetBrowseConfig()
    136 	tmpm := NewMenu().WithBrowseConfig(cfg)
    137 	v, err := tmpm.Render(ctx, 0)
    138 	if err != nil {
    139 		return menuSizes, err
    140 	}
    141 	menuSizes[0] = uint32(len(v))
    142 	tmpm = tmpm.WithPageCount(2)
    143 	v, err = tmpm.Render(ctx, 0)
    144 	if err != nil {
    145 		return menuSizes, err
    146 	}
    147 	menuSizes[1] = uint32(len(v)) - menuSizes[0]
    148 	v, err = tmpm.Render(ctx, 1)
    149 	if err != nil {
    150 		return menuSizes, err
    151 	}
    152 	menuSizes[2] = uint32(len(v)) - menuSizes[0]
    153 	menuSizes[3] = menuSizes[1] + menuSizes[2]
    154 	return menuSizes, nil
    155 }
    156 
    157 func(m *Menu) titleFor(ctx context.Context, title string) (string, error) {
    158 	if m.rs == nil {
    159 		return title, nil
    160 	}
    161 	r, err := m.rs.GetMenu(ctx, title)
    162 	if err != nil {
    163 		return title, err
    164 	}
    165 	return r, nil
    166 }
    167 
    168 // Render returns the full current state of the menu as a string.
    169 //
    170 // After this has been executed, the state of the menu will be empty.
    171 func(m *Menu) Render(ctx context.Context, idx uint16) (string, error) {
    172 	var menuCopy [][2]string
    173 	if m.keep {
    174 		for _, v := range m.menu {
    175 			menuCopy = append(menuCopy, v)
    176 		}
    177 	}
    178 
    179 	err := m.applyPage(idx)
    180 	if err != nil {
    181 		return "", err
    182 	}
    183 
    184 	r := ""
    185 	for true {
    186 		l := len(r)
    187 		choice, title, err := m.shiftMenu()
    188 		if err != nil {
    189 			break
    190 		}
    191 		if l > 0 {
    192 			r += "\n"
    193 		}
    194 		title, err = m.titleFor(ctx, title)
    195 		if err != nil {
    196 			return "", err
    197 		}
    198 		r += fmt.Sprintf("%s:%s", choice, title)
    199 	}
    200 	if m.keep {
    201 		m.menu = menuCopy
    202 	}
    203 	return r, nil
    204 }
    205 
    206 // add available browse options.
    207 func(m *Menu) applyPage(idx uint16) error {
    208 	if m.pageCount == 0 {
    209 		if idx > 0 {
    210 			return fmt.Errorf("index %v > 0 for non-paged menu", idx)
    211 		}
    212 		return nil
    213 	} else if idx >= m.pageCount {
    214 		return &BrowseError{Idx: idx, PageCount: m.pageCount}
    215 		//return fmt.Errorf("index %v out of bounds (%v)", idx, m.pageCount)
    216 	}
    217 	
    218 	m.reset()
    219 
    220 	if idx == m.pageCount - 1 {
    221 		m.canNext = false
    222 	}
    223 	if idx == 0 {
    224 		m.canPrevious = false
    225 	}
    226 	Logg.Debugf("applypage", "m", m, "idx", idx)
    227 
    228 	if m.canNext {
    229 		err := m.Put(m.browse.NextSelector, m.browse.NextTitle)
    230 		if err != nil {
    231 			return err
    232 		}
    233 	}
    234 	if m.canPrevious {
    235 		err := m.Put(m.browse.PreviousSelector, m.browse.PreviousTitle)
    236 		if err != nil {
    237 			return err
    238 		}
    239 	}
    240 	return nil
    241 }
    242 
    243 // removes and returns the first of remaining menu options.
    244 // fails if menu is empty.
    245 func(m *Menu) shiftMenu() (string, string, error) {
    246 	if len(m.menu) == 0 {
    247 		return "", "", fmt.Errorf("menu is empty")
    248 	}
    249 	r := m.menu[0]
    250 	m.menu = m.menu[1:]
    251 	return r[0], r[1], nil
    252 }
    253 
    254 // prepare menu object for re-use.
    255 func(m *Menu) reset() {
    256 	if m.browse.NextAvailable {
    257 		m.canNext = true
    258 	}
    259 	if m.browse.PreviousAvailable {
    260 		m.canPrevious = true
    261 	}
    262 }
    263 
    264 func(m *Menu) Reset() {
    265 	m.menu = [][2]string{}
    266 	m.sink = false
    267 	m.reset()
    268 }