ascii.gno
6.08 Kb ยท 303 lines
1package ascii
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/moul/md"
8)
9
10type WAlignment string
11type HAlignment string
12
13const (
14 // Width Alignment
15 AlignWCenter WAlignment = "center"
16 AlignLeft WAlignment = "left"
17 AlignRight WAlignment = "right"
18
19 // Height Alignment
20 AlignHCenter HAlignment = "center"
21 AlignTop HAlignment = "top"
22 AlignBottom HAlignment = "bottom"
23)
24
25// padLine aligns text within a given width.
26//
27// Supports AlignLeft, AlignRight, and AlignWCenter alignment.
28func padLine(line string, width int, align WAlignment, space string) string {
29 padding := width - len(line)
30 if width < len(line) {
31 padding = 0
32 }
33
34 switch align {
35 case AlignRight:
36 return Repeat(space, padding) + line
37 case AlignWCenter:
38 left := padding / 2
39 right := padding - left
40 return Repeat(space, left) + line + Repeat(space, right)
41 default: // AlignLeft
42 return line + Repeat(space, padding)
43 }
44}
45
46// padHeight pads lines vertically according to alignment.
47//
48// Supports AlignTop, AlignBottom, and AlignHCenter alignment.
49func padHeight(lines []string, height int, align HAlignment) []string {
50 padded := []string{}
51 if height <= 0 {
52 return lines
53 }
54 extra := height - len(lines)
55
56 topPad := 0
57 switch align {
58 case AlignBottom:
59 topPad = extra
60 case AlignHCenter:
61 topPad = extra / 2
62 }
63
64 for i := 0; i < topPad; i++ {
65 padded = append(padded, "")
66 }
67 padded = append(padded, lines...)
68 for len(padded) < height {
69 padded = append(padded, "")
70 }
71 return padded
72}
73
74// Repeat returns repetition of a string n times.
75func Repeat(char string, n int) string {
76 if n <= 0 {
77 return ""
78 }
79 return strings.Repeat(char, n)
80}
81
82// Box draws a single-line text in a simple box.
83// If the string contains newlines, it falls back to FlexFrame function.
84//
85// Example:
86//
87// Box("Hello World\n!")
88//
89// Gives:
90//
91// +-------------+
92// | Hello World |
93// | ! |
94// +-------------+
95func Box(text string) string {
96 return FlexFrame(strings.Split(text, "\n"), AlignLeft)
97}
98
99// FlexFrame draws a frame with automatic width and alignment.
100//
101// Example:
102//
103// FlexFrame([]string{"hello", "worldd", "!!"}, "right")
104//
105// Gives:
106//
107// +-------+
108// | hello |
109// | worldd |
110// | !! |
111// +-------+
112func FlexFrame(lines []string, align WAlignment) string {
113 maxWidth := 0
114
115 for _, line := range lines {
116 if len(line) > maxWidth {
117 maxWidth = len(line)
118 }
119 }
120
121 top := "+" + Repeat("-", maxWidth+2) + "+\n"
122 bottom := "+" + Repeat("-", maxWidth+2) + "+"
123
124 body := ""
125 for i := 0; i < len(lines); i++ {
126 body += "| " + padLine(lines[i], maxWidth, align, " ") + " |\n"
127 }
128
129 return md.CodeBlock(top+body+bottom) + "\n"
130}
131
132// Frame draws a frame with specific width, height and alignment options.
133//
134// Example:
135//
136// Frame([]string{"hello", "world", "!!"}, "center", 10, 5, "center")
137//
138// Gives:
139//
140// +------------+
141// | |
142// | hello |
143// | world |
144// | !! |
145// | |
146// +------------+
147func Frame(
148 lines []string,
149 wAlign WAlignment,
150 width, height int,
151 hAlign HAlignment,
152) string {
153 maxWidth := width
154 for i := 0; i < len(lines); i++ {
155 if len(lines[i]) > maxWidth {
156 maxWidth = len(lines[i])
157 }
158 }
159
160 if len(lines) > height {
161 height = len(lines)
162 }
163 lines = padHeight(lines, height, hAlign)
164
165 top := "+" + Repeat("-", maxWidth+2) + "+\n"
166 bottom := "+" + Repeat("-", maxWidth+2) + "+"
167 body := ""
168
169 for _, line := range lines {
170 body += "| " + padLine(line, maxWidth, wAlign, " ") + " |\n"
171 }
172
173 return md.CodeBlock(top+body+bottom) + "\n"
174}
175
176// FixedFrame draws a frame with a fixed width and height, truncating or wrapping content as needed.
177// Width and height include the content area, not the frame borders.
178//
179// Example:
180//
181// Frame([]string{"hello world!!"}, ascii.AlignWCenter, 10, 4, ascii.AlignHCenter)
182//
183// Gives:
184//
185// +------------+
186// | |
187// | hello |
188// | world!! |
189// | |
190// +------------+
191func FixedFrame(
192 lines []string,
193 wAlign WAlignment,
194 width, height int,
195 hAlign HAlignment,
196) string {
197 var wrapped []string
198 if width < 0 {
199 width = 0
200 }
201 if height < 0 {
202 height = 0
203 }
204
205 for _, line := range lines {
206 words := strings.Fields(line)
207 current := ""
208 for _, word := range words {
209 if len(current)+len(word)+1 > width {
210 wrapped = append(wrapped, current)
211 current = word
212 } else {
213 if current == "" {
214 current = word
215 } else {
216 current += " " + word
217 }
218 }
219 }
220 if current != "" {
221 wrapped = append(wrapped, current)
222 }
223 }
224 wrapped = padHeight(wrapped, height, hAlign)
225
226 top := "+" + Repeat("-", width+2) + "+\n"
227 bottom := "+" + Repeat("-", width+2) + "+"
228
229 body := ""
230 for i, line := range wrapped {
231 if i == height {
232 break
233 }
234 body += "| " + padLine(line, width, wAlign, " ") + " |\n"
235 }
236
237 return md.CodeBlock(top+body+bottom) + "\n"
238}
239
240// ProgressBar renders a visual progress bar, the size represents the number of chars in length for the bar.
241//
242// Example:
243//
244// ProgressBar(2, 6, 10, true)
245//
246// Gives: [###-------] 33%
247func ProgressBar(current int, total int, charSize int, displayPercent bool) string {
248 if total == 0 {
249 return PercentageBar(0, charSize, displayPercent)
250 }
251 percent := (current * 100) / total
252
253 return PercentageBar(percent, charSize, displayPercent)
254}
255
256// PercentageBar renders a visual progress bar, the size represents the number of chars in length for the bar.
257// This differs from ProgressBar in that it does not require a total value, takes a percentage directly.
258//
259// Example:
260//
261// PercentageBar(50, 6, true)
262//
263// Gives: [###---] 50%
264func PercentageBar(percent int, charSize int, displayPercent bool) string {
265 fillLength := (percent * charSize) / 100
266 emptyLength := charSize - fillLength
267
268 filled := Repeat("#", fillLength)
269 empty := Repeat("-", emptyLength)
270
271 out := "[" + filled + empty + "]"
272 if !displayPercent {
273 return out
274 }
275 return out + " " + strconv.Itoa(percent) + "%"
276}
277
278// Grid renders a 2D grid of characters.
279//
280// Example:
281//
282// Grid(3, 3, "x")
283//
284// Gives:
285//
286// xxx
287// xxx
288// xxx
289func Grid(rows int, cols int, fill string) string {
290 out := ""
291 if rows <= 0 || cols <= 0 {
292 return out
293 }
294
295 for r := 0; r < rows; r++ {
296 row := ""
297 for c := 0; c < cols; c++ {
298 row += fill
299 }
300 out += row + "\n"
301 }
302 return out
303}