Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interfaces to expose internal APIs for SIXEL #436

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 62 additions & 29 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,37 @@ import (
)

type cell struct {
currMain rune
currComb []rune
currComb []rune
lastComb []rune

currMain rune
lastMain rune

currStyle Style
lastMain rune
lastStyle Style
lastComb []rune
width int

width int
}

func (c *cell) dirty() bool {
if c.lastMain == rune(0) {
return true
}
if c.lastMain != c.currMain {
return true
}
if c.lastStyle != c.currStyle {
return true
}
if len(c.lastComb) != len(c.currComb) {
return true
}
for i := range c.lastComb {
if c.lastComb[i] != c.currComb[i] {
return true
}
}
return false
}

// CellBuffer represents a two dimensional array of character cells.
Expand All @@ -35,9 +59,9 @@ type cell struct {
//
// CellBuffer is not thread safe.
type CellBuffer struct {
cells []cell
w int
h int
cells []cell
}

// SetContent sets the contents (primary rune, combining runes,
Expand Down Expand Up @@ -67,6 +91,7 @@ func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) {
var combc []rune
var style Style
var width int

if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
mainc, combc, style = c.currMain, c.currComb, c.currStyle
Expand All @@ -90,31 +115,35 @@ func (cb *CellBuffer) Invalidate() {
}
}

