Pixels. I don't like pixels. Some people call them cells. Like prison cells... They are all just abstraction, while me, a terminal, I'm real.
My author here, attempted to write me better. He likes channels. He is more suitable to work on Danube-Black Sea channel. Or just get a life, instead of using Go channels to experiment on me.
He reaped the work of a lifetime from other people for that. Just a bunch of constants and concepts and patterns, if you ask me.
He thinks that something goes faster if you have less allocation. We will see in the end. He also thinks in separation of concerns, whatever that means. It's probably abstraction too. Just like pixels, and I don't like pixels.
Well, everything started while playing with tcell.
I've asked myself why there is no separation of concerns inside of it. I mean, the problem it's simple: we have a file reader - for reading input from keyboard and mouse - and a file writer to display stuff.
Everything else are just rules and functionality which terminals provide, a bunch of predefined []byte
which are used to send commands.
While thinking about separation of concerns, I've asked myself about context.Context
: is it or it is not for cancellation?
It seems that in this context, the context.Context
is suitable for cancellation usage. The Application
will provide a cancellable context.Context
and upon shutdown (think CTRL+Q
or double ESC
), it will cancel it.
This holds various terminal specific commands, which are registered
and looked up by the core
.
For using as little allocation as possible, I've created []byte
slices for each and every used command, so instead of passing string
around, we're just using those slices to write to output.
Also, there are caches for goto
and colors
, so []byte
required to be written in output is cached.
Despite the fact that is has public methods and properties, it's not intended for direct usage, being core
's responsibility to orchestrate the writes to output.
Creates key, event and resize dispatchers. All events are passed via channels, to avoid allocations.
The core
constructor supports functional options : NewCore(termEnv string, options ...Option)
.
Possible options are :
WithFinalizer
- for the case whenApplication
want to execute a function prior shutdown.WithWinSizeBufferedChannelSize
-Application
can set the size of the buffered channel. Defaults toruntime.NumCPU()
.WithRunesFallback
-Application
can set the runes fallback upon constructing.WithTrueColor
- a functional option soApplication
can send "disable" to disable true color
- offers information about terminal (size, colors, has mouse, charset, keys)
- registers/unregister listeners for
term.Pixel
changes - registers/unregister Mouse and Key handlers
- registers/unregister Resize event handlers
- offers ability to show or hide the cursor
- the
core
exists for the wholeApplication
lifecycle
Constructor returns an interface. The Engine
interface:
DyingChan() chan struct{}
- it's a channel that needs to be listened insideApplication
, to allow gracefully shutdowns.Start(ctx context.Context) error
- theApplication
call this method with a cancellable context, socore
will shutdown upon cancellation.ResizeDispatcher() ResizeDispatcher
- exposes the resize dispatcher, soComponents
can Register themselves to listening events.KeyDispatcher() KeyDispatcher
- exposes the key dispatcher, soComponents
can Register themselves to listening events.MouseDispatcher() MouseDispatcher
- exposes the mouse dispatcher, soComponents
can Register themselves to listening events.CanDisplay(r rune, checkFallbacks bool) bool
- checks if the rune can be displayed in terminal.CharacterSet() string
- returns the current char set.SetRuneFallback(orig rune, fallback string)
- sets the fallback for a rune.UnsetRuneFallback(orig rune)
- forgets the fallback set above.NumColors() int
- returns the number of colors that terminal supports.Size() *Size
- returns the current size of the window.HasTrueColor() bool
- returns the terminal support for true colors.Palette() []color.Color
- returns the terminal paletteColors() map[color.Color]color.Color
- returns the terminal color map.ActivePixels(pixels []PixelGetter)
- used byApplication
to orchestrate pixels. Pages will be able to have their own set of pixels.Redraw(pixels []PixelGetter)
- temporary exposed forApplication
to force draw.ShowCursor(where *term.Position)
- displays input cursor at coordinates.HideCursor()
- hides input cursor.Cursor() *term.Position
- returns current input cursor position.Clear()
- clears the screen.Style() Style
- returns the terminal styles and palette. Style is an interface.HasMouse() bool
- returns true if there is mouse support available.
ResizeEvent
is an interface has only one method Size() Size
and Size has - of course - Width and Height properties.
Application
must call Start(ctx context.Context) error
with a cancellable context, in order to use ActivePixels(pixels []PixelGetter)
registration.
A Pixel
is an interface which is known by both Application
and Engine
. The setters will write to a channel, so core
can receive the draw request, when a property of the pixel has changed.
A Pixel
constructor accepts the following functional options:
WithBackground
- presets the background color.WithForeground
- presets the foreground (text) color.WithPoint
- is required, and sets the position of the pixel (column and row).WithRune
- presets the rune.WithUnicode
- presets the unicode (optional, for that reason it is a pointer).WithAttrs
- presets thestyle.Mask
of thePixel
.
The Pixel
interface (includes PixelGetter
and PixelSetter
interfaces):
DrawCh() chan Pixel
- the channel used bycore
to listen redraw request.PositionHash() int
- the position hash of thePixel
.Style() (color.Color, color.Color, style.Mask)
- colors and attributes of thePixel
, expanded as foreground, background and attributesRune() rune
- rune.Width() int
- rune andUnicode
width.HasUnicode() bool
- exposes ifUnicode
is a nil pointer or not.Unicode() Unicode
-Unicode
if declared.Set(r rune, fg, bg color.Color)
- setter for rune and colors. If any of them changed, redraw request gets triggered.SetFgBg(fg, bg color.Color)
- setter for colors. If any of them changed, redraw request gets triggered.SetForeground(c color.Color)
- setter just for foreground.SetBackground(c color.Color)
- setter just for background.SetAttrs(m style.Mask)
- setter forstyle.Mask
.SetUnicode(u Unicode)
- setter forUnicode
.SetRune(r rune)
- setter for rune.SetAll(bg, fg color.Color, m style.Mask, r rune, u Unicode)
- setter for everything. If any of them changed, redraw request gets triggered.
Register(r KeyListener)
- used byComponents
to register to events listening. Events come via a channel (listener must implementKeyListener
interface).LifeCycle(ctx context.Context)
- used bycore
uponStart(ctx context.Context) error
call.HasKey(k Key) bool
- checks if terminal supports the key provided as parameter.DyingChan() chan struct{}
-core
listens to this channel to check if dispatcher has finished shutdown, upon context cancellation.InChan() chan []byte
-core
uses this channel to send input from terminal.
Register(r MouseListener)
- used byComponents
to register to events listening. Events come via a channel (listener must implementMouseListener
interface).LifeCycle(ctx context.Context, out *os.File)
- used bycore
uponStart(ctx context.Context) error
call.Enable()
- enables mouse supportDisable()
- disables mouse supportResizeListen() chan ResizeEvent
-core
uses this channel to send resize events.DyingChan() chan struct{}
-core
listens to this channel to check if dispatcher has finished shutdown, upon context cancellation.InChan() chan []byte
-core
uses this channel to send input from terminal.
Palette() []color.Color
- returns the known paletteColors() map[color.Color]color.Color
- returns all colors map