forked from Mirrors/bubbletea
Only render lines that have changed since the last render
This commit is contained in:
parent
c07cbd1e32
commit
fb64f34bd0
|
@ -1,5 +1,6 @@
|
||||||
package tea
|
package tea
|
||||||
|
|
||||||
|
// renderer is the interface for Bubble Tea renderers.
|
||||||
type renderer interface {
|
type renderer interface {
|
||||||
start()
|
start()
|
||||||
stop()
|
stop()
|
||||||
|
|
|
@ -16,8 +16,8 @@ const (
|
||||||
defaultFramerate = time.Second / 60
|
defaultFramerate = time.Second / 60
|
||||||
)
|
)
|
||||||
|
|
||||||
// renderer is a timer-based renderer, updating the view at a given framerate
|
// standardRenderer is a framerate-based terminal renderer, updating the view
|
||||||
// to avoid overloading the terminal emulator.
|
// at a given framerate to avoid overloading the terminal emulator.
|
||||||
//
|
//
|
||||||
// In cases where very high performance is needed the renderer can be told
|
// In cases where very high performance is needed the renderer can be told
|
||||||
// to exclude ranges of lines, allowing them to be written to directly.
|
// to exclude ranges of lines, allowing them to be written to directly.
|
||||||
|
@ -38,7 +38,7 @@ type standardRenderer struct {
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
|
|
||||||
// lines not to render
|
// lines explicitly set not to render
|
||||||
ignoreLines map[int]struct{}
|
ignoreLines map[int]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,21 +94,36 @@ func (r *standardRenderer) flush() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Output buffer
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
|
|
||||||
|
newLines := strings.Split(r.buf.String(), "\n")
|
||||||
|
oldLines := strings.Split(r.lastRender, "\n")
|
||||||
|
skipLines := make(map[int]struct{})
|
||||||
|
|
||||||
// Clear any lines we painted in the last render.
|
// Clear any lines we painted in the last render.
|
||||||
if r.linesRendered > 0 {
|
if r.linesRendered > 0 {
|
||||||
for i := r.linesRendered - 1; i > 0; i-- {
|
for i := r.linesRendered - 1; i > 0; i-- {
|
||||||
// Check if we should skip rendering for this line. Clearing the
|
// Determine if we should skip rendering for this line. We can skip
|
||||||
// line before painting is part of the standard rendering routine.
|
// for two reasons:
|
||||||
if _, exists := r.ignoreLines[i]; !exists {
|
//
|
||||||
|
// 1. We've explicitly set this line to be ignored so we can render
|
||||||
|
// it elsewhere (for example, via the performance scroll methods
|
||||||
|
// like insertTop and insertBottom).
|
||||||
|
//
|
||||||
|
// 2. The new line is the same as the old line, in which case we
|
||||||
|
// can skip rendering for this line as a performance optimization.
|
||||||
|
_, ignoreLine := r.ignoreLines[i]
|
||||||
|
if ignoreLine || (len(newLines) > i && len(oldLines) > i && newLines[i] == oldLines[i]) {
|
||||||
|
skipLines[i] = struct{}{}
|
||||||
|
} else {
|
||||||
clearLine(out)
|
clearLine(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
cursorUp(out)
|
cursorUp(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := r.ignoreLines[0]; !exists {
|
if _, exists := skipLines[0]; !exists {
|
||||||
// We need to return to the start of the line here to properly
|
// We need to return to the start of the line here to properly
|
||||||
// erase it. Going back the entire width of the terminal will
|
// erase it. Going back the entire width of the terminal will
|
||||||
// usually be farther than we need to go, but terminal emulators
|
// usually be farther than we need to go, but terminal emulators
|
||||||
|
@ -124,28 +139,31 @@ func (r *standardRenderer) flush() {
|
||||||
}
|
}
|
||||||
|
|
||||||
r.linesRendered = 0
|
r.linesRendered = 0
|
||||||
lines := strings.Split(r.buf.String(), "\n")
|
|
||||||
|
|
||||||
// Paint new lines
|
// Paint new lines
|
||||||
for i := 0; i < len(lines); i++ {
|
for i := 0; i < len(newLines); i++ {
|
||||||
if _, exists := r.ignoreLines[r.linesRendered]; exists {
|
if _, skip := skipLines[r.linesRendered]; skip {
|
||||||
cursorDown(out) // skip rendering for this line.
|
// Unless this is the last line, move the cursor down.
|
||||||
|
if i < len(newLines)-1 {
|
||||||
|
cursorDown(out)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
line := lines[i]
|
line := newLines[i]
|
||||||
|
|
||||||
// Truncate lines wider than the width of the window to avoid
|
// Truncate lines wider than the width of the window to avoid
|
||||||
// rendering troubles. If we don't have the width of the window
|
// wrapping, which will mess up rendering. If we don't have the
|
||||||
// this will be ignored.
|
// width of the window this will be ignored.
|
||||||
//
|
//
|
||||||
// Note that on Windows we can't get the width of the window
|
// Note that on Windows we only get the width of the window on
|
||||||
// (signal SIGWINCH is not supported), so this will be ignored.
|
// program initialization, so after a resize this won't perform
|
||||||
|
// correctly (signal SIGWINCH is not supported on Windows).
|
||||||
if r.width > 0 {
|
if r.width > 0 {
|
||||||
line = truncate.String(line, uint(r.width))
|
line = truncate.String(line, uint(r.width))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = io.WriteString(out, line)
|
_, _ = io.WriteString(out, line)
|
||||||
|
|
||||||
if i != len(lines)-1 {
|
if i != len(newLines)-1 {
|
||||||
_, _ = io.WriteString(out, "\r\n")
|
_, _ = io.WriteString(out, "\r\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,8 +173,8 @@ func (r *standardRenderer) flush() {
|
||||||
// Make sure the cursor is at the start of the last line to keep rendering
|
// Make sure the cursor is at the start of the last line to keep rendering
|
||||||
// behavior consistent.
|
// behavior consistent.
|
||||||
if r.altScreenActive {
|
if r.altScreenActive {
|
||||||
// We need this case to fix a bug in macOS terminal. In other terminals
|
// This case fixes a bug in macOS terminal. In other terminals the
|
||||||
// the below case seems to do the job regardless of whether or not we're
|
// other case seems to do the job regardless of whether or not we're
|
||||||
// using the full terminal window.
|
// using the full terminal window.
|
||||||
moveCursor(out, r.linesRendered, 0)
|
moveCursor(out, r.linesRendered, 0)
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue