...

Source file src/github.com/gdamore/tcell/v2/views/view.go

Documentation: github.com/gdamore/tcell/v2/views

     1  // Copyright 2023 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  
    15  package views
    16  
    17  import (
    18  	"github.com/gdamore/tcell/v2"
    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.
    23  type 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.
    53  type 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.
    68  func (v *ViewPort) Clear() {
    69  	v.Fill(' ', tcell.StyleDefault)
    70  }
    71  
    72  // Fill fills the displayed view port with the given character and style.
    73  func (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.
    84  func (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.
    91  func (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.
   104  func (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.
   130  func (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.
   148  func (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.
   159  func (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.
   170  func (v *ViewPort) ValidateView() {
   171  	v.ValidateViewX()
   172  	v.ValidateViewY()
   173  }
   174  
   175  // Center centers the point, if possible, in the View.
   176  func (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.
   186  func (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.
   192  func (v *ViewPort) ScrollDown(rows int) {
   193  	v.viewy += rows
   194  	v.ValidateViewY()
   195  }
   196  
   197  // ScrollLeft moves the view to the left.
   198  func (v *ViewPort) ScrollLeft(cols int) {
   199  	v.viewx -= cols
   200  	v.ValidateViewX()
   201  }
   202  
   203  // ScrollRight moves the view to the left.
   204  func (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.
   211  func (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.
   222  func (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.
   229  func (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.
   239  func (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.
   248  func (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.
   257  func (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 || width > px-x {
   269  		width = px - x
   270  	}
   271  	if height < 0 || height > py-y {
   272  		height = py - y
   273  	}
   274  
   275  	v.width = width
   276  	v.height = height
   277  }
   278  
   279  // SetView is called during setup, to provide the parent View.
   280  func (v *ViewPort) SetView(view View) {
   281  	v.v = view
   282  }
   283  
   284  // NewViewPort returns a new ViewPort (and hence also a View).
   285  // The x and y coordinates are an offset relative to the parent.
   286  // The origin 0,0 represents the upper left.  The width and height
   287  // indicate a width and height. If the value -1 is supplied, then the
   288  // dimension is calculated from the parent.
   289  func NewViewPort(view View, x, y, width, height int) *ViewPort {
   290  	v := &ViewPort{v: view}
   291  	// initial (and possibly poor) assumptions -- all visible
   292  	// cells are addressible, but none beyond that.
   293  	v.limx = width
   294  	v.limy = height
   295  	v.Resize(x, y, width, height)
   296  	return v
   297  }
   298  

View as plain text