1package tview
2
3import (
4	"github.com/gdamore/tcell"
5)
6
7// frameText holds information about a line of text shown in the frame.
8type frameText struct {
9	Text   string      // The text to be displayed.
10	Header bool        // true = place in header, false = place in footer.
11	Align  int         // One of the Align constants.
12	Color  tcell.Color // The text color.
13}
14
15// Frame is a wrapper which adds a border around another primitive. The top area
16// (header) and the bottom area (footer) may also contain text.
17//
18// See https://github.com/rivo/tview/wiki/Frame for an example.
19type Frame struct {
20	*Box
21
22	// The contained primitive.
23	primitive Primitive
24
25	// The lines of text to be displayed.
26	text []*frameText
27
28	// Border spacing.
29	top, bottom, header, footer, left, right int
30}
31
32// NewFrame returns a new frame around the given primitive. The primitive's
33// size will be changed to fit within this frame.
34func NewFrame(primitive Primitive) *Frame {
35	box := NewBox()
36
37	f := &Frame{
38		Box:       box,
39		primitive: primitive,
40		top:       1,
41		bottom:    1,
42		header:    1,
43		footer:    1,
44		left:      1,
45		right:     1,
46	}
47
48	f.focus = f
49
50	return f
51}
52
53// AddText adds text to the frame. Set "header" to true if the text is to appear
54// in the header, above the contained primitive. Set it to false for it to
55// appear in the footer, below the contained primitive. "align" must be one of
56// the Align constants. Rows in the header are printed top to bottom, rows in
57// the footer are printed bottom to top. Note that long text can overlap as
58// different alignments will be placed on the same row.
59func (f *Frame) AddText(text string, header bool, align int, color tcell.Color) *Frame {
60	f.text = append(f.text, &frameText{
61		Text:   text,
62		Header: header,
63		Align:  align,
64		Color:  color,
65	})
66	return f
67}
68
69// Clear removes all text from the frame.
70func (f *Frame) Clear() *Frame {
71	f.text = nil
72	return f
73}
74
75// SetBorders sets the width of the frame borders as well as "header" and
76// "footer", the vertical space between the header and footer text and the
77// contained primitive (does not apply if there is no text).
78func (f *Frame) SetBorders(top, bottom, header, footer, left, right int) *Frame {
79	f.top, f.bottom, f.header, f.footer, f.left, f.right = top, bottom, header, footer, left, right
80	return f
81}
82
83// Draw draws this primitive onto the screen.
84func (f *Frame) Draw(screen tcell.Screen) {
85	f.Box.Draw(screen)
86
87	// Calculate start positions.
88	x, top, width, height := f.GetInnerRect()
89	bottom := top + height - 1
90	x += f.left
91	top += f.top
92	bottom -= f.bottom
93	width -= f.left + f.right
94	if width <= 0 || top >= bottom {
95		return // No space left.
96	}
97
98	// Draw text.
99	var rows [6]int // top-left, top-center, top-right, bottom-left, bottom-center, bottom-right.
100	topMax := top
101	bottomMin := bottom
102	for _, text := range f.text {
103		// Where do we place this text?
104		var y int
105		if text.Header {
106			y = top + rows[text.Align]
107			rows[text.Align]++
108			if y >= bottomMin {
109				continue
110			}
111			if y+1 > topMax {
112				topMax = y + 1
113			}
114		} else {
115			y = bottom - rows[3+text.Align]
116			rows[3+text.Align]++
117			if y <= topMax {
118				continue
119			}
120			if y-1 < bottomMin {
121				bottomMin = y - 1
122			}
123		}
124
125		// Draw text.
126		Print(screen, text.Text, x, y, width, text.Align, text.Color)
127	}
128
129	// Set the size of the contained primitive.
130	if topMax > top {
131		top = topMax + f.header
132	}
133	if bottomMin < bottom {
134		bottom = bottomMin - f.footer
135	}
136	if top > bottom {
137		return // No space for the primitive.
138	}
139	f.primitive.SetRect(x, top, width, bottom+1-top)
140
141	// Finally, draw the contained primitive.
142	f.primitive.Draw(screen)
143}
144
145// Focus is called when this primitive receives focus.
146func (f *Frame) Focus(delegate func(p Primitive)) {
147	delegate(f.primitive)
148}
149
150// HasFocus returns whether or not this primitive has focus.
151func (f *Frame) HasFocus() bool {
152	focusable, ok := f.primitive.(Focusable)
153	if ok {
154		return focusable.HasFocus()
155	}
156	return false
157}
158