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