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	"github.com/gdamore/tcell"
19)
20
21// View represents a logical view on an area.  It will have some underlying
22// physical area as well, generally.  Views are operated on by Widgets.
23type View interface {
24	// SetContent is used to update the content of the View at the given
25	// location.  This will generally be called by the Draw() method of
26	// a Widget.
27	SetContent(x int, y int, ch rune, comb []rune, style tcell.Style)
28
29	// Size represents the visible size.  The actual content may be
30	// larger or smaller.
31	Size() (int, int)
32
33	// Resize tells the View that its visible dimensions have changed.
34	// It also tells it that it has a new offset relative to any parent
35	// view.
36	Resize(x, y, width, height int)
37
38	// Fill fills the displayed content with the given rune and style.
39	Fill(rune, tcell.Style)
40
41	// Clear clears the content.  Often just Fill(' ', tcell.StyleDefault)
42	Clear()
43}
44
45// ViewPort is an implementation of a View, that provides a smaller logical
46// view of larger content area.  For example, a scrollable window of text,
47// the visible window would be the ViewPort, on the underlying content.
48// ViewPorts have a two dimensional size, and a two dimensional offset.
49//
50// In some ways, as the underlying content is not kept persistently by the
51// view port, it can be thought perhaps a little more precisely as a clipping
52// region.
53type ViewPort struct {
54	physx  int  // Anchor to the real world, usually 0
55	physy  int  // Again, anchor to the real world, usually 3
56	viewx  int  // Logical offset of the view
57	viewy  int  // Logical offset of the view
58	limx   int  // Content limits -- can't right scroll past this
59	limy   int  // Content limits -- can't down scroll past this
60	width  int  // View width
61	height int  // View height
62	locked bool // if true, don't autogrow
63	v      View
64}
65
66// Clear clears the displayed content, filling it with spaces of default
67// text attributes.
68func (v *ViewPort) Clear() {
69	v.Fill(' ', tcell.StyleDefault)
70}
71
72// Fill fills the displayed view port with the given character and style.
73func (v *ViewPort) Fill(ch rune, style tcell.Style) {
74	if v.v != nil {
75		for y := 0; y < v.height; y++ {
76			for x := 0; x < v.width; x++ {
77				v.v.SetContent(x+v.physx, y+v.physy, ch, nil, style)
78			}
79		}
80	}
81}
82
83// Size returns the visible size of the ViewPort in character cells.
84func (v *ViewPort) Size() (int, int) {
85	return v.width, v.height
86}
87
88// Reset resets the record of content, and also resets the offset back
89// to the origin.  It doesn't alter the dimensions of the view port, nor
90// the physical location relative to its parent.
91func (v *ViewPort) Reset() {
92	v.limx = 0
93	v.limy = 0
94	v.viewx = 0
95	v.viewy = 0
96}
97
98// SetContent is used to place data at the given cell location.  Note that
99// since the ViewPort doesn't retain this data, if the location is outside
100// of the visible area, it is simply discarded.
101//
102// Generally, this is called during the Draw() phase by the object that
103// represents the content.
104func (v *ViewPort) SetContent(x, y int, ch rune, comb []rune, s tcell.Style) {
105	if v.v == nil {
106		return
107	}
108	if x > v.limx && !v.locked {
109		v.limx = x
110	}
111	if y > v.limy && !v.locked {
112		v.limy = y
113	}
114	if x < v.viewx || y < v.viewy {
115		return
116	}
117	if x >= (v.viewx + v.width) {
118		return
119	}
120	if y >= (v.viewy + v.height) {
121		return
122	}
123	v.v.SetContent(x-v.viewx+v.physx, y-v.viewy+v.physy, ch, comb, s)
124}
125
126// MakeVisible moves the ViewPort the minimum necessary to make the given
127// point visible.  This should be called before any content is changed with
128// SetContent, since otherwise it may be possible to move the location onto
129// a region whose contents have been discarded.
130func (v *ViewPort) MakeVisible(x, y int) {
131	if x < v.limx && x >= v.viewx+v.width {
132		v.viewx = x - (v.width - 1)
133	}
134	if x >= 0 && x < v.viewx {
135		v.viewx = x
136	}
137	if y < v.limy && y >= v.viewy+v.height {
138		v.viewy = y - (v.height - 1)
139	}
140	if y >= 0 && y < v.viewy {
141		v.viewy = y
142	}
143	v.ValidateView()
144}
145
146// ValidateViewY ensures that the Y offset of the view port is limited so that
147// it cannot scroll away from the content.
148func (v *ViewPort) ValidateViewY() {
149	if v.viewy >= v.limy-v.height {
150		v.viewy = (v.limy - v.height)
151	}
152	if v.viewy < 0 {
153		v.viewy = 0
154	}
155}
156
157// ValidateViewX ensures that the X offset of the view port is limited so that
158// it cannot scroll away from the content.
159func (v *ViewPort) ValidateViewX() {
160	if v.viewx >= v.limx-v.width {
161		v.viewx = (v.limx - v.width)
162	}
163	if v.viewx < 0 {
164		v.viewx = 0
165	}
166}
167
168// ValidateView does both ValidateViewX and ValidateViewY, ensuring both
169// offsets are valid.
170func (v *ViewPort) ValidateView() {
171	v.ValidateViewX()
172	v.ValidateViewY()
173}
174
175// Center centers the point, if possible, in the View.
176func (v *ViewPort) Center(x, y int) {
177	if x < 0 || y < 0 || x >= v.limx || y >= v.limy || v.v == nil {
178		return
179	}
180	v.viewx = x - (v.width / 2)
181	v.viewy = y - (v.height / 2)
182	v.ValidateView()
183}
184
185// ScrollUp moves the view up, showing lower numbered rows of content.
186func (v *ViewPort) ScrollUp(rows int) {
187	v.viewy -= rows
188	v.ValidateViewY()
189}
190
191// ScrollDown moves the view down, showingh higher numbered rows of content.
192func (v *ViewPort) ScrollDown(rows int) {
193	v.viewy += rows
194	v.ValidateViewY()
195}
196
197// ScrollLeft moves the view to the left.
198func (v *ViewPort) ScrollLeft(cols int) {
199	v.viewx -= cols
200	v.ValidateViewX()
201}
202
203// ScrollRight moves the view to the left.
204func (v *ViewPort) ScrollRight(cols int) {
205	v.viewx += cols
206	v.ValidateViewX()
207}
208
209// SetSize is used to set the visible size of the view.  Enclosing views or
210// layout managers can use this to inform the View of its correct visible size.
211func (v *ViewPort) SetSize(width, height int) {
212	v.height = height
213	v.width = width
214	v.ValidateView()
215}
216
217// GetVisible returns the upper left and lower right coordinates of the visible
218// content.  That is, it will return x1, y1, x2, y2 where the upper left cell
219// is position x1, y1, and the lower right is x2, y2.  These coordinates are
220// in the space of the content, that is the content area uses coordinate 0,0
221// as its first cell position.
222func (v *ViewPort) GetVisible() (int, int, int, int) {
223	return v.viewx, v.viewy, v.viewx + v.width - 1, v.viewy + v.height - 1
224}
225
226// GetPhysical returns the upper left and lower right coordinates of the visible
227// content in the coordinate space of the parent.  This is may be the physical
228// coordinates of the screen, if the screen is the view's parent.
229func (v *ViewPort) GetPhysical() (int, int, int, int) {
230	return v.physx, v.physy, v.physx + v.width - 1, v.physy + v.height - 1
231}
232
233// SetContentSize sets the size of the content area; this is used to limit
234// scrolling and view moment.  If locked is true, then the content size will
235// not automatically grow even if content is placed outside of this area
236// with the SetContent() method.  If false, and content is drawn outside
237// of the existing size, then the size will automatically grow to include
238// the new content.
239func (v *ViewPort) SetContentSize(width, height int, locked bool) {
240	v.limx = width
241	v.limy = height
242	v.locked = locked
243	v.ValidateView()
244}
245
246// GetContentSize returns the size of content as width, height in character
247// cells.
248func (v *ViewPort) GetContentSize() (int, int) {
249	return v.limx, v.limy
250}
251
252// Resize is called by the enclosing view to change the size of the ViewPort,
253// usually in response to a window resize event.  The x, y refer are the
254// ViewPort's location relative to the parent View.  A negative value for either
255// width or height will cause the ViewPort to expand to fill to the end of parent
256// View in the relevant dimension.
257func (v *ViewPort) Resize(x, y, width, height int) {
258	if v.v == nil {
259		return
260	}
261	px, py := v.v.Size()
262	if x >= 0 && x < px {
263		v.physx = x
264	}
265	if y >= 0 && y < py {
266		v.physy = y
267	}
268	if width < 0 {
269		width = px - x
270	}
271	if height < 0 {
272		height = py - y
273	}
274	if width <= x+px {
275		v.width = width
276	}
277	if height <= y+py {
278		v.height = height
279	}
280}
281
282// SetView is called during setup, to provide the parent View.
283func (v *ViewPort) SetView(view View) {
284	v.v = view
285}
286
287// NewViewPort returns a new ViewPort (and hence also a View).
288// The x and y coordinates are an offset relative to the parent.
289// The origin 0,0 represents the upper left.  The width and height
290// indicate a width and height. If the value -1 is supplied, then the
291// dimension is calculated from the parent.
292func NewViewPort(view View, x, y, width, height int) *ViewPort {
293	v := &ViewPort{v: view}
294	// initial (and possibly poor) assumptions -- all visible
295	// cells are addressible, but none beyond that.
296	v.limx = width
297	v.limy = height
298	v.Resize(x, y, width, height)
299	return v
300}
301