2016-05-24 15:50:38 +08:00
|
|
|
// Copyright 2016 The Tcell Authors
|
2015-11-02 06:44:23 +08:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the license at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package views
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
2016-05-24 15:50:38 +08:00
|
|
|
"sync"
|
2015-11-02 06:44:23 +08:00
|
|
|
|
2020-08-26 07:20:58 +08:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2015-11-02 06:44:23 +08:00
|
|
|
)
|
|
|
|
|
2015-11-05 03:24:00 +08:00
|
|
|
// TextArea is a pannable 2 dimensional text widget. It wraps both
|
|
|
|
// the view and the model for text in a single, convenient widget.
|
|
|
|
// Text is provided as an array of strings, each of which represents
|
|
|
|
// a single line to display. All text in the TextArea has the same
|
|
|
|
// style. An optional soft cursor is available.
|
2015-11-02 06:44:23 +08:00
|
|
|
type TextArea struct {
|
|
|
|
model *linesModel
|
2016-05-24 15:50:38 +08:00
|
|
|
once sync.Once
|
|
|
|
CellView
|
2015-11-02 06:44:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type linesModel struct {
|
2020-04-13 03:17:06 +08:00
|
|
|
runes [][]rune
|
2015-11-02 06:44:23 +08:00
|
|
|
width int
|
|
|
|
height int
|
|
|
|
x int
|
|
|
|
y int
|
|
|
|
hide bool
|
|
|
|
cursor bool
|
2016-05-24 15:50:38 +08:00
|
|
|
style tcell.Style
|
2015-11-02 06:44:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *linesModel) GetCell(x, y int) (rune, tcell.Style, []rune, int) {
|
2020-04-13 03:17:06 +08:00
|
|
|
if x < 0 || y < 0 || y >= m.height || x >= len(m.runes[y]) {
|
|
|
|
return 0, m.style, nil, 1
|
2015-11-02 06:44:23 +08:00
|
|
|
}
|
|
|
|
// XXX: extend this to support combining and full width chars
|
2020-04-13 03:17:06 +08:00
|
|
|
return m.runes[y][x], m.style, nil, 1
|
2015-11-02 06:44:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *linesModel) GetBounds() (int, int) {
|
|
|
|
return m.width, m.height
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *linesModel) limitCursor() {
|
|
|
|
if m.x > m.width-1 {
|
|
|
|
m.x = m.width - 1
|
|
|
|
}
|
|
|
|
if m.y > m.height-1 {
|
|
|
|
m.y = m.height - 1
|
|
|
|
}
|
2017-06-16 21:47:18 +08:00
|
|
|
if m.x < 0 {
|
|
|
|
m.x = 0
|
|
|
|
}
|
|
|
|
if m.y < 0 {
|
|
|
|
m.y = 0
|
|
|
|
}
|
2015-11-02 06:44:23 +08:00
|
|
|
}
|
2017-06-16 21:47:18 +08:00
|
|
|
|
2015-11-02 06:44:23 +08:00
|
|
|
func (m *linesModel) SetCursor(x, y int) {
|
|
|
|
m.x = x
|
|
|
|
m.y = y
|
|
|
|
m.limitCursor()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *linesModel) MoveCursor(x, y int) {
|
|
|
|
m.x += x
|
|
|
|
m.y += y
|
|
|
|
m.limitCursor()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *linesModel) GetCursor() (int, int, bool, bool) {
|
|
|
|
return m.x, m.y, m.cursor, !m.hide
|
|
|
|
}
|
|
|
|
|
2015-11-05 03:24:00 +08:00
|
|
|
// SetLines sets the content text to display.
|
2015-11-02 06:44:23 +08:00
|
|
|
func (ta *TextArea) SetLines(lines []string) {
|
2016-05-24 15:50:38 +08:00
|
|
|
ta.Init()
|
2015-11-02 06:44:23 +08:00
|
|
|
m := ta.model
|
2022-09-01 02:37:42 +08:00
|
|
|
m.width = 0
|
2020-04-13 03:17:06 +08:00
|
|
|
|
|
|
|
// extend slice before using m.runes[row] to avoid panic
|
|
|
|
slice := make([][]rune, len(lines))
|
2020-04-27 02:36:01 +08:00
|
|
|
m.runes = slice
|
2020-04-13 03:17:06 +08:00
|
|
|
|
|
|
|
for row, line := range lines {
|
|
|
|
for _, ch := range line {
|
|
|
|
m.runes[row] = append(m.runes[row], ch)
|
|
|
|
}
|
|
|
|
if len(m.runes[row]) > m.width {
|
|
|
|
m.width = len(m.runes[row])
|
2015-11-02 06:44:23 +08:00
|
|
|
}
|
|
|
|
}
|
2020-04-13 03:17:06 +08:00
|
|
|
|
|
|
|
m.height = len(m.runes)
|
|
|
|
|
2016-05-24 15:50:38 +08:00
|
|
|
ta.CellView.SetModel(m)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ta *TextArea) SetStyle(style tcell.Style) {
|
|
|
|
ta.model.style = style
|
|
|
|
ta.CellView.SetStyle(style)
|
2015-11-02 06:44:23 +08:00
|
|
|
}
|
|
|
|
|
2015-11-05 03:24:00 +08:00
|
|
|
// EnableCursor enables a soft cursor in the TextArea.
|
2015-11-02 06:44:23 +08:00
|
|
|
func (ta *TextArea) EnableCursor(on bool) {
|
2016-05-24 15:50:38 +08:00
|
|
|
ta.Init()
|
2015-11-02 06:44:23 +08:00
|
|
|
ta.model.cursor = on
|
|
|
|
}
|
|
|
|
|
2015-11-05 03:24:00 +08:00
|
|
|
// HideCursor hides or shows the cursor in the TextArea.
|
|
|
|
// If on is true, the cursor is hidden. Note that a cursor is only
|
|
|
|
// shown if it is enabled.
|
2015-11-02 06:44:23 +08:00
|
|
|
func (ta *TextArea) HideCursor(on bool) {
|
2016-05-24 15:50:38 +08:00
|
|
|
ta.Init()
|
2015-11-02 06:44:23 +08:00
|
|
|
ta.model.hide = on
|
|
|
|
}
|
|
|
|
|
2015-11-05 03:24:00 +08:00
|
|
|
// SetContent is used to set the textual content, passed as a
|
|
|
|
// single string. Lines within the string are delimited by newlines.
|
2015-11-02 06:44:23 +08:00
|
|
|
func (ta *TextArea) SetContent(text string) {
|
2016-05-24 15:50:38 +08:00
|
|
|
ta.Init()
|
2015-11-02 06:44:23 +08:00
|
|
|
lines := strings.Split(strings.Trim(text, "\n"), "\n")
|
|
|
|
ta.SetLines(lines)
|
|
|
|
}
|
|
|
|
|
2016-05-24 15:50:38 +08:00
|
|
|
// Init initializes the TextArea.
|
|
|
|
func (ta *TextArea) Init() {
|
|
|
|
ta.once.Do(func() {
|
2020-04-13 03:17:06 +08:00
|
|
|
lm := &linesModel{runes: [][]rune{}, width: 0}
|
2016-05-24 15:50:38 +08:00
|
|
|
ta.model = lm
|
|
|
|
ta.CellView.Init()
|
|
|
|
ta.CellView.SetModel(lm)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-11-05 03:24:00 +08:00
|
|
|
// NewTextArea creates a blank TextArea.
|
2015-11-02 06:44:23 +08:00
|
|
|
func NewTextArea() *TextArea {
|
2016-05-24 15:50:38 +08:00
|
|
|
ta := &TextArea{}
|
|
|
|
ta.Init()
|
2015-11-02 06:44:23 +08:00
|
|
|
return ta
|
|
|
|
}
|