1// Copyright 2016 The Tcell Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use file except in compliance with the License.
5// You may obtain a copy of the license at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package views
16
17import (
18	"strings"
19	"sync"
20
21	"github.com/gdamore/tcell"
22)
23
24// TextArea is a pannable 2 dimensional text widget. It wraps both
25// the view and the model for text in a single, convenient widget.
26// Text is provided as an array of strings, each of which represents
27// a single line to display.  All text in the TextArea has the same
28// style.  An optional soft cursor is available.
29type TextArea struct {
30	model *linesModel
31	once  sync.Once
32	CellView
33}
34
35type linesModel struct {
36	lines  []string
37	width  int
38	height int
39	x      int
40	y      int
41	hide   bool
42	cursor bool
43	style  tcell.Style
44}
45
46func (m *linesModel) GetCell(x, y int) (rune, tcell.Style, []rune, int) {
47	var ch rune
48	if x < 0 || y < 0 || y >= len(m.lines) || x >= len(m.lines[y]) {
49		return ch, m.style, nil, 1
50	}
51	// XXX: extend this to support combining and full width chars
52	return rune(m.lines[y][x]), m.style, nil, 1
53}
54
55func (m *linesModel) GetBounds() (int, int) {
56	return m.width, m.height
57}
58
59func (m *linesModel) limitCursor() {
60	if m.x > m.width-1 {
61		m.x = m.width - 1
62	}
63	if m.y > m.height-1 {
64		m.y = m.height - 1
65	}
66	if m.x < 0 {
67		m.x = 0
68	}
69	if m.y < 0 {
70		m.y = 0
71	}
72}
73
74func (m *linesModel) SetCursor(x, y int) {
75	m.x = x
76	m.y = y
77	m.limitCursor()
78}
79
80func (m *linesModel) MoveCursor(x, y int) {
81	m.x += x
82	m.y += y
83	m.limitCursor()
84}
85
86func (m *linesModel) GetCursor() (int, int, bool, bool) {
87	return m.x, m.y, m.cursor, !m.hide
88}
89
90// SetLines sets the content text to display.
91func (ta *TextArea) SetLines(lines []string) {
92	ta.Init()
93	m := ta.model
94	m.width = 0
95	m.height = len(lines)
96	m.lines = append([]string{}, lines...)
97	for _, l := range lines {
98		if len(l) > m.width {
99			m.width = len(l)
100		}
101	}
102	ta.CellView.SetModel(m)
103}
104
105func (ta *TextArea) SetStyle(style tcell.Style) {
106	ta.model.style = style
107	ta.CellView.SetStyle(style)
108}
109
110// EnableCursor enables a soft cursor in the TextArea.
111func (ta *TextArea) EnableCursor(on bool) {
112	ta.Init()
113	ta.model.cursor = on
114}
115
116// HideCursor hides or shows the cursor in the TextArea.
117// If on is true, the cursor is hidden.  Note that a cursor is only
118// shown if it is enabled.
119func (ta *TextArea) HideCursor(on bool) {
120	ta.Init()
121	ta.model.hide = on
122}
123
124// SetContent is used to set the textual content, passed as a
125// single string.  Lines within the string are delimited by newlines.
126func (ta *TextArea) SetContent(text string) {
127	ta.Init()
128	lines := strings.Split(strings.Trim(text, "\n"), "\n")
129	ta.SetLines(lines)
130}
131
132// Init initializes the TextArea.
133func (ta *TextArea) Init() {
134	ta.once.Do(func() {
135		lm := &linesModel{lines: []string{}, width: 0}
136		ta.model = lm
137		ta.CellView.Init()
138		ta.CellView.SetModel(lm)
139	})
140}
141
142// NewTextArea creates a blank TextArea.
143func NewTextArea() *TextArea {
144	ta := &TextArea{}
145	ta.Init()
146	return ta
147}
148