tuiffects

package module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 14 Imported by: 0

README

tuiffects

Terminal text effects as a Go library. Feed it text or a captured cell grid, pick an effect, and pull frames off it one at a time.

Credit where it is due

This is a port of a port, and it originates none of the art.

TerminalTextEffects by ChrisBuilds is the original. Every effect in this package, and the architecture of the engine that runs them, are that project's design. ttfx by omacom-io translated that work to Rust and says the same thing about itself. This package translates ttfx to Go.

All three are MIT. All three copyrights are preserved in LICENSE, and NOTICE maps every file here to both the ttfx source and the TerminalTextEffects source it came from.

One effect, tuffbaby, is the exception: it is original to this package and has no upstream. It is written against ChrisBuilds' engine like every other effect here. It is not in NOTICE, because NOTICE records what was translated and from where; it declares where its material came from in its own Descriptor.Origin instead. catalogue_test.go holds every effect to one or the other and refuses both, so an original cannot quietly claim an upstream and a port cannot quietly lose one.

If you like what you see, star the original. Effect ideas belong upstream, where they were invented.

Using it

import "github.com/Gaurav-Gosain/tuiffects"
terminal := tuiffects.NewTerminalFromText("hello", tuiffects.TerminalConfig{
    Width: 80, Height: 24,
})
engine := tuiffects.NewEngine(terminal, tuiffects.NewRng(1))

effect := tuiffects.NewDecrypt(tuiffects.DefaultDecryptConfig())
if err := effect.Build(engine); err != nil {
    return err
}
for effect.Advance(engine) {
    fmt.Print("\x1b[H", engine.Frame())
}

Advance does not return the frame. Read it with engine.Frame() for an ANSI string, or engine.FrameRows() for rows of visuals you can style yourself. That is the one place this port deliberately differs in shape from ttfx, which writes to a tty it owns. It is what lets this drive a pane, a widget or a screen saver rather than a terminal.

To animate a screen rather than a string, hand it a cell grid:

terminal := tuiffects.NewTerminalFromCells(cells, tuiffects.TerminalConfig{
    Width:                 cols,
    Height:                rows,
    ExistingColorHandling: tuiffects.DynamicExistingColors,
})

DynamicExistingColors makes every character resolve back to the colour it arrived with, so the screen reassembles as itself rather than in the effect's own palette. It is the mode to use when the input is a picture that was already on the screen.

What is here

The engine, in the shape ttfx found it:

Piece What it is
Coord, geometry.go 1-based grid coordinates, origin bottom left, lines and bezier curves
Color, Gradient colour ramps and the coordinate mappings effects paint across the canvas
Easing the thirty-one standard curves
Waypoint, Path, Motion where a character goes and how fast
Frame, Scene, Animation what a character looks like over time
Event, Action how a scene or path hands off to the next one
Character one cell: its animation, its motion, its handlers
Canvas, Terminal the grid, the character populations, and the frame painter
ParticlePool recycles the short-lived characters an effect throws off
PrimsSimple and the other spanning trees join the canvas into a tree, and give an effect its running order
Clock seconds, for the effects written in seconds rather than frames
Engine the stepping loop that ties all of it together

Thirty-five effects, every one ttfx ships except beams and colorshift:

Effect What it does What it shows
binarypath every character breaks into the binary digits of its code point, which travel the canvas and collapse back into it added characters, right-angled paths, a group released one member a frame, and a two-phase run ending in a diagonal wipe
blackhole the text scatters into a starfield, a ring of stars eats it, then the singularity explodes and it drifts home five phases, looping ring paths, distance-synced scenes, layers, and a colour ramp on both foreground and background
bouncyballs balls fall in from above the screen and bounce into place motion that starts off the canvas, a non-monotonic easing on a path, and a row-by-row release from the bottom up
bubbles groups of characters ride a bubble down the screen, which pops and drops them into place an added anchor character stepped by hand, a rigid ring redrawn around it each frame, and paths chained through a burst
burn fire spreads through the text and each character cools into its colour a spanning tree as the running order, a recycled particle pool for the smoke, and a background carried through a dynamic run
crumble the text dims, falls to the floor as dust, is vacuumed out the top, then flies home and re-forms four stages over one shared set of paths and scenes, a distance-synced dust animation, and a layer change mid-fall
decrypt types out ciphertext, then decrypts it per-character scenes and scene-to-scene chaining, no motion at all
errorcorrect some characters start in each other's places and swap back a scene handing off to a path and back, layer changes while a character is in flight, and a queue released on a delay
expand the text starts piled on one cell and grows out of it eased paths out of a single point, distance-synced colour ramps, and layer swaps in flight
fireworks characters climb as shells, burst apart, and fall into place three chained paths per character, bezier arcs, a looping scene and a step-synced one
highlight a band of light sweeps over the text an eased sequence releasing character groups, brightness-derived gradients, no motion
laseretch a laser beam cuts the text on, one character at a time a spanning tree for the etch order, a particle pool for the sparks
matrix green rain falls down the screen and the text resolves out of it the engine clock, columns cut from the whole canvas including the fill, and drawing by appearance instead of scenes
middleout text collapses onto the centre, spreads along one axis, then expands out two paths per character run in phases the effect drives itself, and a colour ramp that opens on a fixed starting colour
orbittingvolley four launchers circle the canvas and fire the text into place one moving character driving three others' positions, layered paths, and a per-frame launch queue
overflow rows of the text scroll up past the screen out of order, then the real picture scrolls in from the bottom copies of the input as extra characters, whole-canvas row groups, and a scroll that only lands correctly with fill characters
pour characters pour in from one edge and fill the canvas from the near side first row and column groups released in alternating order, one path and one colour ramp per character
print types the canvas out one line at a time on the bottom row and scrolls the page up under it a character of its own as the print head, a path rebuilt per line, row groups over the whole canvas, and one scene per cell
rain characters fall in and settle paths, easing, and a path completion handing off to an animation
randomsequence fades the text back in one character at a time, in a random order a shuffled reveal order, a per-character colour ramp with no motion, paths or events at all
rings text gathers into spinning rings, scatters, and goes home many chained looping paths per character, phase timers, and rings that turn opposite ways
scattered characters start in random places and gather into the text paths from random start coordinates, a distance-synced colour ramp, and layer swaps in flight
slice the picture is cut in two and the halves slide back in from opposite edges eased paths over the fill characters as well as the input, and two halves shearing past each other
slide rows, columns or diagonals push in from off screen groups released on a gap timer, one character per group per frame, each on its own eased path
smoke smoke seeps out from one cell and colours the text as it passes a weighted spanning tree, a breadth-first walk of it one layer per frame, scene-to-scene handover
spotlights beams of light search the screen, meet in the middle, then widen until everything is lit direct appearance changes with no scenes at all, chained looping paths, and a distance-based falloff
spray characters shoot out of one point on the edge and fly into place per-character path speed, a layer lifted for the flight and dropped on arrival, and a burst release sized by the character count
swarm groups of characters fly between gathering points, then land grouped characters, chained paths, and one group member pulling the rest along
sweep two bands cross the canvas, the first uncovering the characters in grey and the second colouring them one eased sequence run twice over different groupings, and fill characters so the whole canvas shimmers
synthgrid a grid draws itself across the screen, fills its blocks in a few at a time, then takes itself back down added characters on a layer above the text, fill characters, and phases driven by a per-block completion count
thunderstorm the text dims, rain crosses it, and lightning strikes and leaves it glowing two particle pools, the seconds clock, and characters the effect adds to the terminal itself
unstable the screen scrambles, shakes itself apart, and flies back together whole-screen coordinate shoves, two eased flights, and a three-phase run
vhstape rows slip and the picture is redrawn paths driving synced scenes, row groups, and several phases
waves a band of blocks sweeps across eased scenes released in bands, a sweep with no motion at all
wipe a line crosses the screen and the text appears behind it an easing curve deciding which character groups are released, and taking them back when it reverses

And one that is not a port:

Effect What it does What it shows
tuffbaby the text on screen gathers into a picture, a short clip plays in it a tone at a time, and everything goes home a deflated frame sequence decoded once and scaled to the canvas, characters appended when the screen has too few and swept off the nearest edge when it has too many, and animation by repaint rather than by motion

tuffbaby is the one effect here nobody upstream wrote; see Credit where it is due and its Origin, which names where its frames came from. It takes whatever is already on the screen and arranges it into the picture, so what the picture is drawn out of is your own text. The cells are the union of every frame of the clip, which works out at a bit over half the canvas at any size: a denser screen parks its surplus off the edges, a sparser one has the rest appended, recycling the glyphs that were there.

It is the one effect that carries data: the frames are 18KB of deflated base64 in tuffbaby_frames.go, decoded once on first use, which is about half the size of the largest hand-written effect here. And it is a continuous-tone photograph rendered in text, so it is softer than the shapes the other effects draw: a glyph fills about a third of its cell and how much varies per glyph, which is louder than a five step ramp. The light end is drawn bold to claw some of that back. It reads best on a wide canvas.

Adding an effect

Write one file. Implement Build (set up scenes and paths on every character) and Advance (release a few characters, call engine.Update(), say whether you are done), and call Register from an init. The ones here are 160 to 1000 lines each and the engine does the rest.

PORTING.md is the full guide for bringing one across from ttfx: the call-for-call mapping, the quirks that are wrong on purpose, what the colour policy does, and what a finished port has to include.

Differences from ttfx

  • No parity with the Python original, and no Mersenne Twister clone. The same effect will not produce the same frames as either upstream. NewRng(seed) makes a run reproducible within this package, which is what the tests need.
  • Time is virtual by default. The engine's clock advances one frame's worth per Update rather than reading the machine, so an effect written in seconds runs to the same number of frames every time. Set Engine.Clock to the rate the host really paints at: NewEngine assumes sixty, and every effect written in seconds runs at the wrong speed on a host that paints at anything else. NewRealClock is there for a host that would rather have wall time.
  • No command line, no tty writer, no resize handling. The host owns the screen.
  • Thirty-five effects rather than thirty-seven. beams and colorshift are not ported.
  • Rounding quirks that change how effects look are kept: half-to-even rounding on coordinates, floor division on gradient channel steps, and the bezier arc-length estimate that stops at t=0.9. Removing them would retune every effect by a little, silently.
  • Several effects behave differently under DynamicExistingColors, because upstream is written for piped text and that mode means the input was already on the screen. Backgrounds a captured cell carried survive the run rather than blinking out; anything an effect throws across the screen carries the background of the cell it is over rather than punching a hole through it; a ramp that closes on a background starts from that background rather than flushing the bar white first; and an effect whose subject is a colour change is given a neutral foreground to work with on a cell that arrived with none. Each is commented where it is made, and the default behaviour is unchanged.

Licence

MIT. See LICENSE and NOTICE.

Documentation

Overview

Package tuiffects animates terminal text.

It is a Go port of ttfx, which is itself a port of TerminalTextEffects by ChrisBuilds. Every effect here, and the architecture of the engine that runs them, are that project's design; this package originates none of the art. See NOTICE for the full chain and LICENSE for the three copyrights it carries.

Index

Constants

This section is empty.

Variables

View Source
var DynamicNeutralGrey = MustParseColor("808080")

DynamicNeutralGrey stands in for a character that arrived with no foreground of its own, in the effects that need one to work with under DynamicExistingColors. A captured screen is mostly cells with no explicit foreground, and an effect whose whole subject is a colour change has nothing to change on them: the band passes over and nothing happens.

It is a deviation from ttfx, which animates piped text where every character is given a gradient colour and this case never arises. Every use of it is scoped to DynamicExistingColors, so the default behaviour stays upstream's.

View Source
var ErrNoParticleSymbols = errors.New("tuiffects: a particle pool needs at least one symbol")

ErrNoParticleSymbols is returned when a pool is built with nothing to draw.

View Source
var ErrNoStartingCharacter = errors.New("tuiffects: no character at the spanning tree's starting coordinate")

ErrNoStartingCharacter is returned when a generator picks a random starting coordinate and finds no character on it. It nearly always means the terminal was built without fill characters.

View Source
var ErrParticleCountAboveMax = errors.New("tuiffects: cannot preallocate more particles than the pool's maximum")

ErrParticleCountAboveMax is returned when Preallocate is asked for more particles than MaxSize allows.

Functions

func Choice

func Choice[T any](g *Rng, items []T) *T

Choice picks one element. It returns nil for an empty slice, so callers that can be handed an empty option list must check.

func FindLengthOfBezierCurve

func FindLengthOfBezierCurve(start Coord, control []Coord, end Coord) float64

FindLengthOfBezierCurve estimates arc length from a 10-sample polyline.

The walk stops at t=0.9 and never adds the last tenth of the curve, so every length comes out short. That is upstream behaviour. It is kept because path speed divides by this number, and correcting it would quietly make every curved path in every effect finish sooner than its author tuned it to.

func FindLengthOfLine

func FindLengthOfLine(a, b Coord, doubleRowDiff bool) float64

FindLengthOfLine returns the distance between two coords. Terminal cells are about twice as tall as they are wide, so callers that want visually even motion pass doubleRowDiff.

func FindNormalizedDistanceFromCenter

func FindNormalizedDistanceFromCenter(bottom, top, left, right int, other Coord) (float64, bool)

FindNormalizedDistanceFromCenter reports how far a coord sits from the centre of a rectangle, from 0 at the centre to 1 at a corner. It returns false when the coord is outside the rectangle.

func LinkCharacters added in v0.3.0

func LinkCharacters(a, b *Character)

LinkCharacters joins two characters in both directions.

Each side's Links stays sorted by character id, and linking a pair twice does nothing the second time. Upstream keeps a Python set here; the sort is what makes traversals of it repeatable.

Upstream and ttfx both take the engine as an argument because their characters live in an arena. Here a character is a pointer, so it does not.

func Names

func Names() []string

Names lists every registered effect, sorted.

func Register

func Register(d Descriptor)

Register adds an effect to the registry. It is meant to be called from an init function, and it panics on a duplicate name because that is a programming mistake rather than a runtime condition.

func Run

func Run(effect Effect, e *Engine, maxFrames int) ([]string, error)

Run builds an effect and returns every frame it produces, capped at maxFrames. It exists for tests and for a caller that wants the whole animation up front rather than one frame per host frame.

func Shuffle

func Shuffle[T any](g *Rng, items []T)

Shuffle reorders a slice in place.

Types

type Action

type Action struct {
	// contains filtered or unexported fields
}

Action is what happens when an event fires.

func ActivatePath

func ActivatePath(id string) Action

ActivatePath starts a path when the event fires.

func ActivateScene

func ActivateScene(id string) Action

ActivateScene starts a scene when the event fires.

func Callback

func Callback(fn func(e *Engine, ch *Character)) Action

Callback runs effect code. Go closures let the effect keep its own state directly, so this port has no callback id table.

func DeactivatePath

func DeactivatePath(id string) Action

DeactivatePath stops a path. An empty id stops whatever is running.

func DeactivateScene

func DeactivateScene(id string) Action

DeactivateScene stops a scene. An empty id stops whatever is running.

func ResetAppearance

func ResetAppearance() Action

ResetAppearance puts the character back to its input symbol and colours.

func SetCoordinate

func SetCoordinate(coord Coord) Action

SetCoordinate teleports the character.

func SetLayer

func SetLayer(layer int) Action

SetLayer moves the character to a drawing layer. Higher layers win where two characters share a cell.

type Anchor

type Anchor int

Anchor is one of the nine compass points a block of text can be pinned to inside the canvas.

const (
	AnchorSW Anchor = iota
	AnchorS
	AnchorSE
	AnchorW
	AnchorC
	AnchorE
	AnchorNW
	AnchorN
	AnchorNE
)

The nine anchors.

type Animation

type Animation struct {
	ExistingColorHandling ExistingColorHandling
	InputColors           ColorPair
	InputBold             bool
	// contains filtered or unexported fields
}

Animation is one character's animation state: its scenes, which one is active, and the visual it currently shows.

func (*Animation) ActiveSceneIsComplete

func (a *Animation) ActiveSceneIsComplete() bool

ActiveSceneIsComplete reports whether the active scene has run out of frames. A looping scene always reads as complete, which is upstream's behaviour and the reason a loop-only character counts as inactive.

func (*Animation) ClearScenes added in v0.3.0

func (a *Animation) ClearScenes()

ClearScenes removes every scene from the character and stops whatever was running. As with Motion.ClearPaths, the active reference goes too.

func (*Animation) CurrentVisual

func (a *Animation) CurrentVisual() *CharacterVisual

CurrentVisual is what the renderer draws for this character right now.

func (*Animation) NewScene

func (a *Animation) NewScene(id string, opts SceneOptions) *Scene

NewScene registers a scene. An empty id gets an auto-allocated one, which is returned. A duplicate id replaces the old scene, as it does upstream.

