Merge branch 'panels'

This commit is contained in:
Luke I. Wilson
2021-04-08 13:05:13 -05:00
9 changed files with 715 additions and 98 deletions

View File

@@ -4,6 +4,7 @@ type Syntax uint8
const (
Default Syntax = iota
Column // Not necessarily a Syntax; useful for Colorscheming editor column
Keyword
String
Special

View File

@@ -15,8 +15,8 @@ type Component interface {
// A component knows its position and size, which is used to draw itself in
// its bounding rectangle.
Draw(tcell.Screen)
// Components can be focused, which may affect how it handles events. For
// example, when a button is focused, the Return key may be pressed to
// Components can be focused, which may affect how it handles events or draws.
// For example, when a button is focused, the Return key may be pressed to
// activate the button.
SetFocused(bool)
// Applies the theme to the component and all of its children.

View File

@@ -148,7 +148,12 @@ func (b *MenuBar) CursorRight() {
// Draw renders the MenuBar and its sub-menus.
func (b *MenuBar) Draw(s tcell.Screen) {
normalStyle := b.theme.GetOrDefault("MenuBar")
var normalStyle tcell.Style
if b.focused {
normalStyle = b.theme.GetOrDefault("MenuBarFocused")
} else {
normalStyle = b.theme.GetOrDefault("MenuBar")
}
// Draw menus based on whether b.focused and which is selected
DrawRect(s, b.x, b.y, b.width, 1, ' ', normalStyle)
@@ -156,7 +161,8 @@ func (b *MenuBar) Draw(s tcell.Screen) {
for i, item := range b.menus {
sty := normalStyle
if b.focused && b.selected == i {
sty = b.theme.GetOrDefault("MenuBarSelected") // Use special style for selected item
fg, bg, attr := normalStyle.Decompose()
sty = tcell.Style{}.Foreground(bg).Background(fg).Attributes(attr)
}
str := fmt.Sprintf(" %s ", item.Name)

227
ui/panel.go Executable file
View File

@@ -0,0 +1,227 @@
package ui
import "github.com/gdamore/tcell/v2"
// A PanelKind describes how to interpret the fields of a Panel.
type PanelKind uint8
const (
PanelKindEmpty PanelKind = iota
PanelKindSingle // Single item. Takes up all available space
PanelKindSplitVert // Items are above or below eachother
PanelKindSplitHor // Items are left or right of eachother
)
// A Panel represents a container for a split view between two items. The Kind
// tells how to interpret the Left and Right fields. The SplitAt is the column
// between 0 and width or height, representing the position of the split between
// the Left and Right, respectively.
//
// If the Kind is equal to PanelKindEmpty, then both Left and Right are nil.
// If the Kind is equal to PanelKindSingle, then only Left has value,
// and its value will NOT be of type Panel. The SplitAt will not be used,
// as the Left will take up the whole space.
// If the Kind is equal to PanelKindSplitVert, then both Left and Right will
// have value, and they will both have to be of type Panel. The split will
// be represented vertically, and the SplitAt spans 0 to height; top to bottom,
// respectively.
// If the Kind is equal to PanelKindSplitHor, then both Left and Right will
// have value, and they will both have to be of type Panel. The split will
// be represented horizontally, and the SplitAt spans 0 to width; left to right.
type Panel struct {
Parent *Panel
Left Component
Right Component
SplitAt int
Kind PanelKind
Focused bool
x, y int
width int
height int
}
// UpdateSplits uses the position and size of the Panel, along with its Weight
// and Kind, to appropriately size and place its children. It calls UpdateSplits()
// on its child Panels.
func (p *Panel) UpdateSplits() {
switch p.Kind {
case PanelKindSingle:
p.Left.SetPos(p.x, p.y)
p.Left.SetSize(p.width, p.height)
case PanelKindSplitVert:
p.Left.SetPos(p.x, p.y)
p.Left.SetSize(p.width, p.SplitAt)
p.Right.SetPos(p.x, p.y+p.SplitAt)
p.Right.SetSize(p.width, p.height-p.SplitAt)
p.Left.(*Panel).UpdateSplits()
p.Right.(*Panel).UpdateSplits()
case PanelKindSplitHor:
p.Left.SetPos(p.x, p.y)
p.Left.SetSize(p.SplitAt, p.height)
p.Right.SetPos(p.x+p.SplitAt, p.y)
p.Right.SetSize(p.width-p.SplitAt, p.height)
p.Left.(*Panel).UpdateSplits()
p.Right.(*Panel).UpdateSplits()
}
}
// Same as EachLeaf, but returns true if any call to `f` returned true.
func (p *Panel) eachLeaf(rightMost bool, f func(*Panel) bool) bool {
switch p.Kind {
case PanelKindEmpty:
fallthrough
case PanelKindSingle:
return f(p)
case PanelKindSplitVert:
fallthrough
case PanelKindSplitHor:
if rightMost {
if p.Right.(*Panel).eachLeaf(rightMost, f) {
return true
}
return p.Left.(*Panel).eachLeaf(rightMost, f)
} else {
if p.Left.(*Panel).eachLeaf(rightMost, f) {
return true
}
return p.Right.(*Panel).eachLeaf(rightMost, f)
}
default:
return false
}
}
// EachLeaf visits the entire tree, and calls function `f` at each leaf Panel.
// If the function `f` returns true, then visiting stops. if `rtl` is true,
// the tree is traversed in right-most order. The default is to traverse
// in left-most order.
//
// The caller of this function can safely assert that Panel's Kind is always
// either `PanelKindSingle` or `PanelKindEmpty`.
func (p *Panel) EachLeaf(rightMost bool, f func(*Panel) bool) {
p.eachLeaf(rightMost, f)
}
// IsLeaf returns whether the Panel is a leaf or not. A leaf is a panel with
// Kind `PanelKindEmpty` or `PanelKindSingle`.
func (p *Panel) IsLeaf() bool {
switch p.Kind {
case PanelKindEmpty:
fallthrough
case PanelKindSingle:
return true
default:
return false
}
}
func (p *Panel) Draw(s tcell.Screen) {
switch p.Kind {
case PanelKindSplitVert:
fallthrough
case PanelKindSplitHor:
p.Right.Draw(s)
fallthrough
case PanelKindSingle:
p.Left.Draw(s)
}
}
// SetFocused sets this Panel's Focused field to `v`. Then, if the Panel's Kind
// is PanelKindSingle, it sets its child (not a Panel) focused to `v`, also.
func (p *Panel) SetFocused(v bool) {
p.Focused = v
switch p.Kind {
case PanelKindSplitVert:
fallthrough
case PanelKindSplitHor:
p.Right.SetFocused(v)
fallthrough
case PanelKindSingle:
p.Left.SetFocused(v)
}
}
func (p *Panel) SetTheme(theme *Theme) {
switch p.Kind {
case PanelKindSplitVert:
fallthrough
case PanelKindSplitHor:
p.Right.SetTheme(theme)
fallthrough
case PanelKindSingle:
p.Left.SetTheme(theme)
}
}
// GetPos returns the position of the panel.
func (p *Panel) GetPos() (int, int) {
return p.width, p.height
}
// SetPos sets the position of the panel.
func (p *Panel) SetPos(x, y int) {
p.x, p.y = x, y
}
// GetMinSize returns the combined minimum sizes of the Panel's children.
func (p *Panel) GetMinSize() (int, int) {
switch p.Kind {
case PanelKindSingle:
return p.Left.GetMinSize()
case PanelKindSplitVert:
// use max width, add heights
lWidth, lHeight := p.Left.GetMinSize()
rWidth, rHeight := p.Right.GetMinSize()
return Max(lWidth, rWidth), lHeight + rHeight
case PanelKindSplitHor:
// use max height, add widths
lWidth, lHeight := p.Left.GetMinSize()
rWidth, rHeight := p.Right.GetMinSize()
return lWidth + rWidth, Max(lHeight, rHeight)
default:
return 0, 0
}
}
func (p *Panel) GetSize() (int, int) {
return p.width, p.height
}
// SetSize sets the Panel size to the given width, and height. It will not check
// against GetMinSize() because it may be costly to do so. SetSize clamps the
// Panel's SplitAt to be within the new size of the Panel.
func (p *Panel) SetSize(width, height int) {
p.width, p.height = width, height
switch p.Kind {
case PanelKindSplitVert:
p.SplitAt = Min(p.SplitAt, height)
case PanelKindSplitHor:
p.SplitAt = Min(p.SplitAt, width)
}
}
// HandleEvent propogates the event to all children, calling HandleEvent()
// on left-most items. As usual: returns true if handled, false if unhandled.
// This function relies on the behavior of the child Components to only handle
// events if they are focused.
func (p *Panel) HandleEvent(event tcell.Event) bool {
switch p.Kind {
case PanelKindSingle:
return p.Left.HandleEvent(event)
case PanelKindSplitVert:
fallthrough
case PanelKindSplitHor:
if p.Left.HandleEvent(event) {
return true
}
return p.Right.HandleEvent(event)
default:
return false
}
}

337
ui/panelcontainer.go Executable file
View File

@@ -0,0 +1,337 @@
package ui
import "github.com/gdamore/tcell/v2"
type SplitKind uint8
const (
SplitVertical SplitKind = SplitKind(PanelKindSplitVert) + iota
SplitHorizontal
)
type PanelContainer struct {
root *Panel
floating []*Panel
selected **Panel // Only Panels with PanelKindSingle
lastNonFloatingSelected **Panel // Used only when focused on floating Panels
floatingMode bool // True if 'selected' is part of a floating Panel
focused bool
theme *Theme
}
func NewPanelContainer(theme *Theme) *PanelContainer {
root := &Panel{Kind: PanelKindEmpty}
return &PanelContainer{
root: root,
floating: make([]*Panel, 0, 3),
selected: &root,
theme: theme,
}
}
// ClearSelected makes the selected Panel empty, but does not delete it from
// the tree.
func (c *PanelContainer) ClearSelected() Component {
item := (**c.selected).Left
(**c.selected).Left = nil
(**c.selected).Kind = PanelKindEmpty
if p := (**c.selected).Parent; p != nil {
p.UpdateSplits()
}
return item
}
// changeSelected sets c.selected to `new`. It also refocuses the Panel.
// Prefer to use this as opposed to performing the instructions manually.
func (c *PanelContainer) changeSelected(new **Panel) {
if c.focused {
(*c.selected).SetFocused(false)
}
c.selected = new
if c.focused {
(*c.selected).SetFocused(true)
}
}
// DeleteSelected deletes the selected Panel and returns its child Component.
// If the selected Panel is the root Panel, ClearSelected() is called, instead.
func (c *PanelContainer) DeleteSelected() Component {
if !(*c.selected).IsLeaf() {
panic("selected is not leaf")
}
// If selected is the root, just make it empty
if *c.selected == c.root {
return c.ClearSelected()
} else {
item := (**c.selected).Left
p := (**c.selected).Parent
if p != nil {
if *c.selected == (*p).Left { // If we're deleting the parent's Left
(*p).Left = (*p).Right
(*p).Right = nil
} else { // Deleting parent's Right
(*p).Right = nil
}
if (*p).Left != nil {
// Parent becomes the Left panel
panel := (*p).Left.(*Panel)
(*p).Left = (*panel).Left
(*p).Right = (*panel).Right
(*p).Kind = (*panel).Kind
(*p).SplitAt = (*panel).SplitAt
} else {
(*p).Kind = PanelKindEmpty
}
// Decide what Panel to select next
if !(*p).IsLeaf() { // If the new panel was a split panel...
// Select the leftmost child of it
(*p).EachLeaf(false, func(l *Panel) bool { c.changeSelected(&l); return true })
} else {
c.changeSelected(&p)
}
(*p).UpdateSplits()
} else if c.floatingMode { // Deleting a floating Panel without a parent
c.floating[0] = nil
copy(c.floating, c.floating[1:]) // Shift items to front
c.floating = c.floating[:len(c.floating)-1] // Shrink slice's len by one
if len(c.floating) <= 0 {
c.SetFloatingFocused(false)
} else {
c.changeSelected(&c.floating[0])
}
} else {
panic("Panel does not have parent and is not floating")
}
return item
}
}
// SwapNeighborsSelected swaps two Left and Right child Panels of a vertical or
// horizontally split Panel. This is necessary to achieve a "split top" or
// "split left" effect, as Panels only split open to the bottom or right.
func (c *PanelContainer) SwapNeighborsSelected() {
parent := (**c.selected).Parent
if parent != nil {
left := (*parent).Left
(*parent).Left = parent.Right
(*parent).Right = left
parent.UpdateSplits() // Updates position and size of reordered children
}
}
// Turns the selected Panel into a split panel, moving its contents to its Left field,
// and putting the given Panel at the Right field. `panel` cannot be nil.
func (c *PanelContainer) splitSelectedWithPanel(kind SplitKind, panel *Panel) {
(**c.selected).Left = &Panel{Parent: *c.selected, Left: (**c.selected).Left, Kind: (**c.selected).Kind}
(**c.selected).Right = panel
(**c.selected).Right.(*Panel).Parent = *c.selected
// Update parent's split information
(**c.selected).Kind = PanelKind(kind)
if kind == SplitVertical {
(**c.selected).SplitAt = (**c.selected).height / 2
} else {
(**c.selected).SplitAt = (**c.selected).width / 2
}
(*c.selected).UpdateSplits()
// Change selected from parent to the previously selected Panel on the Left
panel = (**c.selected).Left.(*Panel)
c.changeSelected(&panel)
}
// SplitSelected splits the selected Panel with the given Component `item`.
// The type of split (vertical or horizontal) is determined with the `kind`.
// If `item` is nil, the new Panel will be of kind empty.
func (c *PanelContainer) SplitSelected(kind SplitKind, item Component) {
if !(*c.selected).IsLeaf() {
panic("selected is not leaf")
}
if item == nil {
c.splitSelectedWithPanel(kind, &Panel{Parent: *c.selected, Kind: PanelKindEmpty})
} else {
c.splitSelectedWithPanel(kind, &Panel{Parent: *c.selected, Left: item, Kind: PanelKindSingle})
}
}
func (c *PanelContainer) IsRootSelected() bool {
return *c.selected == c.root
}
func (c *PanelContainer) GetSelected() Component {
if !(*c.selected).IsLeaf() {
panic("selected is not leaf")
}
return (**c.selected).Left
}
func (c *PanelContainer) SetSelected(item Component) {
if !(*c.selected).IsLeaf() {
panic("selected is not leaf")
}
(**c.selected).Left = item
(**c.selected).Kind = PanelKindSingle
(*c.selected).UpdateSplits()
}
func (c *PanelContainer) raiseFloating(idx int) {
item := c.floating[idx]
copy(c.floating[1:], c.floating[:idx]) // Shift all items before idx right
c.floating[0] = item
}
// GetFloatingFocused returns true if a floating window is selected or focused.
func (c *PanelContainer) GetFloatingFocused() bool {
return c.floatingMode
}
// SetFloatingFocused sets whether the floating Panels are focused. When true,
// the current Panel will be unselected and the front floating Panel will become
// the new selected if there any floating windows. If false, the same, but the
// last selected non-floating Panel will become focused.
//
// The returned boolean is whether floating windows were able to be focused. If
// there are no floating windows when trying to focus them, this will inevitably
// return false, for example.
func (c *PanelContainer) SetFloatingFocused(v bool) bool {
if v {
if len(c.floating) > 0 {
c.lastNonFloatingSelected = c.selected
c.changeSelected(&c.floating[0])
c.floatingMode = true
return true
}
} else {
c.changeSelected(c.lastNonFloatingSelected)
c.floatingMode = false
}
return false
}
// FloatSelected makes the selected Panel floating. This function does not focus
// the newly floated Panel. To focus the floating panel, call SetFloatingFocused().
func (c *PanelContainer) FloatSelected() {
if !(*c.selected).IsLeaf() {
panic("selected is not leaf")
}
if c.floatingMode {
return
}
c.DeleteSelected()
(**c.selected).Parent = nil
(*c.selected).UpdateSplits()
c.floating = append(c.floating, *c.selected)
c.raiseFloating(len(c.floating) - 1)
}
// UnfloatSelected moves any selected floating Panel to the normal tree that is
// accessible in the standard focus mode. This function will cause focus to go to
// the normal tree if there are no remaining floating windows after the operation.
//
// Like SetFloatingFocused(), the boolean returned is whether the PanelContainer
// is focusing floating windows after the operation.
func (c *PanelContainer) UnfloatSelected(kind SplitKind) bool {
if !(*c.selected).IsLeaf() {
panic("selected is not leaf")
}
if !c.floatingMode {
return false
}
c.DeleteSelected()
c.SetFloatingFocused(false)
c.splitSelectedWithPanel(kind, *c.selected)
// Try to return to floating focus
return c.SetFloatingFocused(true)
}
func (c *PanelContainer) selectNext(rightMost bool) {
var nextIsIt bool
c.root.EachLeaf(rightMost, func(p *Panel) bool {
if nextIsIt {
c.changeSelected(&p)
nextIsIt = false
return true
} else if p == *c.selected {
nextIsIt = true
}
return false
})
// This boolean must be false if we found the next leaf.
// Therefore, if it is true, c.selected was the last leaf
// of the tree. We need to wrap around to the first leaf.
if nextIsIt {
// This gets the first leaf in left-most or right-most order
c.root.EachLeaf(rightMost, func(p *Panel) bool { c.changeSelected(&p); return true })
}
}
func (c *PanelContainer) SelectNext() {
c.selectNext(false)
}
func (c *PanelContainer) SelectPrev() {
c.selectNext(true)
}
func (c *PanelContainer) Draw(s tcell.Screen) {
c.root.Draw(s)
for i := len(c.floating) - 1; i >= 0; i-- {
c.floating[i].Draw(s)
}
}
func (c *PanelContainer) SetFocused(v bool) {
c.focused = v
(*c.selected).SetFocused(v)
}
func (c *PanelContainer) SetTheme(theme *Theme) {
c.theme = theme
c.root.SetTheme(theme)
for i := range c.floating {
c.floating[i].SetTheme(theme)
}
}
func (c *PanelContainer) GetPos() (int, int) {
return c.root.GetPos()
}
func (c *PanelContainer) SetPos(x, y int) {
c.root.SetPos(x, y)
c.root.UpdateSplits()
}
func (c *PanelContainer) GetMinSize() (int, int) {
return c.root.GetMinSize()
}
func (c *PanelContainer) GetSize() (int, int) {
return c.root.GetSize()
}
func (c *PanelContainer) SetSize(width, height int) {
c.root.SetSize(width, height)
c.root.UpdateSplits()
}
func (c *PanelContainer) HandleEvent(event tcell.Event) bool {
// Call handle event on selected Panel
return (*c.selected).HandleEvent(event)
}

View File

@@ -86,23 +86,29 @@ func (c *TabContainer) GetTab(idx int) *Tab {
// Draw will draws the border of the BoxContainer, then it draws its child component.
func (c *TabContainer) Draw(s tcell.Screen) {
var styFocused tcell.Style
if c.focused {
styFocused = c.theme.GetOrDefault("TabContainerFocused")
} else {
styFocused = c.theme.GetOrDefault("TabContainer")
}
// Draw outline
DrawRectOutlineDefault(s, c.x, c.y, c.width, c.height, c.theme.GetOrDefault("TabContainer"))
DrawRectOutlineDefault(s, c.x, c.y, c.width, c.height, styFocused)
combinedTabLength := 0
for _, tab := range c.children {
combinedTabLength += len(tab.Name) + 2 // 2 for padding
for i := range c.children {
combinedTabLength += len(c.children[i].Name) + 2 // 2 for padding
}
combinedTabLength += len(c.children) - 1 // add for spacing between tabs
// Draw tabs
col := c.x + c.width/2 - combinedTabLength/2 - 1 // Starting column
col := c.x + c.width/2 - combinedTabLength/2 // Starting column
for i, tab := range c.children {
var sty tcell.Style
sty := styFocused
if c.selected == i {
sty = c.theme.GetOrDefault("TabSelected")
} else {
sty = c.theme.GetOrDefault("Tab")
fg, bg, attr := styFocused.Decompose()
sty = tcell.Style{}.Foreground(bg).Background(fg).Attributes(attr)
}
var dirty bool
@@ -118,7 +124,7 @@ func (c *TabContainer) Draw(s tcell.Screen) {
str := fmt.Sprintf(" %s ", name)
DrawStr(s, c.x+col, c.y, str, sty)
DrawStr(s, col, c.y, str, sty)
col += len(str) + 1 // Add one for spacing between tabs
}

View File

@@ -42,6 +42,7 @@ type TextEdit struct {
curx, cury int // Zero-based: cursor points before the character at that position.
prevCurCol int // Previous maximum column the cursor was at, when the user pressed left or right
scrollx, scrolly int // X and Y offset of view, known as scroll
theme *Theme
selection Region // Selection: selectMode determines if it should be used
selectMode bool // Whether the user is actively selecting text
@@ -115,6 +116,7 @@ loop:
colorscheme := &buffer.Colorscheme{
buffer.Default: tcell.Style{}.Foreground(tcell.ColorLightGray).Background(tcell.ColorBlack),
buffer.Column: tcell.Style{}.Foreground(tcell.ColorDarkGray).Background(tcell.ColorBlack),
buffer.Comment: tcell.Style{}.Foreground(tcell.ColorGray).Background(tcell.ColorBlack),
buffer.String: tcell.Style{}.Foreground(tcell.ColorOlive).Background(tcell.ColorBlack),
buffer.Keyword: tcell.Style{}.Foreground(tcell.ColorNavy).Background(tcell.ColorBlack),
@@ -265,6 +267,20 @@ func (t *TextEdit) GetLineCol() (int, int) {
return t.cury, t.curx
}
// The same as updateTerminalCursor but the caller can provide the tabOffset to
// save the original function a calculation.
func (t *TextEdit) updateTerminalCursorNoHelper(columnWidth, tabOffset int) {
(*t.screen).ShowCursor(t.x+columnWidth+t.curx+tabOffset-t.scrollx, t.y+t.cury-t.scrolly)
}
// updateTerminalCursor sets the position of the cursor with the cursor position
// properties of the TextEdit. Always sends a signal to *show* the cursor.
func (t *TextEdit) updateTerminalCursor() {
columnWidth := t.getColumnWidth()
tabOffset := t.getTabCountInLineAtCol(t.cury, t.curx) * (t.TabSize - 1)
t.updateTerminalCursorNoHelper(columnWidth, tabOffset)
}
// SetLineCol sets the cursor line and column position. Zero is origin for both.
// If `line` is out of bounds, `line` will be clamped to the closest available line.
// If `col` is out of bounds, `col` will be clamped to the closest column available for the line.
@@ -297,9 +313,8 @@ func (t *TextEdit) SetLineCol(line, col int) {
t.cury, t.curx = line, col
if t.focused && !t.selectMode {
(*t.screen).ShowCursor(t.x+columnWidth+col+tabOffset-t.scrollx, t.y+line-t.scrolly)
} else {
(*t.screen).HideCursor()
// Update terminal cursor position
t.updateTerminalCursorNoHelper(columnWidth, tabOffset)
}
}
@@ -357,10 +372,10 @@ func (t *TextEdit) CursorRight() {
// getColumnWidth returns the width of the line numbers column if it is present.
func (t *TextEdit) getColumnWidth() int {
columnWidth := 0
var columnWidth int
if t.LineNumbers {
// Set columnWidth to max count of line number digits
columnWidth = Max(2, len(strconv.Itoa(t.Buffer.Lines()))) // Column has minimum width of 2
columnWidth = Max(3, 1+len(strconv.Itoa(t.Buffer.Lines()))) // Column has minimum width of 2
}
return columnWidth
}
@@ -382,7 +397,7 @@ func (t *TextEdit) Draw(s tcell.Screen) {
bufferLines := t.Buffer.Lines()
selectedStyle := t.theme.GetOrDefault("TextEditSelected")
columnStyle := t.theme.GetOrDefault("TextEditColumn")
columnStyle := t.Highlighter.Colorscheme.GetStyle(buffer.Column)
t.Highlighter.UpdateInvalidatedLines(t.scrolly, t.scrolly+(t.height-1))
@@ -406,22 +421,7 @@ func (t *TextEdit) Draw(s tcell.Screen) {
var origLineBytes []byte = t.Buffer.Line(line)
var lineBytes []byte = origLineBytes // Line to be drawn
// When iterating lineTabs: the value at i is
// the rune index the tab was found at.
// var lineTabs [128]int // Rune index for each hard tab '\t' in lineBytes
// var tabs int // Length of lineTabs (number of hard tabs)
if t.UseHardTabs {
// var ri int // rune index
// var i int
// for i < len(lineBytes) {
// r, size := utf8.DecodeRune(lineBytes[i:])
// if r == '\t' {
// lineTabs[tabs] = ri
// tabs++
// }
// i += size
// ri++
// }
lineBytes = bytes.ReplaceAll(lineBytes, []byte{'\t'}, tabBytes)
}
@@ -433,7 +433,6 @@ func (t *TextEdit) Draw(s tcell.Screen) {
col := t.x + columnWidth
var runeIdx int // Index into lineStr (as runes) we draw the next character at
// REWRITE OF SCROLL FUNC:
for runeIdx < t.scrollx && byteIdx < len(lineBytes) {
_, size := utf8.DecodeRune(lineBytes[byteIdx:]) // Respect UTF-8
byteIdx += size
@@ -543,7 +542,7 @@ func (t *TextEdit) Draw(s tcell.Screen) {
}
}
columnStr := fmt.Sprintf("%s%s", strings.Repeat(" ", columnWidth-len(lineNumStr)), lineNumStr) // Right align line number
columnStr := fmt.Sprintf("%s%s", strings.Repeat(" ", columnWidth-len(lineNumStr)-1), lineNumStr) // Right align line number
DrawStr(s, t.x, lineY, columnStr, columnStyle) // Draw column
}
@@ -557,7 +556,7 @@ func (t *TextEdit) Draw(s tcell.Screen) {
func (t *TextEdit) SetFocused(v bool) {
t.focused = v
if v {
t.SetLineCol(t.cury, t.curx)
t.updateTerminalCursor()
} else {
(*t.screen).HideCursor()
}

View File

@@ -29,19 +29,17 @@ func (theme *Theme) GetOrDefault(key string) tcell.Style {
// DefaultTheme uses only the first 16 colors present in most colored terminals.
var DefaultTheme = Theme{
"Normal": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"Button": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorWhite),
"InputField": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"MenuBar": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
"MenuBarSelected": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"Menu": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
"MenuSelected": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"Tab": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"TabContainer": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"TabSelected": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
"TextEdit": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"TextEditColumn": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorGray),
"TextEditSelected": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
"Window": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
"WindowHeader": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorWhite),
"Normal": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"Button": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
"InputField": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"MenuBar": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorDarkGray),
"MenuBarFocused": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorLightGray),
"Menu": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
"MenuSelected": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"TabContainer": tcell.Style{}.Foreground(tcell.ColorGray).Background(tcell.ColorBlack),
"TabContainerFocused": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"TextEdit": tcell.Style{}.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack),
"TextEditSelected": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
"Window": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorDarkGray),
"WindowHeader": tcell.Style{}.Foreground(tcell.ColorBlack).Background(tcell.ColorSilver),
}