github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/misc/logos/types.go (about)

     1  package logos
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/gdamore/tcell/v2"
     8  )
     9  
    10  // ----------------------------------------
    11  // Page
    12  
    13  // A Page has renderable Elem(ents).
    14  type Page struct {
    15  	Coord // used by parent only. TODO document.
    16  	Size
    17  	*Style
    18  	Attrs
    19  	Elems  []Elem
    20  	Cursor int // selected cursor element index, or -1.
    21  }
    22  
    23  // An elem is something that can draw a portion of itself onto
    24  // a view.  It has a relative coord and a size.  Before it is
    25  // drawn, it is rendered.  Measure will update its size, while
    26  // GetSize() returns the cached size.  ProcessEventKey()
    27  // returns true if event was consumed.
    28  type Elem interface {
    29  	GetParent() Elem
    30  	SetParent(Elem)
    31  	GetCoord() Coord
    32  	SetCoord(Coord)
    33  	GetStyle() *Style
    34  	SetStyle(*Style)
    35  	GetAttrs() *Attrs
    36  	GetIsCursor() bool
    37  	SetIsCursor(bool)
    38  	GetIsDirty() bool
    39  	SetIsDirty(bool)
    40  	GetIsOccluded() bool
    41  	SetIsOccluded(bool)
    42  	GetSize() Size
    43  	Measure() Size
    44  	Render() bool
    45  	Draw(offset Coord, dst View)
    46  	ProcessEventKey(*EventKey) bool
    47  	String() string
    48  	StringIndented(indent string) string
    49  	// NOTE: SetSize(Size) isn't an elem interface, as
    50  	// containers in general can't force elements to be of a
    51  	// certain size, but rather prefers drawing out of
    52  	// bounds; this opinion may distinguishes Logos from
    53  	// other most gui frameworks.
    54  }
    55  
    56  var (
    57  	_ Elem = &Page{}
    58  	_ Elem = &BufferedElemView{}
    59  	_ Elem = &TextElem{}
    60  	_ Elem = &Stack{}
    61  )
    62  
    63  // produces a page from a string.
    64  // width is the width of the page.
    65  // if isCode, width is ignored.
    66  func NewPage(s string, width int, isCode bool, style *Style) *Page {
    67  	page := &Page{
    68  		Size: Size{
    69  			Width:  width,
    70  			Height: -1, // not set
    71  		},
    72  		Style:  style,
    73  		Elems:  nil, // will set
    74  		Cursor: -1,
    75  	}
    76  	elems := []Elem{}
    77  	if s != "" {
    78  		pad := style.GetPadding()
    79  		ypos := 0 + pad.Top
    80  		xpos := 0 + pad.Left
    81  		lines := splitLines(s)
    82  		if isCode {
    83  			for _, line := range lines {
    84  				te := NewTextElem(line, style)
    85  				te.SetParent(page)
    86  				te.SetCoord(Coord{X: xpos, Y: ypos})
    87  				elems = append(elems, te)
    88  				ypos++
    89  				xpos = 0 + pad.Left
    90  			}
    91  		} else {
    92  			for _, line := range lines {
    93  				words := splitSpaces(line)
    94  				for _, word := range words {
    95  					wd := widthOf(word)
    96  					if width < xpos+wd+pad.Left+pad.Right {
    97  						if xpos != 0+pad.Left {
    98  							ypos++
    99  							xpos = 0 + pad.Left
   100  						}
   101  					}
   102  					te := NewTextElem(word, style)
   103  					te.SetParent(page)
   104  					te.SetCoord(Coord{X: xpos, Y: ypos})
   105  					elems = append(elems, te)
   106  					xpos += te.Width // size of word
   107  					xpos += 1        // space after each word (not written)
   108  				}
   109  				ypos++
   110  				xpos = 0 + pad.Left
   111  			}
   112  		}
   113  	}
   114  	page.Elems = elems
   115  	page.Measure()
   116  	page.SetIsDirty(true)
   117  	return page
   118  }
   119  
   120  func (pg *Page) StringIndented(indent string) string {
   121  	elines := []string{}
   122  	eindent := indent + "    "
   123  	for _, elem := range pg.Elems {
   124  		elines = append(elines, eindent+elem.StringIndented(eindent))
   125  	}
   126  	return fmt.Sprintf("Page%v@%p\n%s",
   127  		pg.Size,
   128  		pg,
   129  		strings.Join(elines, "\n"))
   130  }
   131  
   132  func (pg *Page) String() string {
   133  	return fmt.Sprintf("Page%v{%d}@%p",
   134  		pg.Size, len(pg.Elems), pg)
   135  }
   136  
   137  func (pg *Page) NextCoord() Coord {
   138  	if len(pg.Elems) == 0 {
   139  		return Coord{X: pg.GetPadding().Left, Y: pg.GetPadding().Top}
   140  	} else {
   141  		last := pg.Elems[len(pg.Elems)-1]
   142  		last.Measure()
   143  		lcoord := last.GetCoord()
   144  		lsize := last.GetSize()
   145  		return Coord{
   146  			X: pg.GetPadding().Left,
   147  			Y: lcoord.Y + lsize.Height, // no spacers by spec.
   148  		}
   149  	}
   150  }
   151  
   152  func (pg *Page) SetStyle(style *Style) {
   153  	pg.Style = style
   154  }
   155  
   156  // Measures the size of elem and appends to page below the last element,
   157  // or if empty, the top-leftmost coordinate exclusive of padding cells.
   158  func (pg *Page) AppendElem(elem Elem) {
   159  	ncoord := pg.NextCoord()
   160  	elem.SetParent(pg)
   161  	elem.SetCoord(ncoord)
   162  	pg.Elems = append(pg.Elems, elem)
   163  	pg.SetIsDirty(true)
   164  }
   165  
   166  // Assumes page starts at 0,0.
   167  func (pg *Page) Measure() Size {
   168  	pad := pg.GetPadding()
   169  	maxX := pad.Left
   170  	maxY := pad.Top
   171  	for _, view := range pg.Elems {
   172  		coord := view.GetCoord()
   173  		size := view.GetSize()
   174  		if maxX < coord.X+size.Width {
   175  			maxX = coord.X + size.Width
   176  		}
   177  		if maxY < coord.Y+size.Height {
   178  			maxY = coord.Y + size.Height
   179  		}
   180  	}
   181  	size := Size{
   182  		Width:  maxX + pad.Right,
   183  		Height: maxY + pad.Bottom,
   184  	}
   185  	pg.Size = size
   186  	return size
   187  }
   188  
   189  /*
   190     Page draw logic:
   191     Let's say we want to draw a Page.  We want to draw it onto
   192     some buffer, or more specially some "view" of a buffer (as
   193     a slice of an array is a view into an array buffer).
   194  
   195       Page virtual bounds:
   196       0 - - - - - - - - - - +
   197       :\                    :
   198       : \ (3,3)             :
   199       :  @-----------+      :
   200       :  |View       |      :
   201       :  |           |      :
   202       :  +-----------+      :
   203       :                     :
   204       + - - - - - - - - - - +
   205  
   206     0 is the origin point for the Page.
   207     @ is an offset within the page. @ here is (3,3).
   208     It is where the View is conceptually placed, but
   209     otherwise the View isn't aware where @ is.
   210     This offset is passed in as an argument 'offset'.
   211  
   212     NOTE: Offset is relative in the base page.  To offset the
   213     drawing position in the view (e.g. to only write on the
   214     right half of the buffer view), derive another view from
   215     the original.
   216  
   217     The View is associated with an underlying (base) buffer.
   218  
   219         Page virtual bounds:
   220         0 - - - - - - - - - - +
   221       +=:=====================:===+ <-- underlying Buffer
   222       | :                     :   |
   223       | :  @-------------+    :   |
   224       | :  |View         |    :   |
   225       | :  |.Offset=(5,2)|    :   |
   226       | :  +-------------+    :   |
   227       | :                     :   |
   228       | + - - - - - - - - - - +   |
   229       +===========================+
   230  
   231     Each element must be drawn onto the buffer view with the
   232     right offset algebra applied.  Here is a related diagram
   233     showing the buffer in relation to page elements.
   234  
   235        Page virtual bounds:
   236        0 - - - - - - - - - - - +
   237        :elem 1     |elem 2     :
   238        :       @------------+  :
   239        :       |View        |  :
   240        + - - - | - E - - - -|- +
   241        :elem 3 |   |elem 4  |  :
   242        :       +------------+  :
   243        :           |           :
   244        + - - - - - - - - - - - +
   245  
   246     In this example the page is composed of four element tiles.
   247     E is elem-4's offset relative to 0, the page's origin.  To
   248     draw the top-left portion of elem-4 onto the buffer slice
   249     as shown, the element is drawn with an offset of @-E, which
   250     is negative and indicates that the element should be drawn
   251     offset positively (right and bottom) from @.
   252  */
   253  
   254  // Unlike TextElem or BufferedElemView, a Page doesn't keep
   255  // its own buffer.  Its render function calls the elements'
   256  // render functions, and the element buffers are combined
   257  // during Draw(). There is a need for distinction because
   258  // Draw() can't be too slow, so Render() is about optimizing
   259  // Draw() calls.  The distinction between *Page and
   260  // BufferedElemView gives the user more flexibility.
   261  func (pg *Page) Render() (updated bool) {
   262  	if !pg.GetIsDirty() {
   263  		return
   264  	} else {
   265  		defer pg.SetIsDirty(false)
   266  	}
   267  	for _, elem := range pg.Elems {
   268  		elem.Render()
   269  	}
   270  	return true
   271  }
   272  
   273  // Draw the rendered page elements onto the view.
   274  func (pg *Page) Draw(offset Coord, view View) {
   275  	style := pg.GetStyle()
   276  	border := style.GetBorder()
   277  	minX, maxX, minY, maxY := computeIntersection(pg.Size, offset, view.Bounds)
   278  	// First, draw page background style.
   279  	for y := minY; y < maxY; y++ {
   280  		for x := minX; x < maxX; x++ {
   281  			xo, yo := x-offset.X, y-offset.Y
   282  			vcell := view.GetCell(xo, yo)
   283  			// Draw area and border.
   284  			if x == 0 {
   285  				if y == pg.Size.Height-1 {
   286  					// handle this case first so if height is 1,
   287  					// this corner is preferred.
   288  					vcell.SetValue(border.BLCorner(), 1, style, pg)
   289  				} else if y == 0 {
   290  					vcell.SetValue(border.TLCorner(), 1, style, pg)
   291  				} else {
   292  					vcell.SetValue(border.LeftBorder(y), 1, style, pg)
   293  				}
   294  			} else if x == pg.Size.Width-1 {
   295  				if y == pg.Size.Height-1 {
   296  					// ditto for future left-right language support.
   297  					vcell.SetValue(border.BRCorner(), 1, style, pg)
   298  				} else if y == 0 {
   299  					vcell.SetValue(border.TRCorner(), 1, style, pg)
   300  				} else {
   301  					vcell.SetValue(border.RightBorder(y), 1, style, pg)
   302  				}
   303  			} else if y == 0 {
   304  				vcell.SetValue(border.TopBorder(x), 1, style, pg)
   305  			} else if y == pg.Size.Height-1 {
   306  				vcell.SetValue(border.BottomBorder(x), 1, style, pg)
   307  			} else { // Draw area.
   308  				vcell.SetValue(" ", 1, style, pg)
   309  			}
   310  		}
   311  	}
   312  	// Then, draw elems.
   313  	for _, elem := range pg.Elems {
   314  		eoffset := offset.Sub(elem.GetCoord())
   315  		elem.Draw(eoffset, view)
   316  	}
   317  }
   318  
   319  type EventKey = tcell.EventKey
   320  
   321  func (pg *Page) ProcessEventKey(ev *EventKey) bool {
   322  	switch ev.Key() {
   323  	case tcell.KeyEsc:
   324  		return false
   325  	case tcell.KeyUp:
   326  		pg.DecCursor(true)
   327  	case tcell.KeyDown:
   328  		pg.IncCursor(true)
   329  	case tcell.KeyLeft:
   330  		pg.DecCursor(false)
   331  	case tcell.KeyRight:
   332  		pg.IncCursor(false)
   333  	case tcell.KeyEnter:
   334  		if pg.Cursor == -1 {
   335  			// as if pressed down
   336  			pg.IncCursor(true)
   337  			return true
   338  		}
   339  		// XXX this is a test.
   340  		st := StackOf(pg)
   341  		celem := pg.Elems[pg.Cursor]
   342  		coord := AbsCoord(celem).Sub(AbsCoord(st))
   343  		page := NewPage("this is a test", 80, false, pg.Style)
   344  		coord.Y += 1
   345  		coord.X += 2
   346  		page.SetCoord(coord)
   347  		st.PushLayer(page)
   348  	default:
   349  		return false
   350  	}
   351  	// Leave as true for convenience in cases above.
   352  	// If a key event wasn't consumed, return false.
   353  	return true
   354  }
   355  
   356  func (pg *Page) IncCursor(isVertical bool) {
   357  	if pg.Cursor == -1 {
   358  		if len(pg.Elems) == 0 {
   359  			// nothing to select.
   360  		} else {
   361  			pg.Cursor = 0
   362  			pg.Elems[pg.Cursor].SetIsCursor(true)
   363  		}
   364  	} else {
   365  		pg.Elems[pg.Cursor].SetIsCursor(false)
   366  		pg.Cursor++
   367  		if pg.Cursor == len(pg.Elems) {
   368  			pg.Cursor = 0 // roll back.
   369  		}
   370  		pg.Elems[pg.Cursor].SetIsCursor(true)
   371  	}
   372  }
   373  
   374  func (pg *Page) DecCursor(isVertical bool) {
   375  	if pg.Cursor == -1 {
   376  		if len(pg.Elems) == 0 {
   377  			// nothing to select.
   378  		} else {
   379  			pg.Cursor = len(pg.Elems) - 1
   380  			pg.Elems[pg.Cursor].SetIsCursor(true)
   381  		}
   382  	} else {
   383  		pg.Elems[pg.Cursor].SetIsCursor(false)
   384  		pg.Cursor--
   385  		if pg.Cursor == -1 {
   386  			pg.Cursor = len(pg.Elems) - 1 // roll forward.
   387  		}
   388  		pg.Elems[pg.Cursor].SetIsCursor(true)
   389  	}
   390  }
   391  
   392  // ----------------------------------------
   393  // TextElem
   394  
   395  type TextElem struct {
   396  	Coord
   397  	Size
   398  	*Style // ignores padding.
   399  	Attrs
   400  	Text string
   401  	*Buffer
   402  }
   403  
   404  func NewTextElem(text string, style *Style) *TextElem {
   405  	te := &TextElem{
   406  		Style: style,
   407  		Text:  text,
   408  		Buffer: NewBuffer(Size{
   409  			Height: 1,
   410  			Width:  widthOf(text),
   411  		}),
   412  	}
   413  	te.Measure()
   414  	te.SetIsDirty(true)
   415  	return te
   416  }
   417  
   418  func (tel *TextElem) SetStyle(style *Style) {
   419  	tel.Style = style
   420  }
   421  
   422  func (tel *TextElem) StringIndented(indent string) string {
   423  	return tel.String()
   424  }
   425  
   426  func (tel *TextElem) String() string {
   427  	return fmt.Sprintf("Text{%q}", tel.Text)
   428  }
   429  
   430  func (tel *TextElem) Measure() Size {
   431  	size := Size{
   432  		Height: 1,
   433  		Width:  widthOf(tel.Text),
   434  	}
   435  	tel.Size = size
   436  	return size
   437  }
   438  
   439  func (tel *TextElem) Render() (updated bool) {
   440  	if tel.Height != 1 {
   441  		panic("should not happen")
   442  	}
   443  	if !tel.GetIsDirty() {
   444  		return
   445  	} else {
   446  		defer tel.SetIsDirty(false)
   447  	}
   448  	tel.Buffer.Reset()
   449  	style := tel.GetStyle()
   450  	runes := toRunes(tel.Text)
   451  	i := 0
   452  	for 0 < len(runes) {
   453  		s, w, n := nextCharacter(runes)
   454  		if n == 0 {
   455  			panic(fmt.Sprintf(
   456  				"unexpected error reading next character from runes %v",
   457  				runes))
   458  		} else {
   459  			runes = runes[n:]
   460  		}
   461  		cell := tel.Buffer.GetCell(i, 0)
   462  		cell.SetValue(s, w, style, tel)
   463  		for j := 1; j < w; j++ {
   464  			cell := tel.Buffer.GetCell(i+j, 0)
   465  			cell.SetValue("", 0, style, tel) // clear next cells
   466  		}
   467  		i += w
   468  	}
   469  	if i != tel.Buffer.Width {
   470  		panic(fmt.Sprintf(
   471  			"wrote %d cells but there are %d in buffer with text %q",
   472  			i, tel.Buffer.Width, tel.Text))
   473  	}
   474  	return true
   475  }
   476  
   477  func (tel *TextElem) Draw(offset Coord, view View) {
   478  	minX, maxX, minY, maxY := computeIntersection(tel.Size, offset, view.Bounds)
   479  	for y := minY; y < maxY; y++ {
   480  		if minY != 0 {
   481  			panic("should not happen")
   482  		}
   483  		for x := minX; x < maxX; x++ {
   484  			bcell := tel.Buffer.GetCell(x, y)
   485  			vcell := view.GetCell(x-offset.X, y-offset.Y)
   486  			vcell.SetValueFromCell(bcell)
   487  		}
   488  	}
   489  }
   490  
   491  func (tel *TextElem) ProcessEventKey(ev *EventKey) bool {
   492  	return false // TODO: clipboard.
   493  }
   494  
   495  // ----------------------------------------
   496  // misc.
   497  
   498  type Color = tcell.Color
   499  
   500  // Style is purely visual and has no side effects.
   501  // It is generally referred to by pointer; you may need to copy before
   502  // modifying.
   503  type Style struct {
   504  	Foreground Color
   505  	Background Color
   506  	Padding    Padding
   507  	Border     Border
   508  	StyleFlags
   509  	Other       []KVPair
   510  	CursorStyle *Style
   511  }
   512  
   513  func DefaultStyle() *Style {
   514  	return &Style{
   515  		Foreground: gDefaultForeground,
   516  		Background: gDefaultBackground,
   517  		CursorStyle: &Style{
   518  			Background: tcell.ColorYellow,
   519  		},
   520  	}
   521  }
   522  
   523  var (
   524  	gDefaultStyle      = DefaultStyle()
   525  	gDefaultForeground = tcell.ColorBlack
   526  	gDefaultBackground = tcell.ColorLightBlue
   527  )
   528  
   529  func (st *Style) Copy() *Style {
   530  	st2 := *st
   531  	return &st2
   532  }
   533  
   534  func (st *Style) GetStyle() *Style {
   535  	return st
   536  }
   537  
   538  func (st *Style) GetForeground() Color {
   539  	if st == nil {
   540  		return gDefaultStyle.Foreground
   541  	} else {
   542  		return st.Foreground
   543  	}
   544  }
   545  
   546  func (st *Style) GetBackground() Color {
   547  	if st == nil {
   548  		return gDefaultStyle.Background
   549  	} else {
   550  		return st.Background
   551  	}
   552  }
   553  
   554  func (st *Style) GetPadding() Padding {
   555  	if st == nil {
   556  		return gDefaultStyle.Padding
   557  	} else {
   558  		return st.Padding
   559  	}
   560  }
   561  
   562  func (st *Style) GetBorder() *Border {
   563  	if st == nil {
   564  		return &gDefaultStyle.Border
   565  	} else {
   566  		return &st.Border
   567  	}
   568  }
   569  
   570  func (st *Style) GetCursorStyle() *Style {
   571  	if st == nil {
   572  		return gDefaultStyle.CursorStyle
   573  	} else if st.CursorStyle == nil {
   574  		return st
   575  	} else {
   576  		return st.CursorStyle
   577  	}
   578  }
   579  
   580  // NOTE: this should only be called during the last step when
   581  // writing to screen.  The receiver must not be nil and must
   582  // not be modified, and the result is a value, not the style
   583  // of any particular element.
   584  func (st *Style) WithAttrs(attrs *Attrs) (res Style) {
   585  	if st == nil {
   586  		panic("unexpected nil style")
   587  	}
   588  	if attrs.GetIsCursor() {
   589  		res = *st.GetCursorStyle()
   590  	} else {
   591  		res = *st
   592  	}
   593  	if attrs.GetIsOccluded() {
   594  		res.SetIsShaded(true)
   595  	}
   596  	return
   597  }
   598  
   599  func (st Style) GetTStyle() (tst tcell.Style) {
   600  	if st.Foreground.Valid() {
   601  		tst = tst.Foreground(st.Foreground)
   602  	} else {
   603  		tst = tst.Foreground(gDefaultForeground)
   604  	}
   605  	if st.Background.Valid() {
   606  		tst = tst.Background(st.Background)
   607  	} else {
   608  		tst = tst.Background(gDefaultBackground)
   609  	}
   610  	if st.GetIsShaded() {
   611  		tst = tst.Dim(true)
   612  		tst = tst.Background(tcell.ColorGray)
   613  	}
   614  	// TODO StyleFlags
   615  	return tst
   616  }
   617  
   618  type StyleFlags uint32
   619  
   620  func (sf StyleFlags) GetIsDim() bool {
   621  	return (sf & StyleFlagDim) != 0
   622  }
   623  
   624  func (sf *StyleFlags) SetIsDim(id bool) {
   625  	if id {
   626  		*sf |= StyleFlagDim
   627  	} else {
   628  		*sf &= ^StyleFlagDim
   629  	}
   630  }
   631  
   632  func (sf StyleFlags) GetIsShaded() bool {
   633  	return (sf & StyleFlagShaded) != 0
   634  }
   635  
   636  func (sf *StyleFlags) SetIsShaded(id bool) {
   637  	if id {
   638  		*sf |= StyleFlagShaded
   639  	} else {
   640  		*sf &= ^StyleFlagShaded
   641  	}
   642  }
   643  
   644  const StyleFlagNone StyleFlags = 0
   645  
   646  const (
   647  	StyleFlagBold StyleFlags = 1 << iota
   648  	StyleFlagDim
   649  	StyleFlagShaded
   650  	StyleFlagBlink
   651  	StyleFlagUnderline
   652  	StyleFlagItalic
   653  	StyleFlagStrikeThrough
   654  )
   655  
   656  // Attrs have side effects in the Logos system;
   657  // for example, the lone cursor element (one with AttrFlagIsCursor set)
   658  // is where most key events are sent to.
   659  type Attrs struct {
   660  	Parent Elem
   661  	AttrFlags
   662  	Other []KVPair
   663  }
   664  
   665  func (tt *Attrs) GetAttrs() *Attrs {
   666  	return tt
   667  }
   668  
   669  func (tt *Attrs) GetParent() Elem {
   670  	return tt.Parent
   671  }
   672  
   673  func (tt *Attrs) SetParent(p Elem) {
   674  	if tt.Parent != nil && tt.Parent != p {
   675  		panic("parent already set")
   676  	}
   677  	tt.Parent = p
   678  }
   679  
   680  func (tt *Attrs) GetIsCursor() bool {
   681  	return (tt.AttrFlags & AttrFlagIsCursor) != 0
   682  }
   683  
   684  func (tt *Attrs) SetIsCursor(ic bool) {
   685  	if ic {
   686  		tt.AttrFlags |= AttrFlagIsCursor
   687  	} else {
   688  		tt.AttrFlags &= ^AttrFlagIsCursor
   689  	}
   690  	tt.SetIsDirty(true)
   691  }
   692  
   693  func (tt *Attrs) GetIsDirty() bool {
   694  	return (tt.AttrFlags & AttrFlagIsDirty) != 0
   695  }
   696  
   697  func (tt *Attrs) SetIsDirty(id bool) {
   698  	if id {
   699  		tt.AttrFlags |= AttrFlagIsDirty
   700  		if tt.Parent != nil {
   701  			tt.Parent.SetIsDirty(true)
   702  		}
   703  	} else {
   704  		tt.AttrFlags &= ^AttrFlagIsDirty
   705  	}
   706  }
   707  
   708  func (tt *Attrs) GetIsOccluded() bool {
   709  	return (tt.AttrFlags & AttrFlagIsOccluded) != 0
   710  }
   711  
   712  func (tt *Attrs) SetIsOccluded(ic bool) {
   713  	if ic {
   714  		tt.AttrFlags |= AttrFlagIsOccluded
   715  	} else {
   716  		tt.AttrFlags &= ^AttrFlagIsOccluded
   717  	}
   718  	tt.SetIsDirty(true)
   719  }
   720  
   721  func (tt *Attrs) Merge(ot *Attrs) {
   722  	if ot.Parent != nil {
   723  		tt.Parent = ot.Parent
   724  	}
   725  	tt.AttrFlags |= ot.AttrFlags
   726  	tt.Other = ot.Other // TODO merge by key.
   727  }
   728  
   729  // ----------------------------------------
   730  // AttrFlags
   731  
   732  // NOTE: AttrFlags are merged with a simple or-assign op.
   733  type AttrFlags uint32
   734  
   735  func (af AttrFlags) GetAttrFlags() AttrFlags {
   736  	return af
   737  }
   738  
   739  const AttrFlagNone AttrFlags = 0
   740  
   741  const (
   742  	AttrFlagIsCursor   AttrFlags = 1 << iota // is current cursor
   743  	AttrFlagIsSelected                       // is selected (among possibly others)
   744  	AttrFlagIsOccluded                       // is hidden due to stack
   745  	AttrFlagIsDirty                          // is dirty (not yet used)
   746  )
   747  
   748  type KVPair struct {
   749  	Key   string
   750  	Value interface{}
   751  }
   752  
   753  // ----------------------------------------
   754  // computeIntersection()
   755  
   756  // els: element size
   757  // elo: offset within element
   758  // vws: view size
   759  // minX,maxX,minY,maxY are relative to el.
   760  // maxX and maxY are exclusive.
   761  func computeIntersection(els Size, elo Coord, vws Size) (minX, maxX, minY, maxY int) {
   762  	if elo.X < 0 {
   763  		/*
   764  			View
   765  			+----------+
   766  			|   [Elem__|____]
   767  			+----------+
   768  			x   0
   769  		*/
   770  		minX = 0
   771  	} else {
   772  		/*
   773  			     View
   774  			     +----------+
   775  			[____|__Elem]   |
   776  			     +----------+
   777  			0    x
   778  		*/
   779  		minX = elo.X
   780  	}
   781  	if els.Width <= vws.Width+elo.X {
   782  		/*
   783  			     View
   784  			     +----------+
   785  			[____|__Elem]   |
   786  			     +----------+
   787  				        W   w+x
   788  		*/
   789  		maxX = els.Width
   790  	} else {
   791  		/*
   792  			View
   793  			+----------+
   794  			|   [Elem__|____]
   795  			+----------+
   796  			           w+x  W
   797  		*/
   798  		maxX = vws.Width + elo.X
   799  	}
   800  	if elo.Y < 0 {
   801  		minY = 0
   802  	} else {
   803  		minY = elo.Y
   804  	}
   805  	if els.Height <= vws.Height+elo.Y {
   806  		maxY = els.Height
   807  	} else {
   808  		maxY = vws.Height + elo.Y
   809  	}
   810  	return
   811  }
   812  
   813  // ----------------------------------------
   814  // Misc simple types
   815  
   816  type Padding struct {
   817  	Left   int
   818  	Top    int
   819  	Right  int
   820  	Bottom int
   821  }
   822  
   823  func (pd Padding) GetPadding() Padding {
   824  	return pd
   825  }
   826  
   827  // A border can only have width 0 or 1, and is part of the padding.
   828  // Each string should represent a character of width 1.
   829  type Border struct {
   830  	Corners   [4]string // starts upper-left and clockwise, "" draws no corner.
   831  	TopLine   []string  // nil if no top border.
   832  	BotLine   []string  // nil if no bottom border.
   833  	LeftLine  []string  // nil if no left border.
   834  	RightLine []string  // nil if no right border.
   835  }
   836  
   837  func DefaultBorder() Border {
   838  	return Border{
   839  		Corners: [4]string{
   840  			string(tcell.RuneULCorner),
   841  			string(tcell.RuneURCorner),
   842  			string(tcell.RuneLRCorner),
   843  			string(tcell.RuneLLCorner),
   844  		},
   845  		TopLine:   []string{string(tcell.RuneHLine)},
   846  		BotLine:   []string{string(tcell.RuneHLine)},
   847  		LeftLine:  []string{string(tcell.RuneVLine)},
   848  		RightLine: []string{string(tcell.RuneVLine)},
   849  	}
   850  }
   851  
   852  func LeftBorder() Border {
   853  	return Border{
   854  		Corners: [4]string{
   855  			string("\u2553"),
   856  			"",
   857  			"",
   858  			string("\u2559"),
   859  		},
   860  		LeftLine: []string{
   861  			string("\u2551"),
   862  		},
   863  	}
   864  }
   865  
   866  func orSpace(chr string) string {
   867  	if chr == "" {
   868  		return " "
   869  	} else {
   870  		return chr
   871  	}
   872  }
   873  
   874  func (br *Border) GetCorner(i int) string {
   875  	if br == nil {
   876  		return " "
   877  	} else {
   878  		return orSpace(br.Corners[i])
   879  	}
   880  }
   881  
   882  func (br *Border) TLCorner() string {
   883  	return br.GetCorner(0)
   884  }
   885  
   886  func (br *Border) TRCorner() string {
   887  	return br.GetCorner(1)
   888  }
   889  
   890  func (br *Border) BRCorner() string {
   891  	return br.GetCorner(2)
   892  }
   893  
   894  func (br *Border) BLCorner() string {
   895  	return br.GetCorner(3)
   896  }
   897  
   898  func (br *Border) TopBorder(x int) string {
   899  	if br == nil || br.TopLine == nil {
   900  		return " "
   901  	} else {
   902  		return br.TopLine[x%len(br.TopLine)]
   903  	}
   904  }
   905  
   906  func (br *Border) BottomBorder(x int) string {
   907  	if br == nil || br.BotLine == nil {
   908  		return " "
   909  	} else {
   910  		return br.BotLine[x%len(br.BotLine)]
   911  	}
   912  }
   913  
   914  func (br *Border) LeftBorder(y int) string {
   915  	if br == nil || br.LeftLine == nil {
   916  		return " "
   917  	} else {
   918  		return br.LeftLine[y%len(br.LeftLine)]
   919  	}
   920  }
   921  
   922  func (br *Border) RightBorder(y int) string {
   923  	if br == nil || br.RightLine == nil {
   924  		return " "
   925  	} else {
   926  		return br.RightLine[y%len(br.RightLine)]
   927  	}
   928  }
   929  
   930  type Size struct {
   931  	Width  int
   932  	Height int // -1 if not set.
   933  }
   934  
   935  func (sz Size) String() string {
   936  	return fmt.Sprintf("{%d,%d}", sz.Width, sz.Height)
   937  }
   938  
   939  func (sz Size) IsZero() bool {
   940  	return sz.Width == 0 && sz.Height == 0
   941  }
   942  
   943  func (sz Size) GetSize() Size {
   944  	return sz
   945  }
   946  
   947  // zero widths or heights are valid.
   948  func (sz Size) IsValid() bool {
   949  	return 0 <= sz.Width && 0 <= sz.Height
   950  }
   951  
   952  func (sz Size) IsPositive() bool {
   953  	return 0 < sz.Width && 0 < sz.Height
   954  }
   955  
   956  func (sz Size) SubCoord(crd Coord) Size {
   957  	if !crd.IsNonNegative() {
   958  		panic("should not happen")
   959  	}
   960  	sz2 := Size{
   961  		Width:  sz.Width - crd.X,
   962  		Height: sz.Height - crd.Y,
   963  	}
   964  	if !sz2.IsValid() {
   965  		panic("should not happen")
   966  	}
   967  	return sz2
   968  }
   969  
   970  type Coord struct {
   971  	X int
   972  	Y int
   973  }
   974  
   975  func (crd Coord) GetCoord() Coord {
   976  	return crd
   977  }
   978  
   979  func (crd *Coord) SetCoord(nc Coord) {
   980  	*crd = nc
   981  }
   982  
   983  func (crd Coord) IsNonNegative() bool {
   984  	return 0 <= crd.X && 0 <= crd.Y
   985  }
   986  
   987  func (crd Coord) Neg() Coord {
   988  	return Coord{
   989  		X: -crd.X,
   990  		Y: -crd.Y,
   991  	}
   992  }
   993  
   994  func (crd Coord) Add(crd2 Coord) Coord {
   995  	return Coord{
   996  		X: crd.X + crd2.X,
   997  		Y: crd.Y + crd2.Y,
   998  	}
   999  }
  1000  
  1001  func (crd Coord) Sub(crd2 Coord) Coord {
  1002  	return Coord{
  1003  		X: crd.X - crd2.X,
  1004  		Y: crd.Y - crd2.Y,
  1005  	}
  1006  }