tcell/views/cellarea.go

318 lines
6.6 KiB
Go
Raw Normal View History

// Copyright 2024 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 (
"sync"
2020-08-26 07:20:58 +08:00
"github.com/gdamore/tcell/v2"
)
// CellModel models the content of a CellView. The dimensions used within
// a CellModel are always logical, and have origin 0, 0.
type CellModel interface {
GetCell(x, y int) (rune, tcell.Style, []rune, int)
GetBounds() (int, int)
SetCursor(int, int)
GetCursor() (int, int, bool, bool)
MoveCursor(offx, offy int)
}
// CellView is a flexible view of a CellModel, offering both cursor
// management and a panning.
type CellView struct {
port *ViewPort
view View
content Widget
style tcell.Style
lines []string
model CellModel
once sync.Once
WidgetWatchers
}
// Draw draws the content.
func (a *CellView) Draw() {
port := a.port
model := a.model
port.Fill(' ', a.style)
if a.view == nil {
return
}
if model == nil {
return
}
vw, vh := a.view.Size()
for y := 0; y < vh; y++ {
for x := 0; x < vw; x++ {
a.view.SetContent(x, y, ' ', nil, a.style)
}
}
ex, ey := model.GetBounds()
vx, vy := port.Size()
if ex < vx {
ex = vx
}
if ey < vy {
ey = vy
}
cx, cy, en, sh := a.model.GetCursor()
for y := 0; y < ey; y++ {
for x := 0; x < ex; x++ {
ch, style, comb, wid := model.GetCell(x, y)
if ch == 0 {
ch = ' '
style = a.style
}
if en && x == cx && y == cy && sh {
style = style.Reverse(true)
}
port.SetContent(x, y, ch, comb, style)
x += wid - 1
}
}
}
func (a *CellView) keyUp() {
if _, _, en, _ := a.model.GetCursor(); !en {
a.port.ScrollUp(1)
return
}
a.model.MoveCursor(0, -1)
a.MakeCursorVisible()
}
func (a *CellView) keyDown() {
if _, _, en, _ := a.model.GetCursor(); !en {
a.port.ScrollDown(1)
return
}
a.model.MoveCursor(0, 1)
a.MakeCursorVisible()
}
func (a *CellView) keyLeft() {
if _, _, en, _ := a.model.GetCursor(); !en {
a.port.ScrollLeft(1)
return
}
a.model.MoveCursor(-1, 0)
a.MakeCursorVisible()
}
func (a *CellView) keyRight() {
if _, _, en, _ := a.model.GetCursor(); !en {
a.port.ScrollRight(1)
return
}
a.model.MoveCursor(+1, 0)
a.MakeCursorVisible()
}
func (a *CellView) keyPgUp() {
_, vy := a.port.Size()
if _, _, en, _ := a.model.GetCursor(); !en {
a.port.ScrollUp(vy)
return
}
a.model.MoveCursor(0, -vy)
a.MakeCursorVisible()
}
func (a *CellView) keyPgDn() {
_, vy := a.port.Size()
if _, _, en, _ := a.model.GetCursor(); !en {
a.port.ScrollDown(vy)
return
}
a.model.MoveCursor(0, +vy)
a.MakeCursorVisible()
}
func (a *CellView) keyHome() {
vx, vy := a.model.GetBounds()
if _, _, en, _ := a.model.GetCursor(); !en {
a.port.ScrollUp(vy)
a.port.ScrollLeft(vx)
return
}
a.model.SetCursor(0, 0)
a.MakeCursorVisible()
}
func (a *CellView) keyEnd() {
vx, vy := a.model.GetBounds()
if _, _, en, _ := a.model.GetCursor(); !en {
a.port.ScrollDown(vy)
a.port.ScrollRight(vx)
return
}
a.model.SetCursor(vx, vy)
a.MakeCursorVisible()
}
// MakeCursorVisible ensures that the cursor is visible, panning the ViewPort
// as necessary, if the cursor is enabled.
func (a *CellView) MakeCursorVisible() {
if a.model == nil {
return
}
x, y, enabled, _ := a.model.GetCursor()
if enabled {
a.MakeVisible(x, y)
}
}
// HandleEvent handles events. In particular, it handles certain key events
// to move the cursor or pan the view.
func (a *CellView) HandleEvent(e tcell.Event) bool {
if a.model == nil {
return false
}
switch e := e.(type) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyUp, tcell.KeyCtrlP:
a.keyUp()
return true
case tcell.KeyDown, tcell.KeyCtrlN:
a.keyDown()
return true
case tcell.KeyRight, tcell.KeyCtrlF:
a.keyRight()
return true
case tcell.KeyLeft, tcell.KeyCtrlB:
a.keyLeft()
return true
case tcell.KeyPgDn:
a.keyPgDn()
return true
case tcell.KeyPgUp:
a.keyPgUp()
return true
case tcell.KeyEnd:
a.keyEnd()
return true
case tcell.KeyHome:
a.keyHome()
return true
}
}
return false
}
// Size returns the content size, based on the model.
func (a *CellView) Size() (int, int) {
// We always return a minimum of two rows, and two columns.
w, h := a.model.GetBounds()
// Clip to a 2x2 minimum square; we can scroll within that.
if w < 2 {
w = 2
}
if h < 2 {
h = 2
}
return w, h
}
// GetModel gets the model for this CellView
func (a *CellView) GetModel() CellModel {
return a.model
}
// SetModel sets the model for this CellView.
func (a *CellView) SetModel(model CellModel) {
w, h := model.GetBounds()
a.model = model
a.port.SetContentSize(w, h, true)
a.port.ValidateView()
a.PostEventWidgetContent(a)
}
// SetView sets the View context.
func (a *CellView) SetView(view View) {
port := a.port
port.SetView(view)
a.view = view
if view == nil {
return
}
width, height := view.Size()
a.port.Resize(0, 0, width, height)
if a.model != nil {
w, h := a.model.GetBounds()
a.port.SetContentSize(w, h, true)
}
a.Resize()
}
// Resize is called when the View is resized. It will ensure that the
// cursor is visible, if present.
func (a *CellView) Resize() {
// We might want to reflow text
width, height := a.view.Size()
a.port.Resize(0, 0, width, height)
a.port.ValidateView()
a.MakeCursorVisible()
}
// SetCursor sets the the cursor position.
func (a *CellView) SetCursor(x, y int) {
a.model.SetCursor(x, y)
}
// SetCursorX sets the the cursor column.
func (a *CellView) SetCursorX(x int) {
_, y, _, _ := a.model.GetCursor()
a.SetCursor(x, y)
}
// SetCursorY sets the the cursor row.
func (a *CellView) SetCursorY(y int) {
x, _, _, _ := a.model.GetCursor()
a.SetCursor(x, y)
}
// MakeVisible makes the given coordinates visible, if they are not already.
// It does this by moving the ViewPort for the CellView.
func (a *CellView) MakeVisible(x, y int) {
a.port.MakeVisible(x, y)
}
// SetStyle sets the the default fill style.
func (a *CellView) SetStyle(s tcell.Style) {
a.style = s
}
// Init initializes a new CellView for use.
func (a *CellView) Init() {
a.once.Do(func() {
a.port = NewViewPort(nil, 0, 0, 0, 0)
a.style = tcell.StyleDefault
})
}
// NewCellView creates a CellView.
func NewCellView() *CellView {
cv := &CellView{}
cv.Init()
return cv
}