// Dirty checks if a character at the given location needs an
// to be refreshed on the physical display. This returns true
// if the cell content is different since the last time it was
// marked clean.
// Dirty checks if a character at the given location needs an to be refreshed on
// the physical display. This returns true if the cell content is different
// since the last time it was marked clean.
func (cb *CellBuffer) Dirty(x, y int) bool {
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
if c.lastMain == rune(0) {
return true
}
if c.lastMain != c.currMain {
return true
}
if c.lastStyle != c.currStyle {
return true
}
if len(c.lastComb) != len(c.currComb) {
return true
}
for i := range c.lastComb {
if c.lastComb[i] != c.currComb[i] {
return x >= 0 && y >= 0 && x < cb.w && y < cb.h && cb.cells[(y*cb.w)+x].dirty()
}

// DirtyRegion checks if a region needs to be refreshed on the physical display.
// It is effectively the equivalent of calling Dirty in a loop. If the given
// boundaries are larger than the buffer, then false is returned.
func (cb *CellBuffer) DirtyRegion(x1, y1, x2, y2 int) bool {
if x1 > x2 || y1 > y2 || x1 < 0 || y1 < 0 || x2 >= cb.w || y2 >= cb.h {
return true
}

// eliminate bound check.
_ = cb.cells[(y2*cb.w)+x2]

for y := y1; y <= y2; y++ {
// iterate x more often than y for cache line alignments.
line := y * cb.w

for x := x1; x <= x2; x++ {
if cb.cells[line+x].dirty() {
return true
}
}
}

return false
}

Expand All @@ -138,7 +167,7 @@ func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
}

// Resize is used to resize the cells array, with different dimensions,
// while preserving the original contents. The cells will be invalidated
// while preserving the original contents. The cells will be invalidated
// so that they can be redrawn.
func (cb *CellBuffer) Resize(w, h int) {

Expand All @@ -148,9 +177,13 @@ func (cb *CellBuffer) Resize(w, h int) {

newc := make([]cell, w*h)
for y := 0; y < h && y < cb.h; y++ {
yOld := y * cb.w
yNew := y * w

for x := 0; x < w && x < cb.w; x++ {
oc := &cb.cells[(y*cb.w)+x]
nc := &newc[(y*w)+x]
oc := &cb.cells[yOld+x]
nc := &newc[yNew+x]

nc.currMain = oc.currMain
nc.currComb = oc.currComb
nc.currStyle = oc.currStyle
Expand Down
51 changes: 51 additions & 0 deletions interceptors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package tcell

// interceptors implements the DrawInterceptAdder interface. It provides
// somewhere to store the callbacks.
type interceptors struct {
beforeFunc DrawInterceptFunc
afterFunc DrawInterceptFunc
}

func (icepts interceptors) before(s Screen, sync bool) bool {
if icepts.beforeFunc != nil {
return icepts.beforeFunc(s, sync)
}
return false
}

func (icepts interceptors) after(s Screen, sync bool) bool {
if icepts.afterFunc != nil {
return icepts.afterFunc(s, sync)
}
return false
}

// AddDrawIntercept wraps the existing draw intercept function with the given
// one.
func (icepts *interceptors) AddDrawIntercept(fn DrawInterceptFunc) {
icepts.beforeFunc = wrapDrawInterceptFunc(icepts.beforeFunc, fn)
}

// AddDrawInterceptAfter adds the draw interceptor after everything is drawn.
func (icepts *interceptors) AddDrawInterceptAfter(fn DrawInterceptFunc) {
icepts.afterFunc = wrapDrawInterceptFunc(icepts.afterFunc, fn)
}

func wrapDrawInterceptFunc(oldFn, newFn DrawInterceptFunc) DrawInterceptFunc {
// directly use the new function if we don't have an old one.
if oldFn == nil {
return newFn
}

return func(s Screen, sync bool) bool {
a := newFn(s, sync)
b := oldFn(s, sync)
return a || b
}
}

type noopMutex struct{}

func (noopMutex) Lock() {}
func (noopMutex) Unlock() {}
51 changes: 51 additions & 0 deletions screen.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,54 @@ const (
MouseDragEvents = MouseFlags(2) // Click-drag events (includes button events)
MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events)
)

// DrawInterceptFunc is the callback to intercept a draw. It is called when the
// lock is acquired. Sync is true if the whole screen is about to be cleared, or
// for after interceptors, after clearing. If the callback returns true on
// before, then the screen is cleared after the callback is done. If it's an
// after, then the screen will be cleared on the next draw.
//
// The interceptor function must use the given screen instead of referencing the
// existing screen from outside. This is done to prevent deadlocks.
type DrawInterceptFunc func(s Screen, sync bool) (clear bool)

// DrawInterceptAdder is an interface that Screen can implement to add a draw
// interceptor function. This interface could be used to draw custom SIXEL
// images as well as other raw terminal things.
//
// Implementations must call these functions before any state changes, or in the
// case of After, after all state changes. For example, draw interceptors should
// be called before the cursor is hidden, and the after interceptors should be
// called after the cursor is shown and everything is drawn into the buffer (but
// before flushing).
type DrawInterceptAdder interface {
// AddDrawIntercept wraps the existing draw intercept function with the
// given one.
AddDrawIntercept(fn DrawInterceptFunc)
// AddDrawInterceptAfter adds the draw interceptor after everything is
// drawn.
AddDrawInterceptAfter(fn DrawInterceptFunc)
}

// PixelSizer exposes the terminal size in pixels, if available.
type PixelSizer interface {
// PixelSize returns the dimensions in the exact pixels of the terminal. It
// may return (0, 0) or a lesser value if the information is not available.
PixelSize() (xpx, ypx int)
}

// DirectDrawer exposes the raw draw function for the caller to draw code
// unknown to tcell.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API cannot work with Windows console (legacy) terminals, because they don't express the drawing as a byte stream with embedded characters.

Likewise for web based terminals.

type DirectDrawer interface {
// DrawDirectly draws the given bytes as-is directly into the screen.
DrawDirectly([]byte)
}

// CellBufferViewer is an interface that allows exposing the raw cell buffer for
// optimizations regarding damage tracking.
type CellBufferViewer interface {
// ViewCellBuffer exposes the internal cell buffer to the callback while
// acquiring its lock. ViewCellBuffer is not guaranteed to return after f
// did.
ViewCellBuffer(f func(*CellBuffer))
}
Loading