func (*Animation) Scene

func (a *Animation) Scene(id string) *Scene

Scene looks a scene up by id, returning nil when it is absent.

func (*Animation) SetAppearance

func (a *Animation) SetAppearance(symbol string, colors ColorPair, usesInputColors bool)

SetAppearance overrides the current visual outside of any scene.

type BinaryPath added in v0.4.0

type BinaryPath struct {
	// contains filtered or unexported fields
}

BinaryPath breaks every character into the binary digits of its code point, sends those digits on a right-angled path around the canvas, and collapses them back into the character when they arrive. A diagonal wipe then brightens the rebuilt text.

This effect assembles the screen rather than passing over it, so every character stays hidden until its own digits have arrived. That holds under every colour policy, including DynamicExistingColors: showing the picture up front would leave the digits nothing to rebuild.

func NewBinaryPath added in v0.4.0

func NewBinaryPath(config BinaryPathConfig) *BinaryPath

NewBinaryPath builds the effect.

func (*BinaryPath) Advance added in v0.4.0

func (b *BinaryPath) Advance(e *Engine) bool

Advance releases another digit from each travelling group, collapses the groups that have arrived, and runs one frame. It reports whether the effect is still going.

func (*BinaryPath) Build added in v0.4.0

func (b *BinaryPath) Build(e *Engine) error

Build resolves every character's final colour, makes the digits that carry it and the path each digit follows, and gives the character the two scenes it wears once the digits arrive.

type BinaryPathConfig added in v0.4.0

type BinaryPathConfig struct {
	// FinalGradientStops colour the text once it has been rebuilt. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
	// BinaryColors are the colours the travelling digits wear. Each digit
	// picks one at random.
	BinaryColors []Color
	// MovementSpeed is how fast a digit travels along its path.
	MovementSpeed float64
	// ActiveBinaryGroups is how many groups of digits may travel at once, as
	// a fraction of the total number of groups. Lower it to do less work per
	// frame.
	ActiveBinaryGroups float64
}

BinaryPathConfig tunes the binarypath effect.

func DefaultBinaryPathConfig added in v0.4.0

func DefaultBinaryPathConfig() BinaryPathConfig

DefaultBinaryPathConfig is upstream's default binarypath.

type Blackhole added in v0.4.0

type Blackhole struct {
	// contains filtered or unexported fields
}

Blackhole turns the text into a starfield, forms a rotating ring of stars, pulls every other character into the middle of it, collapses the ring onto the same point, and then explodes the point and lets the characters drift home.

This effect assembles the picture rather than passing over it. The screen it was handed is torn apart in the first frame and only exists again at the end, so there is no DynamicExistingColors deviation for visibility here: upstream already shows every character from frame one, because at that point every character is a star somewhere in the starfield rather than a piece of the picture. Revealing the picture in place up front would show the thing the effect is about to destroy.

func NewBlackhole added in v0.4.0

func NewBlackhole(config BlackholeConfig) *Blackhole

NewBlackhole builds the effect.

func (*Blackhole) Advance added in v0.4.0

func (b *Blackhole) Advance(e *Engine) bool

Advance moves the effect on by one frame and reports whether it is still going.

func (*Blackhole) Build added in v0.4.0

func (b *Blackhole) Build(e *Engine) error

Build measures the ring, works out where every character settles, and lays out the starfield.

func (*Blackhole) Err added in v0.4.0

func (b *Blackhole) Err() error

Err reports a failure raised after Build returned. The collapse and the explosion are built while the effect runs, as they are upstream, so their errors cannot be handed back through Build. Advance stops the effect when one happens and leaves it here.

Nothing in either stage can fail with the arguments they are given, so this stays nil in practice. It exists so a failure is reported rather than swallowed.

type BlackholeConfig added in v0.4.0

type BlackholeConfig struct {
	// BlackholeColor is the colour of the stars that make up the ring.
	BlackholeColor Color
	// StarColors colour the singularity while it is unstable, in the moment
	// between the ring collapsing and the explosion. They do not colour the
	// explosion itself; see explodeSingularity.
	StarColors []Color
	// FinalGradientStops colour the text once it is back in place. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

BlackholeConfig tunes the blackhole effect.

func DefaultBlackholeConfig added in v0.4.0

func DefaultBlackholeConfig() BlackholeConfig

DefaultBlackholeConfig is upstream's default blackhole.

type BouncyBalls added in v0.4.0

type BouncyBalls struct {
	// contains filtered or unexported fields
}

BouncyBalls drops every character in as a ball from somewhere above the canvas, bounces it where the character belongs, and fades the ball into the character once it has come to rest.

It assembles the screen rather than passing over it, so a character stays hidden until its ball is dropped. That holds under every colour policy, including DynamicExistingColors: a character shown before its ball landed would be the picture arriving without the animation that puts it there.

func NewBouncyBalls added in v0.4.0

func NewBouncyBalls(config BouncyBallsConfig) *BouncyBalls

NewBouncyBalls builds the effect.

func (*BouncyBalls) Advance added in v0.4.0

func (b *BouncyBalls) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*BouncyBalls) Build added in v0.4.0

func (b *BouncyBalls) Build(e *Engine) error

Build gives every character a ball, a fall path, and a landing fade.

type BouncyBallsConfig added in v0.4.0

