Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}