1// Copyright 2016 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
2// Use of this source code is governed by a MIT license that can
3// be found in the LICENSE file.
4
5package termui
6
7import "image"
8
9// Hline is a horizontal line.
10type Hline struct {
11	X   int
12	Y   int
13	Len int
14	Fg  Attribute
15	Bg  Attribute
16}
17
18// Vline is a vertical line.
19type Vline struct {
20	X   int
21	Y   int
22	Len int
23	Fg  Attribute
24	Bg  Attribute
25}
26
27// Buffer draws a horizontal line.
28func (l Hline) Buffer() Buffer {
29	if l.Len <= 0 {
30		return NewBuffer()
31	}
32	return NewFilledBuffer(l.X, l.Y, l.X+l.Len, l.Y+1, HORIZONTAL_LINE, l.Fg, l.Bg)
33}
34
35// Buffer draws a vertical line.
36func (l Vline) Buffer() Buffer {
37	if l.Len <= 0 {
38		return NewBuffer()
39	}
40	return NewFilledBuffer(l.X, l.Y, l.X+1, l.Y+l.Len, VERTICAL_LINE, l.Fg, l.Bg)
41}
42
43// Buffer draws a box border.
44func (b Block) drawBorder(buf Buffer) {
45	if !b.Border {
46		return
47	}
48
49	min := b.area.Min
50	max := b.area.Max
51
52	x0 := min.X
53	y0 := min.Y
54	x1 := max.X - 1
55	y1 := max.Y - 1
56
57	// draw lines
58	if b.BorderTop {
59		buf.Merge(Hline{x0, y0, x1 - x0, b.BorderFg, b.BorderBg}.Buffer())
60	}
61	if b.BorderBottom {
62		buf.Merge(Hline{x0, y1, x1 - x0, b.BorderFg, b.BorderBg}.Buffer())
63	}
64	if b.BorderLeft {
65		buf.Merge(Vline{x0, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer())
66	}
67	if b.BorderRight {
68		buf.Merge(Vline{x1, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer())
69	}
70
71	// draw corners
72	if b.BorderTop && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 0 {
73		buf.Set(x0, y0, Cell{TOP_LEFT, b.BorderFg, b.BorderBg})
74	}
75	if b.BorderTop && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 0 {
76		buf.Set(x1, y0, Cell{TOP_RIGHT, b.BorderFg, b.BorderBg})
77	}
78	if b.BorderBottom && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 1 {
79		buf.Set(x0, y1, Cell{BOTTOM_LEFT, b.BorderFg, b.BorderBg})
80	}
81	if b.BorderBottom && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 1 {
82		buf.Set(x1, y1, Cell{BOTTOM_RIGHT, b.BorderFg, b.BorderBg})
83	}
84}
85
86func (b Block) drawBorderLabel(buf Buffer) {
87	maxTxtW := b.area.Dx() - 2
88	tx := DTrimTxCls(DefaultTxBuilder.Build(b.BorderLabel, b.BorderLabelFg, b.BorderLabelBg), maxTxtW)
89
90	for i, w := 0, 0; i < len(tx); i++ {
91		buf.Set(b.area.Min.X+1+w, b.area.Min.Y, tx[i])
92		w += tx[i].Width()
93	}
94}
95
96// Block is a base struct for all other upper level widgets,
97// consider it as css: display:block.
98// Normally you do not need to create it manually.
99type Block struct {
100	area          image.Rectangle
101	innerArea     image.Rectangle
102	X             int
103	Y             int
104	Border        bool
105	BorderFg      Attribute
106	BorderBg      Attribute
107	BorderLeft    bool
108	BorderRight   bool
109	BorderTop     bool
110	BorderBottom  bool
111	BorderLabel   string
112	BorderLabelFg Attribute
113	BorderLabelBg Attribute
114	Display       bool
115	Bg            Attribute
116	Width         int
117	Height        int
118	PaddingTop    int
119	PaddingBottom int
120	PaddingLeft   int
121	PaddingRight  int
122	id            string
123	Float         Align
124}
125
126// NewBlock returns a *Block which inherits styles from current theme.
127func NewBlock() *Block {
128	b := Block{}
129	b.Display = true
130	b.Border = true
131	b.BorderLeft = true
132	b.BorderRight = true
133	b.BorderTop = true
134	b.BorderBottom = true
135	b.BorderBg = ThemeAttr("border.bg")
136	b.BorderFg = ThemeAttr("border.fg")
137	b.BorderLabelBg = ThemeAttr("label.bg")
138	b.BorderLabelFg = ThemeAttr("label.fg")
139	b.Bg = ThemeAttr("block.bg")
140	b.Width = 2
141	b.Height = 2
142	b.id = GenId()
143	b.Float = AlignNone
144	return &b
145}
146
147func (b Block) Id() string {
148	return b.id
149}
150
151// Align computes box model
152func (b *Block) Align() {
153	// outer
154	b.area.Min.X = 0
155	b.area.Min.Y = 0
156	b.area.Max.X = b.Width
157	b.area.Max.Y = b.Height
158
159	// float
160	b.area = AlignArea(TermRect(), b.area, b.Float)
161	b.area = MoveArea(b.area, b.X, b.Y)
162
163	// inner
164	b.innerArea.Min.X = b.area.Min.X + b.PaddingLeft
165	b.innerArea.Min.Y = b.area.Min.Y + b.PaddingTop
166	b.innerArea.Max.X = b.area.Max.X - b.PaddingRight
167	b.innerArea.Max.Y = b.area.Max.Y - b.PaddingBottom
168
169	if b.Border {
170		if b.BorderLeft {
171			b.innerArea.Min.X++
172		}
173		if b.BorderRight {
174			b.innerArea.Max.X--
175		}
176		if b.BorderTop {
177			b.innerArea.Min.Y++
178		}
179		if b.BorderBottom {
180			b.innerArea.Max.Y--
181		}
182	}
183}
184
185// InnerBounds returns the internal bounds of the block after aligning and
186// calculating the padding and border, if any.
187func (b *Block) InnerBounds() image.Rectangle {
188	b.Align()
189	return b.innerArea
190}
191
192// Buffer implements Bufferer interface.
193// Draw background and border (if any).
194func (b *Block) Buffer() Buffer {
195	b.Align()
196
197	buf := NewBuffer()
198	buf.SetArea(b.area)
199	buf.Fill(' ', ColorDefault, b.Bg)
200
201	b.drawBorder(buf)
202	b.drawBorderLabel(buf)
203
204	return buf
205}
206
207// GetHeight implements GridBufferer.
208// It returns current height of the block.
209func (b Block) GetHeight() int {
210	return b.Height
211}
212
213// SetX implements GridBufferer interface, which sets block's x position.
214func (b *Block) SetX(x int) {
215	b.X = x
216}
217
218// SetY implements GridBufferer interface, it sets y position for block.
219func (b *Block) SetY(y int) {
220	b.Y = y
221}
222
223// SetWidth implements GridBuffer interface, it sets block's width.
224func (b *Block) SetWidth(w int) {
225	b.Width = w
226}
227
228func (b Block) InnerWidth() int {
229	return b.innerArea.Dx()
230}
231
232func (b Block) InnerHeight() int {
233	return b.innerArea.Dy()
234}
235
236func (b Block) InnerX() int {
237	return b.innerArea.Min.X
238}
239
240func (b Block) InnerY() int { return b.innerArea.Min.Y }
241