diff --git a/ansi/c0.go b/ansi/c0.go deleted file mode 100644 index 6d37ff2..0000000 --- a/ansi/c0.go +++ /dev/null @@ -1,38 +0,0 @@ -package ansi - -// C0 represents a list of C0 control codes. -// https://en.wikipedia.org/wiki/C0_and_C1_control_codes -type C0 byte; const ( - C0_Null C0 = iota - C0_StartOfHeading - C0_StartOfText - C0_EndOfText - C0_EndOfTransmission - C0_Enquiry - C0_Acknowledge - C0_Bell - C0_Backspace - C0_CharacterTab - C0_LineFeed - C0_LineTab - C0_FormFeed - C0_CarriageReturn - C0_ShiftOut - C0_ShiftIn - C0_DataLinkEscape - C0_DeviceControlOne - C0_DeviceControlTwo - C0_DeviceControlThree - C0_DeviceControlFour - C0_NegativeAcknowledge - C0_SynchronousIdle - C0_EndOfTransmissionBlock - C0_Cancel - C0_EndOfMedium - C0_Substitute - C0_Escape - C0_FileSeparator - C0_GroupSeparator - C0_RecordSeparator - C0_UnitSeparator -) diff --git a/ansi/c1.go b/ansi/c1.go deleted file mode 100644 index 2448367..0000000 --- a/ansi/c1.go +++ /dev/null @@ -1,48 +0,0 @@ -package ansi - -// C1 represents a list of C1 control codes. -// https://en.wikipedia.org/wiki/C0_and_C1_control_codes -type C1 byte; const ( - C1_PaddingCharacter C1 = iota + 128 - C1_HighOctetPreset - C1_BreakPermittedHere - C1_NoBreakHere - C1_Index - C1_NextLine - C1_StartOfSelectedArea - C1_EndOfSelectedArea - C1_CharacterTabSet - C1_CharacterTabWithJustification - C1_LineTabSet - C1_PartialLineForward - C1_PartialLineBackward - C1_ReverseLineFeed - C1_SingleShift2 - C1_SingleShift3 - C1_DeviceControlString - C1_PrivateUse1 - C1_PrivateUse2 - C1_SetTransmitState - C1_CancelCharacter - C1_MessageWaiting - C1_StartOfProtectedArea - C1_EndOfProtectedArea - C1_StartOfString - C1_SingleGraphicCharacterIntroducer - C1_SingleCharacterIntroducer - C1_ControlSequenceIntroducer - C1_StringTerminator - C1_OperatingSystemCommand - C1_PrivacyMessage - C1_ApplicationProgramCommand -) - -// Is checks if a byte is equal to a C0 code. -func (code C0) Is (test byte) bool { - return test == byte(code) -} - -// Is checks if a byte is equal to a C1 code. -func (code C1) Is (test byte) bool { - return byte(code) == test -} diff --git a/ansi/color.go b/ansi/color.go deleted file mode 100644 index 5bb052f..0000000 --- a/ansi/color.go +++ /dev/null @@ -1,90 +0,0 @@ -package ansi - -import "image/color" - -var _ color.Color = Color(0) - -// Color represents a 3, 4, or 8-Bit ansi color. -type Color byte; const ( - // Dim/standard colors - ColorBlack Color = iota - ColorRed - ColorGreen - ColorYellow - ColorBlue - ColorMagenta - ColorCyan - ColorWhite - - // Bright colors - ColorBrightBlack - ColorBrightRed - ColorBrightGreen - ColorBrightYellow - ColorBrightBlue - ColorBrightMagenta - ColorBrightCyan - ColorBrightWhite - - // 216 cube colors (16 - 231) - // 24 grayscale colors (232 - 255) -) - -// Is16 returns whether the color is a dim or bright color, and can be assigned -// to a theme palette. -func (c Color) Is16 () bool { - return c.IsDim() || c.IsBright() -} - -// IsDim returns whether the color is dim. -func (c Color) IsDim () bool { - return c < 8 -} - -// IsBright returns whether the color is bright. -func (c Color) IsBright () bool { - return c >= 8 && c < 16 -} - -// IsCube returns whether the color is part of the 6x6x6 cube. -func (c Color) IsCube () bool { - return c >= 16 && c < 232 -} - -// IsGrayscale returns whether the color grayscale. -func (c Color) IsGrayscale () bool { - return c >= 232 && c <= 255 -} - -// RGB returns the 8 bit RGB values of the color as a color.RGBA value. -func (c Color) RGB () (out color.RGBA) { - switch { - case c.Is16(): - // each bit is a color component - out.R = 0xFF * uint8((c & 0x1) >> 0) - out.G = 0xFF * uint8((c & 0x2) >> 1) - out.B = 0xFF * uint8((c & 0x4) >> 3) - // dim if color is in the dim range - if c & 0x8 > 0 { out.R >>= 1; out.G >>= 1; out.B >>= 1 } - - case c.IsCube(): - index := int(c - 16) - out.R = uint8((((index / 36) % 6) * 255) / 5) - out.G = uint8((((index / 6) % 6) * 255) / 5) - out.B = uint8((((index ) % 6) * 255) / 5) - - case c.IsGrayscale(): - out.R = uint8(((int(c) - 232) * 255) / 23) - out.G = out.R - out.B = out.R - } - - out.A = 0xFF - return - -} - -// RGBA fulfills the color.Color interface. -func (c Color) RGBA () (r, g, b, a uint32) { - return c.RGB().RGBA() -} diff --git a/ansi/csi.go b/ansi/csi.go deleted file mode 100644 index 7fb8969..0000000 --- a/ansi/csi.go +++ /dev/null @@ -1,17 +0,0 @@ -package ansi - -// CSI represents a list of CSI sequences that have no parameters. -// FIXME: some of these do indeed have parameters -type CSI int; const ( - CSI_DeviceStatusReport CSI = iota - CSI_SaveCursorPosition - CSI_RestoreCursorPosition - CSI_ShowCursor - CSI_HideCursor - CSI_EnableReportingFocus - CSI_DisableReportingFocus - CSI_EnableAlternativeBuffer - CSI_DisableAlternativeBuffer - CSI_EnableBracketedPasteMode - CSI_DisableBracketedPasteMode -) diff --git a/ansi/decoder.go b/ansi/decoder.go deleted file mode 100644 index 4057e67..0000000 --- a/ansi/decoder.go +++ /dev/null @@ -1,360 +0,0 @@ -package ansi - -import "strconv" -import "strings" -import "image/color" - -// Useful: https://en.wikipedia.org/wiki/ANSI_escape_code - -// Decoder is a state machine capable of decoding text contianing various escape -// codes and sequences. It satisfies io.Writer. It has no constructor and its -// zero value can be used safely. -type Decoder struct { - // OnText is called when a segment of text is processed. - OnText func (string) - - // OnC0 is called when a C0 control code is processed that isn't a - // whitespace character. - OnC0 func (C0) - - // OnC1 is called when a C1 escape sequence is processed. - OnC1 func (C1) - - // OnDCS is called when a device control string is processed. - OnDCS func (string) - - // OnCSI is called when a non-SGR CSI escape sequence with no parameters - // is processed. - OnCSI func (CSI) - - // OnSGR is called when a CSI SGR escape sequence with no parameters is - // processed. - OnSGR func (SGR) - - // OnPM is called when a privacy message is processed. - OnPM func (string) - - // OnAPC is called when an application program command is processed. - OnAPC func (string) - - // Non-SGR CSI sequences with parameters: - OnCursorUp func (distance int) - OnCursorDown func (distance int) - OnCursorForward func (distance int) - OnCursorBack func (distance int) - OnCursorNextLine func (distance int) - OnCursorPreviousLine func (distance int) - OnCursorHorizontalAbsolute func (column int) - OnCursorPosition func (column, row int) - OnEraseInDisplay func (mode int) - OnEraseInLine func (mode int) - OnScrollUp func (distance int) - OnScrollDown func (distance int) - OnHorizontalVerticalPosition func (column, row int) - - // SGR CSI sequences with parameters: - OnForegroundColor func (Color) - OnForegroundColorTrue func (color.RGBA) - OnBackgroundColor func (Color) - OnBackgroundColorTrue func (color.RGBA) - OnUnderlineColor func (Color) - OnUnderlineColorTrue func (color.RGBA) - - // OSC sequences from XTerm: - OnWindowTitle func (title string) - OnIconName func (name string) - OnIconFile func (path string) - OnXProperty func (property, value string) - OnSelectionPut func (selection, text string) - OnSelectionGet func (selection string) - OnQueryAllowed func () - OnQueryDisallowed func () - - // OSC sequences from iTerm2: - OnCursorShape func (shape int) - OnHyperlink func (params map[string] string, text, link string) - OnBackgroundImage func (path string) - - state decodeState - expectingST bool - gathered []byte -} - -type decodeState int; const ( - decodeStateText decodeState = iota - decodeStateAwaitC1 - - decodeStateGatherDCS - decodeStateGatherSOS - decodeStateGatherCSI - decodeStateGatherOSC - decodeStateGatherPM - decodeStateGatherAPC -) - -func (decoder *Decoder) Write (buffer []byte) (wrote int, err error) { - wrote = len(buffer) - - for len(buffer) > 0 { - switch decoder.state { - case decodeStateText: - if C0_Escape.Is(buffer[0]) { - // begin C1 control code - decoder.state = decodeStateAwaitC1 - buffer = buffer[1:] - - } else if buffer[0] < ' ' { - // process C0 control code - if decoder.OnC0 != nil { - decoder.OnC0(C0(buffer[0])) - } - buffer = buffer[1:] - - } else { - // process as much plain text as we can - buffer = decoder.processString(buffer) - } - - case decodeStateAwaitC1: - if buffer[0] < 128 { - // false alarm, this is just a C0 escape - if decoder.OnC0 != nil { - decoder.OnC0(C0_Escape) - } - break - } - - switch C1(buffer[0]) { - case C1_DeviceControlString: - decoder.state = decodeStateGatherDCS - case C1_StartOfString: - decoder.state = decodeStateGatherSOS - case C1_ControlSequenceIntroducer: - decoder.state = decodeStateGatherCSI - case C1_OperatingSystemCommand: - decoder.state = decodeStateGatherOSC - case C1_PrivacyMessage: - decoder.state = decodeStateGatherPM - case C1_ApplicationProgramCommand: - decoder.state = decodeStateGatherAPC - default: - // process C1 control code - if decoder.OnC1 != nil { - decoder.OnC1(C1(buffer[0])) - } - } - buffer = buffer[1:] - - case - decodeStateGatherDCS, - decodeStateGatherSOS, - decodeStateGatherOSC, - decodeStateGatherPM, - decodeStateGatherAPC: - - if decoder.expectingST && C1_StringTerminator.Is(buffer[0]) { - // remove the trailing ESC - decoder.gathered = decoder.gathered [ - :len(decoder.gathered) - 1] - - if decoder.state == decodeStateGatherOSC { - // we understand some OSC codes so we - // handle them differently - decoder.processOSC() - } else { - // generic handler for uncommon stuff - decoder.processGeneric() - } - decoder.state = decodeStateText - } - if C0_Escape.Is(buffer[0]) { - decoder.expectingST = true - } - - case decodeStateGatherCSI: - decoder.gather(buffer[0]) - if buffer[0] < 0x30 || buffer[0] > 0x3F { - decoder.processCSI() - decoder.state = decodeStateText - } - buffer = buffer[1:] - } - } - - return -} - -func (decoder *Decoder) gather (character byte) { - decoder.gathered = append(decoder.gathered, character) -} - -func (decoder *Decoder) processString (buffer []byte) []byte { - for index, char := range buffer { - if C0_Escape.Is(char) { - if decoder.OnText != nil { - decoder.OnText(string(buffer[:index])) - } - return buffer[:index] - } - } - return buffer -} - -func (decoder *Decoder) processGeneric () { - parameter := string(decoder.gathered) - switch decoder.state { - case decodeStateGatherDCS: - if decoder.OnDCS != nil { decoder.OnDCS(parameter) } - case decodeStateGatherSOS: - if decoder.OnText != nil { decoder.OnText(parameter) } - case decodeStateGatherPM: - if decoder.OnPM != nil { decoder.OnPM(parameter) } - case decodeStateGatherAPC: - if decoder.OnAPC != nil { decoder.OnAPC(parameter) } - } -} - -func (decoder *Decoder) processOSC () { - // TODO: analyze OSC -} - -func (decoder *Decoder) processCSI () { - if len(decoder.gathered) < 2 { return } - parameters := ParameterInts(decoder.gathered) - - var p0, p1, p2, p3 int - if len(parameters) > 0 { p0 = parameters[0] } - if len(parameters) > 1 { p1 = parameters[1] } - if len(parameters) > 2 { p2 = parameters[2] } - if len(parameters) > 3 { p3 = parameters[3] } - - switch Last(decoder.gathered) { - case 'A': if decoder.OnCursorUp != nil { decoder.OnCursorUp(clampOne(p0)) } - case 'B': if decoder.OnCursorDown != nil { decoder.OnCursorDown(clampOne(p0)) } - case 'C': if decoder.OnCursorForward != nil { decoder.OnCursorForward(clampOne(p0)) } - case 'D': if decoder.OnCursorBack != nil { decoder.OnCursorBack(clampOne(p0)) } - case 'E': if decoder.OnCursorNextLine != nil { decoder.OnCursorNextLine(clampOne(p0)) } - case 'F': if decoder.OnCursorPreviousLine != nil { decoder.OnCursorPreviousLine(clampOne(p0)) } - case 'G': if decoder.OnCursorHorizontalAbsolute != nil { decoder.OnCursorHorizontalAbsolute(clampOne(p0)) } - case 'H', 'f': if decoder.OnCursorPosition != nil { decoder.OnCursorPosition(clampOne(p0), clampOne(p1)) } - - case 'J': if decoder.OnEraseInDisplay != nil { decoder.OnEraseInDisplay(p0) } - case 'K': if decoder.OnEraseInLine != nil { decoder.OnEraseInLine(p0) } - case 'S': if decoder.OnScrollUp != nil { decoder.OnScrollUp(clampOne(p0)) } - case 'T': if decoder.OnScrollDown != nil { decoder.OnScrollDown(clampOne(p0)) } - - case 'm': - p0 := SGR(p0) - switch { - case - p0 >= SGR_ForegroundColorBlack && - p0 <= SGR_ForegroundColorWhite && - decoder.OnForegroundColor != nil : - decoder.OnForegroundColor(Color(p0 - SGR_ForegroundColorBlack)) - case - p0 >= SGR_ForegroundColorBrightBlack && - p0 <= SGR_ForegroundColorBrightWhite && - decoder.OnForegroundColor != nil : - decoder.OnForegroundColor(Color(p0 - SGR_ForegroundColorBrightBlack + 8)) - - case p0 == SGR_ForegroundColor: - switch p1 { - case 2: - if decoder.OnForegroundColor == nil { break } - decoder.OnForegroundColor(Color(p1)) - case 5: - if decoder.OnForegroundColorTrue == nil { break } - decoder.OnForegroundColorTrue (color.RGBA { - R: uint8(p1), - G: uint8(p2), - B: uint8(p3), - A: 0xFF, - }) - } - - case - p0 >= SGR_BackgroundColorBlack && - p0 <= SGR_BackgroundColorWhite && - decoder.OnBackgroundColor != nil : - decoder.OnBackgroundColor(Color(p0 - SGR_BackgroundColorBlack)) - case - p0 >= SGR_BackgroundColorBrightBlack && - p0 <= SGR_BackgroundColorBrightWhite && - decoder.OnBackgroundColor != nil : - decoder.OnBackgroundColor(Color(p0 - SGR_BackgroundColorBrightBlack + 8)) - - case p0 == SGR_BackgroundColor: - switch p1 { - case 2: - if decoder.OnBackgroundColor == nil { break } - decoder.OnBackgroundColor(Color(p1)) - case 5: - if decoder.OnBackgroundColorTrue == nil { break } - decoder.OnBackgroundColorTrue (color.RGBA { - R: uint8(p1), - G: uint8(p2), - B: uint8(p3), - A: 0xFF, - }) - } - - case p0 == SGR_UnderlineColor: - switch p1 { - case 2: - if decoder.OnUnderlineColor == nil { break } - decoder.OnUnderlineColor(Color(p1)) - case 5: - if decoder.OnUnderlineColorTrue == nil { break } - decoder.OnUnderlineColorTrue (color.RGBA { - R: uint8(p1), - G: uint8(p2), - B: uint8(p3), - A: 0xFF, - }) - } - - default: if decoder.OnSGR != nil { decoder.OnSGR(SGR(p0)) } - } - - // TODO - case 'n': - case 's': - case 'u': - case 'h': - case 'l': - } -} - -func clampOne (number int) int { - if number < 1 { - return 1 - } else { - return number - } -} - -// Last returns the last item of a slice. -func Last[T any] (source []T) T { - return source[len(source) - 1] -} - -// ParameterStrings separates a byte slice by semicolons into a list of strings. -func ParameterStrings (source []byte) (parameters []string) { - parameters = strings.Split(string(source), ";") - for index := range parameters { - parameters[index] = strings.TrimSpace(parameters[index]) - } - return -} - -// ParameterInts is like ParameterStrings, but returns integers instead of -// strings. If a parameter is empty or cannot be converted into an integer, that -// parameter will be zero. -func ParameterInts (source []byte) (parameters []int) { - stringParameters := ParameterStrings(source) - parameters = make([]int, len(stringParameters)) - for index, parameter := range stringParameters { - parameters[index], _ = strconv.Atoi(parameter) - } - return -} diff --git a/ansi/sgr.go b/ansi/sgr.go deleted file mode 100644 index 1aa6197..0000000 --- a/ansi/sgr.go +++ /dev/null @@ -1,97 +0,0 @@ -package ansi - -// SGR represents a list of Select Graphic Rendition parameters. -type SGR int; const ( - SGR_Normal SGR = iota - SGR_Bold - SGR_Dim - SGR_Italic - SGR_Underline - SGR_SlowBlink - SGR_RapidBlink - SGR_Reverse - SGR_Conceal - SGR_Strike - - SGR_FontPrimary - SGR_Font1 - SGR_Font2 - SGR_Font3 - SGR_Font4 - SGR_Font5 - SGR_Font6 - SGR_Font7 - SGR_Font8 - SGR_Font9 - SGR_FontFraktur - - SGR_DoubleUnderline - SGR_NormalIntensity - SGR_NeitherItalicNorBlackletter - SGR_NotUnderlined - SGR_NotBlinking - SGR_PorportionalSpacing - SGR_NotReversed - SGR_NotCrossedOut - - SGR_ForegroundColorBlack - SGR_ForegroundColorRed - SGR_ForegroundColorGreen - SGR_ForegroundColorYellow - SGR_ForegroundColorBlue - SGR_ForegroundColorMagenta - SGR_ForegroundColorCyan - SGR_ForegroundColorWhite - SGR_ForegroundColor - SGR_ForegroundColorDefault - - SGR_BackgroundColorBlack - SGR_BackgroundColorRed - SGR_BackgroundColorGreen - SGR_BackgroundColorYellow - SGR_BackgroundColorBlue - SGR_BackgroundColorMagenta - SGR_BackgroundColorCyan - SGR_BackgroundColorWhite - SGR_BackgroundColor - SGR_BackgroundColorDefault - - SGR_DisablePorportionalSpacing - SGR_Framed - SGR_Encircled - SGR_Overlined - SGR_NeitherFramedNorEncircled - SGR_NotOverlined - - SGR_UnderlineColor - SGR_UnderlineColorDefault - - SGR_IdeogramUnderline - SGR_IdeogramDoubleUnderline - SGR_IdeogramOverline - SGR_IdeogramDoubleOverline - SGR_IdeogramStressMarking - SGR_NoIdeogramAttributes - - SGR_Superscript - SGR_Subscript - SGR_NeitherSuperscriptNorSubscript - - SGR_ForegroundColorBrightBlack - SGR_ForegroundColorBrightRed - SGR_ForegroundColorBrightGreen - SGR_ForegroundColorBrightYellow - SGR_ForegroundColorBrightBlue - SGR_ForegroundColorBrightMagenta - SGR_ForegroundColorBrightCyan - SGR_ForegroundColorBrightWhite - - SGR_BackgroundColorBrightBlack - SGR_BackgroundColorBrightRed - SGR_BackgroundColorBrightGreen - SGR_BackgroundColorBrightYellow - SGR_BackgroundColorBrightBlue - SGR_BackgroundColorBrightMagenta - SGR_BackgroundColorBrightCyan - SGR_BackgroundColorBrightWhite -) diff --git a/elements/grid.go b/elements/grid.go deleted file mode 100644 index 9dbb7b4..0000000 --- a/elements/grid.go +++ /dev/null @@ -1,167 +0,0 @@ -package elements - -import "image" -import "golang.org/x/image/font" -import "git.tebibyte.media/sashakoshka/tomo" -import "git.tebibyte.media/sashakoshka/tomo/input" -import "git.tebibyte.media/sashakoshka/tomo/default/theme" -import "git.tebibyte.media/sashakoshka/tomo/default/config" -// import "git.tebibyte.media/sashakoshka/tomo/textdraw" -import "git.tebibyte.media/sashakoshka/tomo/elements/core" - -type gridCell struct { - rune - tomo.FontStyle - background tomo.Color - foreground tomo.Color - clean bool -} - -func (cell *gridCell) initColor () { - cell.background = tomo.ColorBackground - cell.foreground = tomo.ColorForeground -} - -type gridBuffer struct { - cells []gridCell - stride int -} - -// Grid is an array of monospaced character cells. Each one has a foreground and -// background color. It satisfies io.Writer and can be fed text with ANSI escape -// codes. -type Grid struct { - *core.Core - *core.FocusableCore - core core.CoreControl - - cells []gridCell - stride int - cellWidth int - cellHeight int - - cursor image.Point - - face font.Face - config config.Wrapped - theme theme.Wrapped - - onResize func () -} - -func NewGrid () (element *Grid) { - element = &Grid { } - element.theme.Case = tomo.C("tomo", "grid") - element.Core, element.core = core.NewCore(element, element.drawAndPush) - element.updateFont() - element.updateMinimumSize() - return -} - -func (element *Grid) OnResize (callback func ()) { - element.onResize = callback -} - -func (element *Grid) Write (data []byte) (wrote int, err error) { - // TODO process ansi escape codes etx -} - -func (element *Grid) HandleMouseDown (x, y int, button input.Button) { - -} - -func (element *Grid) HandleMouseUp (x, y int, button input.Button) { - -} - -func (element *Grid) HandleKeyDown (key input.Key, modifiers input.Modifiers) { - // TODO we need to grab shift ctrl c for copying text -} - -func (element *Grid) HandleKeyUp(key input.Key, modifiers input.Modifiers) { } - -// SetTheme sets the element's theme. -func (element *Grid) SetTheme (new tomo.Theme) { - if new == element.theme.Theme { return } - element.theme.Theme = new - element.updateFont() - element.updateMinimumSize() - element.drawAndPush() -} - -// SetConfig sets the element's configuration. -func (element *Grid) SetConfig (new tomo.Config) { - if new == element.config.Config { return } - element.config.Config = new - element.updateMinimumSize() - element.drawAndPush() -} - -func (element *Grid) alloc () bool { - bounds := element.Bounds() - width := bounds.Dx() / element.cellWidth - height := bounds.Dy() / element.cellHeight - unchanged := - width == element.stride && - height == len(element.cells) / element.stride - if unchanged { return false } - - oldCells := element.cells - oldWidth := element.stride - oldHeight := len(element.cells) / element.stride - heightLarger := height < oldHeight - - element.stride = width - element.cells = make([]gridCell, width * height) - - // TODO: attempt to wrap text? - - if heightLarger { - for index := range element.cells[oldHeight * width:] { - element.cells[index].initColor() - }} - - commonHeight := height - if heightLarger { commonHeight = oldHeight } - for index := range element.cells[:commonHeight * width] { - x := index % width - if x < oldWidth { - element.cells[index] = oldCells[x + index / oldWidth] - } else { - element.cells[index].initColor() - } - } - - if element.onResize != nil { element.onResize() } - return true -} - -func (element *Grid) updateFont () { - element.face = element.theme.FontFace ( - tomo.FontStyleMonospace, - tomo.FontSizeNormal) - emSpace, _ := element.face.GlyphAdvance('M') - metrics := element.face.Metrics() - element.cellWidth = emSpace.Round() - element.cellHeight = metrics.Height.Round() -} - -func (element *Grid) updateMinimumSize () { - element.core.SetMinimumSize(element.cellWidth, element.cellHeight) -} - -func (element *Grid) state () tomo.State { - return tomo.State { - - } -} - -func (element *Grid) drawAndPush () { - if element.core.HasImage () { - element.core.DamageRegion(element.draw(true)) - } -} - -func (element *Grid) draw (force bool) image.Rectangle { - -}