gemini-browser

A text-based gemini browser
git clone git://git.laack.co/gemini-browser.git
Log | Files | Refs | README

util.go (4808B)


      1 package tview
      2 
      3 import (
      4 	"math"
      5 	"os"
      6 	"regexp"
      7 
      8 	"github.com/gdamore/tcell/v2"
      9 )
     10 
     11 // Text alignment within a box. Also used to align images.
     12 const (
     13 	AlignLeft = iota
     14 	AlignCenter
     15 	AlignRight
     16 	AlignTop    = 0
     17 	AlignBottom = 2
     18 )
     19 
     20 var (
     21 	// Regular expression used to escape style/region tags.
     22 	escapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\]`)
     23 
     24 	// Regular expression used to unescape escaped style/region tags.
     25 	unescapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\[\]`)
     26 
     27 	// The number of colors available in the terminal.
     28 	availableColors = 256
     29 )
     30 
     31 // Package initialization.
     32 func init() {
     33 	// Determine the number of colors available in the terminal.
     34 	info, err := tcell.LookupTerminfo(os.Getenv("TERM"))
     35 	if err == nil {
     36 		availableColors = info.Colors
     37 	}
     38 }
     39 
     40 // Print prints text onto the screen into the given box at (x,y,maxWidth,1),
     41 // not exceeding that box. "align" is one of AlignLeft, AlignCenter, or
     42 // AlignRight. The screen's background color will not be changed.
     43 //
     44 // You can change the colors and text styles mid-text by inserting a style tag.
     45 // See the package description for details.
     46 //
     47 // Returns the number of actual bytes of the text printed (including style tags)
     48 // and the actual width used for the printed runes.
     49 func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {
     50 	start, end, width := printWithStyle(screen, text, x, y, 0, maxWidth, align, tcell.StyleDefault.Foreground(color), true)
     51 	return end - start, width
     52 }
     53 
     54 // printWithStyle works like [Print] but it takes a style instead of just a
     55 // foreground color. The skipWidth parameter specifies the number of cells
     56 // skipped at the beginning of the text. It returns the start index, end index
     57 // (exclusively), and screen width of the text actually printed. If
     58 // maintainBackground is "true", the existing screen background is not changed
     59 // (i.e. the style's background color is ignored).
     60 func printWithStyle(screen tcell.Screen, text string, x, y, skipWidth, maxWidth, align int, style tcell.Style, maintainBackground bool) (start, end, printedWidth int) {
     61 	totalWidth, totalHeight := screen.Size()
     62 	if maxWidth <= 0 || len(text) == 0 || y < 0 || y >= totalHeight {
     63 		return 0, 0, 0
     64 	}
     65 
     66 	// If we don't overwrite the background, we use the default color.
     67 	if maintainBackground {
     68 		style = style.Background(tcell.ColorDefault)
     69 	}
     70 
     71 	// Skip beginning and measure width.
     72 	var textWidth int
     73 	state := &stepState{
     74 		unisegState: -1,
     75 		style:       style,
     76 	}
     77 	newState := *state
     78 	str := text
     79 	for len(str) > 0 {
     80 		_, str, state = step(str, state, stepOptionsStyle)
     81 		if skipWidth > 0 {
     82 			skipWidth -= state.Width()
     83 			text = str
     84 			newState = *state
     85 			start += state.GrossLength()
     86 		} else {
     87 			textWidth += state.Width()
     88 		}
     89 	}
     90 	state = &newState
     91 
     92 	// Reduce all alignments to AlignLeft.
     93 	if align == AlignRight {
     94 		// Chop off characters on the left until it fits.
     95 		for len(text) > 0 && textWidth > maxWidth {
     96 			_, text, state = step(text, state, stepOptionsStyle)
     97 			textWidth -= state.Width()
     98 			start += state.GrossLength()
     99 		}
    100 		x, maxWidth = x+maxWidth-textWidth, textWidth
    101 	} else if align == AlignCenter {
    102 		// Chop off characters on the left until it fits.
    103 		subtracted := (textWidth - maxWidth) / 2
    104 		for len(text) > 0 && subtracted > 0 {
    105 			_, text, state = step(text, state, stepOptionsStyle)
    106 			subtracted -= state.Width()
    107 			textWidth -= state.Width()
    108 			start += state.GrossLength()
    109 		}
    110 		if textWidth < maxWidth {
    111 			x, maxWidth = x+maxWidth/2-textWidth/2, textWidth
    112 		}
    113 	}
    114 
    115 	// Draw left-aligned text.
    116 	end = start
    117 	rightBorder := x + maxWidth
    118 	for len(text) > 0 && x < rightBorder && x < totalWidth {
    119 		var c string
    120 		c, text, state = step(text, state, stepOptionsStyle)
    121 		if c == "" {
    122 			break // We don't care about the style at the end.
    123 		}
    124 		width := state.Width()
    125 
    126 		if width > 0 {
    127 			finalStyle := state.Style()
    128 			if maintainBackground {
    129 				_, backgroundColor, _ := finalStyle.Decompose()
    130 				if backgroundColor == tcell.ColorDefault {
    131 					_, _, existingStyle, _ := screen.GetContent(x, y)
    132 					_, background, _ := existingStyle.Decompose()
    133 					finalStyle = finalStyle.Background(background)
    134 				}
    135 			}
    136 			for offset := width - 1; offset >= 0; offset-- {
    137 				// To avoid undesired effects, we populate all cells.
    138 				runes := []rune(c)
    139 				if offset == 0 {
    140 					screen.SetContent(x+offset, y, runes[0], runes[1:], finalStyle)
    141 				} else {
    142 					screen.SetContent(x+offset, y, ' ', nil, finalStyle)
    143 				}
    144 			}
    145 		}
    146 
    147 		x += width
    148 		end += state.GrossLength()
    149 		printedWidth += width
    150 	}
    151 
    152 	return
    153 }
    154 
    155 // PrintSimple prints white text to the screen at the given position.
    156 func PrintSimple(screen tcell.Screen, text string, x, y int) {
    157 	Print(screen, text, x, y, math.MaxInt32, AlignLeft, Styles.PrimaryTextColor)
    158 }