From 43f9cc0d07c49f6b6cd4a2c140688ca255a16359 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Wed, 4 Nov 2015 17:05:24 -0800 Subject: [PATCH] fixes #75 Add goreport and fix related issues --- README.md | 1 + attr.go | 2 +- charset_stub.go | 2 +- charset_unix.go | 2 +- charset_windows.go | 2 +- color.go | 11 +- console_win.go | 280 +++++++++++++++++++-------------------------- event.go | 3 + key.go | 200 ++++++++++++++++++++------------ simulation.go | 60 +++------- style.go | 4 +- termbox/compat.go | 32 +++++- tscreen.go | 34 +++--- views/boxlayout.go | 131 ++++++++++++++------- views/text.go | 59 ++++++---- 15 files changed, 450 insertions(+), 373 deletions(-) diff --git a/README.md b/README.md index 8f274e0..25e46fb 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Apache License](https://img.shields.io/badge/license-APACHE2-blue.svg)](https://github.com/gdamore/tcell/blob/master/LICENSE) [![Gitter](https://img.shields.io/badge/gitter-join-brightgreen.svg)](https://gitter.im/gdamore/tcell) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/gdamore/tcell) +[![Go Report Card](http://goreportcard.com/badge/gdamore/tcell)](http://goreportcard.com/report/gdamore/tcell) > _Tcell is a work in progress (Gamma). > Please use with caution; interfaces may change in before final release. diff --git a/attr.go b/attr.go index 710acca..34671a8 100644 --- a/attr.go +++ b/attr.go @@ -31,4 +31,4 @@ const ( AttrNone AttrMask = 0 ) -const AttrMaskAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim +const attrAll = AttrBold | AttrBlink | AttrReverse | AttrUnderline | AttrDim diff --git a/charset_stub.go b/charset_stub.go index 3b16e2b..c1c1594 100644 --- a/charset_stub.go +++ b/charset_stub.go @@ -16,6 +16,6 @@ package tcell -func GetCharset() string { +func getCharset() string { return "" } diff --git a/charset_unix.go b/charset_unix.go index 4785b9a..d66ddc4 100644 --- a/charset_unix.go +++ b/charset_unix.go @@ -21,7 +21,7 @@ import ( "strings" ) -func GetCharset() string { +func getCharset() string { // Determine the character set. This can help us later. // Per POSIX, we search for LC_ALL first, then LC_CTYPE, and // finally LANG. First one set wins. diff --git a/charset_windows.go b/charset_windows.go index 59f8a2b..2400aa8 100644 --- a/charset_windows.go +++ b/charset_windows.go @@ -16,6 +16,6 @@ package tcell -func GetCharset() string { +func getCharset() string { return "UTF-16" } diff --git a/color.go b/color.go index c1934fb..53413c6 100644 --- a/color.go +++ b/color.go @@ -964,6 +964,9 @@ var colorNames = map[string]Color{ "slategrey": ColorSlateGray, } +// Hex returns the color's hexadecimal RGB 24-bit value with each component +// consisting of a single byte, ala R << 16 | G << 8 | B. If the color +// is unknown or unset, -1 is returned. func (c Color) Hex() int32 { if c&ColorIsRGB != 0 { return (int32(c) & 0xffffff) @@ -974,6 +977,9 @@ func (c Color) Hex() int32 { return -1 } +// RGB returns the red, green, and blue components of the color, with +// each component represented as a value 0-255. In the event that the +// color cannot be broken up (not set usually), -1 is returned for each value. func (c Color) RGB() (int32, int32, int32) { v := c.Hex() if v < 0 { @@ -982,15 +988,18 @@ func (c Color) RGB() (int32, int32, int32) { return (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff } +// NewRGBColor returns a new color with the given red, green, and blue values. +// Each value must be represented in the range 0-255. func NewRGBColor(r, g, b int32) Color { return NewHexColor(((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)) } +// NewHexColor returns a color using the given 24-bit RGB value. func NewHexColor(v int32) Color { return ColorIsRGB | Color(v) } -// Given a color name (W3C name), return the actual color. A hex value may +// GetColor creates a Color from a color name (W3C name). A hex value may // be supplied as a string in the format "#ffffff". func GetColor(name string) Color { if c, ok := colorNames[name]; ok { diff --git a/console_win.go b/console_win.go index e6b6e54..ade71f5 100644 --- a/console_win.go +++ b/console_win.go @@ -26,7 +26,6 @@ import ( type cScreen struct { in syscall.Handle out syscall.Handle - mbtns uint32 // debounce mouse buttons evch chan Event quit chan struct{} curx int @@ -372,6 +371,52 @@ const ( vkF24 = 0x87 ) +var vkKeys = map[uint16]Key{ + vkCancel: KeyCancel, + vkBack: KeyBackspace, + vkTab: KeyTab, + vkClear: KeyClear, + vkPause: KeyPause, + vkPrint: KeyPrint, + vkPrtScr: KeyPrint, + vkPrior: KeyPgUp, + vkNext: KeyPgDn, + vkReturn: KeyEnter, + vkEnd: KeyEnd, + vkHome: KeyHome, + vkLeft: KeyLeft, + vkUp: KeyUp, + vkRight: KeyRight, + vkDown: KeyDown, + vkInsert: KeyInsert, + vkDelete: KeyDelete, + vkHelp: KeyHelp, + vkF1: KeyF1, + vkF2: KeyF2, + vkF3: KeyF3, + vkF4: KeyF4, + vkF5: KeyF5, + vkF6: KeyF6, + vkF7: KeyF7, + vkF8: KeyF8, + vkF9: KeyF9, + vkF10: KeyF10, + vkF11: KeyF11, + vkF12: KeyF12, + vkF13: KeyF13, + vkF14: KeyF14, + vkF15: KeyF15, + vkF16: KeyF16, + vkF17: KeyF17, + vkF18: KeyF18, + vkF19: KeyF19, + vkF20: KeyF20, + vkF21: KeyF21, + vkF22: KeyF22, + vkF23: KeyF23, + vkF24: KeyF24, +} + // NB: All Windows platforms are little endian. We assume this // never, ever change. The following code is endian safe. and does // not use unsafe pointers. @@ -406,6 +451,50 @@ func mod2mask(cks uint32) ModMask { return mm } +func mrec2btns(mbtns, flags uint32) ButtonMask { + btns := ButtonNone + if mbtns&0x1 != 0 { + btns |= Button1 + } + if mbtns&0x2 != 0 { + btns |= Button2 + } + if mbtns&0x4 != 0 { + btns |= Button3 + } + if mbtns&0x8 != 0 { + btns |= Button4 + } + if mbtns&0x10 != 0 { + btns |= Button5 + } + if mbtns&0x20 != 0 { + btns |= Button6 + } + if mbtns&0x40 != 0 { + btns |= Button7 + } + if mbtns&0x80 != 0 { + btns |= Button8 + } + + if flags&mouseVWheeled != 0 { + if mbtns&0x80000000 == 0 { + btns |= WheelUp + } else { + btns |= WheelDown + } + } + if flags&mouseHWheeled != 0 { + if mbtns&0x80000000 == 0 { + btns |= WheelRight + } else { + btns |= WheelLeft + } + } + return btns +} + func (s *cScreen) getConsoleInput() error { rec := &inputRecord{} var nrec int32 @@ -444,92 +533,8 @@ func (s *cScreen) getConsoleInput() error { return nil } key := KeyNUL // impossible on Windows - switch krec.kcode { - case vkCancel: - key = KeyCancel - case vkBack: - key = KeyBackspace - case vkTab: - key = KeyTab - case vkClear: - key = KeyClear - case vkPause: - key = KeyPause - case vkPrint, vkPrtScr: - key = KeyPrint - case vkPrior: - key = KeyPgUp - case vkNext: - key = KeyPgDn - case vkReturn: - key = KeyEnter - case vkEnd: - key = KeyEnd - case vkHome: - key = KeyHome - case vkLeft: - key = KeyLeft - case vkUp: - key = KeyUp - case vkRight: - key = KeyRight - case vkDown: - key = KeyDown - case vkInsert: - key = KeyInsert - case vkDelete: - key = KeyDelete - case vkHelp: - key = KeyHelp - case vkF1: - key = KeyF1 - case vkF2: - key = KeyF2 - case vkF3: - key = KeyF3 - case vkF4: - key = KeyF4 - case vkF5: - key = KeyF5 - case vkF6: - key = KeyF6 - case vkF7: - key = KeyF7 - case vkF8: - key = KeyF8 - case vkF9: - key = KeyF9 - case vkF10: - key = KeyF10 - case vkF11: - key = KeyF11 - case vkF12: - key = KeyF12 - case vkF13: - key = KeyF13 - case vkF14: - key = KeyF14 - case vkF15: - key = KeyF15 - case vkF16: - key = KeyF16 - case vkF17: - key = KeyF17 - case vkF18: - key = KeyF18 - case vkF19: - key = KeyF19 - case vkF20: - key = KeyF20 - case vkF21: - key = KeyF21 - case vkF22: - key = KeyF22 - case vkF23: - key = KeyF23 - case vkF24: - key = KeyF24 - default: + ok := false + if key, ok = vkKeys[krec.kcode]; !ok { return nil } for krec.repeat > 0 { @@ -544,49 +549,8 @@ func (s *cScreen) getConsoleInput() error { mrec.y = geti16(rec.data[2:]) mrec.btns = getu32(rec.data[4:]) mrec.mod = getu32(rec.data[8:]) - mrec.flags = getu32(rec.data[12:]) // not using yet - btns := ButtonNone - - s.mbtns = mrec.btns - if mrec.btns&0x1 != 0 { - btns |= Button1 - } - if mrec.btns&0x2 != 0 { - btns |= Button2 - } - if mrec.btns&0x4 != 0 { - btns |= Button3 - } - if mrec.btns&0x8 != 0 { - btns |= Button4 - } - if mrec.btns&0x10 != 0 { - btns |= Button5 - } - if mrec.btns&0x20 != 0 { - btns |= Button6 - } - if mrec.btns&0x40 != 0 { - btns |= Button7 - } - if mrec.btns&0x80 != 0 { - btns |= Button8 - } - - if mrec.flags&mouseVWheeled != 0 { - if mrec.btns&0x80000000 == 0 { - btns |= WheelUp - } else { - btns |= WheelDown - } - } - if mrec.flags&mouseHWheeled != 0 { - if mrec.btns&0x80000000 == 0 { - btns |= WheelRight - } else { - btns |= WheelLeft - } - } + mrec.flags = getu32(rec.data[12:]) + btns := mrec2btns(mrec.btns, mrec.flags) // we ignore double click, events are delivered normally s.PostEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns, mod2mask(mrec.mod))) @@ -615,6 +579,25 @@ func (s *cScreen) Colors() int { return 16 } +var vgaColors = map[Color]uint16{ + ColorBlack: 0, + ColorMaroon: 0x4, + ColorGreen: 0x2, + ColorNavy: 0x1, + ColorOlive: 0x6, + ColorPurple: 0x5, + ColorTeal: 0x3, + ColorSilver: 0x7, + ColorGrey: 0x8, + ColorRed: 0xc, + ColorLime: 0xa, + ColorBlue: 0x9, + ColorYellow: 0xe, + ColorFuchsia: 0xd, + ColorAqua: 0xb, + ColorWhite: 0xf, +} + // Windows uses RGB signals func mapColor2RGB(c Color) uint16 { @@ -628,41 +611,8 @@ func mapColor2RGB(c Color) uint16 { } winLock.Unlock() - switch c { - case ColorBlack: - return 0 - // primaries - case ColorMaroon: - return 0x4 - case ColorGreen: - return 0x2 - case ColorNavy: - return 0x1 - case ColorOlive: - return 0x6 - case ColorPurple: - return 0x5 - case ColorTeal: - return 0x3 - case ColorSilver: - return 0x7 - // bright variants - case ColorGrey: - return 0x8 - case ColorRed: - return 0xc - case ColorLime: - return 0xa - case ColorBlue: - return 0x9 - case ColorYellow: - return 0xe - case ColorFuchsia: - return 0xd - case ColorAqua: - return 0xb - case ColorWhite: - return 0xf + if vc, ok := vgaColors[c]; ok { + return vc } return 0 } diff --git a/event.go b/event.go index 4d34f2d..a3b7700 100644 --- a/event.go +++ b/event.go @@ -31,14 +31,17 @@ type EventTime struct { when time.Time } +// When returns the time stamp when the event occurred. func (e *EventTime) When() time.Time { return e.when } +// SetEventTime sets the time of occurrence for the event. func (e *EventTime) SetEventTime(t time.Time) { e.when = t } +// SetEventNow sets the time of occurrence for the event to the current time. func (e *EventTime) SetEventNow() { e.SetEventTime(time.Now()) } diff --git a/key.go b/key.go index 8685339..52141fb 100644 --- a/key.go +++ b/key.go @@ -78,6 +78,128 @@ func (ev *EventKey) Modifiers() ModMask { return ev.mod } +var keyNames = map[Key]string{ + KeySpace: "Space", + KeyEnter: "Enter", + KeyBackspace: "Backspace", + KeyTab: "Tab", + KeyBacktab: "Backtab", + KeyEsc: "Esc", + KeyBackspace2: "Backspace2", + KeyDelete: "Delete", + KeyInsert: "Insert", + KeyUp: "Up", + KeyDown: "Down", + KeyLeft: "Left", + KeyRight: "Right", + KeyHome: "Home", + KeyEnd: "End", + KeyUpLeft: "UpLeft", + KeyUpRight: "UpRight", + KeyDownLeft: "DownLeft", + KeyDownRight: "DownRight", + KeyCenter: "Center", + KeyPgDn: "PgDn", + KeyPgUp: "PgUp", + KeyClear: "Clear", + KeyExit: "Exit", + KeyCancel: "Cancel", + KeyPause: "Pause", + KeyPrint: "Print", + KeyF1: "F1", + KeyF2: "F2", + KeyF3: "F3", + KeyF4: "F4", + KeyF5: "F5", + KeyF6: "F6", + KeyF7: "F7", + KeyF8: "F8", + KeyF9: "F9", + KeyF10: "F10", + KeyF11: "F11", + KeyF12: "F12", + KeyF13: "F13", + KeyF14: "F14", + KeyF15: "F15", + KeyF16: "F16", + KeyF17: "F17", + KeyF18: "F18", + KeyF19: "F19", + KeyF20: "F20", + KeyF21: "F21", + KeyF22: "F22", + KeyF23: "F23", + KeyF24: "F24", + KeyF25: "F25", + KeyF26: "F26", + KeyF27: "F27", + KeyF28: "F28", + KeyF29: "F29", + KeyF30: "F30", + KeyF31: "F31", + KeyF32: "F32", + KeyF33: "F33", + KeyF34: "F34", + KeyF35: "F35", + KeyF36: "F36", + KeyF37: "F37", + KeyF38: "F38", + KeyF39: "F39", + KeyF40: "F40", + KeyF41: "F41", + KeyF42: "F42", + KeyF43: "F43", + KeyF44: "F44", + KeyF45: "F45", + KeyF46: "F46", + KeyF47: "F47", + KeyF48: "F48", + KeyF49: "F49", + KeyF50: "F50", + KeyF51: "F51", + KeyF52: "F52", + KeyF53: "F53", + KeyF54: "F54", + KeyF55: "F55", + KeyF56: "F56", + KeyF57: "F57", + KeyF58: "F58", + KeyF59: "F59", + KeyF60: "F60", + KeyF61: "F61", + KeyF62: "F62", + KeyF63: "F63", + KeyF64: "F64", + KeyCtrlA: "Ctrl-A", + KeyCtrlB: "Ctrl-B", + KeyCtrlC: "Ctrl-C", + KeyCtrlD: "Ctrl-D", + KeyCtrlE: "Ctrl-E", + KeyCtrlF: "Ctrl-F", + KeyCtrlG: "Ctrl-G", + KeyCtrlJ: "Ctrl-J", + KeyCtrlK: "Ctrl-K", + KeyCtrlL: "Ctrl-L", + KeyCtrlN: "Ctrl-N", + KeyCtrlO: "Ctrl-O", + KeyCtrlP: "Ctrl-P", + KeyCtrlQ: "Ctrl-Q", + KeyCtrlR: "Ctrl-R", + KeyCtrlS: "Ctrl-S", + KeyCtrlT: "Ctrl-T", + KeyCtrlU: "Ctrl-U", + KeyCtrlV: "Ctrl-V", + KeyCtrlW: "Ctrl-W", + KeyCtrlX: "Ctrl-X", + KeyCtrlY: "Ctrl-Y", + KeyCtrlZ: "Ctrl-Z", + KeyCtrlSpace: "Ctrl-Space", + KeyCtrlUnderscore: "Ctrl-_", + KeyCtrlRightSq: "Ctrl-]", + KeyCtrlBackslash: "Ctrl-\\", + KeyCtrlCarat: "Ctrl-^", +} + // Name returns a printable value or the key stroke. This can be used // when printing the event, for example. func (ev *EventKey) Name() string { @@ -96,84 +218,14 @@ func (ev *EventKey) Name() string { m = append(m, "Ctrl") } - switch ev.key { - case KeyRune: - s = "Rune[" + string(ev.ch) + "]" - case KeySpace: - s = "Space" - case KeyEnter: - s = "Enter" - case KeyBackspace: - s = "Backspace" - case KeyTab: - s = "Tab" - case KeyBacktab: - s = "Backtab" - case KeyEsc: - s = "Esc" - case KeyBackspace2: - s = "Backspace2" - case KeyDelete: - s = "Delete" - case KeyInsert: - s = "Insert" - case KeyUp: - s = "Up" - case KeyDown: - s = "Down" - case KeyLeft: - s = "Left" - case KeyRight: - s = "Right" - case KeyHome: - s = "Home" - case KeyEnd: - s = "End" - case KeyUpLeft: - s = "UpLeft" - case KeyUpRight: - s = "UpRight" - case KeyDownLeft: - s = "DownLeft" - case KeyDownRight: - s = "DownRight" - case KeyCenter: - s = "Center" - case KeyPgDn: - s = "PgDn" - case KeyPgUp: - s = "PgUp" - case KeyClear: - s = "Clear" - case KeyExit: - s = "Exit" - case KeyCancel: - s = "Cancel" - case KeyPause: - s = "Pause" - case KeyPrint: - s = "Print" - case KeyCtrlSpace: - s = "Ctrl-Space" - case KeyCtrlUnderscore: - s = "Ctrl-_" - case KeyCtrlRightSq: - s = "Ctrl-]" - case KeyCtrlBackslash: - s = "Ctrl-\\" - case KeyCtrlCarat: - s = "Ctrl-^" - default: - if ev.key >= KeyF1 && ev.key <= KeyF64 { - s = fmt.Sprintf("F%d", int(ev.key-KeyF1)+1) - } else if ev.key >= KeyCtrlA && ev.key <= KeyCtrlZ { - s = fmt.Sprintf("Ctrl-%c", - rune(ev.key-KeyCtrlA)+'A') + ok := false + if s, ok = keyNames[ev.key]; !ok { + if ev.key == KeyRune { + s = "Rune[" + string(ev.ch) + "]" } else { s = fmt.Sprintf("Key[%d,%d]", ev.key, int(ev.ch)) } } - if len(m) != 0 { if ev.mod&ModCtrl != 0 && strings.HasPrefix(s, "Ctrl-") { s = s[5:] diff --git a/simulation.go b/simulation.go index 31a930f..2f4ac87 100644 --- a/simulation.go +++ b/simulation.go @@ -226,11 +226,7 @@ func (s *simscreen) drawCell(x, y int) int { l := utf8.EncodeRune(ubuf, r) - if enc := s.encoder; enc != nil { - nout, _, _ = enc.Transform(lbuf, ubuf[:l], true) - } else { - nout = 0 - } + nout, _, _ = s.encoder.Transform(lbuf, ubuf[:l], true) if nout == 0 || lbuf[0] == '\x1a' { @@ -398,54 +394,24 @@ outer: continue } - switch s.charset { - case "UTF-8": - r, l := utf8.DecodeRune(b) - if r == utf8.RuneError && (l == 0 || l == 1) { - failed = true - // yank off one byte - b = b[1:] - } else { - b = b[l:] - ev := NewEventKey(KeyRune, r, ModNone) - s.PostEvent(ev) - continue - } + utfb := make([]byte, len(b)*4) // worst case + for l := 1; l < len(b); l++ { + s.decoder.Reset() + nout, nin, _ := s.decoder.Transform(utfb, b[:l], true) - case "US-ASCII": - // ASCII cannot generate this, so most likely it was - // entered as an Alt sequence - ev := NewEventKey(KeyRune, rune(b[0]-128), ModAlt) - s.PostEvent(ev) - b = b[1:] - continue - - default: - utfb := make([]byte, len(b)*4) // worst case - dec := s.decoder - if dec == nil { - failed = true - b = b[1:] - continue - } - - // take care to consume at *most* a single rune - for l := 1; l < len(b); l++ { - dec.Reset() - nout, nin, _ := dec.Transform(utfb, b[:l], true) - - if nout != 0 { - r, _ := utf8.DecodeRune(utfb[:nout]) + if nout != 0 { + r, _ := utf8.DecodeRune(utfb[:nout]) + if r != utf8.RuneError { ev := NewEventKey(KeyRune, r, ModNone) s.PostEvent(ev) - b = b[nin:] - continue outer } + b = b[nin:] + continue outer } - failed = true - b = b[1:] - continue } + failed = true + b = b[1:] + continue } return failed == false diff --git a/style.go b/style.go index 2fcebfd..9d0c4d5 100644 --- a/style.go +++ b/style.go @@ -77,7 +77,7 @@ func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) { } else { bg = ColorDefault } - attr = AttrMask(s) & AttrMaskAll + attr = AttrMask(s) & attrAll return fg, bg, attr } @@ -91,7 +91,7 @@ func (s Style) setAttrs(attrs Style, on bool) Style { // Normal returns the style with all attributes disabled. func (s Style) Normal() Style { - return s &^ Style(AttrMaskAll) + return s &^ Style(attrAll) } // Bold returns a new style based on s, with the bold attribute set diff --git a/termbox/compat.go b/termbox/compat.go index 7378d50..addb3d5 100644 --- a/termbox/compat.go +++ b/termbox/compat.go @@ -25,6 +25,7 @@ import ( var screen tcell.Screen var outMode OutputMode +// Init initializes the screen for use. func Init() error { outMode = OutputNormal if s, e := tcell.NewScreen(); e != nil { @@ -37,23 +38,28 @@ func Init() error { } } +// Close cleans up the terminal, restoring terminal modes, etc. func Close() { screen.Fini() } +// Flush updates the screen. func Flush() error { screen.Show() return nil } +// SetCursor displays the terminal cursor at the given location. func SetCursor(x, y int) { screen.ShowCursor(x, y) } +// HideCursor hides the terminal cursor. func HideCursor() { SetCursor(-1, -1) } +// Size returns the screen size as width, height in character cells. func Size() (int, int) { return screen.Size() } @@ -122,18 +128,19 @@ func mkStyle(fg, bg Attribute) tcell.Style { b = tcell.ColorDefault } st = st.Foreground(f).Background(b) - if (fg&AttrBold != 0) || (bg&AttrBold != 0) { + if (fg|bg)&AttrBold != 0 { st = st.Bold(true) } - if (fg&AttrUnderline != 0) || (bg&AttrUnderline != 0) { + if (fg|bg)&AttrUnderline != 0 { st = st.Underline(true) } - if (fg&AttrReverse != 0) || (bg&AttrReverse != 0) { + if (fg|bg)&AttrReverse != 0 { st = st.Reverse(true) } return st } +// Clear clears the screen with the given attributes. func Clear(fg, bg Attribute) { st := mkStyle(fg, bg) w, h := screen.Size() @@ -144,6 +151,7 @@ func Clear(fg, bg Attribute) { } } +// InputMode is not used. type InputMode int const ( @@ -153,11 +161,14 @@ const ( InputMouse ) +// SetInputMode does not do anything in this version. func SetInputMode(mode InputMode) InputMode { // We don't do anything else right now return InputEsc } +// OutputMode represents an output mode, which determines how colors +// are used. See the termbox documentation for an explanation. type OutputMode int const ( @@ -168,6 +179,7 @@ const ( OutputGrayscale ) +// SetOutputMode is used to set the color palette used. func SetOutputMode(mode OutputMode) OutputMode { if screen.Colors() < 256 { mode = OutputNormal @@ -183,20 +195,29 @@ func SetOutputMode(mode OutputMode) OutputMode { } } +// Sync forces a resync of the screen. func Sync() error { screen.Sync() return nil } +// SetCell sets the character cell at a given location to the given +// content (rune) and attributes. func SetCell(x, y int, ch rune, fg, bg Attribute) { st := mkStyle(fg, bg) screen.SetContent(x, y, ch, nil, st) } +// EventType represents the type of event. type EventType uint8 + +// Modifier represents the possible modifier keys. type Modifier tcell.ModMask + +// Key is a key press. type Key tcell.Key +// Event represents an event like a key press, mouse action, or window resize. type Event struct { Type EventType Mod Modifier @@ -309,25 +330,30 @@ func makeEvent(tev tcell.Event) Event { } } +// ParseEvent is not supported. func ParseEvent(data []byte) Event { // Not supported return Event{Type: EventError, Err: errors.New("no raw events")} } +// PollRawEvent is not supported. func PollRawEvent(data []byte) Event { // Not supported return Event{Type: EventError, Err: errors.New("no raw events")} } +// PollEvent blocks until an event is ready, and then returns it. func PollEvent() Event { ev := screen.PollEvent() return makeEvent(ev) } +// Interrupt posts an interrupt event. func Interrupt() { screen.PostEvent(tcell.NewEventInterrupt(nil)) } +// Cell represents a single character cell on screen. type Cell struct { Ch rune Fg Attribute diff --git a/tscreen.go b/tscreen.go index 9b298b1..b407abf 100644 --- a/tscreen.go +++ b/tscreen.go @@ -97,7 +97,7 @@ func (t *tScreen) Init() error { t.indoneq = make(chan struct{}) t.charset = "UTF-8" - t.charset = GetCharset() + t.charset = getCharset() if enc := GetEncoding(t.charset); enc != nil { t.encoder = enc.NewEncoder() t.decoder = enc.NewDecoder() @@ -679,6 +679,23 @@ func (t *tScreen) PostEvent(ev Event) error { } } +func (t *tScreen) clip(x, y int) (int, int) { + w, h := t.cells.Size() + if x < 0 { + x = 0 + } + if y < 0 { + y = 0 + } + if x > w-1 { + x = w - 1 + } + if y > h-1 { + y = h - 1 + } + return x, y +} + func (t *tScreen) postMouseEvent(x, y, btn int) { // XTerm mouse events only report at most one button at a time, @@ -733,19 +750,8 @@ func (t *tScreen) postMouseEvent(x, y, btn int) { // Some terminals will report mouse coordinates outside the // screen, especially with click-drag events. Clip the coordinates // to the screen in that case. - if x < 0 { - x = 0 - } - if y < 0 { - y = 0 - } - w, h := t.cells.Size() - if x > w-1 { - x = w - 1 - } - if y > h-1 { - y = h - 1 - } + x, y = t.clip(x, y) + ev := NewEventMouse(x, y, button, mod) t.PostEvent(ev) } diff --git a/views/boxlayout.go b/views/boxlayout.go index f9ec2fd..ae715bc 100644 --- a/views/boxlayout.go +++ b/views/boxlayout.go @@ -40,37 +40,22 @@ type boxLayoutCell struct { view *ViewPort } -func (b *BoxLayout) layout() { - if b.view == nil { - return - } +func (b *BoxLayout) hLayout() { w, h := b.view.Size() - minx, miny, totx, toty := 0, 0, 0, 0 totf := 0.0 for _, c := range b.cells { x, y := c.widget.Size() - totx += x - toty += y totf += c.fill - if x > minx { - minx = x - } - if y > miny { - miny = y + b.width += x + if y > b.height { + b.height = y } + c.pad = 0 + c.frac = 0 } - extra := 0 - if b.orient == Horizontal { - extra = w - totx - b.width = totx - b.height = miny - } else { - extra = h - toty - b.width = minx - b.height = toty - } + extra := w - b.width if extra < 0 { extra = 0 } @@ -85,9 +70,6 @@ func (b *BoxLayout) layout() { c.pad = int(c.frac) c.frac -= float64(c.pad) resid -= c.pad - } else { - c.pad = 0 - c.frac = 0 } } @@ -109,29 +91,94 @@ func (b *BoxLayout) layout() { resid-- } - x, y, xinc, yinc := 0, 0, 0, 0 + x, y, xinc := 0, 0, 0 for _, c := range b.cells { - cw, ch := c.widget.Size() + cw, _ := c.widget.Size() - switch b.orient { - case Horizontal: - xinc = cw + c.pad - cw += c.pad - ch = h + xinc = cw + c.pad + cw += c.pad - case Vertical: - yinc = ch + c.pad - ch += c.pad - cw = w - - default: - panic("Bad orientation") - - } - c.view.Resize(x, y, cw, ch) + c.view.Resize(x, y, cw, h) x += xinc + } +} + +func (b *BoxLayout) vLayout() { + w, h := b.view.Size() + + totf := 0.0 + for _, c := range b.cells { + x, y := c.widget.Size() + b.height += y + totf += c.fill + if x > b.width { + b.width = x + } + c.pad = 0 + c.frac = 0 + } + + extra := h - b.height + if extra < 0 { + extra = 0 + } + + resid := extra + if totf == 0 { + resid = 0 + } + + for _, c := range b.cells { + if c.fill > 0 { + c.frac = float64(extra) * c.fill / totf + c.pad = int(c.frac) + c.frac -= float64(c.pad) + resid -= c.pad + } + } + + // Distribute any left over padding. We try to give it to the + // the cells with the highest residual fraction. It should be + // the case that no single cell gets more than one more cell. + for resid > 0 { + var best *boxLayoutCell + for _, c := range b.cells { + if c.fill == 0 { + continue + } + if best == nil || c.frac > best.frac { + best = c + } + } + best.pad++ + best.frac = 0 + resid-- + } + + x, y, yinc := 0, 0, 0 + for _, c := range b.cells { + _, ch := c.widget.Size() + + yinc = ch + c.pad + ch += c.pad + c.view.Resize(x, y, w, ch) y += yinc } +} + +func (b *BoxLayout) layout() { + if b.view == nil { + return + } + b.width, b.height = 0, 0 + switch b.orient { + case Horizontal: + b.hLayout() + case Vertical: + b.vLayout() + default: + panic("Bad orientation") + } b.changed = false } diff --git a/views/text.go b/views/text.go index 2a26394..403ddb0 100644 --- a/views/text.go +++ b/views/text.go @@ -36,40 +36,63 @@ type Text struct { WidgetWatchers } +func (t *Text) clear() { + v := t.view + w, h := v.Size() + v.Clear() + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + v.SetContent(x, y, ' ', nil, t.style) + } + } +} + +// calcY figures the initial Y offset. Alignment is top by default. +func (t *Text) calcY(height int) int { + if t.align&VAlignCenter != 0 { + return (height - len(t.lengths)) / 2 + } + if t.align&VAlignBottom != 0 { + return height - len(t.lengths) + } + return 0 +} + +// calcX figures the initial X offset for the given line. +// Alignment is left by default. +func (t *Text) calcX(width, line int) int { + if t.align&HAlignCenter != 0 { + return (width - t.lengths[line]) / 2 + } + if t.align&HAlignRight != 0 { + return width - t.lengths[line] + } + return 0 +} + // Draw draws the Text. func (t *Text) Draw() { v := t.view if v == nil { return } - var x, y int width, height := v.Size() if width == 0 || height == 0 { return } - v.Clear() - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - v.SetContent(x, y, ' ', nil, t.style) - } - } + t.clear() // Note that we might wind up with a negative X if the width // is larger than the length. That's OK, and correct even. // The view will clip it properly in that case. // We align to the left & top by default. - if t.align&VAlignCenter != 0 { - y = (height - 1) / 2 - } else if t.align&VAlignBottom != 0 { - y = height - 1 - } else { - y = 0 - } + y := t.calcY(height) r := rune(0) w := 0 + x := 0 var styl tcell.Style var comb []rune line := 0 @@ -77,13 +100,7 @@ func (t *Text) Draw() { for i, l := range t.text { if newline { - if t.align&HAlignCenter != 0 { - x = (width - t.lengths[line]) / 2 - } else if t.align&HAlignRight != 0 { - x = width - t.lengths[line] - } else { - x = 0 - } + x = t.calcX(width, line) newline = false } if l == '\n' {