Documentation ¶
Overview ¶
package r is the core retort package
TODO: Explain how this all works.
TODO: link to the relevant files and what they do.
Index ¶
- func CreateElement(component Component, props Properties, children Children) *fiber
- func CreateFragment(children Children) *fiber
- func CreateScreenElement(calculateLayout CalculateLayout, render RenderToScreen, props Properties, ...) *fiber
- func Retort(root Element, config RetortConfiguration)
- func UseEffect(effect Effect, deps EffectDependencies)
- func UseQuit() func()
- func UseScreen() tcell.Screen
- func UseState(initial State) (State, SetState)
- type Action
- type ActionCreator
- type BlockLayout
- type BlockLayouts
- type CalculateLayout
- type CalculateLayoutStage
- type Children
- type Component
- type Context
- type DisplayCommand
- type DisplayList
- type DisplayListSortZIndex
- type EdgeSizes
- type Effect
- type EffectCancel
- type EffectDependencies
- type Element
- type EventHandler
- type EventHandlerMouse
- type EventHandlerMouseHover
- type EventMouseClick
- type EventMouseClickDrag
- type EventMouseClickRelease
- type EventMouseScroll
- type Properties
- type RenderToScreen
- type RetortConfiguration
- type SetState
- type State
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CreateElement ¶
func CreateElement( component Component, props Properties, children Children, ) *fiber
CreateElement is used to create the building blocks of a retort application, and the thing that Components are ultimately made up of, Elements.
import ( "github.com/gdamore/tcell" "retort.dev/r" "retort.dev/r/component" ) // Wrapper is a simple component that wraps the // children Components in a box with a white border. func Wrapper(p r.Properties) r.Element { children := r.GetProperty( p, r.Children{}, "Container requires r.Children", ).(r.Children) return r.CreateElement( component.Box, r.Properties{ component.BoxProps{ Border: component.Border{ Style: component.BorderStyleSingle, Foreground: tcell.ColorWhite, }, }, }, children, ) }
By creating an Element and passing Properties and Children seperately, retort can keep track of the entire tree of Components, and decide when to compute which parts, and in turn when to render those to the screen.
func CreateFragment ¶
func CreateFragment(children Children) *fiber
CreateFragment is like CreateElement except you do not need a Component or Properties. This is useful when you need to make Higher Order Components, or other Components that wrap or compose yet more Components.
func CreateScreenElement ¶
func CreateScreenElement( calculateLayout CalculateLayout, render RenderToScreen, props Properties, children Children, ) *fiber
CreateScreenElement is like a normal Element except it has the ability to render output to the screen.
Once retort has finished calculating which components have changed all those with changes are passed to a render function. This walks the tree and finds ScreenElements and calls their RenderToScreen function, passing in the current Screen.
RenderToScreen needs to return a BlockLayout, which is used among other things to direct Mouse Events to the right Component.
func Box(p r.Properties) r.Element { return r.CreateScreenElement( func(s tcell.Screen) r.BlockLayout { return BlockLayout }, nil, ) }
func Retort ¶
func Retort(root Element, config RetortConfiguration)
Retort is called with your root Component and any optional configuration to begin running retort.
func Example_app() { // Call the main function on retort to start the app, // when you call this, retort will take over the screen. r.Retort( // Root Element r.CreateElement( example.ClickableBox, r.Properties{ component.BoxProps{ Width: 100, // Make the root element fill the screen Height: 100, // Make the root element fill the screen Border: component.Border{ Style: component.BorderStyleSingle, Foreground: tcell.ColorWhite, }, }, }, r.Children{ // First Child r.CreateElement( example.ClickableBox, r.Properties{ component.BoxProps{ Border: component.Border{ Style: component.BorderStyleSingle, Foreground: tcell.ColorWhite, }, }, }, nil, // Pass nil as the third argument if there are no children ), // Second Child r.CreateElement( example.ClickableBox, r.Properties{ component.BoxProps{ Border: component.Border{ Style: component.BorderStyleSingle, Foreground: tcell.ColorWhite, }, }, }, nil, ), }, ), // Pass in optional configuration r.RetortConfiguration{} ) }
func UseEffect ¶
func UseEffect(effect Effect, deps EffectDependencies)
UseEffect is a retort hook that can be called in your Component to run side effects.
Where UseState allows your components to re-render when their State changes, UseEffect allows you to change that state when you need to.
Data fetching is a good example of when you would want something like UseEffect.
Example ¶
The example below is a reasonably simple one that changes the color of the border of a box ever 2 seconds. The point here is to show how you can run a goroutine in the UseEffect callback, and clean up the channel in the EffectCancel return function.
import ( "time" "github.com/gdamore/tcell" "retort.dev/component" "retort.dev/r" ) type EffectExampleBoxState struct { Color tcell.Color } func EffectExampleBox(p r.Properties) r.Element { boxProps := p.GetProperty( component.BoxProps{}, "Container requires ContainerProps", ).(component.BoxProps) children := p.GetProperty( r.Children{}, "Container requires r.Children", ).(r.Children) s, setState := r.UseState(r.State{ EffectExampleBoxState{Color: boxProps.Border.Foreground}, }) state := s.GetState( EffectExampleBoxState{}, ).(EffectExampleBoxState) r.UseEffect(func() r.EffectCancel { ticker := time.NewTicker(2 * time.Second) done := make(chan bool) go func() { for { select { case <-done: return case <-ticker.C: setState(func(s r.State) r.State { ms := s.GetState( EffectExampleBoxState{}, ).(EffectExampleBoxState) color := tcell.ColorGreen if ms.Color == tcell.ColorGreen { color = tcell.ColorBlue } if ms.Color == tcell.ColorBlue { color = tcell.ColorGreen } return r.State{EffectExampleBoxState{ Color: color, }, } }) } } }() return func() { <-done } }, r.EffectDependencies{}) // var mouseEventHandler r.MouseEventHandler mouseEventHandler := func(e *tcell.EventMouse) { color := tcell.ColorGreen if state.Color == tcell.ColorGreen { color = tcell.ColorBlue } if state.Color == tcell.ColorBlue { color = tcell.ColorGreen } setState(func(s r.State) r.State { return r.State{EffectExampleBoxState{ Color: color, }, } }) } boxProps.Border.Foreground = state.Color return r.CreateElement( component.Box, r.Properties{ boxProps, mouseEventHandler, }, children, ) }
func UseQuit ¶
func UseQuit() func()
UseQuit returns a single function that when invoked will exit the application
func UseScreen ¶
UseScreen returns a tcell.Screen allowing you to read and interact with the Screen directly.
Even though this means you can modify the Screen from anywhere, just as you should avoid DOM manipulation directly in React, you should avoid manipulating the Screen with this hook.
Use this hook to read information from the screen only.
If you need to write to the Screen, use a ScreenElement. This ensures when your Component has changes, retort will call your DisplayCommand function. Doing this any other way will gaurentee at some point things will get out of sync.
func UseState ¶
UseState provides local state for a Component.
With UseState you can make your components interactive, and repsonsive to either user input or anything else that changes.
UseState by itself only gives you the ability to change state, you generally need to pair this with either an EventHandler or UseEffect to provide interactivity.
Don't call setState inside your Component, as this will create an infinite rendering loop.
Example ¶
The following example shows how you can use state to change the color of a Box border when it's clicked.
import ( "github.com/gdamore/tcell" "retort.dev/component" "retort.dev/r/debug" "retort.dev/r" ) type MovingBoxState struct { Color tcell.Color } func ClickableBox(p r.Properties) r.Element { boxProps := p.GetProperty( component.BoxProps{}, "Container requires ContainerProps", ).(component.BoxProps) children := p.GetProperty( r.Children{}, "Container requires r.Children", ).(r.Children) s, setState := r.UseState(r.State{ MovingBoxState{Color: boxProps.Border.Foreground}, }) state := s.GetState( MovingBoxState{}, ).(MovingBoxState) mouseEventHandler := func(e *tcell.EventMouse) { debug.Log("mouseEventHandler", e, state) color := tcell.ColorGreen if state.Color == tcell.ColorGreen { color = tcell.ColorBlue } if state.Color == tcell.ColorBlue { color = tcell.ColorGreen } setState(func(s r.State) r.State { debug.Log("mouseEventHandler update state", e, state) return r.State{MovingBoxState{ Color: color, }, } }) } boxProps.Border.Foreground = state.Color return r.CreateElement( component.Box, r.Properties{ boxProps, mouseEventHandler, }, children, ) }
Types ¶
type ActionCreator ¶
type ActionCreator struct {
// contains filtered or unexported fields
}
type BlockLayout ¶
type BlockLayout struct {
X, Y, Rows, Columns int
Padding, Border, Margin EdgeSizes
// Grow, like flex-grow
// TODO: better docs
Grow int
// ZIndex is the layer this Box is printed on.
// Specifically, it determines the order of painting on the screen, with
// higher numbers being painted later, and appearing on top.
// This is also used to direct some events, where the highest zindex is used.
ZIndex int
// Order is set to control the display order of a group of children
Order int
// Fixed if the Rows, Columns are an explicit fixed size, else they're fluid
FixedRows, FixedColumns bool
// Valid is set when the BlockLayout has been initialised somewhere
// if it's false, it means we've got a default
Valid bool
}
BlockLayout is used by ScreenElements to determine the exact location to calculate/render from. It represents the concrete positioning information specific to the size of the terminal screen.
You never set this directly, it's calculated via a component like Box. Which allows for more expressive objects, with padding, and margin.
It is recalculated when the screen is resized.
This layout information is also used to calculate which elements mouse events effect.
You shouldn't use this except for a call to r.CreateScreenElement
type BlockLayouts ¶
type BlockLayouts = []BlockLayout
type CalculateLayout ¶
type CalculateLayout func( s tcell.Screen, stage CalculateLayoutStage, parentBlockLayout BlockLayout, children BlockLayouts, ) ( blockLayout BlockLayout, innerBlockLayout BlockLayout, childrenBlockLayouts BlockLayouts, )
CalculateLayout
childrenBlockLayouts will be empty until at least CalculateLayoutStageWithChildren
innerBlockLayout is the draw area for children blocks, and will be smaller due to padding or border effects
type CalculateLayoutStage ¶
type CalculateLayoutStage int
const ( // Initial Pass // Calculate implicit or explicit absolute bounds CalculateLayoutStageInitial CalculateLayoutStage = iota // After this Blocks children have calculated their layouts // we recalculate this blocks layou CalculateLayoutStageWithChildren // Final Pass CalculateLayoutStageFinal )
type Children ¶
type Children []*fiber
Children is a slice of Elements (or pointers to a fiber) It's used in r.CreateElement or r.CreateFragment to specify the child nodes of the Element. It can also be extracted from props with GetProperty or GetOptionalProperty, if you want to pass children on.
In general, unless you're creating a ScreenElement, you should pass any children passed into props on to the return Element.
type Component ¶
type Component func(props Properties) Element
Component is main thing you will be making to create a retort app. Your component must match this function signature.
type Context ¶
type Context struct {
// contains filtered or unexported fields
}
func CreateContext ¶
CreateContext allows you to create a Context that can be used with UseContext It must be called from outside your Component.
type DisplayCommand ¶
type DisplayCommand struct { RenderToScreen *RenderToScreen BlockLayout BlockLayout }
type DisplayList ¶
type DisplayList []DisplayCommand
DisplayList https://en.wikipedia.org/wiki/Display_list
func (DisplayList) Sort ¶
func (dl DisplayList) Sort()
Sort a DisplayList for rendering to screen, with respect to ZIndexes
type DisplayListSortZIndex ¶
type DisplayListSortZIndex []DisplayCommand
func (DisplayListSortZIndex) Len ¶
func (dl DisplayListSortZIndex) Len() int
func (DisplayListSortZIndex) Less ¶
func (dl DisplayListSortZIndex) Less(i, j int) bool
func (DisplayListSortZIndex) Swap ¶
func (dl DisplayListSortZIndex) Swap(i, j int)
type Effect ¶
type Effect func() EffectCancel
Effect is the function type you pass to UseEffect.
It must return an EffectCancel, even if there is nothing to clean up.
In the Effect you can have a routine to do something (such as fetching data), and then call SetState from a UseState hook, to update your Component.
type EffectCancel ¶
type EffectCancel func()
EffectCancel is a function that must be returned by your Effect, and is called when the effect is cleaned up or canceled. This allows you to finish anything you were doing such as closing channels, connections or files.
type EffectDependencies ¶
type EffectDependencies []interface{}
EffectDependencies lets you pass in what your Effect depends upon. If they change, your Effect will be re-run.
type Element ¶
type Element *fiber
Element is the smallest building block of retort. You create them with r.CreateElement or r.CreateFragment
Internally they are a pointer to a fiber, which is used to keep track of the render tree.
type EventHandler ¶
EventHandler is a Property you can add to a Component that will be called on every *tcell.Event that is created.
Use this sparingly as it's very noisy.
type EventHandlerMouse ¶
type EventHandlerMouse = func(e *tcell.EventMouse)
EventHandlerMouse is a Property you can add to a Component to be called when a *tcell.EventMouse is created.
type EventHandlerMouseHover ¶
type EventHandlerMouseHover = func()
EventHandlerMouseHover is called when a mouse is over your Component
type EventMouseClick ¶
type EventMouseClick = func( isPrimary, isSecondary bool, buttonMask tcell.ButtonMask, ) EventMouseClickRelease
EventMouseClick is called when a mouse clicks on your component. For conveince we pass isPrimary and isSecondary as aliases for Button1 and Button2.
type EventMouseClickDrag ¶
type EventMouseClickDrag = func()
EventMouseClickDrag is not yet implemented, but could be called to allow a component to render a version that is being dragged around
type EventMouseClickRelease ¶
type EventMouseClickRelease = func()
EventMouseClickRelease is called when the mouse click has been released. TODO: this can probably be enhanced to enable drag and drop
type EventMouseScroll ¶
type EventMouseScroll = func(up, down, left, right bool)
type Properties ¶
type Properties []interface{}
Properties are immutable state that is passed into a component, and pass down to components to share data.
Properties is ultimately a slice of interfaces, which lets you and retort and any components your using add any concrete structs to it. Because of this, there are some helper methods to retrieve props. These are GetProperty and GetOptionalProperty.
Properties can only contain one struct of a given type. In this sense the type of the struct is a key.
Sometimes called props.
func AddPropsIfNone ¶
func AddPropsIfNone(props Properties, prop interface{}) Properties
AddPropsIfNone will add the prop to props is no existing prop of that type is found.
func ReplaceProps ¶
func ReplaceProps(props Properties, prop interface{}) Properties
ReplaceProps by replacing with the same type you passed.
func (Properties) GetOptionalProperty ¶
func (props Properties) GetOptionalProperty( propType interface{}, ) interface{}
GetOptionalProperty will search props for the Property matching the type of struct you passed in. If it was not in props, the struct passed into propType will be returned.
You need to cast the return type of the function exactly the same as the struct you pass in.
This allows you to specify a defaults for a property.
In the following example if Wrapper is not passed a Property of the type component.BoxProps, the default values provided will be used.
func Wrapper(p r.Properties) r.Element { boxProps := p.GetOptionalProperty( component.BoxProps{ Border: component.Border{ Style: component.BorderStyleSingle, Foreground: tcell.ColorWhite, }, }, ).(component.BoxProps) return r.CreateElement( component.Box, r.Properties{ boxProps }, children, ) }
func (Properties) GetProperty ¶
func (props Properties) GetProperty( propType interface{}, errorMessage string, ) interface{}
Border: component.Border{ Style: component.BorderStyleSingle, Foreground: tcell.ColorWhite, }, }, }, children, ) }
type RenderToScreen ¶
type RenderToScreen func( s tcell.Screen, blockLayout BlockLayout, )
RenderToScreen is the callback passed to create a Screen Element
type RetortConfiguration ¶
type RetortConfiguration struct { // UseSimulationScreen to output to a simulated screen // this is useful for automated testing UseSimulationScreen bool // UseDebugger to show a d overlay with output from // the retort.dev/d#Log function UseDebugger bool // DisableMouse to prevent Mouse Events from being created DisableMouse bool }
RetortConfiguration allows you to enable features your app may want to use
type State ¶
type State []interface{}
State is local to a component. It is mutable via the setState function from UseState. Don't edit State directly, as retort will not know that you have, and will not trigger an update and re-render. It can be used to create new props to pass down to other components.
func UseContext ¶
UseContext lets you subscribe to changes of Context without nesting.
func (State) GetState ¶
func (state State) GetState(stateType interface{}) interface{}
GetState lets you retrieve the state of your passed in type from a UseState hook.
Because we cannot use generics this is the closest we can get. This is like Properties where the stateType type is a key to the struct in the slice of interfaces. As such, you can only have one of a given type in state.