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