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

    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

              func UseScreen() tcell.Screen

                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

                func UseState(initial State) (State, SetState)

                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 Action

                type Action = func(s State) State

                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

                        func CreateContext(initial State) *Context

                          CreateContext allows you to create a Context that can be used with UseContext It must be called from outside your Component.

                          func (*Context) Mount

                          func (c *Context) Mount(state State)

                          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 EdgeSizes

                              type EdgeSizes struct {
                              	Top    int
                              	Right  int
                              	Bottom int
                              	Left   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

                                      type EventHandler = func(e *tcell.Event)

                                        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 SetState

                                                                type SetState = func(a Action)

                                                                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

                                                                  func UseContext(c *Context) State

                                                                    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.

                                                                      Directories

                                                                      Path Synopsis
                                                                      internal
                                                                      quadtree
                                                                      Via https://github.com/JamesLMilner/quadtree-go Converted to ints for our use case
                                                                      Via https://github.com/JamesLMilner/quadtree-go Converted to ints for our use case