type BouncyBallsConfig struct {
	// BallColors are picked at random, one per ball.
	BallColors []Color
	// BallSymbols are the glyphs a ball can wear on the way down.
	BallSymbols []string
	// BallDelay is how many frames pass between one group of balls being
	// dropped and the next. Raise it to drop fewer balls at once.
	BallDelay int
	// MovementSpeed is how fast a ball falls.
	MovementSpeed float64
	// MovementEasing shapes the fall. The default bounces on landing, which
	// is what the effect is named for.
	MovementEasing Easing
	// FinalGradientStops colour the text once a ball has landed. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

BouncyBallsConfig tunes the bouncyballs effect.

func DefaultBouncyBallsConfig added in v0.4.0

func DefaultBouncyBallsConfig() BouncyBallsConfig

DefaultBouncyBallsConfig is upstream's default bouncyballs.

type BreadthFirst added in v0.3.0

type BreadthFirst struct {
	// StartingChar is where the sweep began.
	StartingChar *Character
	// ExploredLastStep are the characters this Step reached. This is the
	// layer, and it is what an effect animates.
	ExploredLastStep []*Character
	// CharExploreOrder is every character reached so far, in order. It does
	// not include the starting character.
	CharExploreOrder []*Character
	// Complete says the sweep ran out of frontier.
	Complete bool
	// contains filtered or unexported fields
}

BreadthFirst walks a tree that one of the generators above has already built, one whole layer per Step. It builds nothing itself and draws no random numbers, so it is what an effect uses to sweep outwards from a point along links that are already there.

func NewBreadthFirst added in v0.3.0

func NewBreadthFirst(e *Engine, startingChar *Character, limitToTextBoundary bool) (*BreadthFirst, error)

NewBreadthFirst starts a sweep. A nil startingChar picks a random coordinate. The links it follows must already exist: run one of the tree generators to completion first.

func (*BreadthFirst) Step added in v0.3.0

func (b *BreadthFirst) Step()

Step drains the whole frontier and makes everything it reached the next one. One Step is one layer, however wide that layer is.

type Bubbles added in v0.4.0

type Bubbles struct {
	// contains filtered or unexported fields
}

Bubbles gathers the input into groups of five to twenty characters, hangs each group on the rim of a bubble above the canvas, and drifts the bubbles down one at a time. A bubble bursts when it reaches its row, throws its characters outwards, and each one then flies to where it belongs and ramps from the burst colour into its final colour.

It assembles the screen rather than passing over it. A character is hidden until the bubble carrying it is released, and that holds under every colour policy including DynamicExistingColors: the characters are the bubbles, so showing the finished picture from the first frame would deliver the screen before a single bubble had been let go.

func NewBubbles added in v0.4.0

func NewBubbles(config BubblesConfig) *Bubbles

NewBubbles builds the effect.

func (*Bubbles) Advance added in v0.4.0

func (b *Bubbles) Advance(e *Engine) bool

Advance releases a bubble when its delay is up, bursts the ones that have landed, drifts the rest, and runs one frame. It reports whether the effect is still going.

func (*Bubbles) Build added in v0.4.0

func (b *Bubbles) Build(e *Engine) error

Build gives every character its burst, its settle ramp and its flight home, then packs the characters into bubbles.

type BubblesConfig added in v0.4.0

type BubblesConfig struct {
	// Rainbow makes each bubble cycle a rainbow instead of wearing one
	// colour. BubbleColors is ignored when it is set.
	Rainbow bool
	// BubbleColors are picked at random, one per bubble.
	BubbleColors []Color
	// PopColor is the colour of the burst, and the colour every character
	// ramps away from as it settles.
	PopColor Color
	// BubbleSpeed is how fast a bubble drifts down the canvas.
	BubbleSpeed float64
	// BubbleDelay is how many frames pass between one bubble being released
	// and the next.
	BubbleDelay int
	// PopCondition decides when a bubble bursts.
	PopCondition PopCondition
	// MovementEasing is upstream's flag of the same name. Neither ttfx nor
	// the Python it came from reads it: the burst uses out_expo and the drop
	// into place uses in_out_expo, both fixed. It is kept so the config still
	// matches upstream's command line, and setting it does nothing.
	MovementEasing Easing
	// FinalGradientStops colour the text once it has settled. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

BubblesConfig tunes the bubbles effect.

func DefaultBubblesConfig added in v0.4.0

func DefaultBubblesConfig() BubblesConfig

DefaultBubblesConfig is upstream's default bubbles.

type Burn added in v0.4.0

type Burn struct {
	// contains filtered or unexported fields
}

Burn sets the text alight. A spanning tree grows outwards from a random cell and the fire follows it, two to four characters a frame. A character that has finished burning cools into its final colour and may give off a smoke particle that drifts up off the top of the canvas.

This effect passes over the screen rather than assembling it. Every character is visible and wearing StartingColor from the first frame, in every colour mode, because burn's subject is paper that is already there. So there is no reveal to defer and no waves-style pre-show to add under DynamicExistingColors: the deviation that mode needs here is about backgrounds, not about visibility. See Build.

func NewBurn added in v0.4.0

func NewBurn(config BurnConfig) *Burn

NewBurn builds the effect.

func (*Burn) Advance added in v0.4.0

func (b *Burn) Advance(e *Engine) bool

Advance lights the next two to four characters the tree reached and runs one frame. The effect is over once the tree's order has run out and the last character and the last of the smoke have finished.

func (*Burn) Build added in v0.4.0

func (b *Burn) Build(e *Engine) error

Build lights the tree, stocks the smoke pool, and gives every character the two scenes it needs: the fire that crosses it and the cooling that follows.

Deviation, scoped to DynamicExistingColors: a character's background is carried through both scenes and never ramped. Upstream sets a foreground alone while a character burns, then ramps the background from the last fire colour to the input background at the end. On piped text there is no background to lose. On a captured screen that blanks every selection bar and filled panel for the length of the run and then flushes them dark red on the way back, so here the background a cell arrived with simply stays put and the fire plays over it. The other two colour modes are untouched.

type BurnConfig added in v0.4.0

type BurnConfig struct {
	// StartingColor is the unburnt paper: every character wears it from the
	// first frame until the fire reaches it.
	StartingColor Color
	// BurnColors are the colours a character passes through while it burns.
	BurnColors []Color
	// SmokeChance is how often a character that has finished burning throws
	// off a smoke particle. Zero means no smoke.
	SmokeChance float64
	// FinalGradientStops colour the text once the fire has passed. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

BurnConfig tunes the burn effect.

func DefaultBurnConfig added in v0.4.0

func DefaultBurnConfig() BurnConfig

DefaultBurnConfig is upstream's default burn.

type Caller

type Caller struct {
	// contains filtered or unexported fields
}

Caller identifies what raised an event. Upstream hashes a whole Waypoint dataclass, so two waypoints with the same id and coord collide even across paths; the coord is kept in the key to match that.

func PathCaller

func PathCaller(id string) Caller

PathCaller keys handlers to a path finishing, starting or holding.

func SceneCaller

func SceneCaller(id string) Caller

SceneCaller keys handlers to a scene finishing or starting.

func WaypointCaller

func WaypointCaller(w *Waypoint) Caller

WaypointCaller keys handlers to a segment ending at a waypoint.

type Canvas

type Canvas struct {
	Top    int
	Right  int
	Bottom int
	Left   int

	CenterRow    int
	CenterColumn int
	Center       Coord

	Width  int
	Height int

	TextTop    int
	TextRight  int
	TextBottom int
	TextLeft   int

	TextWidth  int
	TextHeight int

	TextCenterRow    int
	TextCenterColumn int
	TextCenter       Coord
}

Canvas is the drawable rectangle, in 1-based coordinates with the origin at the bottom left. It also tracks the smaller rectangle the input text actually occupies, which is what gradients are painted across.

func NewCanvas

func NewCanvas(width, height int) *Canvas

NewCanvas builds a canvas of the given size, anchored at (1,1).

func (*Canvas) CoordIsInCanvas

func (c *Canvas) CoordIsInCanvas(coord Coord) bool

CoordIsInCanvas reports whether a coordinate falls inside the canvas.

func (*Canvas) CoordIsInText

func (c *Canvas) CoordIsInText(coord Coord) bool

CoordIsInText reports whether a coordinate falls inside the text block.

func (*Canvas) RandomColumn

func (c *Canvas) RandomColumn(rng *Rng, withinText bool) int

RandomColumn picks a column, either from the whole canvas or from the text block only.

func (*Canvas) RandomCoord

func (c *Canvas) RandomCoord(rng *Rng, outsideScope, withinText bool) Coord

RandomCoord picks a coordinate. With outsideScope set it picks one cell past a randomly chosen edge, which is where effects launch characters from.

func (*Canvas) RandomRow

func (c *Canvas) RandomRow(rng *Rng, withinText bool) int

RandomRow picks a row, either from the whole canvas or from the text block only.

type Character

type Character struct {
	// ID is allocation order. It is the canonical ordering key everywhere,
	// because effects that walk characters must walk them the same way twice.
	ID int

	InputSymbol string
	InputCoord  Coord

	IsVisible bool
	Layer     int

	Animation Animation
	Motion    Motion

	// UsesInputColors marks a character that carried its own colours in, which
	// the input-colour policies key off.
	UsesInputColors bool
	// IsFill marks a character the engine invented to pad the canvas rather
	// than one that came from the input.
	IsFill bool

	// Links are the characters this one has been joined to by a spanning
	// tree, kept in ascending id order. Upstream holds a Python set here and
	// several effects walk it, so the order is pinned rather than left to
	// whatever a set happens to yield. LinkCharacters is the only thing that
	// writes it.
	Links []*Character
	// contains filtered or unexported fields
}

Character is one cell of the input, with everything needed to animate it: where it came from, where it is now, what it looks like, and what it does when its motion or animation reaches a milestone.

func (*Character) ClearEvents added in v0.3.0

func (c *Character) ClearEvents()

ClearEvents drops every action registered on this character. A particle pool calls it when a reused particle must not still be carrying the handlers its previous life registered.

func (*Character) IsActive

func (c *Character) IsActive() bool

IsActive reports whether the character still has work to do. Upstream counts a looping scene as complete, so a character that only loops reads as inactive; that quirk is kept because effects rely on it to decide when they have finished.

func (*Character) RegisterEvent

func (c *Character) RegisterEvent(event Event, from Caller, action Action)

RegisterEvent hangs an action off an event raised by a scene, path or waypoint on this character.

type CharacterFilter

type CharacterFilter struct {
	Input     bool
	InnerFill bool
	OuterFill bool
	Added     bool
}

CharacterFilter selects which populations a character query returns.

func InputOnly

func InputOnly() CharacterFilter

InputOnly is the default filter: the characters that came from the input.

type CharacterGroup

type CharacterGroup int

CharacterGroup buckets a character query into ordered groups.

const (
	GroupColumnLeftToRight CharacterGroup = iota
	GroupColumnRightToLeft
	GroupRowTopToBottom
	GroupRowBottomToTop
	GroupDiagonalBottomLeftToTopRight
	GroupDiagonalTopRightToBottomLeft
	GroupDiagonalTopLeftToBottomRight
	GroupDiagonalBottomRightToTopLeft
	GroupCenterToOutside
	GroupOutsideToCenter
)

The character groupings.

type CharacterSort

type CharacterSort int

CharacterSort orders a character query.

const (
	SortTopToBottomLeftToRight CharacterSort = iota
	SortTopToBottomRightToLeft
	SortBottomToTopLeftToRight
	SortBottomToTopRightToLeft
	SortOutsideRowToMiddle
	SortMiddleRowToOutside
	SortRandom
)

The character sorts.

type CharacterVisual

type CharacterVisual struct {
	Symbol    string
	Bold      bool
	Italic    bool
	Underline bool
	Colors    ColorPair
	// contains filtered or unexported fields
}

CharacterVisual is one character's appearance for one frame, with its SGR string precomputed because the renderer emits it per visible cell per frame.

func NewCharacterVisual

func NewCharacterVisual(symbol string, p VisualParams) *CharacterVisual

NewCharacterVisual builds a visual and formats its SGR string.

func PlainVisual

func PlainVisual(symbol string) *CharacterVisual

PlainVisual is the character with no styling at all.

func (*CharacterVisual) Formatted

func (v *CharacterVisual) Formatted() string

Formatted returns the symbol wrapped in its SGR sequences.

type Clock added in v0.3.0

type Clock struct {
	// contains filtered or unexported fields
}

Clock is the engine's source of time, in seconds.

A handful of effects are written against the wall clock rather than against a frame count: matrix runs its rain for a number of seconds, thunderstorm runs its storm for a number of seconds. Everything else in this engine is reproducible from a seed, and a real clock would take that away from those two: the same seed would give a different number of frames on a fast machine than on a slow one, and a test could not pin either.

So the default is a virtual clock. It advances a fixed step once per Engine.Update, which every effect calls exactly once per frame, and it therefore reports the time the animation would have taken if the host had kept up. A seeded run stays reproducible and "run the rain for eight seconds" still means eight seconds of animation.

The step is 1/frameRate. Set it to the rate the host actually paints at, or a two-second effect will not last two seconds on the screen.

A host that would rather have real elapsed time, and does not mind that the two clock-driven effects stop being reproducible, can put a real clock on the engine instead. Nothing else in the package reads the clock.

func NewRealClock added in v0.3.0

func NewRealClock() *Clock

NewRealClock builds a clock that reads the machine. An effect driven by it is no longer reproducible from its seed, so use it only when the host wants wall time more than it wants a repeatable run.

func NewVirtualClock added in v0.3.0

func NewVirtualClock(frameRate int) *Clock

NewVirtualClock builds the default clock: time advances step by step with the frames, not with the machine. A frame rate at or below zero means sixty.

func (*Clock) AdvanceFrame added in v0.3.0

func (c *Clock) AdvanceFrame()

AdvanceFrame moves a virtual clock on by one frame and does nothing to a real one. Engine.Update calls it, so an effect never has to; calling it as well would run the clock at twice the frame rate.

func (*Clock) Elapsed added in v0.3.0

func (c *Clock) Elapsed() float64

Elapsed is seconds since the run started, upstream's time.monotonic().

func (*Clock) Wall added in v0.3.0

func (c *Clock) Wall() float64

Wall is seconds since the Unix epoch, upstream's time.time(). The virtual clock has no epoch, so it reports seconds since the run started, the same number Elapsed gives. Effects only ever subtract two readings of it, so the origin does not matter to them.

type Color

type Color struct {
	R uint8
	G uint8
	B uint8
}

Color is a 24-bit RGB colour. Upstream also carries the original constructor argument so that Color(255) and Color("ffffff") hash apart; that only mattered for Python dict keying during parity checks, so it is dropped here.

func AdjustColorBrightness added in v0.2.0

func AdjustColorBrightness(color Color, brightness float64) Color

AdjustColorBrightness scales a colour's lightness, keeping its hue and saturation. A factor below 1 darkens and above 1 brightens.

It is a hand-rolled RGB to HSL round trip rather than a library conversion, because that is what upstream does and the results differ in the last unit of each channel. Effects that fade a character and brighten it back are tuned to these numbers. Note this rounds half to even at the end, unlike ShiftColorTowards which truncates.

func MustParseColor

func MustParseColor(hex string) Color

MustParseColor is ParseColor for compile-time constants. It panics on a bad string, so only pass literals.

func ParseColor

func ParseColor(hex string) (Color, error)

ParseColor reads a colour from a hex string, with or without a leading hash.

func RGB

func RGB(r, g, b uint8) Color

RGB builds a Color from channel values.

func ShiftColorTowards

func ShiftColorTowards(color, target Color, factor float64) Color

ShiftColorTowards interpolates between two colours.

Upstream truncates back to an integer channel rather than rounding, so a shift lands one value low most of the time. Kept for the same reason as the gradient floor division.

func (Color) Hex

func (c Color) Hex() string

Hex renders the colour as six lowercase hex digits, with no leading hash.

type ColorPair

type ColorPair struct {
	Fg    Color
	HasFg bool
	Bg    Color
	HasBg bool
}

ColorPair holds an optional foreground and an optional background.

func Bg

func Bg(c Color) ColorPair

Bg builds a background-only pair.

func Fg

func Fg(c Color) ColorPair

Fg builds a foreground-only pair.

func FgBg

func FgBg(fg, bg Color) ColorPair

FgBg builds a pair with both channels set.

type Coord

type Coord struct {
	Column int
	Row    int
}

Coord is a 1-based canvas coordinate. Column grows right and row grows up, so the origin sits at the bottom left, as it does upstream.

func C

func C(column, row int) Coord

C builds a Coord. It exists because effect code writes thousands of them.

func ExtrapolateAlongRay added in v0.2.0

func ExtrapolateAlongRay(origin, target Coord, offsetFromTarget float64) Coord

ExtrapolateAlongRay returns the coordinate reached by carrying on past target, along the line from origin, by offsetFromTarget cells.

The line length here is the raw one, with no row doubling, unlike most of this file. That is upstream's choice and effects are tuned to it.

func FindCoordOnBezierCurve

func FindCoordOnBezierCurve(start Coord, control []Coord, end Coord, t float64) Coord

FindCoordOnBezierCurve runs De Casteljau over any number of control points.

func FindCoordOnLine

func FindCoordOnLine(start, end Coord, t float64) Coord

FindCoordOnLine interpolates between two coords and rounds the result.

func FindCoordsInCircle added in v0.2.0

func FindCoordsInCircle(center Coord, diameter int) []Coord

FindCoordsInCircle returns every coordinate inside an ellipse centred on the given coord.

Upstream calls this a circle and builds an ellipse: the horizontal semi-axis is the diameter and the vertical one is half of it. That is the terminal cell aspect correction done in the shape rather than in the distance, and effects are tuned to the shape it actually produces.

func FindCoordsInRect added in v0.2.0

func FindCoordsInRect(origin Coord, distance int) []Coord

FindCoordsInRect returns every coordinate in the square block reaching distance cells out from the origin. A distance of zero returns nothing.

func FindCoordsOnCircle

func FindCoordsOnCircle(origin Coord, radius, coordsLimit int, unique bool) []Coord

FindCoordsOnCircle returns points on a circle around origin. The column offset is doubled so the circle looks round in a terminal cell grid.

func FindCoordsOnRect added in v0.2.0

func FindCoordsOnRect(origin Coord, halfWidth, halfHeight int) []Coord

FindCoordsOnRect returns the perimeter of a rectangle. Either half-dimension being zero returns nothing.

type CoordColorMap

type CoordColorMap map[Coord]Color

CoordColorMap maps canvas coordinates to gradient colours.

func (CoordColorMap) At

func (m CoordColorMap) At(coord Coord, fallback Color) Color

At reads a coordinate, falling back to the first spectrum colour for coords outside the mapped rectangle. Effects index this map by input coordinate and a miss would otherwise show as a black character.

type Crumble added in v0.4.0

type Crumble struct {
	// contains filtered or unexported fields
}

Crumble breaks the text up and puts it back. Every character dims, turns to dust, falls to the bottom of the canvas, gets sucked out through the top, and then flies home and flashes white as it re-forms.

This effect passes over the screen rather than assembling it, so every character is visible from the first frame, wearing a dimmed version of the colour it will settle back to. That is upstream's own behaviour here, not a deviation: crumble starts from a picture that is already on screen, which is exactly what DynamicExistingColors wants.

func NewCrumble added in v0.4.0

func NewCrumble(config CrumbleConfig) *Crumble

NewCrumble builds the effect.

func (*Crumble) Advance added in v0.4.0

func (c *Crumble) Advance(e *Engine) bool

Advance runs one frame of whichever stage the effect is in and reports whether it is still going.

func (*Crumble) Build added in v0.4.0

func (c *Crumble) Build(e *Engine) error

Build gives every character its four passes: the dimmed scene it starts in, the fall to the floor with a dust animation synced to it, the lift out through the top, and the flight home with the white flash that follows.

type CrumbleConfig added in v0.4.0

type CrumbleConfig struct {
	// FinalGradientStops colour the text once it reforms. They are ignored
	// when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

CrumbleConfig tunes the crumble effect.

func DefaultCrumbleConfig added in v0.4.0

func DefaultCrumbleConfig() CrumbleConfig

DefaultCrumbleConfig is upstream's default crumble.

type Decrypt

type Decrypt struct {
	// contains filtered or unexported fields
}

Decrypt types the text out as ciphertext, then resolves it character by character.

func NewDecrypt

func NewDecrypt(config DecryptConfig) *Decrypt

NewDecrypt builds the effect.

func (*Decrypt) Advance

func (d *Decrypt) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*Decrypt) Build

func (d *Decrypt) Build(e *Engine) error

Build sets up every character's typing and decrypting scenes.

type DecryptConfig

type DecryptConfig struct {
	// TypingSpeed is how many characters appear per typing tick.
	TypingSpeed int
	// CiphertextColors are picked at random per character for the scrambled
	// phase.
	CiphertextColors []Color
	// FinalGradientStops colour the text once it resolves. They are ignored
	// when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

DecryptConfig tunes the decrypt effect.

func DefaultDecryptConfig

func DefaultDecryptConfig() DecryptConfig

DefaultDecryptConfig is upstream's default decrypt.

type Descriptor

type Descriptor struct {
	Name        string
	Description string
	New         Factory
	// NeedsFillCharacters says the effect animates the empty cells of the
	// canvas as well as the input, so the terminal must be built with
	// MakeFillCharacters set.
	//
	// It is declared here rather than discovered in Build because the
	// terminal is built before the effect is, and a fill character cannot be
	// added afterwards. An effect that queries InnerFill or OuterFill without
	// setting this gets an empty result and quietly animates nothing, which
	// is the failure this field exists to prevent.
	NeedsFillCharacters bool
	// Origin credits an effect this package wrote rather than ported.
	//
	// A port leaves it empty. Ports are credited in NOTICE, which maps every
	// file here to the ttfx source and the TerminalTextEffects source it was
	// translated from, and that is the only credit a port needs.
	//
	// An effect that was not translated has no line to write there, so it
	// carries its credit here instead: where its material came from, in one
	// line, in the effect's own registration where the person adding it
	// cannot avoid deciding. An empty Origin is therefore a claim in itself,
	// that this effect is a translation and NOTICE says of what.
	//
	// catalogue_test.go holds both halves to their word.
	Origin string
}

Descriptor is a registered effect: its name, one line about what it does, how to build one, and what it needs from the terminal before it can build.

func Descriptors

func Descriptors() []Descriptor

Descriptors lists every registered effect with its description, sorted by name.

func Lookup

func Lookup(name string) (Descriptor, bool)

Lookup finds a registered effect by name.

type Easing

type Easing int

Easing names one of the standard easing curves. Ported from ttfx src/utils/easing.rs, which ports TerminalTextEffects utils/easing.py.

const (
	Linear Easing = iota
	InSine
	OutSine
	InOutSine
	InQuad
	OutQuad
	InOutQuad
	InCubic
	OutCubic
	InOutCubic
	InQuart
	OutQuart
	InOutQuart
	InQuint
	OutQuint
	InOutQuint
	InExpo
	OutExpo
	InOutExpo
	InCirc
	OutCirc
	InOutCirc
	InBack
	OutBack
	InOutBack
	InElastic
	OutElastic
	InOutElastic
	InBounce
	OutBounce
	InOutBounce
)

The easing curves. Linear is the zero value, so a Path with no easing set moves at a constant rate.

func ParseEasing

func ParseEasing(name string) (Easing, bool)

ParseEasing looks up an easing curve by its upstream name.

func (Easing) Ease

func (e Easing) Ease(p float64) float64

Ease maps a progress ratio in [0,1] through the curve.

type Effect

type Effect interface {
	Build(e *Engine) error
	Advance(e *Engine) bool
}

Effect is one animation. Build runs once and sets up every character's scenes and paths; Advance then runs one frame at a time until it reports that the effect is over.

Advance does not return the frame. The caller reads it from the engine with Frame or FrameRows, so an effect never has to care whether its host wants an ANSI string or a cell grid.

type Engine

type Engine struct {
	Terminal *Terminal
	Rng      *Rng
	// Clock is the source of time for the few effects written against
	// seconds rather than frames. NewEngine installs a virtual one so those
	// effects stay reproducible from the seed; see clock.go.
	Clock *Clock
	// contains filtered or unexported fields
}

Engine is the mutable world an effect runs in: the terminal, the random source, and the set of characters that still have work to do.

Every stepping routine that can raise an event lives here rather than on the character, because an event action runs inline at the point it is raised and may reach back into the same structures being stepped. Ported from ttfx src/engine/ctx.rs.

func NewEngine

func NewEngine(terminal *Terminal, rng *Rng) *Engine

NewEngine builds an engine over a terminal. Its clock is a virtual one running at sixty frames a second; set Engine.Clock if the host paints at a different rate, or wants real elapsed time.

func (*Engine) Activate

func (e *Engine) Activate(ch *Character)

Activate adds a character to the active set.

func (*Engine) ActivatePath

func (e *Engine) ActivatePath(ch *Character, pathID string)

ActivatePath starts a path, splicing in a segment from wherever the character currently stands to the path's first waypoint.

func (*Engine) ActivateScene

func (e *Engine) ActivateScene(ch *Character, sceneID string)

ActivateScene starts a scene. It resumes rather than restarts: a scene that was part way through picks up where it stopped.

func (*Engine) ActiveCharacters

func (e *Engine) ActiveCharacters() []*Character

ActiveCharacters returns the active set in ascending id order. The slice is reused between calls.

func (*Engine) ActiveCount

func (e *Engine) ActiveCount() int

ActiveCount is how many characters are still animating.

func (*Engine) ChainPaths

func (e *Engine) ChainPaths(ch *Character, pathIDs []string, loop bool)

ChainPaths makes each path activate the next one when it completes.

func (*Engine) ClearActive

func (e *Engine) ClearActive()

ClearActive empties the active set.

func (*Engine) Deactivate

func (e *Engine) Deactivate(ch *Character)

Deactivate removes a character from the active set.

func (*Engine) DeactivateScene

func (e *Engine) DeactivateScene(ch *Character, sceneID string)

DeactivateScene stops a scene. An empty id stops whatever is running.

func (*Engine) Frame

func (e *Engine) Frame() string

Frame renders the current state as an ANSI string.

func (*Engine) FrameRows

func (e *Engine) FrameRows() [][]*CharacterVisual

FrameRows renders the current state as rows of visuals, top row first.

func (*Engine) MotionMove

func (e *Engine) MotionMove(ch *Character)

MotionMove advances the character along its active path by one step.

func (*Engine) StepAnimation

func (e *Engine) StepAnimation(ch *Character)

StepAnimation advances the character's active scene by one tick.

func (*Engine) Tick

func (e *Engine) Tick(ch *Character)

Tick advances one character: motion first, then animation.

func (*Engine) Update

func (e *Engine) Update()

Update ticks every active character, then drops the ones that finished, then moves the clock on by one frame.

The clock moves here rather than in Frame because an effect calls Update exactly once per frame, while a host may read Frame twice or not at all.

type ErrorCorrect added in v0.4.0

type ErrorCorrect struct {
	// contains filtered or unexported fields
}

ErrorCorrect starts with the whole picture on screen and some of it transposed. It then works through the misplaced pairs, flashing each one red, wiping it into a block, flying the two characters past each other and settling them into their own cells.

The picture is there from the first frame in every colour mode, so this is an effect that passes over the screen rather than one that assembles it. Upstream already shows every character during the build, so it needs none of the reveal-from-frame-one handling a sweep like waves does.

func NewErrorCorrect added in v0.4.0

func NewErrorCorrect(config ErrorCorrectConfig) *ErrorCorrect

NewErrorCorrect builds the effect.

func (*ErrorCorrect) Advance added in v0.4.0

func (x *ErrorCorrect) Advance(e *Engine) bool

Advance releases one pair every SwapDelay frames and reports whether the effect is still going.

func (*ErrorCorrect) Build added in v0.4.0

func (x *ErrorCorrect) Build(e *Engine) error

Build shows the whole picture, picks the pairs that will start transposed, and gives each of them the path and the scenes that put it back.

type ErrorCorrectConfig added in v0.4.0

type ErrorCorrectConfig struct {
	// ErrorPairs is the share of the characters that start in the wrong
	// place, between 0 and 1. Each pair takes two characters, so 0.1 over a
	// hundred characters makes ten pairs and misplaces twenty of them.
	ErrorPairs float64
	// SwapDelay is how many frames pass between one pair being released and
	// the next.
	SwapDelay int
	// ErrorColor marks a character that is in the wrong place.
	ErrorColor Color
	// CorrectColor marks a character that has just been put right. It is the
	// colour the closing ramp starts from.
	CorrectColor Color
	// MovementSpeed is how fast a character travels back to its own cell.
	MovementSpeed float64
	// FinalGradientStops colour the text once every pair is home. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

ErrorCorrectConfig tunes the errorcorrect effect.

func DefaultErrorCorrectConfig added in v0.4.0

func DefaultErrorCorrectConfig() ErrorCorrectConfig

DefaultErrorCorrectConfig is upstream's default errorcorrect.

type EtchPattern added in v0.4.0

type EtchPattern int

EtchPattern picks the order the laser etches characters in.

const (
	// EtchAlgorithm cuts along a recursive backtracker's walk over the text
	// block, which gives the laser long corridors and dead ends rather than a
	// tidy sweep. This is the default and the only pattern that etches.
	EtchAlgorithm EtchPattern = iota
	// EtchGroup is upstream's grouped ordering, and it etches nothing.
	//
	// Upstream parses the option into a CharacterGroup member and then tests
	// that member against the enum's member *names*, which never matches. The
	// grouped branch is unreachable, no character is ever queued, and the
	// effect emits a single frame. ttfx reproduced that after checking it
	// against the reference build, and so does this.
	//
	// ttfx carries the requested group alongside the variant. Nothing reads
	// it, so this does not: the outcome is the same whichever group is asked
	// for.
	EtchGroup
)

The etch patterns.

type Event

type Event int

Event is something a character's motion or animation reached. Effects hang actions off these, which is how a character chains one scene or path into the next without the effect polling for it.

const (
	SegmentEntered Event = iota
	SegmentExited
	PathActivated
	PathComplete
	PathHolding
	SceneActivated
	SceneComplete
)

The seven events, matching upstream's EventHandler.Event.

type ExistingColorHandling

type ExistingColorHandling int

ExistingColorHandling says what an effect does with colours the input already carried.

const (
	// IgnoreExistingColors throws the input colours away and uses the
	// effect's own gradient. This is upstream's default.
	IgnoreExistingColors ExistingColorHandling = iota
	// DynamicExistingColors resolves every character back to the colour it
	// arrived with. A screen saver over a captured screen wants this: the
	// screen reassembles in its own colours.
	DynamicExistingColors
	// AlwaysExistingColors pins every frame to the input colour, so the
	// effect animates shape but never colour.
	AlwaysExistingColors
)

The three input-colour policies.

type Expand added in v0.4.0

type Expand struct {
	// contains filtered or unexported fields
}

Expand stacks every character on the middle of the canvas and moves it out to where it belongs, colouring it as it goes.

This effect ASSEMBLES the screen rather than passing over it, so it does not show the picture in place on the first frame. Upstream makes every character visible during the build, all of them piled on the centre cell, and the picture only exists once they have travelled. Under DynamicExistingColors that is the right behaviour unchanged: the screen collapses to a point and grows back into itself.

func NewExpand added in v0.4.0

func NewExpand(config ExpandConfig) *Expand

NewExpand builds the effect.

func (*Expand) Advance added in v0.4.0

func (x *Expand) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going. Every character is released during the build, so there is nothing to release here.

func (*Expand) Build added in v0.4.0

func (x *Expand) Build(e *Engine) error

Build puts every character on the centre of the canvas and gives it the path out and the colour ramp it wears on the way.

type ExpandConfig added in v0.4.0

type ExpandConfig struct {
	// ExpandEasing shapes the travel from the middle of the canvas outwards.
	// The default starts and ends slowly, so the picture swells rather than
	// bursts.
	ExpandEasing Easing
	// MovementSpeed is how fast a character travels to where it belongs.
	MovementSpeed float64
	// FinalGradientStops colour the text once it is in place. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

ExpandConfig tunes the expand effect.

func DefaultExpandConfig added in v0.4.0

func DefaultExpandConfig() ExpandConfig

DefaultExpandConfig is upstream's default expand.

type ExpandDirection added in v0.4.0

type ExpandDirection int

ExpandDirection is the axis middleout spreads along before it expands out to the rest of the canvas.

The names carry an Expand prefix because Vertical and Horizontal already name the axes of a GradientDirection.

const (
	// ExpandVertical spreads the text across the centre row first, then
	// expands up and down.
	ExpandVertical ExpandDirection = iota
	// ExpandHorizontal spreads the text down the centre column first, then
	// expands left and right.
	ExpandHorizontal
)

The two expansion axes.

func ParseExpandDirection added in v0.4.0

func ParseExpandDirection(name string) (ExpandDirection, bool)

ParseExpandDirection looks up an expansion axis by its upstream name.

type Factory

type Factory func() Effect

Factory builds a fresh instance of an effect with its default settings.

type Fireworks added in v0.4.0

type Fireworks struct {
	// contains filtered or unexported fields
}

Fireworks packs the characters into shells, launches each shell from the bottom of the canvas, bursts it, and lets the pieces fall into the cells they belong in.

It assembles the screen rather than passing over it. Every character starts off its own cell, at the bottom edge, and is hidden until its shell is launched. That holds under DynamicExistingColors too: showing the picture from the first frame would leave the shells climbing over the finished screen they are supposed to be delivering.

func NewFireworks added in v0.4.0

func NewFireworks(config FireworksConfig) *Fireworks

NewFireworks builds the effect.

func (*Fireworks) Advance added in v0.4.0

func (f *Fireworks) Advance(e *Engine) bool

Advance launches a shell when the delay has run out, runs one frame, and reports whether the effect is still going.

func (*Fireworks) Build added in v0.4.0

func (f *Fireworks) Build(e *Engine) error

Build sizes the shells, gives every character its flight, and dresses it.

type FireworksConfig added in v0.4.0

type FireworksConfig struct {
	// ExplodeAnywhere lets a shell burst anywhere on the canvas. Left off, a
	// shell bursts at or above the row its first character belongs on, so the
	// bursts stay clear of the settled text.
	ExplodeAnywhere bool
	// FireworkColors are picked at random, one per shell.
	FireworkColors []Color
	// FireworkSymbol is the glyph a character wears while it climbs.
	FireworkSymbol string
	// FireworkVolume is how many characters go into one shell, as a fraction
	// of the total. It is at least one character.
	FireworkVolume float64
	// LaunchDelay is roughly how many frames pass between one shell and the
	// next. Each wait is scaled by a random half to one and a half.
	LaunchDelay int
	// ExplodeDistance is how far a character flies from the burst point, as a
	// fraction of the canvas width. It is capped at fifteen cells.
	ExplodeDistance float64
	// FinalGradientStops colour the text once it has fallen into place. They
	// are ignored when the engine is set to resolve to the input's own
	// colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

FireworksConfig tunes the fireworks effect.

func DefaultFireworksConfig added in v0.4.0

func DefaultFireworksConfig() FireworksConfig

DefaultFireworksConfig is upstream's default fireworks.

type Frame

type Frame struct {
	Visual   *CharacterVisual
	Duration int
	// contains filtered or unexported fields
}

Frame is one visual held for a number of ticks.

type Gradient

type Gradient struct {
	Spectrum []Color
}

Gradient is a precomputed list of colours between a set of stops.

func NewGradient

func NewGradient(stops []Color, steps []int, doLoop bool) (*Gradient, error)

NewGradient builds a spectrum from stops and a per-pair step count.

The channel deltas use integer floor division rather than a float lerp, and the exact end stop is appended after each pair. That is upstream's arithmetic. It biases every ramp slightly and gives the spectrum its characteristic banding, so it is reproduced rather than corrected.

func NewGradientSteps

func NewGradientSteps(stops []Color, steps int, doLoop bool) (*Gradient, error)

NewGradientSteps is NewGradient with one step count for every pair, which is how nearly every effect calls it.

func (*Gradient) BuildCoordinateColorMapping

func (g *Gradient) BuildCoordinateColorMapping(minRow, maxRow, minColumn, maxColumn int, direction GradientDirection) (CoordColorMap, error)

BuildCoordinateColorMapping paints a gradient across a rectangle.

func (*Gradient) ColorAtFraction

func (g *Gradient) ColorAtFraction(fraction float64) Color

ColorAtFraction picks the spectrum entry covering a position in [0,1].

type GradientDirection

type GradientDirection int

GradientDirection is the axis a coordinate colour mapping runs along.

const (
	Vertical GradientDirection = iota
	Horizontal
	Radial
	Diagonal
)

The gradient axes.

func ParseGradientDirection

func ParseGradientDirection(name string) (GradientDirection, bool)

ParseGradientDirection looks up a gradient axis by its upstream name.

type Highlight added in v0.4.0

type Highlight struct {
	// contains filtered or unexported fields
}

Highlight sweeps a band of brighter colour across the screen. Nothing moves and nothing is hidden: every character is on screen from the first frame wearing the colour it settles at, and the band brightens each one in turn as it passes over.

Upstream drops the map it builds from character to final colour without ever reading it, and the Rust port keeps it only to stay faithful. It is left out here because a field nothing reads is not faithfulness, it is dead weight.

func NewHighlight added in v0.4.0

func NewHighlight(config HighlightConfig) *Highlight

NewHighlight builds the effect.

func (*Highlight) Advance added in v0.4.0

func (h *Highlight) Advance(e *Engine) bool

Advance releases the groups the easer reached this step and reports whether the effect is still going.

func (*Highlight) Build added in v0.4.0

func (h *Highlight) Build(e *Engine) error

Build gives every character the band scene it will run when the sweep reaches it, then groups the characters into the bands the sweep travels through.

type HighlightConfig added in v0.4.0

type HighlightConfig struct {
	// HighlightBrightness is how much brighter the middle of the band is than
	// the colour the character settles at. It scales lightness, so a value
	// above 1 brightens and a value below 1 darkens.
	HighlightBrightness float64
	// HighlightDirection is the axis the band travels along. The characters
	// are grouped by it and released one group at a time.
	HighlightDirection CharacterGroup
	// HighlightWidth is how many frames the band holds its brightest colour.
	// A wider band spends longer at full brightness, so it reads as a broader
	// stripe of light. It must be at least 1.
	HighlightWidth int
	// FinalGradientStops colour the text the band travels over. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

HighlightConfig tunes the highlight effect.

func DefaultHighlightConfig added in v0.4.0

func DefaultHighlightConfig() HighlightConfig

DefaultHighlightConfig is upstream's default highlight.

type InputCell

type InputCell struct {
	// Symbol is the grapheme cluster in the cell. An empty string is a blank.
	Symbol string
	Fg     Color
	HasFg  bool
	Bg     Color
	HasBg  bool
	Bold   bool
}

InputCell is one cell of a screen capture handed to the engine.

type LaserEtch added in v0.4.0

type LaserEtch struct {
	// contains filtered or unexported fields
}

LaserEtch cuts the text onto the screen. A beam swings in from off the bottom left corner, stops on one character at a time in the order a recursive backtracker walked the text block, and leaves that character glowing and cooling behind it. Every stop throws a spark that falls to the bottom of the canvas and burns out.

func NewLaserEtch added in v0.4.0

func NewLaserEtch(config LaserEtchConfig) *LaserEtch

NewLaserEtch builds the effect.

func (*LaserEtch) Advance added in v0.4.0

func (l *LaserEtch) Advance(e *Engine) bool

Advance etches the next characters, keeps the beam alive while any remain, and reports whether the effect is still going.

func (*LaserEtch) Build added in v0.4.0

func (l *LaserEtch) Build(e *Engine) error

Build gives every character the scene it wears while it cools, works out the order the laser will etch in, then builds the beam.

type LaserEtchConfig added in v0.4.0

type LaserEtchConfig struct {
	// EtchPattern is the order characters are etched in. See EtchPattern:
	// only EtchAlgorithm etches.
	EtchPattern EtchPattern
	// EtchSpeed is how many characters are etched in one go, and EtchDelay is
	// how many frames pass before the next go. Together they set the pace.
	EtchSpeed int
	EtchDelay int
	// CoolGradientStops colour a character as it cools from laser-hot back to
	// its settled colour.
	CoolGradientStops []Color
	// LaserGradientStops colour the beam itself. The gradient loops and each
	// cell of the beam starts one step further along it, so the colour runs
	// up the beam.
	LaserGradientStops []Color
	// SparkGradientStops colour a spark as it cools on the way down, and
	// SparkCoolingFrames is how long each of those colours is held. Raise it
	// to cool sparks more slowly.
	SparkGradientStops []Color
	SparkCoolingFrames int
	// FinalGradientStops colour the text once it has cooled. They are ignored
	// when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
	// FinalGradientFrames is carried for parity with upstream's options and
	// changes nothing. Upstream declares it and never reads it: laseretch has
	// no separate final-gradient scene, because the cool gradient already
	// ends on the final colour.
	FinalGradientFrames int
}

LaserEtchConfig tunes the laseretch effect.

func DefaultLaserEtchConfig added in v0.4.0

func DefaultLaserEtchConfig() LaserEtchConfig

DefaultLaserEtchConfig is upstream's default laseretch.

type Matrix added in v0.4.0

type Matrix struct {
	// contains filtered or unexported fields
}

Matrix rains characters down the screen for a while, fills the screen with them, then resolves the input out of the fill a few characters at a time.

func NewMatrix added in v0.4.0

func NewMatrix(config MatrixConfig) *Matrix

NewMatrix builds the effect.

func (*Matrix) Advance added in v0.4.0

func (m *Matrix) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*Matrix) Build added in v0.4.0

func (m *Matrix) Build(e *Engine) error

Build gives every input character the scene it resolves through, then cuts the whole canvas into columns and shuffles them.

type MatrixConfig added in v0.4.0

type MatrixConfig struct {
	// HighlightColor is the colour of the character at the bottom of a
	// falling column, the drop's leading edge.
	HighlightColor Color
	// RainColorGradient is the ramp the rest of a column is coloured from.
	// Colours are picked from it at random.
	RainColorGradient []Color
	// RainSymbols are the glyphs the rain is drawn with.
	RainSymbols []string
	// RainFallDelayLow and RainFallDelayHigh bound how many frames a column
	// waits between rows. A column picks one value and keeps it.
	RainFallDelayLow  int
	RainFallDelayHigh int
	// RainColumnDelayLow and RainColumnDelayHigh bound how many frames pass
	// between starting new columns.
	RainColumnDelayLow  int
	RainColumnDelayHigh int
	// RainTime is how many seconds the rain falls for before the columns fill
	// the screen and the text resolves. Zero means the rain never ends on its
	// own.
	RainTime int
	// SymbolSwapChance and ColorSwapChance are the per-frame odds that a
	// character already in the rain changes its glyph or its colour.
	SymbolSwapChance float64
	ColorSwapChance  float64
	// ResolveDelay is how many frames pass between resolving one group of
	// characters and the next. Raise it to slow the ending down.
	ResolveDelay int
	// FinalGradientStops colour the text once it resolves. They are ignored
	// when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientFrames    int
	FinalGradientDirection GradientDirection
}

MatrixConfig tunes the matrix effect.

func DefaultMatrixConfig added in v0.4.0

func DefaultMatrixConfig() MatrixConfig

DefaultMatrixConfig is upstream's default matrix.

type Middleout added in v0.4.0

type Middleout struct {
	// contains filtered or unexported fields
}

Middleout collapses the whole input onto the centre of the canvas, spreads it out along one axis into a single line, and then expands that line back out until every character is home.

func NewMiddleout added in v0.4.0

func NewMiddleout(config MiddleoutConfig) *Middleout

NewMiddleout builds the effect.

func (*Middleout) Advance added in v0.4.0

func (m *Middleout) Advance(e *Engine) bool

Advance runs one frame. When the first expansion has finished it starts the second one on every character at once, and reports whether anything is still moving.

func (*Middleout) Build added in v0.4.0

func (m *Middleout) Build(e *Engine) error

Build stacks every character on the centre of the canvas and gives it the two paths and the one scene it needs: out to the centre line, then out to where it came from, ramping from the starting colour to its final one as it goes.

type MiddleoutConfig added in v0.4.0

type MiddleoutConfig struct {
	// StartingColor is what every character wears while it is stacked on the
	// centre and spreading out along the first axis. The closing ramp starts
	// from it.
	StartingColor Color
	// ExpandDirection is the axis the text spreads along first.
	ExpandDirection ExpandDirection
	// CenterMovementSpeed is how fast a character travels during the first
	// expansion, in cells per frame.
	CenterMovementSpeed float64
	// FullMovementSpeed is how fast it travels during the second one.
	FullMovementSpeed float64
	// CenterEasing shapes the first expansion, FullEasing the second.
	CenterEasing Easing
	FullEasing   Easing
	// FinalGradientStops colour the text once it has arrived. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

MiddleoutConfig tunes the middleout effect.

func DefaultMiddleoutConfig added in v0.4.0

func DefaultMiddleoutConfig() MiddleoutConfig

DefaultMiddleoutConfig is upstream's default middleout.

type Motion

type Motion struct {
	CurrentCoord  Coord
	PreviousCoord Coord
	// contains filtered or unexported fields
}

Motion is one character's movement state.

func (*Motion) ClearPaths added in v0.3.0

func (m *Motion) ClearPaths()

ClearPaths removes every path from the character and stops whatever was running. Upstream clears the path table without touching the active path reference; this clears both, because a reference to a path that no longer exists leaves MovementIsComplete false forever and the character never leaves the active set.

func (*Motion) DeactivatePath

func (m *Motion) DeactivatePath(id string)

DeactivatePath stops the named path. An empty id stops whatever is running.

func (*Motion) MovementIsComplete

func (m *Motion) MovementIsComplete() bool

MovementIsComplete reports whether the character has no path running.

func (*Motion) NewPath

func (m *Motion) NewPath(id string, opts PathOptions) (*Path, error)

NewPath registers a path. An empty id gets an auto-allocated one.

func (*Motion) Path

func (m *Motion) Path(id string) *Path

Path looks a path up by id, returning nil when it is absent.

func (*Motion) SetCoordinate

func (m *Motion) SetCoordinate(coord Coord)

SetCoordinate teleports the character without touching any path.

type OrbittingVolley added in v0.4.0

type OrbittingVolley struct {
	// contains filtered or unexported fields
}

OrbittingVolley circles four launchers around the edge of the canvas and fires the characters out of them a volley at a time. Each character flies from whichever launcher holds it to the cell it came from.

func NewOrbittingVolley added in v0.4.0

func NewOrbittingVolley(config OrbittingVolleyConfig) *OrbittingVolley

NewOrbittingVolley builds the effect.

func (*OrbittingVolley) Advance added in v0.4.0

func (o *OrbittingVolley) Advance(e *Engine) bool

Advance moves the launchers, fires a volley when the delay runs out, and reports whether the effect is still going. The last frame it produces is the one where the launchers are hidden, so the text is left on its own.

func (*OrbittingVolley) Build added in v0.4.0

func (o *OrbittingVolley) Build(e *Engine) error

Build gives every character the path home that a launcher will send it along, then places the four launchers and deals the characters out between them from the middle of the text outwards.

type OrbittingVolleyConfig added in v0.4.0

type OrbittingVolleyConfig struct {
	// TopLauncherSymbol and the three that follow are the glyphs the four
	// launchers are drawn with. The top one is the launcher that actually
	// moves; the other three are placed from its progress each frame.
	TopLauncherSymbol    string
	RightLauncherSymbol  string
	BottomLauncherSymbol string
	LeftLauncherSymbol   string
	// LauncherMovementSpeed is how fast the top launcher crosses the canvas.
	LauncherMovementSpeed float64
	// CharacterMovementSpeed is how fast a fired character flies to its home.
	CharacterMovementSpeed float64
	// VolleySize is the share of the input each volley fires, split across the
	// four launchers. One character per launcher is the floor.
	VolleySize float64
	// LaunchDelay is how many frames pass between volleys.
	LaunchDelay int
	// CharacterEasing shapes a fired character's flight.
	CharacterEasing Easing
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

OrbittingVolleyConfig tunes the orbittingvolley effect.

func DefaultOrbittingVolleyConfig added in v0.4.0

func DefaultOrbittingVolleyConfig() OrbittingVolleyConfig

DefaultOrbittingVolleyConfig is upstream's default orbittingvolley.

type Overflow added in v0.4.0

type Overflow struct {
	// contains filtered or unexported fields
}

Overflow scrolls the text up the canvas in the wrong order, over and over, and then scrolls the real rows in behind it so the picture arrives from the bottom edge.

This effect assembles the screen rather than passing over it, so every character stays hidden until the row carrying it enters from the bottom. That holds under every colour policy, DynamicExistingColors included: showing the picture up front would leave the scroll nothing to deliver, and the copies would scrape across a screen that was already finished.

func NewOverflow added in v0.4.0

func NewOverflow(config OverflowConfig) *Overflow

NewOverflow builds the effect.

func (*Overflow) Advance added in v0.4.0

func (o *Overflow) Advance(e *Engine) bool

Advance releases the next few rows, lifts everything on screen by a row, and runs one frame. It reports whether the effect is still going.

func (*Overflow) Build added in v0.4.0

func (o *Overflow) Build(e *Engine) error

Build makes the scrambled copies of the text, queues them ahead of the real rows, and settles every real character on the colour it will end on.

type OverflowConfig added in v0.4.0

type OverflowConfig struct {
	// OverflowGradientStops colour the rows while they are scrolling past.
	// The gradient runs up the canvas, so a row changes colour as it climbs.
	OverflowGradientStops []Color
	// OverflowCyclesLow and OverflowCyclesHigh bound how many times the text
	// is scrambled and scrolled past before the real rows arrive. Setting
	// OverflowCyclesHigh to zero skips the scramble entirely.
	OverflowCyclesLow  int
	OverflowCyclesHigh int
	// OverflowSpeed is the most rows that can be released in one frame.
	OverflowSpeed int
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

OverflowConfig tunes the overflow effect.

func DefaultOverflowConfig added in v0.4.0

func DefaultOverflowConfig() OverflowConfig

DefaultOverflowConfig is upstream's default overflow.

type ParticlePool added in v0.3.0

type ParticlePool struct {
	// Symbols is what a new particle can look like. Emit picks one at random
	// unless it is given a symbol.
	Symbols []string
	// MaxSize caps how many particles the pool will ever create. Zero means
	// no cap. Once the cap is reached and the free list is empty, Emit
	// returns nil rather than growing.
	MaxSize int
	// Coord is where a newly created particle is placed before anything moves
	// it. Emit moves it, so this only matters for the frame a particle is
	// made in.
	Coord Coord
	// Particles is every particle the pool owns, free or in flight, in the
	// order they were created. An effect that has to register something on
	// each of them walks this.
	Particles []*Character
	// contains filtered or unexported fields
}

ParticlePool recycles characters that are not part of the input: sparks, smoke, raindrops. An effect that throws off thousands of short-lived characters would otherwise allocate a thousand more every second and never free any of them, because a character lives on the terminal for the whole run.

A particle goes round a loop. Emit takes one off the free list, puts it where the effect wants it, lets the effect give it a path and a scene, shows it and makes it active. When it is finished, Reclaim hides it and puts it back on the free list. Reclaim usually runs from an event: ReclaimOnEvent wires it to the scene or path whose completion means the particle is spent.

pool, err := NewParticlePool([]string{"*", "."}, 2000, Coord{}, initSpark)
pool.Preallocate(e, 2000)
...
spark := pool.Emit(e, origin, "", true, ParticleReset{}, func(e *Engine, ch *Character) {
    // give it a path and a scene, then activate them
})

Ported from ttfx src/engine/particles.rs.

func NewParticlePool added in v0.3.0

func NewParticlePool(symbols []string, maxSize int, coord Coord, initializer func(e *Engine, ch *Character)) (*ParticlePool, error)

NewParticlePool builds a pool. maxSize of zero means no cap.

initializer runs once on each particle the pool creates, and never on one it reuses. It is where the effect builds the scenes a particle needs for its whole life, so that emitting one is cheap. It may be nil.

Upstream passes the initializer to every call that might create a particle instead of holding it. Every effect in the catalogue passes the same closure to all of them, and passing two different ones would mean particles that behave differently depending on whether the pool happened to be empty, so this holds it once.

func (*ParticlePool) Acquire added in v0.3.0

func (p *ParticlePool) Acquire(e *Engine, symbol string, reset ParticleReset) *Character

Acquire takes a particle off the free list, or creates one if the free list is empty and the cap allows. It returns nil when the pool is capped out.

An empty symbol keeps whatever the particle already looks like, which for a new one is a symbol drawn at random from Symbols. A given symbol replaces it.

The particle is not placed, shown or activated: Acquire is for an effect that wants to do all of that itself. Emit is the usual call.

func (*ParticlePool) AvailableCount added in v0.3.0

func (p *ParticlePool) AvailableCount() int

AvailableCount is how many particles are on the free list.

func (*ParticlePool) Emit added in v0.3.0

func (p *ParticlePool) Emit(e *Engine, origin Coord, symbol string, visible bool, reset ParticleReset, onEmit func(e *Engine, ch *Character)) *Character

Emit acquires a particle, puts it at origin, hands it to onEmit, then shows it and makes it active. It returns nil when the pool is capped out and the free list is empty, which an effect throwing off decoration can ignore.

onEmit is where the effect gives the particle the path and scene for this flight and activates them. It may be nil.

func (*ParticlePool) Extend added in v0.3.0

func (p *ParticlePool) Extend(particles ...*Character)

Extend adopts characters the effect made itself. They join the free list as they are, with no reset, so whatever the effect set up on them survives.

func (*ParticlePool) Len added in v0.3.0

func (p *ParticlePool) Len() int

Len is how many particles the pool owns in total, free and in flight.

func (*ParticlePool) Preallocate added in v0.3.0

func (p *ParticlePool) Preallocate(e *Engine, count int) error

Preallocate creates count particles up front and puts them on the free list, so the first burst of emissions does not build characters mid-frame.

func (*ParticlePool) Reclaim added in v0.3.0

func (p *ParticlePool) Reclaim(e *Engine, ch *Character, hide, deactivate bool)

Reclaim puts a particle back on the free list. Reclaiming one that is already there does nothing, so an effect may call it from more handlers than will actually fire.

func (*ParticlePool) ReclaimOnEvent added in v0.3.0

func (p *ParticlePool) ReclaimOnEvent(ch *Character, event Event, from Caller, hide, deactivate bool)

ReclaimOnEvent registers Reclaim against an event, which is how a particle puts itself back once its scene or path finishes.

pool.ReclaimOnEvent(spark, SceneComplete, SceneCaller("glow"), true, true)

Registering it again on the same particle and event is harmless: reclaiming twice does nothing the second time. Upstream reaches this through a callback id table because Rust cannot hold the closure; here it is the closure.

type ParticleReset added in v0.3.0

type ParticleReset struct {
	// KeepPaths leaves the particle's paths in place. Upstream's
	// clear_paths=False.
	KeepPaths bool
	// ClearScenes throws the particle's scenes away as well. Upstream's
	// clear_scenes=True. An effect that sets this pays to rebuild the scenes
	// on every emission.
	ClearScenes bool
	// ClearEvents drops the actions registered on the particle. Upstream's
	// clear_events=True. Set it when the effect registers a fresh handler per
	// emission, or they accumulate over the run.
	ClearEvents bool
	// KeepActivePath leaves the particle travelling. Upstream's
	// deactivate_path=False.
	KeepActivePath bool
	// KeepActiveScene leaves the particle's scene running. Upstream's
	// deactivate_scene=False.
	KeepActiveScene bool
	// ResetAppearance puts the particle back to its input symbol with no
	// colours. Upstream's reset_appearance=True.
	ResetAppearance bool
}

ParticleReset says how much of a particle's previous life to wipe when it is handed out again.

The zero value is upstream's default, and it is what every effect in the catalogue uses: the particle's paths are thrown away and its running path and scene are stopped, while its scenes are kept because building them again per emission is the cost the pool exists to avoid.

Three of these fields are inverted against upstream, which names them clear_paths, deactivate_path and deactivate_scene and defaults all three to true. A Go struct defaults to false, so a faithful set of names would make the zero value mean "reset nothing" and quietly hand out a particle still flying along its last path. Inverting them puts upstream's default on the zero value, and asking for the non-default now has to be written down.

type Path

type Path struct {
	ID       string
	Speed    float64
	Ease     Easing
	HasEase  bool
	Layer    int
	HasLayer bool
	HoldTime int
	Loop     bool

	Segments  []Segment
	Waypoints []Waypoint
	// contains filtered or unexported fields
}

Path is an ordered set of waypoints a character travels along at a fixed speed, optionally eased over the whole path.

func (*Path) NewWaypoint

func (p *Path) NewWaypoint(coord Coord, bezierControl []Coord, id string) (*Waypoint, error)

NewWaypoint appends a waypoint and extends the path to reach it. An empty id gets an auto-allocated one.

type PathOptions

type PathOptions struct {
	Speed    float64
	Ease     Easing
	HasEase  bool
	Layer    int
	HasLayer bool
	HoldTime int
	Loop     bool
}

PathOptions are the knobs NewPath accepts.

type PopCondition added in v0.4.0

type PopCondition int

PopCondition decides when a floating bubble bursts.

const (
	// PopOnRow bursts a bubble once it reaches the lowest input row of the
	// characters riding it, so each group pops near where it belongs.
	PopOnRow PopCondition = iota
	// PopOnBottom carries every bubble down to the bottom of the canvas
	// first.
	PopOnBottom
	// PopAnywhere is PopOnBottom with a small chance of bursting on any
	// frame along the way.
	PopAnywhere
)

The three pop conditions. Row is first so the zero value is upstream's default.

func ParsePopCondition added in v0.4.0

func ParsePopCondition(name string) (PopCondition, bool)

ParsePopCondition looks up a pop condition by its upstream name.

type Pour added in v0.4.0

type Pour struct {
	// contains filtered or unexported fields
}

Pour empties the text into the canvas one row or column at a time. Every character starts off the far edge, travels to where it belongs, and ramps from the starting colour to its final one on the way.

This effect assembles the screen rather than passing over it, so every character stays hidden until it is released. That holds under every colour policy, including DynamicExistingColors: showing the picture up front would leave nothing for the pour to fill in.

func NewPour added in v0.4.0

func NewPour(config PourConfig) *Pour

NewPour builds the effect.

func (*Pour) Advance added in v0.4.0

func (p *Pour) Advance(e *Engine) bool

Advance releases the next few characters and runs one frame. It reports whether the effect is still going.

func (*Pour) Build added in v0.4.0

func (p *Pour) Build(e *Engine) error

Build parks every character off the edge it pours from, gives it a path home, and gives it a scene that ramps to its final colour.

type PourConfig added in v0.4.0

type PourConfig struct {
	// Direction is the edge the text pours towards.
	Direction PourDirection
	// PourSpeed is how many characters are released per tick. Raise it to
	// pour faster.
	PourSpeed int
	// MovementSpeedLow and MovementSpeedHigh bound each character's travel
	// speed.
	MovementSpeedLow  float64
	MovementSpeedHigh float64
	// Gap is how many frames to wait between releases.
	Gap int
	// StartingColor is the colour a character wears while it is falling,
	// before its closing ramp begins.
	StartingColor Color
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientFrames    int
	FinalGradientDirection GradientDirection
	// MovementEasing shapes the travel. The default accelerates towards the
	// resting place.
	MovementEasing Easing
}

PourConfig tunes the pour effect.

func DefaultPourConfig added in v0.4.0

func DefaultPourConfig() PourConfig

DefaultPourConfig is upstream's default pour.

type PourDirection added in v0.4.0

type PourDirection int

PourDirection is the way the text pours: the edge it falls towards.

const (
	// PourDown fills the bottom row first, from characters entering at the
	// top of the canvas.
	PourDown PourDirection = iota
	// PourUp fills the top row first, from characters entering at the bottom.
	PourUp
	// PourLeft fills the left column first, from characters entering at the
	// right edge.
	PourLeft
	// PourRight fills the right column first, from characters entering at the
	// left edge.
	PourRight
)

The four pour directions.

type PrimsSimple added in v0.3.0

type PrimsSimple struct {
	// LimitToTextBoundary keeps the tree inside the text block rather than
	// letting it run out over the whole canvas.
	LimitToTextBoundary bool

	// CharLastLinked is the character the most recent Step joined to the
	// tree, or nil if that Step joined none.
	CharLastLinked *Character
	// CharLinkOrder is every character the tree has reached, starting one,
	// in the order it reached them. This is the order effects animate in.
	CharLinkOrder []*Character
	// EdgeChars are the characters still able to grow the tree.
	EdgeChars []*Character
	// EdgeLastAdded and EdgeLastPopped are the last character put on the edge
	// and the last one taken off it.
	EdgeLastAdded  *Character
	EdgeLastPopped *Character
	// Complete says the tree has stopped growing. See Step: it turns true one
	// Step after the last edge is used up, not on the same one.
	Complete bool
	// contains filtered or unexported fields
}

PrimsSimple grows a tree by repeatedly picking a random character off the edge of what it has already built and linking it to a random unlinked neighbour. The result wanders: it is what burn uses to spread a fire.

func NewPrimsSimple added in v0.3.0

func NewPrimsSimple(e *Engine, startingChar *Character, limitToTextBoundary bool) (*PrimsSimple, error)

NewPrimsSimple starts a tree. A nil startingChar picks a random coordinate on the canvas, which needs fill characters to be reliable.

func (*PrimsSimple) Step added in v0.3.0

func (p *PrimsSimple) Step(e *Engine)

Step grows the tree by at most one edge.

Complete turns true on the Step that finds the edge already empty, not on the Step that empties it. That is upstream's behaviour and it costs one extra frame; an effect that loops until Complete gets one Step where nothing happens. Do not tighten it: the frame count of every effect built on this is tuned to it.

type PrimsWeighted added in v0.3.0

type PrimsWeighted struct {
	// LimitToTextBoundary keeps the tree inside the text block.
	LimitToTextBoundary bool

	// CharLastLinked is the character the most recent Step joined, or nil
	// once the tree is finished.
	CharLastLinked *Character
	// CharLinkOrder is every character the tree has reached, in order.
	CharLinkOrder []*Character
	// NeighborsLastAdded are the characters the most recent Step offered up
	// as new candidates.
	NeighborsLastAdded []*Character
	// Complete says the tree has stopped growing.
	Complete bool
	// contains filtered or unexported fields
}

PrimsWeighted gives every character a random weight once, then always grows towards the cheapest character on the frontier. The tree it builds looks less like a random walk and more like something seeping outwards, which is why smoke uses it.

func NewPrimsWeighted added in v0.3.0

func NewPrimsWeighted(e *Engine, startingChar *Character, limitToTextBoundary bool) (*PrimsWeighted, error)

NewPrimsWeighted starts a tree. A nil startingChar picks a random coordinate.

func (*PrimsWeighted) Step added in v0.3.0

func (p *PrimsWeighted) Step(e *Engine)

Step grows the tree by one edge, taking the cheapest one available.

type Print added in v0.4.0

type Print struct {
	// contains filtered or unexported fields
}

Print types the canvas out one line at a time on the bottom row, lifting the finished lines above it as it goes, so the text arrives the way a teletype would deliver it. Between lines a block runs back along the bottom row to the column the next line starts in.

This effect assembles the screen rather than passing over it, so every character stays hidden until the head strikes it. That holds under every colour policy, including DynamicExistingColors: showing the picture up front would leave the head nothing to type.

func NewPrint added in v0.4.0

func NewPrint(config PrintConfig) *Print

NewPrint builds the effect.

func (*Print) Advance added in v0.4.0

func (p *Print) Advance(e *Engine) bool

Advance types the next few characters, or runs the head back to the margin, and reports whether the effect is still going.

func (*Print) Build added in v0.4.0

func (p *Print) Build(e *Engine) error

Build parks every character on the bottom row under its own column, gives it the scene that fades it in from the head's strike, and splits the canvas into the lines the head will type.

type PrintConfig added in v0.4.0

type PrintConfig struct {
	// PrintHeadReturnSpeed is how fast the head travels back to the start of
	// the next line. Raise it to shorten the pause between lines.
	PrintHeadReturnSpeed float64
	// PrintSpeed is how many characters are typed per frame.
	PrintSpeed int
	// PrintHeadEasing shapes the head's travel during a carriage return.
	PrintHeadEasing Easing
	// FinalGradientStops colour the typed text. They are ignored when the
	// engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

PrintConfig tunes the print effect.

func DefaultPrintConfig added in v0.4.0

func DefaultPrintConfig() PrintConfig

DefaultPrintConfig is upstream's default print.

type Rain

type Rain struct {
	// contains filtered or unexported fields
}

Rain drops every character in from the top of the canvas, one row at a time from the bottom up, and fades it to its final colour when it lands.

func NewRain

func NewRain(config RainConfig) *Rain

NewRain builds the effect.

func (*Rain) Advance

func (r *Rain) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*Rain) Build

func (r *Rain) Build(e *Engine) error

Build gives every character a fall path and a landing fade.

type RainConfig

type RainConfig struct {
	// RainColors are picked at random per drop.
	RainColors []Color
	// MovementSpeedLow and MovementSpeedHigh bound each drop's fall speed.
	MovementSpeedLow  float64
	MovementSpeedHigh float64
	// RainSymbols are the glyphs a drop can wear on the way down.
	RainSymbols []string
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
	// MovementEasing shapes the fall. The default accelerates downward.
	MovementEasing Easing
}

RainConfig tunes the rain effect.

func DefaultRainConfig

func DefaultRainConfig() RainConfig

DefaultRainConfig is upstream's default rain.

type RandomSequence added in v0.4.0

type RandomSequence struct {
	// contains filtered or unexported fields
}

RandomSequence hides the whole text, then fades characters back in one at a time in a random order. Nothing moves and nothing waits on anything else: each character runs its own short ramp from the terminal background to its final colour as soon as its turn comes up.

func NewRandomSequence added in v0.4.0

func NewRandomSequence(config RandomSequenceConfig) *RandomSequence

NewRandomSequence builds the effect.

func (*RandomSequence) Advance added in v0.4.0

func (r *RandomSequence) Advance(e *Engine) bool

Advance releases the next few characters and reports whether the effect is still going.

func (*RandomSequence) Build added in v0.4.0

func (r *RandomSequence) Build(e *Engine) error

Build hides every character, gives it a fade-in scene, and shuffles the order they will be released in.

type RandomSequenceConfig added in v0.4.0

type RandomSequenceConfig struct {
	// Speed is the share of the text revealed per frame, so 0.007 reveals
	// seven characters per thousand. It is turned into a whole number of
	// characters once, at build time, and never drops below one.
	Speed float64
	// FinalGradientStops colour the text as it fades in. They are ignored
	// when the engine is set to resolve to the input's own colours.
	FinalGradientStops []Color
	FinalGradientSteps []int
	// FinalGradientFrames is how many frames each step of a character's
	// fade-in holds. Raise it to make each character arrive more slowly.
	FinalGradientFrames    int
	FinalGradientDirection GradientDirection
}

RandomSequenceConfig tunes the randomsequence effect.

func DefaultRandomSequenceConfig added in v0.4.0

func DefaultRandomSequenceConfig() RandomSequenceConfig

DefaultRandomSequenceConfig is upstream's default randomsequence.

type RecursiveBacktracker added in v0.3.0

type RecursiveBacktracker struct {
	// LimitToTextBoundary keeps the walk inside the text block.
	LimitToTextBoundary bool

	// CharLastLinked is the character this Step joined, or nil if this Step
	// backtracked instead of moving forward.
	CharLastLinked *Character
	// CharLinkOrder is every character reached, in order.
	CharLinkOrder []*Character
	// Stack is the route back to the last character with an unvisited
	// neighbour.
	Stack []*Character
	// StackLastPopped is the character this Step backtracked off the stack,
	// or nil if this Step moved forward instead.
	StackLastPopped *Character
	// Complete says the walk has stopped.
	Complete bool
	// contains filtered or unexported fields
}

RecursiveBacktracker walks as far as it can in one direction, then backs up to the last character with somewhere left to go. The tree it makes is long corridors rather than a spreading blob, which is what laseretch cuts along.

func NewRecursiveBacktracker added in v0.3.0

func NewRecursiveBacktracker(e *Engine, startingChar *Character, limitToTextBoundary bool) (*RecursiveBacktracker, error)

NewRecursiveBacktracker starts a walk. A nil startingChar picks a random coordinate.

func (*RecursiveBacktracker) Step added in v0.3.0

func (r *RecursiveBacktracker) Step(e *Engine)

Step either moves forward into an unvisited neighbour or backs up one. Exactly one of CharLastLinked and StackLastPopped is set afterwards, and both are cleared first, so an effect can tell which of the two happened.

type Rings added in v0.4.0

type Rings struct {
	// contains filtered or unexported fields
}

Rings gathers the text into concentric spinning rings, scatters it, spins it again, and finally walks every character home.

This effect neither passes over the screen nor assembles it: it starts from the assembled picture and takes it apart. Upstream already shows every character in place, in its final colour, on the first frame, because the rings have to form out of text the viewer has seen. That is exactly what DynamicExistingColors needs, so nothing here is scoped to the colour policy beyond the colours themselves.

func NewRings added in v0.4.0

func NewRings(config RingsConfig) *Rings

NewRings builds the effect.

func (*Rings) Advance added in v0.4.0

func (r *Rings) Advance(e *Engine) bool

Advance runs one frame of whichever phase the effect is in and reports whether it is still going.

func (*Rings) Build added in v0.4.0

func (r *Rings) Build(e *Engine) error

Build shows the text, lays out the rings, and hands every character either a place on a ring or a one-way trip off the edge of the canvas.

type RingsConfig added in v0.4.0

type RingsConfig struct {
	// RingColors are cycled through as the rings are built, so ring 0 takes
	// the first colour, ring 1 the second, and so on.
	RingColors []Color
	// RingGap is the distance between rings, as a share of the smaller canvas
	// dimension. It is also how far a character wanders while dispersed.
	RingGap float64
	// SpinDuration is how many frames each spinning stretch lasts.
	SpinDuration int
	// SpinSpeedLow and SpinSpeedHigh bound a ring's rotation speed. Each ring
	// draws its own speed from the range, which is what stops the rings
	// turning as one disc.
	SpinSpeedLow  float64
	SpinSpeedHigh float64
	// DisperseDuration is how many frames the characters spend scattered
	// between spinning stretches.
	DisperseDuration int
	// SpinDisperseCycles is how many times the effect spins and scatters
	// before the text goes home.
	SpinDisperseCycles int
	// FinalGradientStops colour the text once it settles. They are ignored
	// when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

RingsConfig tunes the rings effect.

func DefaultRingsConfig added in v0.4.0

func DefaultRingsConfig() RingsConfig

DefaultRingsConfig is upstream's default rings.

type Rng

type Rng struct {
	// contains filtered or unexported fields
}

Rng is the engine's source of randomness.

ttfx clones Python's Mersenne Twister so that it can reproduce upstream frame for frame. This port makes no parity claim, so it uses Go's own generator. Seeding it makes a run reproducible, which is what the tests need; nothing here will match either of the other two implementations.

func NewRng

func NewRng(seed uint64) *Rng

NewRng builds a generator from a seed. The same seed gives the same run.

func (*Rng) Float

func (g *Rng) Float() float64

Float returns a float in [0, 1).

func (*Rng) IndexBelow

func (g *Rng) IndexBelow(n int) int

IndexBelow returns an index in [0, n). It exists so the generic helpers below can stay free functions: Go does not allow a generic method.

func (*Rng) IntBelow

func (g *Rng) IntBelow(low, high int) int

IntBelow returns an integer in [low, high). Upstream calls this randrange.

func (*Rng) IntBetween

func (g *Rng) IntBetween(low, high int) int

IntBetween returns an integer in [low, high]. Both ends are included, which is what upstream's randint does. A reversed range returns low.

func (*Rng) Uniform

func (g *Rng) Uniform(low, high float64) float64

Uniform returns a float in [low, high].

type Scattered added in v0.4.0

type Scattered struct {
	// contains filtered or unexported fields
}

Scattered drops every character at a random spot on the canvas, holds that still for a moment, then walks each one to where it belongs.

This effect ASSEMBLES the screen rather than passing over it, so it does not show the picture in place on the first frame. Upstream already makes every character visible during the build, because a character has to be seen at its scattered start for the gathering to read at all. Under DynamicExistingColors that is the right behaviour unchanged: the screen starts as the same picture shuffled, and reassembles into itself.

func NewScattered added in v0.4.0

func NewScattered(config ScatteredConfig) *Scattered

NewScattered builds the effect.

func (*Scattered) Advance added in v0.4.0

func (s *Scattered) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going. The first frames of a run hold the scattered picture still without stepping anything, which is what gives the eye time to see it is scattered.

func (*Scattered) Build added in v0.4.0

func (s *Scattered) Build(e *Engine) error

Build scatters every character across the canvas and gives it the path home and the colour ramp it wears on the way.

type ScatteredConfig added in v0.4.0

type ScatteredConfig struct {
	// MovementSpeed is how fast a character travels to where it belongs.
	MovementSpeed float64
	// MovementEasing shapes that travel. The default overshoots at both ends,
	// which is what makes the gathering read as a snap rather than a drift.
	MovementEasing Easing
	// FinalGradientStops colour the text once it is in place. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
	// FinalGradientFrames is how many frames each colour step holds.
	FinalGradientFrames int
}

ScatteredConfig tunes the scattered effect.

func DefaultScatteredConfig added in v0.4.0

func DefaultScatteredConfig() ScatteredConfig

DefaultScatteredConfig is upstream's default scattered.

type Scene

type Scene struct {
	ID        string
	IsLooping bool
	Sync      SyncMetric
	Ease      Easing
	HasEase   bool
	// contains filtered or unexported fields
}

Scene is a named sequence of frames on one character.

func (*Scene) AddFrame

func (s *Scene) AddFrame(symbol string, duration int, p VisualParams) error

AddFrame appends a frame held for duration ticks.

func (*Scene) ApplyGradientToSymbols

func (s *Scene) ApplyGradientToSymbols(symbols []string, duration int, fg, bg *Gradient) error

ApplyGradientToSymbols spreads a colour spectrum across a list of symbols, pairing whichever list is longer against the shorter one.

type SceneOptions

type SceneOptions struct {
	Looping         bool
	Sync            SyncMetric
	Ease            Easing
	HasEase         bool
	UsesInputColors bool
	// Frames is how many frames the caller is about to add, if it knows.
	// Nothing depends on it being right, but a scene with eighty frames
	// regrows its slices seven times without it, and over a full screen that
	// regrowth is most of what the build allocates.
	Frames int
}

SceneOptions are the knobs NewScene accepts.

type Segment

type Segment struct {
	Start    Waypoint
	End      Waypoint
	Distance float64
	// contains filtered or unexported fields
}

Segment is the span between two waypoints, with the flags that stop its enter and exit events firing twice.

type Slice added in v0.4.0

type Slice struct {
	// contains filtered or unexported fields
}

Slice cuts the picture in two, throws each half off the canvas by a different edge, and slides both back at once. Every character is placed and released in Build, so Advance only has to run frames until the movement stops.

This effect assembles the screen rather than passing over it: nothing stands on its own coordinate when the first frame is drawn. That holds under every colour policy, DynamicExistingColors included. Revealing the whole picture up front, which is what a sweep like waves has to do, would leave the slice with nothing to bring in.

func NewSlice added in v0.4.0

func NewSlice(config SliceConfig) *Slice

NewSlice builds the effect.

func (*Slice) Advance added in v0.4.0

func (s *Slice) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*Slice) Build added in v0.4.0

func (s *Slice) Build(e *Engine) error

Build gives every character its final colour, parks it off canvas, and starts it on its way home.

type SliceConfig added in v0.4.0

type SliceConfig struct {
	// Direction is the axis the cut runs along.
	Direction SliceDirection
	// MovementSpeed is how fast a character travels back to its place. The
	// horizontal cut doubles it, because it crosses the width of the canvas
	// rather than its height.
	MovementSpeed float64
	// MovementEasing shapes the travel. The default holds the halves still,
	// throws them across, and settles them, which is what makes the two sides
	// read as one cut rather than two slides.
	MovementEasing Easing
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

SliceConfig tunes the slice effect.

func DefaultSliceConfig added in v0.4.0

func DefaultSliceConfig() SliceConfig

DefaultSliceConfig is upstream's default slice.

type SliceDirection added in v0.4.0

type SliceDirection int

SliceDirection is the axis the cut runs along.

const (
	// SliceVertical cuts down the middle column. The left half falls in from
	// above and the right half rises in from below.
	SliceVertical SliceDirection = iota
	// SliceHorizontal cuts across the middle row. The bottom half comes in
	// from the left and the top half from the right.
	SliceHorizontal
	// SliceDiagonal cuts along the diagonals. Half the diagonals rise from
	// below and half fall from above.
	SliceDiagonal
)

The three directions upstream offers.

func ParseSliceDirection added in v0.4.0

func ParseSliceDirection(name string) (SliceDirection, bool)

ParseSliceDirection looks a direction up by its upstream name.

type Slide added in v0.4.0

type Slide struct {
	// contains filtered or unexported fields
}

Slide parks every character just off the edge of the canvas and pushes the groups on one at a time, each group feeding its characters in one per frame.

This effect assembles the screen rather than passing over it, so a character stays hidden until its turn comes. That holds under every colour policy, DynamicExistingColors included: revealing the whole picture up front would leave nothing for the slide to bring in.

func NewSlide added in v0.4.0

func NewSlide(config SlideConfig) *Slide

NewSlide builds the effect.

func (*Slide) Advance added in v0.4.0

func (s *Slide) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*Slide) Build added in v0.4.0

func (s *Slide) Build(e *Engine) error

Build parks every character off canvas, gives it the path back to its own coordinate, and the colour ramp it wears once it is moving.

type SlideConfig added in v0.4.0

type SlideConfig struct {
	// MovementSpeed is how fast a character travels to its place. Raise it to
	// make the slide snappier.
	MovementSpeed float64
	// Grouping is the axis the characters travel along.
	Grouping SlideGrouping
	// Gap is how many frames to wait before releasing the next group.
	Gap int
	// ReverseDirection sends the groups in from the other side. It is ignored
	// when Merge is set, because Merge already uses both sides.
	ReverseDirection bool
	// Merge sends every other group in from the opposite side, so the groups
	// meet in the middle.
	Merge bool
	// MovementEasing shapes the travel. The default eases in and out, which is
	// what makes a group look like it is being pushed rather than dropped.
	MovementEasing Easing
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
	// FinalGradientFrames is how many frames each step of a character's
	// colour ramp holds.
	FinalGradientFrames int
}

SlideConfig tunes the slide effect.

func DefaultSlideConfig added in v0.4.0

func DefaultSlideConfig() SlideConfig

DefaultSlideConfig is upstream's default slide.

type SlideGrouping added in v0.4.0

type SlideGrouping int

SlideGrouping is the axis the characters travel along.

const (
	// SlideByRow slides each row in sideways.
	SlideByRow SlideGrouping = iota
	// SlideByColumn slides each column in vertically.
	SlideByColumn
	// SlideByDiagonal slides each diagonal in along itself.
	SlideByDiagonal
)

The three groupings upstream offers.

func ParseSlideGrouping added in v0.4.0

func ParseSlideGrouping(name string) (SlideGrouping, bool)

ParseSlideGrouping looks a grouping up by its upstream name.

type Smoke added in v0.4.0

type Smoke struct {
	// contains filtered or unexported fields
}

Smoke lights one cell of the canvas and lets the smoke seep outwards from it. Every character sits dull until the smoke arrives, plays a run of block symbols while it drifts through, and is left wearing its final colour.

The order the smoke spreads in is a weighted spanning tree: every character is given a random cost once, the tree always grows towards the cheapest cell on its frontier, and a breadth-first walk of the finished tree is what the effect animates, one layer per frame. That is why the smoke wanders and pools rather than expanding as a clean ring.

This effect passes over the screen rather than assembling it, so every character is visible from the first frame. That is upstream's own behaviour: smoke never hides anything, it only changes what a character is wearing.

func NewSmoke added in v0.4.0

func NewSmoke(config SmokeConfig) *Smoke

NewSmoke builds the effect.

func (*Smoke) Advance added in v0.4.0

func (s *Smoke) Advance(e *Engine) bool

Advance spreads the smoke by one layer of the tree and reports whether the effect is still running. It keeps running after the last layer, until every character the smoke touched has finished playing it out.

func (*Smoke) Build added in v0.4.0

func (s *Smoke) Build(e *Engine) error

Build grows the spanning tree the smoke will follow and gives every character its two scenes: the smoke drifting through it, and the paint that is left behind.

type SmokeConfig added in v0.4.0

type SmokeConfig struct {
	// StartingColor is what the text wears before the smoke reaches it. It is
	// ignored when the engine resolves to the input's own colours, except for
	// a character that arrived carrying none.
	StartingColor Color
	// SmokeSymbols are played in order as the smoke passes over a character.
	SmokeSymbols []string
	// SmokeGradientStops colour the smoke itself. They run into the final
	// gradient's stops, reversed, so the smoke thins out into the colour the
	// text is about to take.
	SmokeGradientStops []Color
	// UseWholeCanvas lets the smoke out of the text block and over the whole
	// canvas.
	UseWholeCanvas bool
	// FinalGradientStops colour the text once the smoke has passed. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

SmokeConfig tunes the smoke effect.

func DefaultSmokeConfig added in v0.4.0

func DefaultSmokeConfig() SmokeConfig

DefaultSmokeConfig is upstream's default smoke.

type Spotlights added in v0.4.0

type Spotlights struct {
	// contains filtered or unexported fields
}

Spotlights sweeps beams of light over a darkened screen. Nothing moves and nothing is hidden: every character is on screen from the first frame wearing a dimmed version of the colour it will end on, and a beam passing over it brings it up to full brightness and drops it back again.

It is an effect that passes over the screen rather than one that assembles it, so under DynamicExistingColors the whole picture, backgrounds included, is there from frame one. Upstream already builds it that way.

The beams themselves are characters the effect adds to the terminal and never makes visible. They exist to carry a path, and their coordinate is what the illumination is measured from.

func NewSpotlights added in v0.4.0

func NewSpotlights(config SpotlightsConfig) *Spotlights

NewSpotlights builds the effect.

func (*Spotlights) Advance added in v0.4.0

func (s *Spotlights) Advance(e *Engine) bool

Advance lights the characters under the beams, then moves the beams on.

func (*Spotlights) Build added in v0.4.0

func (s *Spotlights) Build(e *Engine) error

Build works out the lit and unlit colour of every character, shows the whole screen in its unlit colour, and sends the beams off on their wander.

type SpotlightsConfig added in v0.4.0

type SpotlightsConfig struct {
	// BeamWidthRatio sets how wide a beam is. The beam reaches the smaller of
	// the canvas dimensions divided by this number, so a larger value gives a
	// narrower beam. It must be above zero.
	BeamWidthRatio float64
	// BeamFalloff is how much of the beam's width is soft edge, as a fraction
	// of the whole. At 0.3 the outer three tenths fade off, and a character
	// right on the rim shows at a fifth of its brightness. Zero gives a hard
	// edged beam.
	BeamFalloff float64
	// SearchDuration is how many frames the beams wander for before they head
	// for the middle of the canvas.
	SearchDuration int
	// SearchSpeedMin and SearchSpeedMax bound the speed of each leg of a
	// beam's wandering. Every leg picks its own speed from this range, which
	// is what stops several beams moving in step.
	//
	// ttfx carries this as one range argument. Two fields read better in Go
	// and hold the same two numbers.
	SearchSpeedMin float64
	SearchSpeedMax float64
	// SpotlightCount is how many beams search. They all converge on the same
	// point, and only the first of them stays on to do the widening.
	SpotlightCount int
	// FinalGradientStops colour the text the beams travel over. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

SpotlightsConfig tunes the spotlights effect.

func DefaultSpotlightsConfig added in v0.4.0

func DefaultSpotlightsConfig() SpotlightsConfig

DefaultSpotlightsConfig is upstream's default spotlights.

type Spray added in v0.4.0

type Spray struct {
	// contains filtered or unexported fields
}

Spray fires every character out of a single point and lets it fly to where it belongs. Nothing is on the screen until the nozzle reaches it, so the text assembles rather than being swept over.

func NewSpray added in v0.4.0

func NewSpray(config SprayConfig) *Spray

NewSpray builds the effect.

func (*Spray) Advance added in v0.4.0

func (s *Spray) Advance(e *Engine) bool

Advance fires a burst and reports whether the effect is still going.

func (*Spray) Build added in v0.4.0

func (s *Spray) Build(e *Engine) error

Build parks every character on the nozzle and gives it a path home and a scene to wear on the way.

type SprayConfig added in v0.4.0

type SprayConfig struct {
	// SprayPosition is where on the canvas the characters are fired from.
	SprayPosition SprayPosition
	// SprayVolume is how many characters leave the nozzle each frame, as a
	// fraction of the total. It is a ceiling: each frame fires somewhere
	// between one character and that many.
	SprayVolume float64
	// MovementSpeedLow and MovementSpeedHigh bound each character's flight
	// speed.
	MovementSpeedLow  float64
	MovementSpeedHigh float64
	// MovementEasing shapes the flight. The default leaves the nozzle fast
	// and drifts into place.
	MovementEasing Easing
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

SprayConfig tunes the spray effect.

func DefaultSprayConfig added in v0.4.0

func DefaultSprayConfig() SprayConfig

DefaultSprayConfig is upstream's default spray.

type SprayPosition added in v0.4.0

type SprayPosition int

SprayPosition is the point on the canvas every character is fired from.

const (
	SprayEast SprayPosition = iota
	SprayNorth
	SprayNorthEast
	SpraySouth
	SpraySouthEast
	SprayWest
	SprayNorthWest
	SpraySouthWest
	SprayCenter
)

The nine spray origins. They name a compass point on the canvas edge, plus the middle. East is first so the zero value is upstream's default.

func ParseSprayPosition added in v0.4.0

func ParseSprayPosition(name string) (SprayPosition, bool)

ParseSprayPosition looks up a spray origin by its upstream name.

type Swarm added in v0.4.0

type Swarm struct {
	// contains filtered or unexported fields
}

Swarm splits the text into groups, launches one group at a time from a point off the canvas, and flies each group between a few gathering areas before its members break away to their own cells.

This effect assembles the screen rather than passing over it, so under DynamicExistingColors it still starts from an empty canvas: a character becomes visible only when its swarm launches. Showing every character from the first frame, which is what a sweeping effect has to do, would leave the finished picture on screen with the swarms crawling over the top of it.

func NewSwarm added in v0.4.0

func NewSwarm(config SwarmConfig) *Swarm

NewSwarm builds the effect.

func (*Swarm) Advance added in v0.4.0

func (s *Swarm) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*Swarm) Build added in v0.4.0

func (s *Swarm) Build(e *Engine) error

Build cuts the text into swarms and gives every character its flight plan: a flash scene for the moves, a path per gathering area with two short hops inside it, and a landing path back to its own cell.

type SwarmConfig added in v0.4.0

type SwarmConfig struct {
	// BaseColors are the swarm body colours. Each swarm picks one.
	BaseColors []Color
	// FlashColor is what a character wears at the top of every flight.
	FlashColor Color
	// SwarmSize is the share of all characters in one swarm, from 0 to 1.
	SwarmSize float64
	// SwarmCoordination is the chance that a character follows the first of
	// its swarm to reach the next gathering area, from 0 to 1.
	SwarmCoordination float64
	// SwarmAreaCountLow and SwarmAreaCountHigh bound how many gathering areas
	// a swarm visits.
	SwarmAreaCountLow  int
	SwarmAreaCountHigh int
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

SwarmConfig tunes the swarm effect.

func DefaultSwarmConfig added in v0.4.0

func DefaultSwarmConfig() SwarmConfig

DefaultSwarmConfig is upstream's default swarm.

type Sweep added in v0.4.0

type Sweep struct {
	// contains filtered or unexported fields
}

Sweep runs two bands across the canvas. The first uncovers every cell in grey, the second passes back the other way and leaves each cell in its final colour. Nothing moves: a cell wears the band for a moment as it goes by.

This effect assembles the screen rather than passing over it. The first band is the reveal: every character starts hidden and is only shown when the band reaches it. So under DynamicExistingColors the characters stay hidden at build time, unlike waves, which passes over a picture that is already there.

func NewSweep added in v0.4.0

func NewSweep(config SweepConfig) *Sweep

NewSweep builds the effect.

func (*Sweep) Advance added in v0.4.0

func (s *Sweep) Advance(e *Engine) bool

Advance releases whichever bands the easing curve has reached and reports whether the effect is still going. When the first band runs out the easer is pointed at the second sweep's groups and reset, which is what turns one pass into two.

func (*Sweep) Build added in v0.4.0

func (s *Sweep) Build(e *Engine) error

Build gives every character on the canvas a grey scene for the first band and a colouring scene for the second, then groups the characters into the bands each sweep travels through.

type SweepConfig added in v0.4.0

type SweepConfig struct {
	// SweepSymbols are the glyphs a cell cycles through while a band is on it.
	// The default fades from a solid block down to a light one, which is what
	// makes the band read as a shimmer rather than a wipe.
	SweepSymbols []string
	// FirstSweepDirection is the axis the first band travels along. That band
	// uncovers the characters in grey.
	FirstSweepDirection CharacterGroup
	// SecondSweepDirection is the axis the second band travels along. That
	// band colours the characters.
	SecondSweepDirection CharacterGroup
	// FinalGradientStops colour the text once both bands have passed. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

SweepConfig tunes the sweep effect.

func DefaultSweepConfig added in v0.4.0

func DefaultSweepConfig() SweepConfig

DefaultSweepConfig is upstream's default sweep.

type SyncMetric

type SyncMetric int

SyncMetric ties an animation to the progress of the character's motion.

const (
	SyncNone SyncMetric = iota
	SyncDistance
	SyncStep
)

The two sync metrics, plus the absence of one.

type SynthGrid added in v0.4.0

type SynthGrid struct {
	// contains filtered or unexported fields
}

SynthGrid draws a grid over the canvas, then fills the blocks it made a few at a time, then takes the grid back down.

It assembles the screen rather than passing over it: every character starts hidden and is shown when its block's turn comes. So under DynamicExistingColors it keeps upstream's hiding, unlike a sweep such as waves, which has to show the picture from the first frame. Showing everything up front here would leave nothing for the blocks to fill in.

func NewSynthGrid added in v0.4.0

func NewSynthGrid(config SynthGridConfig) *SynthGrid

NewSynthGrid builds the effect.

func (*SynthGrid) Advance added in v0.4.0

func (s *SynthGrid) Advance(e *Engine) bool

Advance runs one frame of whichever phase the effect is in and reports whether it is still going.

func (*SynthGrid) Build added in v0.4.0

func (s *SynthGrid) Build(e *Engine) error

Build lays out the grid, works out which characters fall in which block, and gives every character the scene that flickers it into place.

type SynthGridConfig added in v0.4.0

type SynthGridConfig struct {
	// GridGradientStops and GridGradientSteps colour the grid lines, painted
	// across the whole canvas rather than across the text.
	GridGradientStops []Color
	GridGradientSteps []int
	// GridGradientDirection is the axis the grid colour runs along.
	GridGradientDirection GradientDirection
	// TextGradientStops and TextGradientSteps colour the text once it
	// resolves. They are ignored when the engine is set to resolve to the
	// input's own colours.
	TextGradientStops []Color
	TextGradientSteps []int
	// TextGradientDirection is the axis the text colour runs along.
	TextGradientDirection GradientDirection
	// GridRowSymbol and GridColumnSymbol draw the horizontal and vertical
	// grid lines.
	GridRowSymbol    string
	GridColumnSymbol string
	// TextGenerationSymbols are the glyphs a cell flickers through before it
	// settles into its own character.
	TextGenerationSymbols []string
	// MaxActiveBlocks is the share of the grid's blocks allowed to be filling
	// in at once. At 0.1 a tenth of the screen is working at any moment, so
	// the fill reads as a sweep rather than as one flash.
	MaxActiveBlocks float64
}

SynthGridConfig tunes the synthgrid effect.

func DefaultSynthGridConfig added in v0.4.0

func DefaultSynthGridConfig() SynthGridConfig

DefaultSynthGridConfig is upstream's default synthgrid.

type Terminal

type Terminal struct {
	Config TerminalConfig
	Canvas *Canvas

	// Characters is every character, in allocation order.
	Characters []*Character

	InputCharacters     []*Character
	InnerFillCharacters []*Character
	OuterFillCharacters []*Character
	AddedCharacters     []*Character
	// contains filtered or unexported fields
}

Terminal owns every character and paints the frame.

func NewTerminalFromCells

func NewTerminalFromCells(grid [][]InputCell, cfg TerminalConfig) *Terminal

NewTerminalFromCells builds a terminal from a captured cell grid. Row zero of the grid is the top of the screen, matching how screen captures are stored; the canvas flips it so row 1 is the bottom.

A cell's own colours become the character's input colours, so an effect run with DynamicExistingColors resolves the screen back to how it looked.

func NewTerminalFromText

func NewTerminalFromText(input string, cfg TerminalConfig) *Terminal

NewTerminalFromText builds a terminal from plain text. Tabs expand, trailing blank space is dropped, and each remaining rune becomes a character.

func (*Terminal) AddCharacter

func (t *Terminal) AddCharacter(symbol string, coord Coord) *Character

AddCharacter creates a character that was not in the input. It joins the added population only, so ordinary queries do not see it.

func (*Terminal) CharacterAtInputCoord

func (t *Terminal) CharacterAtInputCoord(coord Coord) *Character

CharacterAtInputCoord finds the character that started at a coordinate.

func (*Terminal) CollectCharacters

func (t *Terminal) CollectCharacters(filter CharacterFilter) []*Character

CollectCharacters returns the selected populations in allocation order.

func (*Terminal) Frame

func (t *Terminal) Frame() string

Frame returns the current frame as an ANSI string, top row first, with rows separated by newlines and no trailing newline.

func (*Terminal) FrameRows

func (t *Terminal) FrameRows() [][]*CharacterVisual

FrameRows returns the current frame as rows of visuals, top row first. A nil entry is an empty cell. The slices are reused between calls, so a caller that keeps them must copy.

func (*Terminal) GetCharacters

func (t *Terminal) GetCharacters(rng *Rng, filter CharacterFilter, order CharacterSort) []*Character

GetCharacters returns the selected populations in the requested order.

func (*Terminal) GetCharactersGrouped

func (t *Terminal) GetCharactersGrouped(filter CharacterFilter, grouping CharacterGroup) [][]*Character

GetCharactersGrouped buckets the selected populations into ordered groups.

func (*Terminal) Neighbors added in v0.3.0

func (t *Terminal) Neighbors(ch *Character) []*Character

Neighbors returns the characters directly north, east, south and west of a character, in that order, skipping any of the four cells that holds none. Diagonals are not neighbours.

Upstream snapshots these four slots onto every character when the terminal is built. This looks them up on demand instead. The two are equivalent: the snapshot is taken from the same input-coordinate table this reads, and the table never changes afterwards because AddCharacter deliberately stays out of it. Looking them up saves four pointers per character over a full screen.

A character created with AddCharacter has no neighbours, because it has no entry in the table. That matches upstream.

func (*Terminal) SetCharacterVisibility

func (t *Terminal) SetCharacterVisibility(ch *Character, visible bool)

SetCharacterVisibility shows or hides a character.

type TerminalConfig

type TerminalConfig struct {
	// Width and Height are the canvas size in cells. Both must be above zero.
	Width  int
	Height int
	// TabWidth is how many columns a tab expands to. Zero means four.
	TabWidth int
	// ExistingColorHandling decides what happens to colours the input carried.
	ExistingColorHandling ExistingColorHandling
	// AnchorText pins the input block inside the canvas.
	AnchorText Anchor
	// MakeFillCharacters populates every empty canvas cell with a space
	// character that effects can animate. Effects that only touch input
	// characters do not need it, and it costs one character per empty cell.
	MakeFillCharacters bool
}

TerminalConfig sets up the canvas an effect draws on.

type Thunderstorm added in v0.4.0

type Thunderstorm struct {
	// contains filtered or unexported fields
}

Thunderstorm dims the text, rains on it, and strikes it with lightning.

The text is on screen the whole time: it fades down to a dim version of itself, weather happens over the top of it, and it fades back up at the end. A strike is drawn as a column of added characters that walks down from the top of the canvas one small batch a frame, forking now and then. When it lands, every character on the screen flashes, the cells the strike passed through are left glowing, and a handful of sparks fly out of the point of impact and cool as they go.

This effect passes over the screen rather than assembling it. Every character is visible and wearing the colour it will settle back to from the first frame, in every colour mode, because there is nothing here that reveals the text: the storm plays over a picture that is already there. So there is no reveal to defer and no waves-style pre-show to add under DynamicExistingColors, and upstream needs no deviation for it. Backgrounds need none either: ttfx already carries the input background through the dim, the flash and the glow, so a selection bar or a filled panel on a captured screen dims and brightens with everything else instead of blinking out.

func NewThunderstorm added in v0.4.0

func NewThunderstorm(config ThunderstormConfig) *Thunderstorm

NewThunderstorm builds the effect.

func (*Thunderstorm) Advance added in v0.4.0

func (t *Thunderstorm) Advance(e *Engine) bool

Advance runs one frame.

func (*Thunderstorm) Build added in v0.4.0

func (t *Thunderstorm) Build(e *Engine) error

Build stocks the two particle pools and the strike characters, then gives every input character the four scenes it needs: the dim it starts with, the brighten it ends with, the flash a strike sets off and the glow a strike leaves behind.

type ThunderstormConfig added in v0.4.0

type ThunderstormConfig struct {
	// LightningColor is what a strike is drawn in.
	LightningColor Color
	// GlowingTextColor is the colour a character is left glowing after a
	// strike has passed through its cell.
	GlowingTextColor Color
	// TextGlowTime is how long each colour of that glow is held. Raise it to
	// cool the text more slowly.
	TextGlowTime int
	// RaindropSymbols are what a raindrop can look like.
	RaindropSymbols []string
	// SparkSymbols are what a spark thrown off by an impact can look like.
	SparkSymbols []string
	// SparkGlowColor is the colour a spark starts at before it cools.
	SparkGlowColor Color
	// SparkGlowTime is how long each colour of a spark's cooling is held.
	SparkGlowTime int
	// StormTime is how long the storm lasts, in seconds. The engine's clock
	// counts frames rather than the machine, so this is seconds of animation
	// at the rate the host says it paints at; see clock.go.
	StormTime int
	// FinalGradientStops colour the text once the sky has cleared. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
	// FinalGradientFrames is carried for parity with upstream's options and
	// changes nothing. Upstream declares it and never reads it: thunderstorm
	// has no separate final-gradient scene, because the text is already
	// wearing its final colour before the storm starts and only dims away
	// from it and back.
	FinalGradientFrames int
}

ThunderstormConfig tunes the thunderstorm effect.

func DefaultThunderstormConfig added in v0.4.0

func DefaultThunderstormConfig() ThunderstormConfig

DefaultThunderstormConfig is upstream's default thunderstorm.

type TuffBaby added in v0.5.0

type TuffBaby struct {
	// contains filtered or unexported fields
}

TuffBaby gathers the text on screen into a portrait, plays the clip in it, and puts the text back.

The characters that make up the picture are the ones that were already there. When there are more than the picture needs, the surplus flies off the nearest edge and waits there; when there are fewer, the effect appends characters of its own, which fly in from off screen and leave the same way.

The cells are the union of every frame, so a character keeps one cell for the whole run. A frame that does not cover its cell hides it, which is how the silhouette moves without anything moving.

func NewTuffBaby added in v0.5.0

func NewTuffBaby(config TuffBabyConfig) *TuffBaby

NewTuffBaby builds the effect.

func (*TuffBaby) Advance added in v0.5.0

func (t *TuffBaby) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*TuffBaby) Build added in v0.5.0

func (t *TuffBaby) Build(e *Engine) error

Build scales the picture to the canvas, hands out its cells, and starts every character on its way there.

type TuffBabyConfig added in v0.5.0

type TuffBabyConfig struct {
	// GatherEase and GatherSpeed shape the flight from the text into the
	// picture. HomeEase and HomeSpeed shape the flight back.
	GatherEase  Easing
	GatherSpeed float64
	HomeEase    Easing
	HomeSpeed   float64

	// ExitEase and ExitSpeed shape the surplus's flight off the screen.
	//
	// The surplus is not part of the picture and on a wide screen there is
	// more of it than there is picture: 240 columns of text hands the picture
	// about five thousand characters and leaves about six thousand over.
	// Flown at the gather's speed and curve, that surplus takes three
	// quarters of a second to clear, and it does not clear evenly. The
	// gather eases out, so a character covers most of its distance at once
	// and then creeps the last of the way; applied to a flight whose target
	// is an edge, that puts every surplus character near an edge early and
	// leaves it there. The left and right of the screen carry a readable
	// band of text for the whole of that time, while the picture in the
	// middle is still nothing anyone can recognise. The wider the terminal
	// the further the far characters have to come and the longer the band
	// lasts, which is why it reads as debris rather than as motion.
	//
	// So the surplus leaves at its own speed and, by default, without the
	// gather's easing. The surplus is draining off the edges of the screen
	// and a drain wants an even flux: at a constant rate nothing queues up
	// at the door, and the band that forms is both thinner than the gather's
	// and gone in a third of the time. The step per frame stays what the
	// gather's own opening frames are, so it still reads as flight.
	//
	// It also flies the straight line rather than the gather's arc. The arc
	// turns a character into the picture, which is what a character joining
	// the picture is doing; a character leaving is only getting out of the
	// way, and an arc there is a detour across the thing the reader is
	// trying to watch form.
	//
	// A speed of zero or less falls back to GatherSpeed, GatherEase and the
	// arc, so a config built by hand before these fields existed behaves
	// exactly as it did.
	ExitEase  Easing
	ExitSpeed float64

	// PoseFrames is how long the first frame of the clip is held before it
	// starts playing, so a reader gets to see what the picture is.
	PoseFrames int
	// Loops is how many times the clip plays.
	Loops int
	// SourceFrameRate is how many of the clip's frames play per second of
	// engine time. The source runs at ten, so ten plays it at its own speed.
	//
	// It is read against Engine.Clock rather than against a count of host
	// frames, so the clip lasts the same number of seconds whether the host
	// paints sixty frames a second or two hundred and forty. A host that
	// leaves the engine's clock at a rate it does not paint at gets the clip
	// at the wrong speed, which is the clock's contract, not this field's.
	SourceFrameRate float64

	// FillerSymbols are the symbols appended characters wear when the screen
	// carries no text of its own to recycle.
	FillerSymbols []string

	// Tones colour the picture, darkest first. There must be exactly
	// tuffToneCount of them, matching the levels the reference was reduced to.
	//
	// The defaults are not the source's own greys. The source is a light
	// picture on a white ground and a terminal is the other way up: the
	// darkest band painted as the near-black it really is takes the whole
	// shadowed side of the face out. The ramp keeps the source's tonal order
	// and lifts its dark end until every band reads against a dark terminal.
	Tones []Color

	// FinalGradientStops colour the text once it is home again. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

TuffBabyConfig tunes the tuffbaby effect.

func DefaultTuffBabyConfig added in v0.5.0

func DefaultTuffBabyConfig() TuffBabyConfig

DefaultTuffBabyConfig is the effect as it is meant to be seen.

type Unstable added in v0.4.0

type Unstable struct {
	// contains filtered or unexported fields
}

Unstable scatters the characters into each other's places, shakes the whole screen while it heats up, throws every character off the nearest edge, and then flies them all back where they belong.

It reassembles rather than sweeps, but nothing is hidden at any point: the characters are all on screen from the first frame, standing in the wrong places. That is upstream's own behaviour, so DynamicExistingColors needs no deviation for it here.

func NewUnstable added in v0.4.0

func NewUnstable(config UnstableConfig) *Unstable

NewUnstable builds the effect.

func (*Unstable) Advance added in v0.4.0

func (u *Unstable) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

It steps the characters itself rather than calling Engine.Update, as ttfx does: the rumble advances animation without motion, and both flights decide what stays active by where a character has got to rather than by whether it still has work. The clock therefore does not move during this effect, which is also ttfx's behaviour and costs nothing, since unstable never reads it.

func (*Unstable) Build added in v0.4.0

func (u *Unstable) Build(e *Engine) error

Build scrambles the characters into each other's coordinates, gives each one the flight out and the flight home, and starts the rumble.

type UnstableConfig added in v0.4.0

type UnstableConfig struct {
	// UnstableColor is the colour a character heats up to while it rumbles.
	UnstableColor Color
	// ExplosionEase and ExplosionSpeed shape the flight out to the canvas edge.
	ExplosionEase  Easing
	ExplosionSpeed float64
	// ReassemblyEase and ReassemblySpeed shape the flight back home.
	ReassemblyEase  Easing
	ReassemblySpeed float64
	// FinalGradientStops colour the text once it lands. They are ignored when
	// the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

UnstableConfig tunes the unstable effect.

func DefaultUnstableConfig added in v0.4.0

func DefaultUnstableConfig() UnstableConfig

DefaultUnstableConfig is upstream's default unstable.

type VhsTape

type VhsTape struct {
	// contains filtered or unexported fields
}

VhsTape plays the screen back off a worn tape: rows slip sideways with colour fringing, a band of tracking noise walks up the picture, the whole thing dissolves into snow, and then it is redrawn row by row.

func NewVhsTape

func NewVhsTape(config VhsTapeConfig) *VhsTape

NewVhsTape builds the effect.

func (*VhsTape) Advance

func (v *VhsTape) Advance(e *Engine) bool

Advance runs one frame and reports whether the effect is still going.

func (*VhsTape) Build

func (v *VhsTape) Build(e *Engine) error

Build gives every row its slip paths and every character its colour-fringing, snow and redraw scenes.

type VhsTapeConfig

type VhsTapeConfig struct {
	// GlitchLineColors are cycled through by a row that slips, which is what
	// gives it the colour fringing of a misaligned tape head.
	GlitchLineColors []Color
	// NoiseColors are the greys the snow is drawn in.
	NoiseColors []Color
	// GlitchLineChance is the chance per frame that another row slips.
	GlitchLineChance float64
	// NoiseChance is the chance per frame that the whole picture takes snow.
	NoiseChance float64
	// TotalGlitchTime is how many frames the glitching lasts before the tape
	// gives up and the picture is redrawn.
	TotalGlitchTime int
	// FinalGradientStops colour the redrawn picture. They are ignored when the
	// engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

VhsTapeConfig tunes the vhstape effect.

func DefaultVhsTapeConfig

func DefaultVhsTapeConfig() VhsTapeConfig

DefaultVhsTapeConfig is upstream's default vhstape.

type VisualParams

type VisualParams struct {
	Bold      bool
	Italic    bool
	Underline bool
	Colors    ColorPair
}

VisualParams are the settable fields of a CharacterVisual.

type Waves

type Waves struct {
	// contains filtered or unexported fields
}

Waves runs a band of rising and falling blocks across the screen. Nothing moves: each character wears the wave for a moment as it passes and is left behind it in its own colour.

func NewWaves

func NewWaves(config WavesConfig) *Waves

NewWaves builds the effect.

func (*Waves) Advance

func (w *Waves) Advance(e *Engine) bool

Advance releases one band per frame and reports whether the effect is still going.

func (*Waves) Build

func (w *Waves) Build(e *Engine) error

Build gives every character a wave scene and the settling scene that follows it, then groups the characters into the bands the wave travels through.

type WavesConfig

type WavesConfig struct {
	// WaveSymbols are the glyphs a character cycles through as a wave passes
	// over it. The default rises and falls, which is what makes it read as a
	// wave rather than a flicker.
	WaveSymbols []string
	// WaveGradientStops and WaveGradientSteps colour the wave itself.
	WaveGradientStops []Color
	WaveGradientSteps []int
	// WaveCount is how many times the wave runs before the characters settle.
	WaveCount int
	// WaveLength is how many frames each step of the wave holds. Raise it to
	// slow the wave down.
	WaveLength int
	// WaveDirection is the axis the wave travels along.
	WaveDirection CharacterGroup
	// WaveEasing shapes the wave's travel across each character.
	WaveEasing Easing
	// FinalGradientStops colour the text once the waves stop. They are ignored
	// when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
}

WavesConfig tunes the waves effect.

func DefaultWavesConfig

func DefaultWavesConfig() WavesConfig

DefaultWavesConfig is upstream's default waves.

type Waypoint

type Waypoint struct {
	ID            string
	Coord         Coord
	BezierControl []Coord
}

Waypoint is a point a Path passes through. Bezier control points bend the approach to it.

type WeightedLink struct {
	CharA  *Character
	CharB  *Character
	Weight int
}

WeightedLink is one candidate edge and the weight it was drawn with.

type Wipe added in v0.4.0

type Wipe struct {
	// contains filtered or unexported fields
}

Wipe sweeps a line across the screen and shows each character as the line reaches it. Nothing moves and nothing is thrown off: the effect assembles the picture behind the line, so a character is hidden until its turn comes.

func NewWipe added in v0.4.0

func NewWipe(config WipeConfig) *Wipe

NewWipe builds the effect.

func (*Wipe) Advance added in v0.4.0

func (w *Wipe) Advance(e *Engine) bool

Advance moves the line on by one step and reports whether the effect is still going.

func (*Wipe) Build added in v0.4.0

func (w *Wipe) Build(e *Engine) error

Build gives every character the scene it plays when the line reaches it, and groups the characters into the lines the wipe travels through.

type WipeConfig added in v0.4.0

type WipeConfig struct {
	// WipeDirection is the axis the wipe line travels along. The groups it
	// names are released one after another, and each group is one line of the
	// wipe.
	WipeDirection CharacterGroup
	// WipeDelay is how many frames to wait between groups. Zero releases one
	// group per frame.
	WipeDelay int
	// WipeEase shapes how fast the line crosses the screen. It is applied to
	// the position of the line, not to any one character, so an easing that
	// overshoots and comes back takes characters off the screen again on the
	// way back.
	WipeEase Easing
	// FinalGradientStops colour the text once the line has passed. They are
	// ignored when the engine is set to resolve to the input's own colours.
	FinalGradientStops     []Color
	FinalGradientSteps     []int
	FinalGradientDirection GradientDirection
	// FinalGradientFrames is how many frames each step of that colour ramp
	// holds.
	FinalGradientFrames int
}

WipeConfig tunes the wipe effect.

func DefaultWipeConfig added in v0.4.0

func DefaultWipeConfig() WipeConfig

DefaultWipeConfig is upstream's default wipe.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL