├── LICENSE ├── README.md ├── component.go ├── components ├── progress.go ├── sparkline.go ├── spinner.go └── stopwatch.go ├── context.go ├── document.go ├── document_test.go ├── examples ├── counter.go ├── layout.go └── sparkline.go ├── finalize.go ├── flex ├── LICENSE ├── PATENTS ├── README.md ├── absolute_position_test.go ├── align_content_test.go ├── align_items_test.go ├── align_self_test.go ├── aspect_ratio_test.go ├── baseline_func_test.go ├── border_test.go ├── compute_margin_test.go ├── compute_padding_test.go ├── default_values_test.go ├── dimension_test.go ├── dirty_marking_test.go ├── display_test.go ├── edge_test.go ├── enums.go ├── flex_direction_test.go ├── flex_test.go ├── flex_wrap_test.go ├── had_overflow_test.go ├── issue5_test.go ├── justify_content_test.go ├── margin_test.go ├── math.go ├── math_test.go ├── measure_cache_test.go ├── measure_mode_test.go ├── measure_test.go ├── min_max_dimension_test.go ├── node_child_test.go ├── padding_test.go ├── percentage_test.go ├── print.go ├── relayout_test.go ├── rounding_function_test.go ├── rounding_measure_func_test.go ├── rounding_test.go ├── size_overflow_test.go ├── style_test.go ├── yoga.go ├── yoga_h.go └── yoga_props.go ├── fragment.go ├── go.mod ├── go.sum ├── internal └── layout │ └── builder.go ├── layout.go ├── layout_test.go ├── measure.go ├── measure_test.go ├── renderer.go ├── renderer_string.go ├── renderer_string_test.go ├── renderer_term.go ├── style.go ├── testing.go ├── text.go └── tree.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mitchell Hashimoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-glint [![Godoc](https://godoc.org/github.com/mitchellh/go-glint?status.svg)](https://godoc.org/github.com/mitchellh/go-glint) 2 | 3 | Glint is a component-based UI framework specifically targeted towards 4 | command-line interfaces. This allows you to create highly dynamic CLI interfaces 5 | using shared, easily testable components. Glint uses a Flexbox implementation 6 | to make it easy to lay out components in the CLI, including paddings, margins, 7 | and more. 8 | 9 | **API Status: Unstable.** We're still actively working on the API and 10 | may change it in backwards incompatible ways. See the roadmap section in 11 | particular for work that may impact the API. In particular, we have 12 | integrated this library into [Waypoint](https://github.com/hashicorp/waypoint), 13 | and the experience of using this library in the real world will likely drive major 14 | changes. 15 | 16 | ## Example 17 | 18 | The example below shows a simple dynamic counter: 19 | 20 | ```go 21 | func main() { 22 | var counter uint32 23 | go func() { 24 | for { 25 | time.Sleep(100 * time.Millisecond) 26 | atomic.AddUint32(&counter, 1) 27 | } 28 | }() 29 | 30 | d := glint.New() 31 | d.Append( 32 | glint.Style( 33 | glint.TextFunc(func(rows, cols uint) string { 34 | return fmt.Sprintf("%d tests passed", atomic.LoadUint32(&counter)) 35 | }), 36 | glint.Color("green"), 37 | ), 38 | ) 39 | d.Render(context.Background()) 40 | } 41 | ``` 42 | 43 | Output: 44 | 45 | ![Example](https://user-images.githubusercontent.com/1299/92431533-9baf8000-f14c-11ea-94ad-8ff97ed26fec.gif) 46 | 47 | ## Roadmap 48 | 49 | Glint is still an early stage project and there is a lot that we want to 50 | improve on. This may introduce some backwards incompatibilities but we are 51 | trying to stabilize the API as quickly as possible. 52 | 53 | * **Non-interactive interfaces.** We want to add support for rendering to 54 | non-interactive interfaces and allowing components to provide custom behavior 55 | in these cases. For now, users of Glint should detect non-interactivity and 56 | avoid using Glint. 57 | 58 | * **Windows PowerShell and Cmd.** Glint works fine in ANSI-compatible terminals 59 | on Windows, but doesn't work with PowerShell and Cmd. We want to make this 60 | work. 61 | 62 | * **Dirty tracking.** Glint currently rerenders the entire frame on each 63 | tick. I'd like components to be able to report if there are changes (if they 64 | are "dirty") and need to be rerendered. We could then more efficiently 65 | recalculate layouts and rerender outputs. 66 | 67 | * **User Input.** Glint should be able to query for user input and render 68 | this within its existing set of components. 69 | 70 | * **Expose styling to custom renderers.** Currently the `Style` component 71 | is a special-case for the terminal renderer to render colors. I'd like to expose 72 | the styles in a way that other renderers could use it in some meaningful way. 73 | 74 | ## Thanks 75 | 76 | This library is heavily inspired by the [Ink project](https://github.com/vadimdemedes/ink). 77 | I saw this project and thought that having a central render loop along with 78 | a full layout engine was a fantastic idea. Most of my projects are in Go 79 | so I wanted to be able to realize these benefits with Go. Thank you! 80 | -------------------------------------------------------------------------------- /component.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/mitchellh/go-glint/internal/layout" 7 | ) 8 | 9 | // Components are the individual items that are rendered within a document. 10 | type Component interface { 11 | // Body returns the body of this component. This can be another custom 12 | // component or a standard component such as Text. 13 | // 14 | // The context parameter is used to send parameters across multiple 15 | // components. It should not be used for timeouts; Body should aim to 16 | // not block ever since this can block the render loop. 17 | // 18 | // Components are highly encouraged to support finalization (see 19 | // ComponentFinalizer). Components can finalize early by wrapping 20 | // their body in a Finalize built-in component. Finalization allows 21 | // the renderer to highly optimize output. 22 | Body(context.Context) Component 23 | } 24 | 25 | // ComponentFinalizer allows components to be notified they are going to 26 | // be finalized. A finalized component may never be re-rendered again. The 27 | // next call to Body should be considered the final call. 28 | // 29 | // In a Document, if the component list has a set of finalized components 30 | // at the front, the renderer will draw it once and only re-draw non-finalized 31 | // components. For example, consider a document that is a set of text components 32 | // followed by a progress bar. If the text components are static, then they 33 | // will be written to the output once and only the progress bar will redraw. 34 | // 35 | // Currently, Body may be called multiple times after Finalize. Implementers 36 | // should return the same result after being finalized. 37 | type ComponentFinalizer interface { 38 | Component 39 | 40 | // Finalize notifies the component that it will be finalized. This may 41 | // be called multiple times. 42 | Finalize() 43 | } 44 | 45 | // ComponentMounter allows components to be notified when they are 46 | // mounted and unmounted. A mounted component is one that is added to 47 | // a render tree for the first time. A component is unmounted when it is 48 | // removed from the render tree. 49 | // 50 | // The callbacks here may be called multiple times under certain scenarios: 51 | // (1) a component is used in multiple Document instances, (2) a component 52 | // is unmounted and then remounted in the future. 53 | // 54 | // A component mounted multiple times in the same render tree does NOT 55 | // have the mount callbacks called multiple times. 56 | // 57 | // A good use case for this interface is setting up and cleaning up resources. 58 | type ComponentMounter interface { 59 | Component 60 | 61 | // Mount is called when the component is added to a render tree. The 62 | // context given to this is used to access data set by Glint and the 63 | // renderer in use. 64 | Mount(context.Context) 65 | 66 | // Unmount is called when the component is removed from a render tree. 67 | // This will be called under ANY scenario where the component is 68 | // removed from the render tree, including finalization. 69 | Unmount(context.Context) 70 | } 71 | 72 | // componentLayout can be implemented to set custom layout settings 73 | // for the component. This can only be implemented by internal components 74 | // since we use an internal library. 75 | // 76 | // End users should use the "Layout" component to set layout options. 77 | type componentLayout interface { 78 | Component 79 | 80 | // Layout should return the layout settings for this component. 81 | Layout() *layout.Builder 82 | } 83 | 84 | // terminalComponent is an embeddable struct for internal usage that 85 | // satisfies Component. This is used since terminal components are handled 86 | // as special cases. 87 | type terminalComponent struct{} 88 | 89 | func (terminalComponent) Body(context.Context) Component { return nil } 90 | -------------------------------------------------------------------------------- /components/progress.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cheggaaa/pb/v3" 7 | "github.com/mitchellh/go-glint" 8 | ) 9 | 10 | // ProgressElement renders a progress bar. This wraps the cheggaaa/pb package 11 | // since that provides important functionality. This uses single call renders 12 | // to render the progress bar as values change. 13 | type ProgressElement struct { 14 | *pb.ProgressBar 15 | } 16 | 17 | // Progress creates a new progress bar element with the given total. 18 | // For more fine-grained control, please construct a ProgressElement 19 | // directly. 20 | func Progress(total int) *ProgressElement { 21 | return &ProgressElement{ 22 | ProgressBar: pb.New(total), 23 | } 24 | } 25 | 26 | func (el *ProgressElement) Body(context.Context) glint.Component { 27 | // If we have no progress bar render nothing. 28 | if el.ProgressBar == nil { 29 | return nil 30 | } 31 | 32 | // Write the current progress 33 | return glint.TextFunc(func(rows, cols uint) string { 34 | el.ProgressBar.SetWidth(int(cols)) 35 | 36 | return el.ProgressBar.String() 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /components/sparkline.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "container/ring" 5 | "context" 6 | "math" 7 | "sync" 8 | 9 | "github.com/mitchellh/go-glint" 10 | ) 11 | 12 | // SparklineComponent renders a sparkline graph. 13 | type SparklineComponent struct { 14 | sync.Mutex 15 | 16 | // If set, this will style the peak value. 17 | PeakStyle []glint.StyleOption 18 | 19 | values *ring.Ring 20 | } 21 | 22 | // Sparkline creates a SparklineComponent with the given set of initial values. 23 | // These initial values will also specify the max width for this sparkline 24 | // unless values are replaced with Set. 25 | func Sparkline(values []uint) *SparklineComponent { 26 | var c SparklineComponent 27 | c.Set(values) 28 | return &c 29 | } 30 | 31 | // Set sets the full set of values to the given slice. This will also reset 32 | // the size of the sparkline to this length. 33 | func (c *SparklineComponent) Set(values []uint) { 34 | c.Lock() 35 | defer c.Unlock() 36 | c.values = ring.New(len(values)) 37 | for _, v := range values { 38 | c.values.Value = v 39 | c.values = c.values.Next() 40 | } 41 | } 42 | 43 | // Append adds the given values to the end of the values buffer. The buffer 44 | // size is determined by the values list given in Sparkline or Set. This will 45 | // overwrite the oldest values. 46 | func (c *SparklineComponent) Append(values ...uint) { 47 | c.Lock() 48 | defer c.Unlock() 49 | for _, v := range values { 50 | c.values.Value = v 51 | c.values = c.values.Next() 52 | } 53 | } 54 | 55 | func (c *SparklineComponent) valuesSlice() []uint { 56 | result := make([]uint, c.values.Len()) 57 | for i := range result { 58 | result[i] = c.values.Value.(uint) 59 | c.values = c.values.Next() 60 | } 61 | 62 | return result 63 | } 64 | 65 | func (c *SparklineComponent) Body(context.Context) glint.Component { 66 | c.Lock() 67 | defer c.Unlock() 68 | 69 | values := c.valuesSlice() 70 | 71 | // If we have nothing we render nothing 72 | if len(values) == 0 { 73 | return nil 74 | } 75 | 76 | // Find the max 77 | max := values[0] 78 | if len(values) > 1 { 79 | for _, v := range values[1:] { 80 | if v > max { 81 | max = v 82 | } 83 | } 84 | } 85 | 86 | // Build each symbol 87 | peak := false 88 | parts := make([]glint.Component, len(values)) 89 | for i, v := range values { 90 | symbolIdx := int(math.Ceil(float64(v) / float64(max) * float64(len(sparklineSymbols)-1))) 91 | parts[i] = glint.Text(string(sparklineSymbols[symbolIdx])) 92 | 93 | if len(c.PeakStyle) > 0 && v == max && !peak { 94 | peak = true 95 | parts[i] = glint.Style(parts[i], c.PeakStyle...) 96 | } 97 | } 98 | 99 | // Render them in a row 100 | return glint.Layout(parts...).Row() 101 | } 102 | 103 | var sparklineSymbols = []rune{ 104 | '\u2581', 105 | '\u2582', 106 | '\u2583', 107 | '\u2584', 108 | '\u2585', 109 | '\u2586', 110 | '\u2587', 111 | '\u2588', 112 | } 113 | -------------------------------------------------------------------------------- /components/spinner.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/mitchellh/go-glint" 8 | "github.com/tj/go-spin" 9 | ) 10 | 11 | // Spinner creates a new spinner. The created spinner should NOT be started 12 | // or data races will occur that can result in a panic. 13 | func Spinner() *SpinnerComponent { 14 | // Create our spinner and setup our default frames 15 | s := spin.New() 16 | s.Set(spin.Default) 17 | 18 | return &SpinnerComponent{ 19 | s: s, 20 | } 21 | } 22 | 23 | type SpinnerComponent struct { 24 | s *spin.Spinner 25 | last time.Time 26 | } 27 | 28 | func (c *SpinnerComponent) Body(context.Context) glint.Component { 29 | current := time.Now() 30 | if c.last.IsZero() || current.Sub(c.last) > 150*time.Millisecond { 31 | c.last = current 32 | c.s.Next() 33 | } 34 | 35 | return glint.Text(c.s.Current()) 36 | } 37 | -------------------------------------------------------------------------------- /components/stopwatch.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/mitchellh/go-glint" 8 | ) 9 | 10 | // Stopwatch creates a new stopwatch component that starts at the given time. 11 | func Stopwatch(start time.Time) *StopwatchComponent { 12 | return &StopwatchComponent{ 13 | start: start, 14 | } 15 | } 16 | 17 | type StopwatchComponent struct { 18 | start time.Time 19 | } 20 | 21 | func (c *StopwatchComponent) Body(context.Context) glint.Component { 22 | return glint.Text(time.Now().Sub(c.start).Truncate(100 * time.Millisecond).String()) 23 | } 24 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | // Context is a component type that can be used to set data on the context 4 | // given to Body calls for components that are children of this component. 5 | func Context(inner Component, kv ...interface{}) Component { 6 | if len(kv)%2 != 0 { 7 | panic("kv must be set in pairs") 8 | } 9 | 10 | return &contextComponent{inner: inner, pairs: kv} 11 | } 12 | 13 | type contextComponent struct { 14 | terminalComponent 15 | 16 | inner Component 17 | pairs []interface{} 18 | } 19 | -------------------------------------------------------------------------------- /document.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "os" 7 | "sync" 8 | "time" 9 | 10 | "github.com/mitchellh/go-glint/flex" 11 | ) 12 | 13 | // Document is the primary structure for managing and drawing components. 14 | // 15 | // A document represents a terminal window or session. The output can be set and 16 | // components can be added, rendered, and drawn. All the methods on a Document 17 | // are thread-safe unless otherwise documented. This allows you to draw, 18 | // add components, replace components, etc. all while the render loop is active. 19 | // 20 | // Currently, this can only render directly to an io.Writer that expects to 21 | // be a terminal session. In the future, we'll further abstract the concept 22 | // of a "renderer" so that rendering can be done to other mediums as well. 23 | type Document struct { 24 | mu sync.Mutex 25 | r Renderer 26 | els []Component 27 | refreshRate time.Duration 28 | prevRoot *flex.Node 29 | mounted map[ComponentMounter]struct{} 30 | paused bool 31 | closed bool 32 | } 33 | 34 | // New returns a Document that will output to stdout. 35 | func New() *Document { 36 | var d Document 37 | d.SetRenderer(&TerminalRenderer{ 38 | Output: os.Stdout, 39 | }) 40 | 41 | return &d 42 | } 43 | 44 | // SetRenderer sets the renderer to use. If this isn't set then Render 45 | // will do nothing and return immediately. Changes to this will have no 46 | // impact on active render loops. 47 | func (d *Document) SetRenderer(r Renderer) { 48 | d.mu.Lock() 49 | defer d.mu.Unlock() 50 | d.r = r 51 | } 52 | 53 | // SetRefreshRate sets the rate at which output is rendered. 54 | func (d *Document) SetRefreshRate(dur time.Duration) { 55 | d.mu.Lock() 56 | defer d.mu.Unlock() 57 | d.refreshRate = dur 58 | } 59 | 60 | // Append appends components to the document. 61 | func (d *Document) Append(el ...Component) { 62 | d.mu.Lock() 63 | defer d.mu.Unlock() 64 | d.els = append(d.els, el...) 65 | } 66 | 67 | // Set sets the components for the document. This will replace all 68 | // previous components. 69 | func (d *Document) Set(els ...Component) { 70 | d.mu.Lock() 71 | defer d.mu.Unlock() 72 | d.els = els 73 | } 74 | 75 | // Close ensures that all elements are unmounted by finalizing all the 76 | // output and then calling RenderFrame. Users of Document should ensure 77 | // that Close is always called. 78 | func (d *Document) Close() error { 79 | d.mu.Lock() 80 | if d.closed { 81 | d.mu.Unlock() 82 | return nil 83 | } 84 | 85 | for i, el := range d.els { 86 | d.els[i] = Finalize(el) 87 | } 88 | 89 | d.closed = true 90 | r := d.r 91 | d.mu.Unlock() 92 | 93 | // We call RenderFrame twice to ensure we remove the elements AND 94 | // call Unmount on them. 95 | d.RenderFrame() 96 | d.RenderFrame() 97 | 98 | // If our renderer implements closer then call close 99 | if c, ok := r.(io.Closer); ok { 100 | c.Close() 101 | } 102 | 103 | return nil 104 | } 105 | 106 | // Pause will pause the renderer. This will case RenderFrame to do nothing 107 | // until Resume is called. The use case for this is if you want to wait for 108 | // input (stdin) or any other reason. 109 | func (d *Document) Pause() { 110 | d.mu.Lock() 111 | defer d.mu.Unlock() 112 | d.paused = true 113 | } 114 | 115 | // Resume undoes a Pause call. If not paused, this does nothing. 116 | func (d *Document) Resume() { 117 | d.mu.Lock() 118 | defer d.mu.Unlock() 119 | d.paused = false 120 | } 121 | 122 | // Render starts a render loop that continues to render until the 123 | // context is cancelled. This will render at the configured refresh rate. 124 | // If the refresh rate is changed, it will not affect an active render loop. 125 | // You must cancel and restart the render loop. 126 | func (d *Document) Render(ctx context.Context) { 127 | d.mu.Lock() 128 | dur := d.refreshRate 129 | d.mu.Unlock() 130 | if dur == 0 { 131 | dur = time.Second / 24 132 | } 133 | 134 | for { 135 | // Render. We time the render so that we can adapt the framerate 136 | // if the render is taking too long. 137 | start := time.Now() 138 | d.RenderFrame() 139 | renderDur := time.Now().Sub(start) 140 | 141 | // If our context is canceled, end. 142 | if ctx.Err() != nil { 143 | return 144 | } 145 | 146 | // If the duration is greater than our goal duration, then we 147 | // adapt our duration. Otherwise, we sleep the duration we want 148 | // and continue 149 | sleepDur := dur 150 | if renderDur > dur { 151 | sleepDur = renderDur 152 | 153 | // We slow our attempted framerate down at most to 1 fps 154 | if sleepDur > 1*time.Second { 155 | sleepDur = 1 * time.Second 156 | } 157 | } 158 | 159 | time.Sleep(sleepDur) 160 | } 161 | } 162 | 163 | // RenderFrame will render a single frame and return. 164 | // 165 | // If a manual size is not configured, this will recalcualte the window 166 | // size on each call. This typically requires a syscall. This is a bit 167 | // expensive but something we can optimize in the future if it ends up being 168 | // a real source of FPS issues. 169 | func (d *Document) RenderFrame() { 170 | d.mu.Lock() 171 | defer d.mu.Unlock() 172 | 173 | // If we're paused do nothing. 174 | if d.paused { 175 | return 176 | } 177 | 178 | // If we don't have a renderer set, then don't render anything. 179 | if d.r == nil { 180 | return 181 | } 182 | 183 | // Our context 184 | ctx := WithRenderer(context.Background(), d.r) 185 | 186 | // Setup our root node 187 | root := d.r.LayoutRoot() 188 | if root == nil { 189 | return 190 | } 191 | 192 | // Build our render tree 193 | tree(ctx, root, Fragment(d.els...), false) 194 | 195 | // Calculate the layout 196 | flex.CalculateLayout(root, flex.Undefined, flex.Undefined, flex.DirectionLTR) 197 | 198 | // Fix any text nodes that need to be fixed. 199 | d.handleNodes(ctx, root, nil) 200 | 201 | // If the height of the root is zero then we do nothing. 202 | if uint(root.LayoutGetHeight()) == 0 { 203 | return 204 | } 205 | 206 | // Render the tree 207 | d.r.RenderRoot(root, d.prevRoot) 208 | 209 | // Store how much we drew 210 | height := uint(root.LayoutGetHeight()) 211 | 212 | // If our component list is prefixed with finalized components, we 213 | // prune these out and do not re-render them. 214 | finalIdx := -1 215 | for i, el := range d.els { 216 | child := root.GetChild(i) 217 | if child == nil { 218 | break 219 | } 220 | 221 | // If the component is not finalized then we exit. If the 222 | // component doesn't match our expectations it means we hit 223 | // something weird and we exit too. 224 | ctx, ok := child.Context.(*parentContext) 225 | if !ok || ctx == nil || ctx.C != el || !ctx.Finalized { 226 | break 227 | } 228 | 229 | // If this is finalized, then we have to subtract from the 230 | // height the height of this child since we're not going to redraw. 231 | // Then continue until we find one that isn't finalized. 232 | height -= uint(child.LayoutGetHeight()) 233 | finalIdx = i 234 | } 235 | if finalIdx >= 0 { 236 | // Change our elements 237 | els := d.els[finalIdx+1:] 238 | d.els = make([]Component, len(els)) 239 | copy(d.els, els) 240 | 241 | // Reset the height on the root so that it reflects this change 242 | root.Layout.Dimensions[flex.DimensionHeight] = float32(height) 243 | } 244 | 245 | // Store our previous root 246 | d.prevRoot = root 247 | } 248 | 249 | func (d *Document) handleNodes( 250 | ctx context.Context, 251 | parent *flex.Node, 252 | seen map[ComponentMounter]struct{}, 253 | ) { 254 | // For our first call, we detect the root since we use it later 255 | // to do some final calls. 256 | root := seen == nil 257 | if root { 258 | seen = map[ComponentMounter]struct{}{} 259 | } 260 | 261 | for _, child := range parent.Children { 262 | if tctx, ok := child.Context.(treeContext); ok { 263 | c := tctx.Component() 264 | 265 | // Mount callbacks 266 | if mc, ok := c.(ComponentMounter); ok { 267 | // Only if we haven't seen this already... 268 | if _, ok := seen[mc]; !ok { 269 | seen[mc] = struct{}{} 270 | 271 | if d.mounted == nil { 272 | d.mounted = map[ComponentMounter]struct{}{} 273 | } 274 | 275 | // And we haven't notified this already... 276 | if _, ok := d.mounted[mc]; !ok { 277 | d.mounted[mc] = struct{}{} 278 | 279 | // Notify 280 | mc.Mount(ctx) 281 | } 282 | } 283 | } 284 | 285 | continue 286 | } 287 | 288 | // If the height/width that the layout engine calculated is less than 289 | // the height that we originally measured, then we need to give the 290 | // element a chance to rerender into that dimension. 291 | if tctx, ok := child.Context.(*TextNodeContext); ok { 292 | height := child.LayoutGetHeight() 293 | width := child.LayoutGetWidth() 294 | if height < tctx.Size.Height || width < tctx.Size.Width { 295 | child.Measure(child, 296 | width, flex.MeasureModeAtMost, 297 | height, flex.MeasureModeAtMost, 298 | ) 299 | } 300 | } 301 | 302 | d.handleNodes(ctx, child, seen) 303 | } 304 | 305 | // If we're the root call, then we preform some final calls. Otherwise 306 | // we just return, we're done. 307 | if !root { 308 | return 309 | } 310 | 311 | // Go through our previously mounted set and if we didn't see it, 312 | // then call unmount on it. After we're done, what we saw is our new 313 | // map of mounted elements. 314 | for mc := range d.mounted { 315 | if _, ok := seen[mc]; !ok { 316 | mc.Unmount(ctx) 317 | } 318 | } 319 | d.mounted = seen 320 | } 321 | -------------------------------------------------------------------------------- /document_test.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "sync/atomic" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestDocument_mountUnmount(t *testing.T) { 13 | require := require.New(t) 14 | 15 | // Create our doc 16 | r := &StringRenderer{} 17 | d := New() 18 | d.SetRenderer(r) 19 | 20 | // Add our component 21 | var c testMount 22 | d.Append(&c) 23 | require.Equal(uint32(0), atomic.LoadUint32(&c.mount)) 24 | require.Equal(uint32(0), atomic.LoadUint32(&c.unmount)) 25 | 26 | // Render once 27 | d.RenderFrame() 28 | require.Equal(uint32(1), atomic.LoadUint32(&c.mount)) 29 | require.Equal(uint32(0), atomic.LoadUint32(&c.unmount)) 30 | 31 | // Render again 32 | d.RenderFrame() 33 | require.Equal(uint32(1), atomic.LoadUint32(&c.mount)) 34 | require.Equal(uint32(0), atomic.LoadUint32(&c.unmount)) 35 | 36 | // Remove the old components 37 | d.Set() 38 | d.RenderFrame() 39 | require.Equal(uint32(1), atomic.LoadUint32(&c.mount)) 40 | require.Equal(uint32(1), atomic.LoadUint32(&c.unmount)) 41 | 42 | // Render again 43 | d.RenderFrame() 44 | require.Equal(uint32(1), atomic.LoadUint32(&c.mount)) 45 | require.Equal(uint32(1), atomic.LoadUint32(&c.unmount)) 46 | } 47 | 48 | func TestDocument_unmountClose(t *testing.T) { 49 | require := require.New(t) 50 | 51 | // Create our doc 52 | r := &StringRenderer{} 53 | d := New() 54 | d.SetRenderer(r) 55 | 56 | // Add our component 57 | var c testMount 58 | d.Append(&c) 59 | require.Equal(uint32(0), atomic.LoadUint32(&c.mount)) 60 | require.Equal(uint32(0), atomic.LoadUint32(&c.unmount)) 61 | 62 | // Render once 63 | d.RenderFrame() 64 | require.Equal(uint32(1), atomic.LoadUint32(&c.mount)) 65 | require.Equal(uint32(0), atomic.LoadUint32(&c.unmount)) 66 | 67 | // Render again 68 | require.NoError(d.Close()) 69 | require.Equal(uint32(1), atomic.LoadUint32(&c.mount)) 70 | require.Equal(uint32(1), atomic.LoadUint32(&c.unmount)) 71 | } 72 | 73 | func TestDocument_renderingWithoutLayout(t *testing.T) { 74 | var buf bytes.Buffer 75 | 76 | d := New() 77 | d.SetRenderer(&TerminalRenderer{ 78 | Output: &buf, 79 | }) 80 | 81 | var c testMount 82 | d.Append(&c) 83 | 84 | // Render once 85 | d.RenderFrame() 86 | require.Empty(t, buf.String()) 87 | require.Zero(t, atomic.LoadUint32(&c.mount)) 88 | } 89 | 90 | type testMount struct { 91 | terminalComponent 92 | 93 | mount uint32 94 | unmount uint32 95 | } 96 | 97 | func (c *testMount) Mount(context.Context) { atomic.AddUint32(&c.mount, 1) } 98 | func (c *testMount) Unmount(context.Context) { atomic.AddUint32(&c.unmount, 1) } 99 | 100 | var ( 101 | _ Component = (*testMount)(nil) 102 | _ ComponentMounter = (*testMount)(nil) 103 | ) 104 | -------------------------------------------------------------------------------- /examples/counter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync/atomic" 7 | "time" 8 | 9 | "github.com/mitchellh/go-glint" 10 | ) 11 | 12 | func main() { 13 | var counter uint32 14 | go func() { 15 | for { 16 | time.Sleep(100 * time.Millisecond) 17 | atomic.AddUint32(&counter, 1) 18 | } 19 | }() 20 | 21 | d := glint.New() 22 | d.Append( 23 | glint.Style( 24 | glint.TextFunc(func(rows, cols uint) string { 25 | return fmt.Sprintf("%d tests passed", atomic.LoadUint32(&counter)) 26 | }), 27 | glint.Color("green"), 28 | ), 29 | ) 30 | d.Render(context.Background()) 31 | } 32 | -------------------------------------------------------------------------------- /examples/layout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/mitchellh/go-glint" 8 | gc "github.com/mitchellh/go-glint/components" 9 | ) 10 | 11 | func main() { 12 | d := glint.New() 13 | d.Append( 14 | glint.Style( 15 | glint.Layout( 16 | gc.Spinner(), 17 | glint.Layout(glint.Text("Build site and validate links...")).MarginLeft(1), 18 | glint.Layout(gc.Stopwatch(time.Now())).MarginLeft(1), 19 | ).Row(), 20 | glint.Color("green"), 21 | ), 22 | glint.Layout( 23 | gc.Spinner(), 24 | glint.Layout(glint.Text("Preparing execution environment...")).MarginLeft(1), 25 | glint.Layout(gc.Stopwatch(time.Now())).MarginLeft(1), 26 | ).MarginLeft(2).Row(), 27 | glint.Layout( 28 | glint.Text("Preparing volume to work with..."), 29 | ).MarginLeft(4), 30 | glint.Text("\nWaiting..."), 31 | ) 32 | d.Render(context.Background()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/sparkline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/mitchellh/go-glint" 11 | gc "github.com/mitchellh/go-glint/components" 12 | ) 13 | 14 | func main() { 15 | max := 25 16 | min := 1 17 | 18 | values := make([]uint, 40) 19 | for i := range values { 20 | values[i] = uint(rand.Intn(max-min) + min) 21 | } 22 | 23 | // Create our sparkline 24 | sl := gc.Sparkline(values) 25 | sl.PeakStyle = []glint.StyleOption{glint.Color("green")} 26 | 27 | // Start up a timer that adds values 28 | lastValue := uint32(values[len(values)-1]) 29 | go func() { 30 | for { 31 | time.Sleep(100 * time.Millisecond) 32 | value := uint(rand.Intn(max-min) + min) 33 | sl.Append(value) 34 | atomic.StoreUint32(&lastValue, uint32(value)) 35 | } 36 | }() 37 | 38 | d := glint.New() 39 | d.Append( 40 | glint.Layout( 41 | glint.Layout(glint.Text("requests/second")).MarginRight(2), 42 | glint.Layout(sl).MarginRight(2), 43 | glint.TextFunc(func(h, w uint) string { 44 | return fmt.Sprintf("%d req/s", atomic.LoadUint32(&lastValue)) 45 | }), 46 | ).Row(), 47 | ) 48 | d.Render(context.Background()) 49 | } 50 | -------------------------------------------------------------------------------- /finalize.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import "context" 4 | 5 | // Finalize reutrns a component that will finalize the input component. 6 | // See ComponentFinalizer for documentation on what finalization means. 7 | func Finalize(c Component) Component { 8 | return &finalizedComponent{ 9 | Component: c, 10 | } 11 | } 12 | 13 | type finalizedComponent struct { 14 | Component 15 | } 16 | 17 | func (c *finalizedComponent) Body(context.Context) Component { 18 | return c.Component 19 | } 20 | -------------------------------------------------------------------------------- /flex/LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For yoga software 4 | 5 | Copyright (c) 2014-present, Facebook, Inc. All rights reserved. 6 | Copyright (c) 2017-present, Krzysztof Kowalczyk. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | * Neither the name Facebook nor the names of its contributors may be used to 19 | endorse or promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /flex/PATENTS: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the yoga software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. (“Facebook”) hereby grants to each recipient of the Software 6 | (“you”) a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Facebook's rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. -------------------------------------------------------------------------------- /flex/README.md: -------------------------------------------------------------------------------- 1 | # flex - CSS flexbox layout implementation in Go 2 | 3 | Go implementation of [flexbox CSS](https://www.w3.org/TR/css-flexbox-1/) layout algorithm. 4 | 5 | A pure Go port of [Facebook's Yoga](https://github.com/facebook/yoga). 6 | 7 | ## How to use 8 | 9 | Read [tutorial](https://blog.kowalczyk.info/article/9/tutorial-on-using-github.comkjkflex-go-package.html) or look at `_test.go` files. 10 | 11 | ## Status 12 | 13 | The port is finished. The code works and passess all Yoga tests. 14 | 15 | The API is awkward by Go standards but it's the best I could do given that I want to stay close to C version. 16 | 17 | Logic is currently synced up to https://github.com/facebook/yoga/commit/f45059e1e696727c1282742b89d2c8bf06345254 18 | 19 | ## How the port was made 20 | 21 | You can read a [detailed story](https://blog.kowalczyk.info/article/wN9R/experience-porting-4.5k-loc-of-c-to-go-facebooks-css-flexbox-implementation-yoga.html). 22 | 23 | In short: 24 | 25 | * manually ported [C code](https://github.com/facebook/yoga/tree/master/yoga) to Go, line-by-line 26 | * manually ported [tests](https://github.com/facebook/yoga/tree/master/tests) to Go 27 | * tweak the API from C style to be more Go like. The structure and logic still is very close to C code (this makes porting future C changes easy) 28 | -------------------------------------------------------------------------------- /flex/align_self_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestAlign_self_center(t *testing.T) { 6 | config := NewConfig() 7 | 8 | root := NewNodeWithConfig(config) 9 | root.StyleSetWidth(100) 10 | root.StyleSetHeight(100) 11 | 12 | rootChild0 := NewNodeWithConfig(config) 13 | rootChild0.StyleSetAlignSelf(AlignCenter) 14 | rootChild0.StyleSetWidth(10) 15 | rootChild0.StyleSetHeight(10) 16 | root.InsertChild(rootChild0, 0) 17 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 18 | 19 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 20 | assertFloatEqual(t, 0, root.LayoutGetTop()) 21 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 22 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 23 | 24 | assertFloatEqual(t, 45, rootChild0.LayoutGetLeft()) 25 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 26 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 27 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 28 | 29 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 30 | 31 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 32 | assertFloatEqual(t, 0, root.LayoutGetTop()) 33 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 34 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 35 | 36 | assertFloatEqual(t, 45, rootChild0.LayoutGetLeft()) 37 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 38 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 39 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 40 | } 41 | 42 | func TestAlign_self_flex_end(t *testing.T) { 43 | config := NewConfig() 44 | 45 | root := NewNodeWithConfig(config) 46 | root.StyleSetWidth(100) 47 | root.StyleSetHeight(100) 48 | 49 | rootChild0 := NewNodeWithConfig(config) 50 | rootChild0.StyleSetAlignSelf(AlignFlexEnd) 51 | rootChild0.StyleSetWidth(10) 52 | rootChild0.StyleSetHeight(10) 53 | root.InsertChild(rootChild0, 0) 54 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 55 | 56 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 57 | assertFloatEqual(t, 0, root.LayoutGetTop()) 58 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 59 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 60 | 61 | assertFloatEqual(t, 90, rootChild0.LayoutGetLeft()) 62 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 63 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 64 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 65 | 66 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 67 | 68 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 69 | assertFloatEqual(t, 0, root.LayoutGetTop()) 70 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 71 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 72 | 73 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 74 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 75 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 76 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 77 | } 78 | 79 | func TestAlign_self_flex_start(t *testing.T) { 80 | config := NewConfig() 81 | 82 | root := NewNodeWithConfig(config) 83 | root.StyleSetWidth(100) 84 | root.StyleSetHeight(100) 85 | 86 | rootChild0 := NewNodeWithConfig(config) 87 | rootChild0.StyleSetAlignSelf(AlignFlexStart) 88 | rootChild0.StyleSetWidth(10) 89 | rootChild0.StyleSetHeight(10) 90 | root.InsertChild(rootChild0, 0) 91 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 92 | 93 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 94 | assertFloatEqual(t, 0, root.LayoutGetTop()) 95 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 96 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 97 | 98 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 99 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 100 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 101 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 102 | 103 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 104 | 105 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 106 | assertFloatEqual(t, 0, root.LayoutGetTop()) 107 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 108 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 109 | 110 | assertFloatEqual(t, 90, rootChild0.LayoutGetLeft()) 111 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 112 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 113 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 114 | } 115 | 116 | func TestAlign_self_flex_end_override_flex_start(t *testing.T) { 117 | config := NewConfig() 118 | 119 | root := NewNodeWithConfig(config) 120 | root.StyleSetAlignItems(AlignFlexStart) 121 | root.StyleSetWidth(100) 122 | root.StyleSetHeight(100) 123 | 124 | rootChild0 := NewNodeWithConfig(config) 125 | rootChild0.StyleSetAlignSelf(AlignFlexEnd) 126 | rootChild0.StyleSetWidth(10) 127 | rootChild0.StyleSetHeight(10) 128 | root.InsertChild(rootChild0, 0) 129 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 130 | 131 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 132 | assertFloatEqual(t, 0, root.LayoutGetTop()) 133 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 134 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 135 | 136 | assertFloatEqual(t, 90, rootChild0.LayoutGetLeft()) 137 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 138 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 139 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 140 | 141 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 142 | 143 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 144 | assertFloatEqual(t, 0, root.LayoutGetTop()) 145 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 146 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 147 | 148 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 149 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 150 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 151 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 152 | } 153 | 154 | func TestAlign_self_baseline(t *testing.T) { 155 | config := NewConfig() 156 | 157 | root := NewNodeWithConfig(config) 158 | root.StyleSetFlexDirection(FlexDirectionRow) 159 | root.StyleSetWidth(100) 160 | root.StyleSetHeight(100) 161 | 162 | rootChild0 := NewNodeWithConfig(config) 163 | rootChild0.StyleSetAlignSelf(AlignBaseline) 164 | rootChild0.StyleSetWidth(50) 165 | rootChild0.StyleSetHeight(50) 166 | root.InsertChild(rootChild0, 0) 167 | 168 | rootChild1 := NewNodeWithConfig(config) 169 | rootChild1.StyleSetAlignSelf(AlignBaseline) 170 | rootChild1.StyleSetWidth(50) 171 | rootChild1.StyleSetHeight(20) 172 | root.InsertChild(rootChild1, 1) 173 | 174 | rootChild1child0 := NewNodeWithConfig(config) 175 | rootChild1child0.StyleSetWidth(50) 176 | rootChild1child0.StyleSetHeight(10) 177 | rootChild1.InsertChild(rootChild1child0, 0) 178 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 179 | 180 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 181 | assertFloatEqual(t, 0, root.LayoutGetTop()) 182 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 183 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 184 | 185 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 186 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 187 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 188 | assertFloatEqual(t, 50, rootChild0.LayoutGetHeight()) 189 | 190 | assertFloatEqual(t, 50, rootChild1.LayoutGetLeft()) 191 | assertFloatEqual(t, 40, rootChild1.LayoutGetTop()) 192 | assertFloatEqual(t, 50, rootChild1.LayoutGetWidth()) 193 | assertFloatEqual(t, 20, rootChild1.LayoutGetHeight()) 194 | 195 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetLeft()) 196 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetTop()) 197 | assertFloatEqual(t, 50, rootChild1child0.LayoutGetWidth()) 198 | assertFloatEqual(t, 10, rootChild1child0.LayoutGetHeight()) 199 | 200 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 201 | 202 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 203 | assertFloatEqual(t, 0, root.LayoutGetTop()) 204 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 205 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 206 | 207 | assertFloatEqual(t, 50, rootChild0.LayoutGetLeft()) 208 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 209 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 210 | assertFloatEqual(t, 50, rootChild0.LayoutGetHeight()) 211 | 212 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 213 | assertFloatEqual(t, 40, rootChild1.LayoutGetTop()) 214 | assertFloatEqual(t, 50, rootChild1.LayoutGetWidth()) 215 | assertFloatEqual(t, 20, rootChild1.LayoutGetHeight()) 216 | 217 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetLeft()) 218 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetTop()) 219 | assertFloatEqual(t, 50, rootChild1child0.LayoutGetWidth()) 220 | assertFloatEqual(t, 10, rootChild1child0.LayoutGetHeight()) 221 | } 222 | -------------------------------------------------------------------------------- /flex/baseline_func_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func baselineFunc(node *Node, width float32, height float32) float32 { 6 | baseline := node.Context.(float32) 7 | return baseline 8 | } 9 | 10 | func TestAlign_baseline_customer_func(t *testing.T) { 11 | root := NewNode() 12 | root.StyleSetFlexDirection(FlexDirectionRow) 13 | root.StyleSetAlignItems(AlignBaseline) 14 | root.StyleSetWidth(100) 15 | root.StyleSetHeight(100) 16 | 17 | rootChild0 := NewNode() 18 | rootChild0.StyleSetWidth(50) 19 | rootChild0.StyleSetHeight(50) 20 | root.InsertChild(rootChild0, 0) 21 | 22 | rootChild1 := NewNode() 23 | rootChild1.StyleSetWidth(50) 24 | rootChild1.StyleSetHeight(20) 25 | root.InsertChild(rootChild1, 1) 26 | 27 | var baselineValue float32 = 10 28 | rootChild1child0 := NewNode() 29 | rootChild1child0.Context = baselineValue 30 | rootChild1child0.StyleSetWidth(50) 31 | rootChild1child0.Baseline = baselineFunc 32 | rootChild1child0.StyleSetHeight(20) 33 | rootChild1.InsertChild(rootChild1child0, 0) 34 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 35 | 36 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 37 | assertFloatEqual(t, 0, root.LayoutGetTop()) 38 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 39 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 40 | 41 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 42 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 43 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 44 | assertFloatEqual(t, 50, rootChild0.LayoutGetHeight()) 45 | 46 | assertFloatEqual(t, 50, rootChild1.LayoutGetLeft()) 47 | assertFloatEqual(t, 40, rootChild1.LayoutGetTop()) 48 | assertFloatEqual(t, 50, rootChild1.LayoutGetWidth()) 49 | assertFloatEqual(t, 20, rootChild1.LayoutGetHeight()) 50 | 51 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetLeft()) 52 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetTop()) 53 | assertFloatEqual(t, 50, rootChild1child0.LayoutGetWidth()) 54 | assertFloatEqual(t, 20, rootChild1child0.LayoutGetHeight()) 55 | } 56 | -------------------------------------------------------------------------------- /flex/border_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestBorder_no_size(t *testing.T) { 6 | config := NewConfig() 7 | 8 | root := NewNodeWithConfig(config) 9 | root.StyleSetBorder(EdgeLeft, 10) 10 | root.StyleSetBorder(EdgeTop, 10) 11 | root.StyleSetBorder(EdgeRight, 10) 12 | root.StyleSetBorder(EdgeBottom, 10) 13 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 14 | 15 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 16 | assertFloatEqual(t, 0, root.LayoutGetTop()) 17 | assertFloatEqual(t, 20, root.LayoutGetWidth()) 18 | assertFloatEqual(t, 20, root.LayoutGetHeight()) 19 | 20 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 21 | 22 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 23 | assertFloatEqual(t, 0, root.LayoutGetTop()) 24 | assertFloatEqual(t, 20, root.LayoutGetWidth()) 25 | assertFloatEqual(t, 20, root.LayoutGetHeight()) 26 | } 27 | 28 | func TestBorder_container_match_child(t *testing.T) { 29 | config := NewConfig() 30 | 31 | root := NewNodeWithConfig(config) 32 | root.StyleSetBorder(EdgeLeft, 10) 33 | root.StyleSetBorder(EdgeTop, 10) 34 | root.StyleSetBorder(EdgeRight, 10) 35 | root.StyleSetBorder(EdgeBottom, 10) 36 | 37 | rootChild0 := NewNodeWithConfig(config) 38 | rootChild0.StyleSetWidth(10) 39 | rootChild0.StyleSetHeight(10) 40 | root.InsertChild(rootChild0, 0) 41 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 42 | 43 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 44 | assertFloatEqual(t, 0, root.LayoutGetTop()) 45 | assertFloatEqual(t, 30, root.LayoutGetWidth()) 46 | assertFloatEqual(t, 30, root.LayoutGetHeight()) 47 | 48 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 49 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 50 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 51 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 52 | 53 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 54 | 55 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 56 | assertFloatEqual(t, 0, root.LayoutGetTop()) 57 | assertFloatEqual(t, 30, root.LayoutGetWidth()) 58 | assertFloatEqual(t, 30, root.LayoutGetHeight()) 59 | 60 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 61 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 62 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 63 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 64 | } 65 | 66 | func TestBorder_flex_child(t *testing.T) { 67 | config := NewConfig() 68 | 69 | root := NewNodeWithConfig(config) 70 | root.StyleSetBorder(EdgeLeft, 10) 71 | root.StyleSetBorder(EdgeTop, 10) 72 | root.StyleSetBorder(EdgeRight, 10) 73 | root.StyleSetBorder(EdgeBottom, 10) 74 | root.StyleSetWidth(100) 75 | root.StyleSetHeight(100) 76 | 77 | rootChild0 := NewNodeWithConfig(config) 78 | rootChild0.StyleSetFlexGrow(1) 79 | rootChild0.StyleSetWidth(10) 80 | root.InsertChild(rootChild0, 0) 81 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 82 | 83 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 84 | assertFloatEqual(t, 0, root.LayoutGetTop()) 85 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 86 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 87 | 88 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 89 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 90 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 91 | assertFloatEqual(t, 80, rootChild0.LayoutGetHeight()) 92 | 93 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 94 | 95 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 96 | assertFloatEqual(t, 0, root.LayoutGetTop()) 97 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 98 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 99 | 100 | assertFloatEqual(t, 80, rootChild0.LayoutGetLeft()) 101 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 102 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 103 | assertFloatEqual(t, 80, rootChild0.LayoutGetHeight()) 104 | } 105 | 106 | func TestBorder_stretch_child(t *testing.T) { 107 | config := NewConfig() 108 | 109 | root := NewNodeWithConfig(config) 110 | root.StyleSetBorder(EdgeLeft, 10) 111 | root.StyleSetBorder(EdgeTop, 10) 112 | root.StyleSetBorder(EdgeRight, 10) 113 | root.StyleSetBorder(EdgeBottom, 10) 114 | root.StyleSetWidth(100) 115 | root.StyleSetHeight(100) 116 | 117 | rootChild0 := NewNodeWithConfig(config) 118 | rootChild0.StyleSetHeight(10) 119 | root.InsertChild(rootChild0, 0) 120 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 121 | 122 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 123 | assertFloatEqual(t, 0, root.LayoutGetTop()) 124 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 125 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 126 | 127 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 128 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 129 | assertFloatEqual(t, 80, rootChild0.LayoutGetWidth()) 130 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 131 | 132 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 133 | 134 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 135 | assertFloatEqual(t, 0, root.LayoutGetTop()) 136 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 137 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 138 | 139 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 140 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 141 | assertFloatEqual(t, 80, rootChild0.LayoutGetWidth()) 142 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 143 | } 144 | 145 | func TestBorder_center_child(t *testing.T) { 146 | config := NewConfig() 147 | 148 | root := NewNodeWithConfig(config) 149 | root.StyleSetJustifyContent(JustifyCenter) 150 | root.StyleSetAlignItems(AlignCenter) 151 | root.StyleSetBorder(EdgeStart, 10) 152 | root.StyleSetBorder(EdgeEnd, 20) 153 | root.StyleSetBorder(EdgeBottom, 20) 154 | root.StyleSetWidth(100) 155 | root.StyleSetHeight(100) 156 | 157 | rootChild0 := NewNodeWithConfig(config) 158 | rootChild0.StyleSetWidth(10) 159 | rootChild0.StyleSetHeight(10) 160 | root.InsertChild(rootChild0, 0) 161 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 162 | 163 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 164 | assertFloatEqual(t, 0, root.LayoutGetTop()) 165 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 166 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 167 | 168 | assertFloatEqual(t, 40, rootChild0.LayoutGetLeft()) 169 | assertFloatEqual(t, 35, rootChild0.LayoutGetTop()) 170 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 171 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 172 | 173 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 174 | 175 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 176 | assertFloatEqual(t, 0, root.LayoutGetTop()) 177 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 178 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 179 | 180 | assertFloatEqual(t, 50, rootChild0.LayoutGetLeft()) 181 | assertFloatEqual(t, 35, rootChild0.LayoutGetTop()) 182 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 183 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 184 | } 185 | -------------------------------------------------------------------------------- /flex/compute_margin_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestComputed_layout_margin(t *testing.T) { 6 | root := NewNode() 7 | root.StyleSetWidth(100) 8 | root.StyleSetHeight(100) 9 | root.StyleSetMarginPercent(EdgeStart, 10) 10 | 11 | CalculateLayout(root, 100, 100, DirectionLTR) 12 | 13 | assertFloatEqual(t, 10, root.LayoutGetMargin(EdgeLeft)) 14 | assertFloatEqual(t, 0, root.LayoutGetMargin(EdgeRight)) 15 | 16 | CalculateLayout(root, 100, 100, DirectionRTL) 17 | 18 | assertFloatEqual(t, 0, root.LayoutGetMargin(EdgeLeft)) 19 | assertFloatEqual(t, 10, root.LayoutGetMargin(EdgeRight)) 20 | } 21 | -------------------------------------------------------------------------------- /flex/compute_padding_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestComputed_layout_padding(t *testing.T) { 6 | root := NewNode() 7 | root.StyleSetWidth(100) 8 | root.StyleSetHeight(100) 9 | root.StyleSetPaddingPercent(EdgeStart, 10) 10 | 11 | CalculateLayout(root, 100, 100, DirectionLTR) 12 | 13 | assertFloatEqual(t, 10, root.LayoutGetPadding(EdgeLeft)) 14 | assertFloatEqual(t, 0, root.LayoutGetPadding(EdgeRight)) 15 | 16 | CalculateLayout(root, 100, 100, DirectionRTL) 17 | 18 | assertFloatEqual(t, 0, root.LayoutGetPadding(EdgeLeft)) 19 | assertFloatEqual(t, 10, root.LayoutGetPadding(EdgeRight)) 20 | } 21 | -------------------------------------------------------------------------------- /flex/default_values_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestAssert_default_values(t *testing.T) { 10 | root := NewNode() 11 | 12 | assert.Equal(t, 0, len(root.Children)) 13 | var nilNode *Node 14 | assert.Equal(t, nilNode, root.GetChild(1)) 15 | assert.Equal(t, nilNode, root.GetChild(0)) 16 | 17 | assert.Equal(t, DirectionInherit, root.Style.Direction) 18 | assert.Equal(t, FlexDirectionColumn, root.Style.FlexDirection) 19 | assert.Equal(t, JustifyFlexStart, root.Style.JustifyContent) 20 | assert.Equal(t, AlignFlexStart, root.Style.AlignContent) 21 | assert.Equal(t, AlignStretch, root.Style.AlignItems) 22 | assert.Equal(t, AlignAuto, root.Style.AlignSelf) 23 | assert.Equal(t, PositionTypeRelative, root.Style.PositionType) 24 | assert.Equal(t, WrapNoWrap, root.Style.FlexWrap) 25 | assert.Equal(t, OverflowVisible, root.Style.Overflow) 26 | assertFloatEqual(t, 0, root.StyleGetFlexGrow()) 27 | assertFloatEqual(t, 0, root.StyleGetFlexShrink()) 28 | assert.Equal(t, root.Style.FlexBasis.Unit, UnitAuto) 29 | 30 | assert.Equal(t, root.StyleGetPosition(EdgeLeft).Unit, UnitUndefined) 31 | assert.Equal(t, root.StyleGetPosition(EdgeTop).Unit, UnitUndefined) 32 | assert.Equal(t, root.StyleGetPosition(EdgeRight).Unit, UnitUndefined) 33 | assert.Equal(t, root.StyleGetPosition(EdgeBottom).Unit, UnitUndefined) 34 | assert.Equal(t, root.StyleGetPosition(EdgeStart).Unit, UnitUndefined) 35 | assert.Equal(t, root.StyleGetPosition(EdgeEnd).Unit, UnitUndefined) 36 | 37 | assert.Equal(t, root.StyleGetMargin(EdgeLeft).Unit, UnitUndefined) 38 | assert.Equal(t, root.StyleGetMargin(EdgeTop).Unit, UnitUndefined) 39 | assert.Equal(t, root.StyleGetMargin(EdgeRight).Unit, UnitUndefined) 40 | assert.Equal(t, root.StyleGetMargin(EdgeBottom).Unit, UnitUndefined) 41 | assert.Equal(t, root.StyleGetMargin(EdgeStart).Unit, UnitUndefined) 42 | assert.Equal(t, root.StyleGetMargin(EdgeEnd).Unit, UnitUndefined) 43 | 44 | assert.Equal(t, root.StyleGetPadding(EdgeLeft).Unit, UnitUndefined) 45 | assert.Equal(t, root.StyleGetPadding(EdgeTop).Unit, UnitUndefined) 46 | assert.Equal(t, root.StyleGetPadding(EdgeRight).Unit, UnitUndefined) 47 | assert.Equal(t, root.StyleGetPadding(EdgeBottom).Unit, UnitUndefined) 48 | assert.Equal(t, root.StyleGetPadding(EdgeStart).Unit, UnitUndefined) 49 | assert.Equal(t, root.StyleGetPadding(EdgeEnd).Unit, UnitUndefined) 50 | 51 | assert.True(t, FloatIsUndefined(root.StyleGetBorder(EdgeLeft))) 52 | assert.True(t, FloatIsUndefined(root.StyleGetBorder(EdgeTop))) 53 | assert.True(t, FloatIsUndefined(root.StyleGetBorder(EdgeRight))) 54 | assert.True(t, FloatIsUndefined(root.StyleGetBorder(EdgeBottom))) 55 | assert.True(t, FloatIsUndefined(root.StyleGetBorder(EdgeStart))) 56 | assert.True(t, FloatIsUndefined(root.StyleGetBorder(EdgeEnd))) 57 | 58 | assert.Equal(t, root.StyleGetWidth().Unit, UnitAuto) 59 | assert.Equal(t, root.StyleGetHeight().Unit, UnitAuto) 60 | assert.Equal(t, root.StyleGetMinWidth().Unit, UnitUndefined) 61 | assert.Equal(t, root.StyleGetMinHeight().Unit, UnitUndefined) 62 | assert.Equal(t, root.StyleGetMaxWidth().Unit, UnitUndefined) 63 | assert.Equal(t, root.StyleGetMaxHeight().Unit, UnitUndefined) 64 | 65 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 66 | assertFloatEqual(t, 0, root.LayoutGetTop()) 67 | assertFloatEqual(t, 0, root.LayoutGetRight()) 68 | assertFloatEqual(t, 0, root.LayoutGetBottom()) 69 | 70 | assertFloatEqual(t, 0, root.LayoutGetMargin(EdgeLeft)) 71 | assertFloatEqual(t, 0, root.LayoutGetMargin(EdgeTop)) 72 | assertFloatEqual(t, 0, root.LayoutGetMargin(EdgeRight)) 73 | assertFloatEqual(t, 0, root.LayoutGetMargin(EdgeBottom)) 74 | 75 | assertFloatEqual(t, 0, root.LayoutGetPadding(EdgeLeft)) 76 | assertFloatEqual(t, 0, root.LayoutGetPadding(EdgeTop)) 77 | assertFloatEqual(t, 0, root.LayoutGetPadding(EdgeRight)) 78 | assertFloatEqual(t, 0, root.LayoutGetPadding(EdgeBottom)) 79 | 80 | assertFloatEqual(t, 0, root.LayoutGetBorder(EdgeLeft)) 81 | assertFloatEqual(t, 0, root.LayoutGetBorder(EdgeTop)) 82 | assertFloatEqual(t, 0, root.LayoutGetBorder(EdgeRight)) 83 | assertFloatEqual(t, 0, root.LayoutGetBorder(EdgeBottom)) 84 | 85 | assert.True(t, FloatIsUndefined(root.LayoutGetWidth())) 86 | assert.True(t, FloatIsUndefined(root.LayoutGetHeight())) 87 | assert.Equal(t, DirectionInherit, root.Layout.Direction) 88 | 89 | } 90 | 91 | func TestAssert_webdefault_values(t *testing.T) { 92 | config := NewConfig() 93 | config.UseWebDefaults = true 94 | root := NewNodeWithConfig(config) 95 | 96 | assert.Equal(t, FlexDirectionRow, root.Style.FlexDirection) 97 | assert.Equal(t, AlignStretch, root.Style.AlignContent) 98 | assertFloatEqual(t, 1, root.StyleGetFlexShrink()) 99 | 100 | } 101 | 102 | func TestAssert_webdefault_values_reset(t *testing.T) { 103 | config := NewConfig() 104 | config.UseWebDefaults = true 105 | root := NewNodeWithConfig(config) 106 | root.Reset() 107 | 108 | assert.Equal(t, FlexDirectionRow, root.Style.FlexDirection) 109 | assert.Equal(t, AlignStretch, root.Style.AlignContent) 110 | assertFloatEqual(t, 1, root.StyleGetFlexShrink()) 111 | 112 | } 113 | -------------------------------------------------------------------------------- /flex/dimension_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestWrap_child(t *testing.T) { 6 | config := NewConfig() 7 | root := NewNodeWithConfig(config) 8 | 9 | rootChild0 := NewNodeWithConfig(config) 10 | rootChild0.StyleSetWidth(100) 11 | rootChild0.StyleSetHeight(100) 12 | root.InsertChild(rootChild0, 0) 13 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 14 | 15 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 16 | assertFloatEqual(t, 0, root.LayoutGetTop()) 17 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 18 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 19 | 20 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 21 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 22 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 23 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 24 | 25 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 26 | 27 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 28 | assertFloatEqual(t, 0, root.LayoutGetTop()) 29 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 30 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 31 | 32 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 33 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 34 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 35 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 36 | } 37 | 38 | func TestWrap_grandchild(t *testing.T) { 39 | config := NewConfig() 40 | 41 | root := NewNodeWithConfig(config) 42 | 43 | rootChild0 := NewNodeWithConfig(config) 44 | root.InsertChild(rootChild0, 0) 45 | 46 | rootChild0Child0 := NewNodeWithConfig(config) 47 | rootChild0Child0.StyleSetWidth(100) 48 | rootChild0Child0.StyleSetHeight(100) 49 | rootChild0.InsertChild(rootChild0Child0, 0) 50 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 51 | 52 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 53 | assertFloatEqual(t, 0, root.LayoutGetTop()) 54 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 55 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 56 | 57 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 58 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 59 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 60 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 61 | 62 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetLeft()) 63 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 64 | assertFloatEqual(t, 100, rootChild0Child0.LayoutGetWidth()) 65 | assertFloatEqual(t, 100, rootChild0Child0.LayoutGetHeight()) 66 | 67 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 68 | 69 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 70 | assertFloatEqual(t, 0, root.LayoutGetTop()) 71 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 72 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 73 | 74 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 75 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 76 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 77 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 78 | 79 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetLeft()) 80 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 81 | assertFloatEqual(t, 100, rootChild0Child0.LayoutGetWidth()) 82 | assertFloatEqual(t, 100, rootChild0Child0.LayoutGetHeight()) 83 | } 84 | -------------------------------------------------------------------------------- /flex/dirty_marking_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDirty_propagation(t *testing.T) { 10 | root := NewNode() 11 | root.StyleSetAlignItems(AlignFlexStart) 12 | root.StyleSetWidth(100) 13 | root.StyleSetHeight(100) 14 | 15 | rootChild0 := NewNode() 16 | rootChild0.StyleSetWidth(50) 17 | rootChild0.StyleSetHeight(20) 18 | root.InsertChild(rootChild0, 0) 19 | 20 | rootChild1 := NewNode() 21 | rootChild1.StyleSetWidth(50) 22 | rootChild1.StyleSetHeight(20) 23 | root.InsertChild(rootChild1, 1) 24 | 25 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 26 | 27 | rootChild0.StyleSetWidth(20) 28 | 29 | assert.True(t, rootChild0.IsDirty) 30 | assert.False(t, rootChild1.IsDirty) 31 | assert.True(t, root.IsDirty) 32 | 33 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 34 | 35 | assert.False(t, rootChild0.IsDirty) 36 | assert.False(t, rootChild1.IsDirty) 37 | assert.False(t, root.IsDirty) 38 | 39 | } 40 | 41 | func TestDirty_propagation_only_if_prop_changed(t *testing.T) { 42 | root := NewNode() 43 | root.StyleSetAlignItems(AlignFlexStart) 44 | root.StyleSetWidth(100) 45 | root.StyleSetHeight(100) 46 | 47 | rootChild0 := NewNode() 48 | rootChild0.StyleSetWidth(50) 49 | rootChild0.StyleSetHeight(20) 50 | root.InsertChild(rootChild0, 0) 51 | 52 | rootChild1 := NewNode() 53 | rootChild1.StyleSetWidth(50) 54 | rootChild1.StyleSetHeight(20) 55 | root.InsertChild(rootChild1, 1) 56 | 57 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 58 | 59 | rootChild0.StyleSetWidth(50) 60 | 61 | assert.False(t, rootChild0.IsDirty) 62 | assert.False(t, rootChild1.IsDirty) 63 | assert.False(t, root.IsDirty) 64 | 65 | } 66 | 67 | func TestDirty_mark_all_children_as_dirty_when_display_changes(t *testing.T) { 68 | root := NewNode() 69 | root.StyleSetFlexDirection(FlexDirectionRow) 70 | root.StyleSetHeight(100) 71 | 72 | child0 := NewNode() 73 | child0.StyleSetFlexGrow(1) 74 | child1 := NewNode() 75 | child1.StyleSetFlexGrow(1) 76 | 77 | child1Child0 := NewNode() 78 | child1Child0Child0 := NewNode() 79 | child1Child0Child0.StyleSetWidth(8) 80 | child1Child0Child0.StyleSetHeight(16) 81 | 82 | child1Child0.InsertChild(child1Child0Child0, 0) 83 | 84 | child1.InsertChild(child1Child0, 0) 85 | root.InsertChild(child0, 0) 86 | root.InsertChild(child1, 0) 87 | 88 | child0.StyleSetDisplay(DisplayFlex) 89 | child1.StyleSetDisplay(DisplayNone) 90 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 91 | assertFloatEqual(t, 0, child1Child0Child0.LayoutGetWidth()) 92 | assertFloatEqual(t, 0, child1Child0Child0.LayoutGetHeight()) 93 | 94 | child0.StyleSetDisplay(DisplayNone) 95 | child1.StyleSetDisplay(DisplayFlex) 96 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 97 | assertFloatEqual(t, 8, child1Child0Child0.LayoutGetWidth()) 98 | assertFloatEqual(t, 16, child1Child0Child0.LayoutGetHeight()) 99 | 100 | child0.StyleSetDisplay(DisplayFlex) 101 | child1.StyleSetDisplay(DisplayNone) 102 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 103 | assertFloatEqual(t, 0, child1Child0Child0.LayoutGetWidth()) 104 | assertFloatEqual(t, 0, child1Child0Child0.LayoutGetHeight()) 105 | 106 | child0.StyleSetDisplay(DisplayNone) 107 | child1.StyleSetDisplay(DisplayFlex) 108 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 109 | assertFloatEqual(t, 8, child1Child0Child0.LayoutGetWidth()) 110 | assertFloatEqual(t, 16, child1Child0Child0.LayoutGetHeight()) 111 | } 112 | 113 | func TestDirty_node_only_if_children_are_actually_removed(t *testing.T) { 114 | root := NewNode() 115 | root.StyleSetAlignItems(AlignFlexStart) 116 | root.StyleSetWidth(50) 117 | root.StyleSetHeight(50) 118 | 119 | child0 := NewNode() 120 | child0.StyleSetWidth(50) 121 | child0.StyleSetHeight(25) 122 | root.InsertChild(child0, 0) 123 | 124 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 125 | 126 | child1 := NewNode() 127 | root.RemoveChild(child1) 128 | assert.False(t, root.IsDirty) 129 | 130 | root.RemoveChild(child0) 131 | assert.True(t, root.IsDirty) 132 | } 133 | -------------------------------------------------------------------------------- /flex/display_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestDisplay_none(t *testing.T) { 6 | config := NewConfig() 7 | 8 | root := NewNodeWithConfig(config) 9 | root.StyleSetFlexDirection(FlexDirectionRow) 10 | root.StyleSetWidth(100) 11 | root.StyleSetHeight(100) 12 | 13 | rootChild0 := NewNodeWithConfig(config) 14 | rootChild0.StyleSetFlexGrow(1) 15 | root.InsertChild(rootChild0, 0) 16 | 17 | rootChild1 := NewNodeWithConfig(config) 18 | rootChild1.StyleSetFlexGrow(1) 19 | rootChild1.StyleSetDisplay(DisplayNone) 20 | root.InsertChild(rootChild1, 1) 21 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 22 | 23 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 24 | assertFloatEqual(t, 0, root.LayoutGetTop()) 25 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 26 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 27 | 28 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 29 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 30 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 31 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 32 | 33 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 34 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 35 | assertFloatEqual(t, 0, rootChild1.LayoutGetWidth()) 36 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 37 | 38 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 39 | 40 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 41 | assertFloatEqual(t, 0, root.LayoutGetTop()) 42 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 43 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 44 | 45 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 46 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 47 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 48 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 49 | 50 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 51 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 52 | assertFloatEqual(t, 0, rootChild1.LayoutGetWidth()) 53 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 54 | } 55 | 56 | func TestDisplay_none_fixed_size(t *testing.T) { 57 | config := NewConfig() 58 | 59 | root := NewNodeWithConfig(config) 60 | root.StyleSetFlexDirection(FlexDirectionRow) 61 | root.StyleSetWidth(100) 62 | root.StyleSetHeight(100) 63 | 64 | rootChild0 := NewNodeWithConfig(config) 65 | rootChild0.StyleSetFlexGrow(1) 66 | root.InsertChild(rootChild0, 0) 67 | 68 | rootChild1 := NewNodeWithConfig(config) 69 | rootChild1.StyleSetWidth(20) 70 | rootChild1.StyleSetHeight(20) 71 | rootChild1.StyleSetDisplay(DisplayNone) 72 | root.InsertChild(rootChild1, 1) 73 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 74 | 75 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 76 | assertFloatEqual(t, 0, root.LayoutGetTop()) 77 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 78 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 79 | 80 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 81 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 82 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 83 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 84 | 85 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 86 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 87 | assertFloatEqual(t, 0, rootChild1.LayoutGetWidth()) 88 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 89 | 90 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 91 | 92 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 93 | assertFloatEqual(t, 0, root.LayoutGetTop()) 94 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 95 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 96 | 97 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 98 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 99 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 100 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 101 | 102 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 103 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 104 | assertFloatEqual(t, 0, rootChild1.LayoutGetWidth()) 105 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 106 | } 107 | 108 | func TestDisplay_none_with_margin(t *testing.T) { 109 | config := NewConfig() 110 | 111 | root := NewNodeWithConfig(config) 112 | root.StyleSetFlexDirection(FlexDirectionRow) 113 | root.StyleSetWidth(100) 114 | root.StyleSetHeight(100) 115 | 116 | rootChild0 := NewNodeWithConfig(config) 117 | rootChild0.StyleSetMargin(EdgeLeft, 10) 118 | rootChild0.StyleSetMargin(EdgeTop, 10) 119 | rootChild0.StyleSetMargin(EdgeRight, 10) 120 | rootChild0.StyleSetMargin(EdgeBottom, 10) 121 | rootChild0.StyleSetWidth(20) 122 | rootChild0.StyleSetHeight(20) 123 | rootChild0.StyleSetDisplay(DisplayNone) 124 | root.InsertChild(rootChild0, 0) 125 | 126 | rootChild1 := NewNodeWithConfig(config) 127 | rootChild1.StyleSetFlexGrow(1) 128 | root.InsertChild(rootChild1, 1) 129 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 130 | 131 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 132 | assertFloatEqual(t, 0, root.LayoutGetTop()) 133 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 134 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 135 | 136 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 137 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 138 | assertFloatEqual(t, 0, rootChild0.LayoutGetWidth()) 139 | assertFloatEqual(t, 0, rootChild0.LayoutGetHeight()) 140 | 141 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 142 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 143 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 144 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 145 | 146 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 147 | 148 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 149 | assertFloatEqual(t, 0, root.LayoutGetTop()) 150 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 151 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 152 | 153 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 154 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 155 | assertFloatEqual(t, 0, rootChild0.LayoutGetWidth()) 156 | assertFloatEqual(t, 0, rootChild0.LayoutGetHeight()) 157 | 158 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 159 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 160 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 161 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 162 | } 163 | 164 | func TestDisplay_none_with_child(t *testing.T) { 165 | config := NewConfig() 166 | 167 | root := NewNodeWithConfig(config) 168 | root.StyleSetFlexDirection(FlexDirectionRow) 169 | root.StyleSetWidth(100) 170 | root.StyleSetHeight(100) 171 | 172 | rootChild0 := NewNodeWithConfig(config) 173 | rootChild0.StyleSetFlexGrow(1) 174 | rootChild0.StyleSetFlexShrink(1) 175 | rootChild0.StyleSetFlexBasisPercent(0) 176 | root.InsertChild(rootChild0, 0) 177 | 178 | rootChild1 := NewNodeWithConfig(config) 179 | rootChild1.StyleSetFlexGrow(1) 180 | rootChild1.StyleSetFlexShrink(1) 181 | rootChild1.StyleSetFlexBasisPercent(0) 182 | rootChild1.StyleSetDisplay(DisplayNone) 183 | root.InsertChild(rootChild1, 1) 184 | 185 | rootChild1child0 := NewNodeWithConfig(config) 186 | rootChild1child0.StyleSetFlexGrow(1) 187 | rootChild1child0.StyleSetFlexShrink(1) 188 | rootChild1child0.StyleSetFlexBasisPercent(0) 189 | rootChild1child0.StyleSetWidth(20) 190 | rootChild1child0.StyleSetMinWidth(0) 191 | rootChild1child0.StyleSetMinHeight(0) 192 | rootChild1.InsertChild(rootChild1child0, 0) 193 | 194 | rootChild2 := NewNodeWithConfig(config) 195 | rootChild2.StyleSetFlexGrow(1) 196 | rootChild2.StyleSetFlexShrink(1) 197 | rootChild2.StyleSetFlexBasisPercent(0) 198 | root.InsertChild(rootChild2, 2) 199 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 200 | 201 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 202 | assertFloatEqual(t, 0, root.LayoutGetTop()) 203 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 204 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 205 | 206 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 207 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 208 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 209 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 210 | 211 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 212 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 213 | assertFloatEqual(t, 0, rootChild1.LayoutGetWidth()) 214 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 215 | 216 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetLeft()) 217 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetTop()) 218 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetWidth()) 219 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetHeight()) 220 | 221 | assertFloatEqual(t, 50, rootChild2.LayoutGetLeft()) 222 | assertFloatEqual(t, 0, rootChild2.LayoutGetTop()) 223 | assertFloatEqual(t, 50, rootChild2.LayoutGetWidth()) 224 | assertFloatEqual(t, 100, rootChild2.LayoutGetHeight()) 225 | 226 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 227 | 228 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 229 | assertFloatEqual(t, 0, root.LayoutGetTop()) 230 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 231 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 232 | 233 | assertFloatEqual(t, 50, rootChild0.LayoutGetLeft()) 234 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 235 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 236 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 237 | 238 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 239 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 240 | assertFloatEqual(t, 0, rootChild1.LayoutGetWidth()) 241 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 242 | 243 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetLeft()) 244 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetTop()) 245 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetWidth()) 246 | assertFloatEqual(t, 0, rootChild1child0.LayoutGetHeight()) 247 | 248 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 249 | assertFloatEqual(t, 0, rootChild2.LayoutGetTop()) 250 | assertFloatEqual(t, 50, rootChild2.LayoutGetWidth()) 251 | assertFloatEqual(t, 100, rootChild2.LayoutGetHeight()) 252 | } 253 | 254 | func TestDisplay_none_with_position(t *testing.T) { 255 | config := NewConfig() 256 | 257 | root := NewNodeWithConfig(config) 258 | root.StyleSetFlexDirection(FlexDirectionRow) 259 | root.StyleSetWidth(100) 260 | root.StyleSetHeight(100) 261 | 262 | rootChild0 := NewNodeWithConfig(config) 263 | rootChild0.StyleSetFlexGrow(1) 264 | root.InsertChild(rootChild0, 0) 265 | 266 | rootChild1 := NewNodeWithConfig(config) 267 | rootChild1.StyleSetFlexGrow(1) 268 | rootChild1.StyleSetPosition(EdgeTop, 10) 269 | rootChild1.StyleSetDisplay(DisplayNone) 270 | root.InsertChild(rootChild1, 1) 271 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 272 | 273 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 274 | assertFloatEqual(t, 0, root.LayoutGetTop()) 275 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 276 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 277 | 278 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 279 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 280 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 281 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 282 | 283 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 284 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 285 | assertFloatEqual(t, 0, rootChild1.LayoutGetWidth()) 286 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 287 | 288 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 289 | 290 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 291 | assertFloatEqual(t, 0, root.LayoutGetTop()) 292 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 293 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 294 | 295 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 296 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 297 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 298 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 299 | 300 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 301 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 302 | assertFloatEqual(t, 0, rootChild1.LayoutGetWidth()) 303 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 304 | } 305 | -------------------------------------------------------------------------------- /flex/edge_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestStart_overrides(t *testing.T) { 6 | root := NewNode() 7 | root.StyleSetFlexDirection(FlexDirectionRow) 8 | root.StyleSetWidth(100) 9 | root.StyleSetHeight(100) 10 | 11 | rootChild0 := NewNode() 12 | rootChild0.StyleSetFlexGrow(1) 13 | rootChild0.StyleSetMargin(EdgeStart, 10) 14 | rootChild0.StyleSetMargin(EdgeLeft, 20) 15 | rootChild0.StyleSetMargin(EdgeRight, 20) 16 | root.InsertChild(rootChild0, 0) 17 | 18 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 19 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 20 | assertFloatEqual(t, 20, rootChild0.LayoutGetRight()) 21 | 22 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 23 | assertFloatEqual(t, 20, rootChild0.LayoutGetLeft()) 24 | assertFloatEqual(t, 10, rootChild0.LayoutGetRight()) 25 | } 26 | 27 | func TestEnd_overrides(t *testing.T) { 28 | root := NewNode() 29 | root.StyleSetFlexDirection(FlexDirectionRow) 30 | root.StyleSetWidth(100) 31 | root.StyleSetHeight(100) 32 | 33 | rootChild0 := NewNode() 34 | rootChild0.StyleSetFlexGrow(1) 35 | rootChild0.StyleSetMargin(EdgeEnd, 10) 36 | rootChild0.StyleSetMargin(EdgeLeft, 20) 37 | rootChild0.StyleSetMargin(EdgeRight, 20) 38 | root.InsertChild(rootChild0, 0) 39 | 40 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 41 | assertFloatEqual(t, 20, rootChild0.LayoutGetLeft()) 42 | assertFloatEqual(t, 10, rootChild0.LayoutGetRight()) 43 | 44 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 45 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 46 | assertFloatEqual(t, 20, rootChild0.LayoutGetRight()) 47 | } 48 | 49 | func TestHorizontal_overridden(t *testing.T) { 50 | root := NewNode() 51 | root.StyleSetFlexDirection(FlexDirectionRow) 52 | root.StyleSetWidth(100) 53 | root.StyleSetHeight(100) 54 | 55 | rootChild0 := NewNode() 56 | rootChild0.StyleSetFlexGrow(1) 57 | rootChild0.StyleSetMargin(EdgeHorizontal, 10) 58 | rootChild0.StyleSetMargin(EdgeLeft, 20) 59 | root.InsertChild(rootChild0, 0) 60 | 61 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 62 | assertFloatEqual(t, 20, rootChild0.LayoutGetLeft()) 63 | assertFloatEqual(t, 10, rootChild0.LayoutGetRight()) 64 | } 65 | 66 | func TestVertical_overridden(t *testing.T) { 67 | root := NewNode() 68 | root.StyleSetFlexDirection(FlexDirectionColumn) 69 | root.StyleSetWidth(100) 70 | root.StyleSetHeight(100) 71 | 72 | rootChild0 := NewNode() 73 | rootChild0.StyleSetFlexGrow(1) 74 | rootChild0.StyleSetMargin(EdgeVertical, 10) 75 | rootChild0.StyleSetMargin(EdgeTop, 20) 76 | root.InsertChild(rootChild0, 0) 77 | 78 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 79 | assertFloatEqual(t, 20, rootChild0.LayoutGetTop()) 80 | assertFloatEqual(t, 10, rootChild0.LayoutGetBottom()) 81 | } 82 | 83 | func TestHorizontal_overrides_all(t *testing.T) { 84 | root := NewNode() 85 | root.StyleSetFlexDirection(FlexDirectionColumn) 86 | root.StyleSetWidth(100) 87 | root.StyleSetHeight(100) 88 | 89 | rootChild0 := NewNode() 90 | rootChild0.StyleSetFlexGrow(1) 91 | rootChild0.StyleSetMargin(EdgeHorizontal, 10) 92 | rootChild0.StyleSetMargin(EdgeAll, 20) 93 | root.InsertChild(rootChild0, 0) 94 | 95 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 96 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 97 | assertFloatEqual(t, 20, rootChild0.LayoutGetTop()) 98 | assertFloatEqual(t, 10, rootChild0.LayoutGetRight()) 99 | assertFloatEqual(t, 20, rootChild0.LayoutGetBottom()) 100 | } 101 | 102 | func TestVertical_overrides_all(t *testing.T) { 103 | root := NewNode() 104 | root.StyleSetFlexDirection(FlexDirectionColumn) 105 | root.StyleSetWidth(100) 106 | root.StyleSetHeight(100) 107 | 108 | rootChild0 := NewNode() 109 | rootChild0.StyleSetFlexGrow(1) 110 | rootChild0.StyleSetMargin(EdgeVertical, 10) 111 | rootChild0.StyleSetMargin(EdgeAll, 20) 112 | root.InsertChild(rootChild0, 0) 113 | 114 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 115 | assertFloatEqual(t, 20, rootChild0.LayoutGetLeft()) 116 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 117 | assertFloatEqual(t, 20, rootChild0.LayoutGetRight()) 118 | assertFloatEqual(t, 10, rootChild0.LayoutGetBottom()) 119 | } 120 | 121 | func TestAll_overridden(t *testing.T) { 122 | root := NewNode() 123 | root.StyleSetFlexDirection(FlexDirectionColumn) 124 | root.StyleSetWidth(100) 125 | root.StyleSetHeight(100) 126 | 127 | rootChild0 := NewNode() 128 | rootChild0.StyleSetFlexGrow(1) 129 | rootChild0.StyleSetMargin(EdgeLeft, 10) 130 | rootChild0.StyleSetMargin(EdgeTop, 10) 131 | rootChild0.StyleSetMargin(EdgeRight, 10) 132 | rootChild0.StyleSetMargin(EdgeBottom, 10) 133 | rootChild0.StyleSetMargin(EdgeAll, 20) 134 | root.InsertChild(rootChild0, 0) 135 | 136 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 137 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 138 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 139 | assertFloatEqual(t, 10, rootChild0.LayoutGetRight()) 140 | assertFloatEqual(t, 10, rootChild0.LayoutGetBottom()) 141 | } 142 | -------------------------------------------------------------------------------- /flex/enums.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | // port of YGEnums.h 4 | 5 | // Align describes align flex attribute 6 | type Align int 7 | 8 | const ( 9 | // AlignAuto is "auto" 10 | AlignAuto Align = iota 11 | // AlignFlexStart is "flex-start" 12 | AlignFlexStart 13 | // AlignCenter if "center" 14 | AlignCenter 15 | // AlignFlexEnd is "flex-end" 16 | AlignFlexEnd 17 | // AlignStretch is "strech" 18 | AlignStretch 19 | // AlignBaseline is "baseline" 20 | AlignBaseline 21 | // AlignSpaceBetween is "space-between" 22 | AlignSpaceBetween 23 | // AlignSpaceAround is "space-around" 24 | AlignSpaceAround 25 | ) 26 | 27 | // Dimension represents dimention 28 | type Dimension int 29 | 30 | const ( 31 | // DimensionWidth is width 32 | DimensionWidth Dimension = iota 33 | // DimensionHeight is height 34 | DimensionHeight 35 | ) 36 | 37 | // Direction represents right-to-left or left-to-right direction 38 | type Direction int 39 | 40 | const ( 41 | // DirectionInherit is "inherit" 42 | DirectionInherit Direction = iota 43 | // DirectionLTR is "ltr" 44 | DirectionLTR 45 | // DirectionRTL is "rtl" 46 | DirectionRTL 47 | ) 48 | 49 | // Display is "display" property 50 | type Display int 51 | 52 | const ( 53 | // DisplayFlex is "flex" 54 | DisplayFlex Display = iota 55 | // DisplayNone is "none" 56 | DisplayNone 57 | ) 58 | 59 | // Edge represents an edge 60 | type Edge int 61 | 62 | const ( 63 | // EdgeLeft is left edge 64 | EdgeLeft Edge = iota 65 | // EdgeTop is top edge 66 | EdgeTop 67 | // EdgeRight is right edge 68 | EdgeRight 69 | // EdgeBottom is bottom edge 70 | EdgeBottom 71 | // EdgeStart is start edge 72 | EdgeStart 73 | // EdgeEnd is end edge 74 | EdgeEnd 75 | // EdgeHorizontal is horizontal edge 76 | EdgeHorizontal 77 | // EdgeVertical is vertical edge 78 | EdgeVertical 79 | // EdgeAll is all edge 80 | EdgeAll 81 | ) 82 | 83 | const ( 84 | // EdgeCount is count of edges 85 | EdgeCount = 9 86 | ) 87 | 88 | // ExperimentalFeature defines experimental features 89 | type ExperimentalFeature int 90 | 91 | const ( 92 | // ExperimentalFeatureWebFlexBasis is web flex basis 93 | ExperimentalFeatureWebFlexBasis ExperimentalFeature = iota 94 | ) 95 | 96 | const ( 97 | experimentalFeatureCount = 1 98 | ) 99 | 100 | // FlexDirection describes "flex-direction" property 101 | type FlexDirection int 102 | 103 | const ( 104 | // FlexDirectionColumn is "column" 105 | FlexDirectionColumn FlexDirection = iota 106 | // FlexDirectionColumnReverse is "column-reverse" 107 | FlexDirectionColumnReverse 108 | // FlexDirectionRow is "row" 109 | FlexDirectionRow 110 | // FlexDirectionRowReverse is "row-reverse" 111 | FlexDirectionRowReverse 112 | ) 113 | 114 | // Justify is "justify" property 115 | type Justify int 116 | 117 | const ( 118 | // JustifyFlexStart is "flex-start" 119 | JustifyFlexStart Justify = iota 120 | // JustifyCenter is "center" 121 | JustifyCenter 122 | // JustifyFlexEnd is "flex-end" 123 | JustifyFlexEnd 124 | // JustifySpaceBetween is "space-between" 125 | JustifySpaceBetween 126 | // JustifySpaceAround is "space-around" 127 | JustifySpaceAround 128 | ) 129 | 130 | // LogLevel represents log level 131 | type LogLevel int 132 | 133 | const ( 134 | LogLevelError LogLevel = iota 135 | LogLevelWarn 136 | LogLevelInfo 137 | LogLevelDebug 138 | LogLevelVerbose 139 | LogLevelFatal 140 | ) 141 | 142 | // MeasureMode defines measurement mode 143 | type MeasureMode int 144 | 145 | const ( 146 | // MeasureModeUndefined is undefined 147 | MeasureModeUndefined MeasureMode = iota 148 | // MeasureModeExactly is exactly 149 | MeasureModeExactly 150 | // MeasureModeAtMost is at-most 151 | MeasureModeAtMost 152 | ) 153 | 154 | const ( 155 | measureModeCount = 3 156 | ) 157 | 158 | // NodeType defines node type 159 | type NodeType int 160 | 161 | const ( 162 | // NodeTypeDefault is default node 163 | NodeTypeDefault NodeType = iota 164 | // NodeTypeText is text node 165 | NodeTypeText 166 | ) 167 | 168 | // Overflow describes "overflow" property 169 | type Overflow int 170 | 171 | const ( 172 | // OverflowVisible is "visible" 173 | OverflowVisible Overflow = iota 174 | // OverflowHidden is "hidden" 175 | OverflowHidden 176 | // OverflowScroll is "scroll" 177 | OverflowScroll 178 | ) 179 | 180 | // PositionType is "position" property 181 | type PositionType int 182 | 183 | const ( 184 | // PositionTypeRelative is "relative" 185 | PositionTypeRelative PositionType = iota 186 | // PositionTypeAbsolute is "absolute" 187 | PositionTypeAbsolute 188 | ) 189 | 190 | type PrintOptions int 191 | 192 | const ( 193 | PrintOptionsLayout PrintOptions = 1 << iota 194 | PrintOptionsStyle 195 | PrintOptionsChildren 196 | ) 197 | 198 | // Unit is "unit" property 199 | type Unit int 200 | 201 | const ( 202 | // UnitUndefined is "undefined" 203 | UnitUndefined Unit = iota 204 | // UnitPoint is "point" 205 | UnitPoint 206 | // UnitPercent is "percent" 207 | UnitPercent 208 | // UnitAuto is "auto" 209 | UnitAuto 210 | ) 211 | 212 | // Wrap is "wrap" property 213 | type Wrap int 214 | 215 | const ( 216 | // WrapNoWrap is "no-wrap" 217 | WrapNoWrap Wrap = iota 218 | // WrapWrap is "wrap" 219 | WrapWrap 220 | // WrapWrapReverse is "reverse" 221 | WrapWrapReverse 222 | ) 223 | 224 | // AlignToString returns string version of Align enum 225 | func AlignToString(value Align) string { 226 | switch value { 227 | case AlignAuto: 228 | return "auto" 229 | case AlignFlexStart: 230 | return "flex-start" 231 | case AlignCenter: 232 | return "center" 233 | case AlignFlexEnd: 234 | return "flex-end" 235 | case AlignStretch: 236 | return "stretch" 237 | case AlignBaseline: 238 | return "baseline" 239 | case AlignSpaceBetween: 240 | return "space-between" 241 | case AlignSpaceAround: 242 | return "space-around" 243 | } 244 | return "unknown" 245 | } 246 | 247 | // DimensionToString returns string version of Dimension enum 248 | func DimensionToString(value Dimension) string { 249 | switch value { 250 | case DimensionWidth: 251 | return "width" 252 | case DimensionHeight: 253 | return "height" 254 | } 255 | return "unknown" 256 | } 257 | 258 | // DirectionToString returns string version of Direction enum 259 | func DirectionToString(value Direction) string { 260 | switch value { 261 | case DirectionInherit: 262 | return "inherit" 263 | case DirectionLTR: 264 | return "ltr" 265 | case DirectionRTL: 266 | return "rtl" 267 | } 268 | return "unknown" 269 | } 270 | 271 | // DisplayToString returns string version of Display enum 272 | func DisplayToString(value Display) string { 273 | switch value { 274 | case DisplayFlex: 275 | return "flex" 276 | case DisplayNone: 277 | return "none" 278 | } 279 | return "unknown" 280 | } 281 | 282 | // EdgeToString returns string version of Edge enum 283 | func EdgeToString(value Edge) string { 284 | switch value { 285 | case EdgeLeft: 286 | return "left" 287 | case EdgeTop: 288 | return "top" 289 | case EdgeRight: 290 | return "right" 291 | case EdgeBottom: 292 | return "bottom" 293 | case EdgeStart: 294 | return "start" 295 | case EdgeEnd: 296 | return "end" 297 | case EdgeHorizontal: 298 | return "horizontal" 299 | case EdgeVertical: 300 | return "vertical" 301 | case EdgeAll: 302 | return "all" 303 | } 304 | return "unknown" 305 | } 306 | 307 | // ExperimentalFeatureToString returns string version of ExperimentalFeature enum 308 | func ExperimentalFeatureToString(value ExperimentalFeature) string { 309 | switch value { 310 | case ExperimentalFeatureWebFlexBasis: 311 | return "web-flex-basis" 312 | } 313 | return "unknown" 314 | } 315 | 316 | // FlexDirectionToString returns string version of FlexDirection enum 317 | func FlexDirectionToString(value FlexDirection) string { 318 | switch value { 319 | case FlexDirectionColumn: 320 | return "column" 321 | case FlexDirectionColumnReverse: 322 | return "column-reverse" 323 | case FlexDirectionRow: 324 | return "row" 325 | case FlexDirectionRowReverse: 326 | return "row-reverse" 327 | } 328 | return "unknown" 329 | } 330 | 331 | // JustifyToString returns string version of Justify enum 332 | func JustifyToString(value Justify) string { 333 | switch value { 334 | case JustifyFlexStart: 335 | return "flex-start" 336 | case JustifyCenter: 337 | return "center" 338 | case JustifyFlexEnd: 339 | return "flex-end" 340 | case JustifySpaceBetween: 341 | return "space-between" 342 | case JustifySpaceAround: 343 | return "space-around" 344 | } 345 | return "unknown" 346 | } 347 | 348 | // LogLevelToString returns string version of LogLevel enum 349 | func LogLevelToString(value LogLevel) string { 350 | switch value { 351 | case LogLevelError: 352 | return "error" 353 | case LogLevelWarn: 354 | return "warn" 355 | case LogLevelInfo: 356 | return "info" 357 | case LogLevelDebug: 358 | return "debug" 359 | case LogLevelVerbose: 360 | return "verbose" 361 | case LogLevelFatal: 362 | return "fatal" 363 | } 364 | return "unknown" 365 | } 366 | 367 | // MeasureModeToString returns string version of MeasureMode enum 368 | func MeasureModeToString(value MeasureMode) string { 369 | switch value { 370 | case MeasureModeUndefined: 371 | return "undefined" 372 | case MeasureModeExactly: 373 | return "exactly" 374 | case MeasureModeAtMost: 375 | return "at-most" 376 | } 377 | return "unknown" 378 | } 379 | 380 | // NodeTypeToString returns string version of NodeType enum 381 | func NodeTypeToString(value NodeType) string { 382 | switch value { 383 | case NodeTypeDefault: 384 | return "default" 385 | case NodeTypeText: 386 | return "text" 387 | } 388 | return "unknown" 389 | } 390 | 391 | // OverflowToString returns string version of Overflow enum 392 | func OverflowToString(value Overflow) string { 393 | switch value { 394 | case OverflowVisible: 395 | return "visible" 396 | case OverflowHidden: 397 | return "hidden" 398 | case OverflowScroll: 399 | return "scroll" 400 | } 401 | return "unknown" 402 | } 403 | 404 | // PositionTypeToString returns string version of PositionType enum 405 | func PositionTypeToString(value PositionType) string { 406 | switch value { 407 | case PositionTypeRelative: 408 | return "relative" 409 | case PositionTypeAbsolute: 410 | return "absolute" 411 | } 412 | return "unknown" 413 | } 414 | 415 | // PrintOptionsToString returns string version of PrintOptions enum 416 | func PrintOptionsToString(value PrintOptions) string { 417 | switch value { 418 | case PrintOptionsLayout: 419 | return "layout" 420 | case PrintOptionsStyle: 421 | return "style" 422 | case PrintOptionsChildren: 423 | return "children" 424 | } 425 | return "unknown" 426 | } 427 | 428 | // UnitToString returns string version of Unit enum 429 | func UnitToString(value Unit) string { 430 | switch value { 431 | case UnitUndefined: 432 | return "undefined" 433 | case UnitPoint: 434 | return "point" 435 | case UnitPercent: 436 | return "percent" 437 | case UnitAuto: 438 | return "auto" 439 | } 440 | return "unknown" 441 | } 442 | 443 | // WrapToString returns string version of Wrap enum 444 | func WrapToString(value Wrap) string { 445 | switch value { 446 | case WrapNoWrap: 447 | return "no-wrap" 448 | case WrapWrap: 449 | return "wrap" 450 | case WrapWrapReverse: 451 | return "wrap-reverse" 452 | } 453 | return "unknown" 454 | } 455 | -------------------------------------------------------------------------------- /flex/flex_direction_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestFlex_direction_column_no_height(t *testing.T) { 6 | config := NewConfig() 7 | root := NewNodeWithConfig(config) 8 | root.StyleSetWidth(100) 9 | 10 | rootChild0 := NewNodeWithConfig(config) 11 | rootChild0.StyleSetHeight(10) 12 | root.InsertChild(rootChild0, 0) 13 | 14 | rootChild1 := NewNodeWithConfig(config) 15 | rootChild1.StyleSetHeight(10) 16 | root.InsertChild(rootChild1, 1) 17 | 18 | rootChild2 := NewNodeWithConfig(config) 19 | rootChild2.StyleSetHeight(10) 20 | root.InsertChild(rootChild2, 2) 21 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 22 | 23 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 24 | assertFloatEqual(t, 0, root.LayoutGetTop()) 25 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 26 | assertFloatEqual(t, 30, root.LayoutGetHeight()) 27 | 28 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 29 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 30 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 31 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 32 | 33 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 34 | assertFloatEqual(t, 10, rootChild1.LayoutGetTop()) 35 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 36 | assertFloatEqual(t, 10, rootChild1.LayoutGetHeight()) 37 | 38 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 39 | assertFloatEqual(t, 20, rootChild2.LayoutGetTop()) 40 | assertFloatEqual(t, 100, rootChild2.LayoutGetWidth()) 41 | assertFloatEqual(t, 10, rootChild2.LayoutGetHeight()) 42 | 43 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 44 | 45 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 46 | assertFloatEqual(t, 0, root.LayoutGetTop()) 47 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 48 | assertFloatEqual(t, 30, root.LayoutGetHeight()) 49 | 50 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 51 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 52 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 53 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 54 | 55 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 56 | assertFloatEqual(t, 10, rootChild1.LayoutGetTop()) 57 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 58 | assertFloatEqual(t, 10, rootChild1.LayoutGetHeight()) 59 | 60 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 61 | assertFloatEqual(t, 20, rootChild2.LayoutGetTop()) 62 | assertFloatEqual(t, 100, rootChild2.LayoutGetWidth()) 63 | assertFloatEqual(t, 10, rootChild2.LayoutGetHeight()) 64 | } 65 | 66 | func TestFlex_direction_row_no_width(t *testing.T) { 67 | config := NewConfig() 68 | 69 | root := NewNodeWithConfig(config) 70 | root.StyleSetFlexDirection(FlexDirectionRow) 71 | root.StyleSetHeight(100) 72 | 73 | rootChild0 := NewNodeWithConfig(config) 74 | rootChild0.StyleSetWidth(10) 75 | root.InsertChild(rootChild0, 0) 76 | 77 | rootChild1 := NewNodeWithConfig(config) 78 | rootChild1.StyleSetWidth(10) 79 | root.InsertChild(rootChild1, 1) 80 | 81 | rootChild2 := NewNodeWithConfig(config) 82 | rootChild2.StyleSetWidth(10) 83 | root.InsertChild(rootChild2, 2) 84 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 85 | 86 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 87 | assertFloatEqual(t, 0, root.LayoutGetTop()) 88 | assertFloatEqual(t, 30, root.LayoutGetWidth()) 89 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 90 | 91 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 92 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 93 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 94 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 95 | 96 | assertFloatEqual(t, 10, rootChild1.LayoutGetLeft()) 97 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 98 | assertFloatEqual(t, 10, rootChild1.LayoutGetWidth()) 99 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 100 | 101 | assertFloatEqual(t, 20, rootChild2.LayoutGetLeft()) 102 | assertFloatEqual(t, 0, rootChild2.LayoutGetTop()) 103 | assertFloatEqual(t, 10, rootChild2.LayoutGetWidth()) 104 | assertFloatEqual(t, 100, rootChild2.LayoutGetHeight()) 105 | 106 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 107 | 108 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 109 | assertFloatEqual(t, 0, root.LayoutGetTop()) 110 | assertFloatEqual(t, 30, root.LayoutGetWidth()) 111 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 112 | 113 | assertFloatEqual(t, 20, rootChild0.LayoutGetLeft()) 114 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 115 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 116 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 117 | 118 | assertFloatEqual(t, 10, rootChild1.LayoutGetLeft()) 119 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 120 | assertFloatEqual(t, 10, rootChild1.LayoutGetWidth()) 121 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 122 | 123 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 124 | assertFloatEqual(t, 0, rootChild2.LayoutGetTop()) 125 | assertFloatEqual(t, 10, rootChild2.LayoutGetWidth()) 126 | assertFloatEqual(t, 100, rootChild2.LayoutGetHeight()) 127 | } 128 | 129 | func TestFlex_direction_column(t *testing.T) { 130 | config := NewConfig() 131 | 132 | root := NewNodeWithConfig(config) 133 | root.StyleSetWidth(100) 134 | root.StyleSetHeight(100) 135 | 136 | rootChild0 := NewNodeWithConfig(config) 137 | rootChild0.StyleSetHeight(10) 138 | root.InsertChild(rootChild0, 0) 139 | 140 | rootChild1 := NewNodeWithConfig(config) 141 | rootChild1.StyleSetHeight(10) 142 | root.InsertChild(rootChild1, 1) 143 | 144 | rootChild2 := NewNodeWithConfig(config) 145 | rootChild2.StyleSetHeight(10) 146 | root.InsertChild(rootChild2, 2) 147 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 148 | 149 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 150 | assertFloatEqual(t, 0, root.LayoutGetTop()) 151 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 152 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 153 | 154 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 155 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 156 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 157 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 158 | 159 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 160 | assertFloatEqual(t, 10, rootChild1.LayoutGetTop()) 161 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 162 | assertFloatEqual(t, 10, rootChild1.LayoutGetHeight()) 163 | 164 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 165 | assertFloatEqual(t, 20, rootChild2.LayoutGetTop()) 166 | assertFloatEqual(t, 100, rootChild2.LayoutGetWidth()) 167 | assertFloatEqual(t, 10, rootChild2.LayoutGetHeight()) 168 | 169 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 170 | 171 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 172 | assertFloatEqual(t, 0, root.LayoutGetTop()) 173 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 174 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 175 | 176 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 177 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 178 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 179 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 180 | 181 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 182 | assertFloatEqual(t, 10, rootChild1.LayoutGetTop()) 183 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 184 | assertFloatEqual(t, 10, rootChild1.LayoutGetHeight()) 185 | 186 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 187 | assertFloatEqual(t, 20, rootChild2.LayoutGetTop()) 188 | assertFloatEqual(t, 100, rootChild2.LayoutGetWidth()) 189 | assertFloatEqual(t, 10, rootChild2.LayoutGetHeight()) 190 | } 191 | 192 | func TestFlex_direction_row(t *testing.T) { 193 | config := NewConfig() 194 | 195 | root := NewNodeWithConfig(config) 196 | root.StyleSetFlexDirection(FlexDirectionRow) 197 | root.StyleSetWidth(100) 198 | root.StyleSetHeight(100) 199 | 200 | rootChild0 := NewNodeWithConfig(config) 201 | rootChild0.StyleSetWidth(10) 202 | root.InsertChild(rootChild0, 0) 203 | 204 | rootChild1 := NewNodeWithConfig(config) 205 | rootChild1.StyleSetWidth(10) 206 | root.InsertChild(rootChild1, 1) 207 | 208 | rootChild2 := NewNodeWithConfig(config) 209 | rootChild2.StyleSetWidth(10) 210 | root.InsertChild(rootChild2, 2) 211 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 212 | 213 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 214 | assertFloatEqual(t, 0, root.LayoutGetTop()) 215 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 216 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 217 | 218 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 219 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 220 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 221 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 222 | 223 | assertFloatEqual(t, 10, rootChild1.LayoutGetLeft()) 224 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 225 | assertFloatEqual(t, 10, rootChild1.LayoutGetWidth()) 226 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 227 | 228 | assertFloatEqual(t, 20, rootChild2.LayoutGetLeft()) 229 | assertFloatEqual(t, 0, rootChild2.LayoutGetTop()) 230 | assertFloatEqual(t, 10, rootChild2.LayoutGetWidth()) 231 | assertFloatEqual(t, 100, rootChild2.LayoutGetHeight()) 232 | 233 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 234 | 235 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 236 | assertFloatEqual(t, 0, root.LayoutGetTop()) 237 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 238 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 239 | 240 | assertFloatEqual(t, 90, rootChild0.LayoutGetLeft()) 241 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 242 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 243 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 244 | 245 | assertFloatEqual(t, 80, rootChild1.LayoutGetLeft()) 246 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 247 | assertFloatEqual(t, 10, rootChild1.LayoutGetWidth()) 248 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 249 | 250 | assertFloatEqual(t, 70, rootChild2.LayoutGetLeft()) 251 | assertFloatEqual(t, 0, rootChild2.LayoutGetTop()) 252 | assertFloatEqual(t, 10, rootChild2.LayoutGetWidth()) 253 | assertFloatEqual(t, 100, rootChild2.LayoutGetHeight()) 254 | } 255 | 256 | func TestFlex_direction_column_reverse(t *testing.T) { 257 | config := NewConfig() 258 | 259 | root := NewNodeWithConfig(config) 260 | root.StyleSetFlexDirection(FlexDirectionColumnReverse) 261 | root.StyleSetWidth(100) 262 | root.StyleSetHeight(100) 263 | 264 | rootChild0 := NewNodeWithConfig(config) 265 | rootChild0.StyleSetHeight(10) 266 | root.InsertChild(rootChild0, 0) 267 | 268 | rootChild1 := NewNodeWithConfig(config) 269 | rootChild1.StyleSetHeight(10) 270 | root.InsertChild(rootChild1, 1) 271 | 272 | rootChild2 := NewNodeWithConfig(config) 273 | rootChild2.StyleSetHeight(10) 274 | root.InsertChild(rootChild2, 2) 275 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 276 | 277 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 278 | assertFloatEqual(t, 0, root.LayoutGetTop()) 279 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 280 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 281 | 282 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 283 | assertFloatEqual(t, 90, rootChild0.LayoutGetTop()) 284 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 285 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 286 | 287 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 288 | assertFloatEqual(t, 80, rootChild1.LayoutGetTop()) 289 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 290 | assertFloatEqual(t, 10, rootChild1.LayoutGetHeight()) 291 | 292 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 293 | assertFloatEqual(t, 70, rootChild2.LayoutGetTop()) 294 | assertFloatEqual(t, 100, rootChild2.LayoutGetWidth()) 295 | assertFloatEqual(t, 10, rootChild2.LayoutGetHeight()) 296 | 297 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 298 | 299 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 300 | assertFloatEqual(t, 0, root.LayoutGetTop()) 301 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 302 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 303 | 304 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 305 | assertFloatEqual(t, 90, rootChild0.LayoutGetTop()) 306 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 307 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 308 | 309 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 310 | assertFloatEqual(t, 80, rootChild1.LayoutGetTop()) 311 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 312 | assertFloatEqual(t, 10, rootChild1.LayoutGetHeight()) 313 | 314 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 315 | assertFloatEqual(t, 70, rootChild2.LayoutGetTop()) 316 | assertFloatEqual(t, 100, rootChild2.LayoutGetWidth()) 317 | assertFloatEqual(t, 10, rootChild2.LayoutGetHeight()) 318 | } 319 | 320 | func TestFlex_direction_row_reverse(t *testing.T) { 321 | config := NewConfig() 322 | 323 | root := NewNodeWithConfig(config) 324 | root.StyleSetFlexDirection(FlexDirectionRowReverse) 325 | root.StyleSetWidth(100) 326 | root.StyleSetHeight(100) 327 | 328 | rootChild0 := NewNodeWithConfig(config) 329 | rootChild0.StyleSetWidth(10) 330 | root.InsertChild(rootChild0, 0) 331 | 332 | rootChild1 := NewNodeWithConfig(config) 333 | rootChild1.StyleSetWidth(10) 334 | root.InsertChild(rootChild1, 1) 335 | 336 | rootChild2 := NewNodeWithConfig(config) 337 | rootChild2.StyleSetWidth(10) 338 | root.InsertChild(rootChild2, 2) 339 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 340 | 341 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 342 | assertFloatEqual(t, 0, root.LayoutGetTop()) 343 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 344 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 345 | 346 | assertFloatEqual(t, 90, rootChild0.LayoutGetLeft()) 347 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 348 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 349 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 350 | 351 | assertFloatEqual(t, 80, rootChild1.LayoutGetLeft()) 352 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 353 | assertFloatEqual(t, 10, rootChild1.LayoutGetWidth()) 354 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 355 | 356 | assertFloatEqual(t, 70, rootChild2.LayoutGetLeft()) 357 | assertFloatEqual(t, 0, rootChild2.LayoutGetTop()) 358 | assertFloatEqual(t, 10, rootChild2.LayoutGetWidth()) 359 | assertFloatEqual(t, 100, rootChild2.LayoutGetHeight()) 360 | 361 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 362 | 363 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 364 | assertFloatEqual(t, 0, root.LayoutGetTop()) 365 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 366 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 367 | 368 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 369 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 370 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 371 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 372 | 373 | assertFloatEqual(t, 10, rootChild1.LayoutGetLeft()) 374 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 375 | assertFloatEqual(t, 10, rootChild1.LayoutGetWidth()) 376 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 377 | 378 | assertFloatEqual(t, 20, rootChild2.LayoutGetLeft()) 379 | assertFloatEqual(t, 0, rootChild2.LayoutGetTop()) 380 | assertFloatEqual(t, 10, rootChild2.LayoutGetWidth()) 381 | assertFloatEqual(t, 100, rootChild2.LayoutGetHeight()) 382 | } 383 | -------------------------------------------------------------------------------- /flex/flex_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestFlex_basis_flex_grow_column(t *testing.T) { 6 | config := NewConfig() 7 | 8 | root := NewNodeWithConfig(config) 9 | root.StyleSetWidth(100) 10 | root.StyleSetHeight(100) 11 | 12 | rootChild0 := NewNodeWithConfig(config) 13 | rootChild0.StyleSetFlexGrow(1) 14 | rootChild0.StyleSetFlexBasis(50) 15 | root.InsertChild(rootChild0, 0) 16 | 17 | rootChild1 := NewNodeWithConfig(config) 18 | rootChild1.StyleSetFlexGrow(1) 19 | root.InsertChild(rootChild1, 1) 20 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 21 | 22 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 23 | assertFloatEqual(t, 0, root.LayoutGetTop()) 24 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 25 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 26 | 27 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 28 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 29 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 30 | assertFloatEqual(t, 75, rootChild0.LayoutGetHeight()) 31 | 32 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 33 | assertFloatEqual(t, 75, rootChild1.LayoutGetTop()) 34 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 35 | assertFloatEqual(t, 25, rootChild1.LayoutGetHeight()) 36 | 37 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 38 | 39 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 40 | assertFloatEqual(t, 0, root.LayoutGetTop()) 41 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 42 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 43 | 44 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 45 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 46 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 47 | assertFloatEqual(t, 75, rootChild0.LayoutGetHeight()) 48 | 49 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 50 | assertFloatEqual(t, 75, rootChild1.LayoutGetTop()) 51 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 52 | assertFloatEqual(t, 25, rootChild1.LayoutGetHeight()) 53 | } 54 | 55 | func TestFlex_basis_flex_grow_row(t *testing.T) { 56 | config := NewConfig() 57 | 58 | root := NewNodeWithConfig(config) 59 | root.StyleSetFlexDirection(FlexDirectionRow) 60 | root.StyleSetWidth(100) 61 | root.StyleSetHeight(100) 62 | 63 | rootChild0 := NewNodeWithConfig(config) 64 | rootChild0.StyleSetFlexGrow(1) 65 | rootChild0.StyleSetFlexBasis(50) 66 | root.InsertChild(rootChild0, 0) 67 | 68 | rootChild1 := NewNodeWithConfig(config) 69 | rootChild1.StyleSetFlexGrow(1) 70 | root.InsertChild(rootChild1, 1) 71 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 72 | 73 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 74 | assertFloatEqual(t, 0, root.LayoutGetTop()) 75 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 76 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 77 | 78 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 79 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 80 | assertFloatEqual(t, 75, rootChild0.LayoutGetWidth()) 81 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 82 | 83 | assertFloatEqual(t, 75, rootChild1.LayoutGetLeft()) 84 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 85 | assertFloatEqual(t, 25, rootChild1.LayoutGetWidth()) 86 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 87 | 88 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 89 | 90 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 91 | assertFloatEqual(t, 0, root.LayoutGetTop()) 92 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 93 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 94 | 95 | assertFloatEqual(t, 25, rootChild0.LayoutGetLeft()) 96 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 97 | assertFloatEqual(t, 75, rootChild0.LayoutGetWidth()) 98 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 99 | 100 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 101 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 102 | assertFloatEqual(t, 25, rootChild1.LayoutGetWidth()) 103 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 104 | } 105 | 106 | func TestFlex_basis_flex_shrink_column(t *testing.T) { 107 | config := NewConfig() 108 | 109 | root := NewNodeWithConfig(config) 110 | root.StyleSetWidth(100) 111 | root.StyleSetHeight(100) 112 | 113 | rootChild0 := NewNodeWithConfig(config) 114 | rootChild0.StyleSetFlexShrink(1) 115 | rootChild0.StyleSetFlexBasis(100) 116 | root.InsertChild(rootChild0, 0) 117 | 118 | rootChild1 := NewNodeWithConfig(config) 119 | rootChild1.StyleSetFlexBasis(50) 120 | root.InsertChild(rootChild1, 1) 121 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 122 | 123 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 124 | assertFloatEqual(t, 0, root.LayoutGetTop()) 125 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 126 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 127 | 128 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 129 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 130 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 131 | assertFloatEqual(t, 50, rootChild0.LayoutGetHeight()) 132 | 133 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 134 | assertFloatEqual(t, 50, rootChild1.LayoutGetTop()) 135 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 136 | assertFloatEqual(t, 50, rootChild1.LayoutGetHeight()) 137 | 138 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 139 | 140 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 141 | assertFloatEqual(t, 0, root.LayoutGetTop()) 142 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 143 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 144 | 145 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 146 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 147 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 148 | assertFloatEqual(t, 50, rootChild0.LayoutGetHeight()) 149 | 150 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 151 | assertFloatEqual(t, 50, rootChild1.LayoutGetTop()) 152 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 153 | assertFloatEqual(t, 50, rootChild1.LayoutGetHeight()) 154 | } 155 | 156 | func TestFlex_basis_flex_shrink_row(t *testing.T) { 157 | config := NewConfig() 158 | 159 | root := NewNodeWithConfig(config) 160 | root.StyleSetFlexDirection(FlexDirectionRow) 161 | root.StyleSetWidth(100) 162 | root.StyleSetHeight(100) 163 | 164 | rootChild0 := NewNodeWithConfig(config) 165 | rootChild0.StyleSetFlexShrink(1) 166 | rootChild0.StyleSetFlexBasis(100) 167 | root.InsertChild(rootChild0, 0) 168 | 169 | rootChild1 := NewNodeWithConfig(config) 170 | rootChild1.StyleSetFlexBasis(50) 171 | root.InsertChild(rootChild1, 1) 172 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 173 | 174 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 175 | assertFloatEqual(t, 0, root.LayoutGetTop()) 176 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 177 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 178 | 179 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 180 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 181 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 182 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 183 | 184 | assertFloatEqual(t, 50, rootChild1.LayoutGetLeft()) 185 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 186 | assertFloatEqual(t, 50, rootChild1.LayoutGetWidth()) 187 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 188 | 189 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 190 | 191 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 192 | assertFloatEqual(t, 0, root.LayoutGetTop()) 193 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 194 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 195 | 196 | assertFloatEqual(t, 50, rootChild0.LayoutGetLeft()) 197 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 198 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 199 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 200 | 201 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 202 | assertFloatEqual(t, 0, rootChild1.LayoutGetTop()) 203 | assertFloatEqual(t, 50, rootChild1.LayoutGetWidth()) 204 | assertFloatEqual(t, 100, rootChild1.LayoutGetHeight()) 205 | } 206 | 207 | func TestFlex_shrink_to_zero(t *testing.T) { 208 | config := NewConfig() 209 | 210 | root := NewNodeWithConfig(config) 211 | root.StyleSetHeight(75) 212 | 213 | rootChild0 := NewNodeWithConfig(config) 214 | rootChild0.StyleSetWidth(50) 215 | rootChild0.StyleSetHeight(50) 216 | root.InsertChild(rootChild0, 0) 217 | 218 | rootChild1 := NewNodeWithConfig(config) 219 | rootChild1.StyleSetFlexShrink(1) 220 | rootChild1.StyleSetWidth(50) 221 | rootChild1.StyleSetHeight(50) 222 | root.InsertChild(rootChild1, 1) 223 | 224 | rootChild2 := NewNodeWithConfig(config) 225 | rootChild2.StyleSetWidth(50) 226 | rootChild2.StyleSetHeight(50) 227 | root.InsertChild(rootChild2, 2) 228 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 229 | 230 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 231 | assertFloatEqual(t, 0, root.LayoutGetTop()) 232 | assertFloatEqual(t, 50, root.LayoutGetWidth()) 233 | assertFloatEqual(t, 75, root.LayoutGetHeight()) 234 | 235 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 236 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 237 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 238 | assertFloatEqual(t, 50, rootChild0.LayoutGetHeight()) 239 | 240 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 241 | assertFloatEqual(t, 50, rootChild1.LayoutGetTop()) 242 | assertFloatEqual(t, 50, rootChild1.LayoutGetWidth()) 243 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 244 | 245 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 246 | assertFloatEqual(t, 50, rootChild2.LayoutGetTop()) 247 | assertFloatEqual(t, 50, rootChild2.LayoutGetWidth()) 248 | assertFloatEqual(t, 50, rootChild2.LayoutGetHeight()) 249 | 250 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 251 | 252 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 253 | assertFloatEqual(t, 0, root.LayoutGetTop()) 254 | assertFloatEqual(t, 50, root.LayoutGetWidth()) 255 | assertFloatEqual(t, 75, root.LayoutGetHeight()) 256 | 257 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 258 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 259 | assertFloatEqual(t, 50, rootChild0.LayoutGetWidth()) 260 | assertFloatEqual(t, 50, rootChild0.LayoutGetHeight()) 261 | 262 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 263 | assertFloatEqual(t, 50, rootChild1.LayoutGetTop()) 264 | assertFloatEqual(t, 50, rootChild1.LayoutGetWidth()) 265 | assertFloatEqual(t, 0, rootChild1.LayoutGetHeight()) 266 | 267 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 268 | assertFloatEqual(t, 50, rootChild2.LayoutGetTop()) 269 | assertFloatEqual(t, 50, rootChild2.LayoutGetWidth()) 270 | assertFloatEqual(t, 50, rootChild2.LayoutGetHeight()) 271 | } 272 | 273 | func TestFlex_basis_overrides_main_size(t *testing.T) { 274 | config := NewConfig() 275 | 276 | root := NewNodeWithConfig(config) 277 | root.StyleSetWidth(100) 278 | root.StyleSetHeight(100) 279 | 280 | rootChild0 := NewNodeWithConfig(config) 281 | rootChild0.StyleSetFlexGrow(1) 282 | rootChild0.StyleSetFlexBasis(50) 283 | rootChild0.StyleSetHeight(20) 284 | root.InsertChild(rootChild0, 0) 285 | 286 | rootChild1 := NewNodeWithConfig(config) 287 | rootChild1.StyleSetFlexGrow(1) 288 | rootChild1.StyleSetHeight(10) 289 | root.InsertChild(rootChild1, 1) 290 | 291 | rootChild2 := NewNodeWithConfig(config) 292 | rootChild2.StyleSetFlexGrow(1) 293 | rootChild2.StyleSetHeight(10) 294 | root.InsertChild(rootChild2, 2) 295 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 296 | 297 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 298 | assertFloatEqual(t, 0, root.LayoutGetTop()) 299 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 300 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 301 | 302 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 303 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 304 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 305 | assertFloatEqual(t, 60, rootChild0.LayoutGetHeight()) 306 | 307 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 308 | assertFloatEqual(t, 60, rootChild1.LayoutGetTop()) 309 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 310 | assertFloatEqual(t, 20, rootChild1.LayoutGetHeight()) 311 | 312 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 313 | assertFloatEqual(t, 80, rootChild2.LayoutGetTop()) 314 | assertFloatEqual(t, 100, rootChild2.LayoutGetWidth()) 315 | assertFloatEqual(t, 20, rootChild2.LayoutGetHeight()) 316 | 317 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 318 | 319 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 320 | assertFloatEqual(t, 0, root.LayoutGetTop()) 321 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 322 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 323 | 324 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 325 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 326 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 327 | assertFloatEqual(t, 60, rootChild0.LayoutGetHeight()) 328 | 329 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 330 | assertFloatEqual(t, 60, rootChild1.LayoutGetTop()) 331 | assertFloatEqual(t, 100, rootChild1.LayoutGetWidth()) 332 | assertFloatEqual(t, 20, rootChild1.LayoutGetHeight()) 333 | 334 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 335 | assertFloatEqual(t, 80, rootChild2.LayoutGetTop()) 336 | assertFloatEqual(t, 100, rootChild2.LayoutGetWidth()) 337 | assertFloatEqual(t, 20, rootChild2.LayoutGetHeight()) 338 | } 339 | 340 | func TestFlex_grow_shrink_at_most(t *testing.T) { 341 | config := NewConfig() 342 | 343 | root := NewNodeWithConfig(config) 344 | root.StyleSetWidth(100) 345 | root.StyleSetHeight(100) 346 | 347 | rootChild0 := NewNodeWithConfig(config) 348 | root.InsertChild(rootChild0, 0) 349 | 350 | rootChild0Child0 := NewNodeWithConfig(config) 351 | rootChild0Child0.StyleSetFlexGrow(1) 352 | rootChild0Child0.StyleSetFlexShrink(1) 353 | rootChild0.InsertChild(rootChild0Child0, 0) 354 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 355 | 356 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 357 | assertFloatEqual(t, 0, root.LayoutGetTop()) 358 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 359 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 360 | 361 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 362 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 363 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 364 | assertFloatEqual(t, 0, rootChild0.LayoutGetHeight()) 365 | 366 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetLeft()) 367 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 368 | assertFloatEqual(t, 100, rootChild0Child0.LayoutGetWidth()) 369 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetHeight()) 370 | 371 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 372 | 373 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 374 | assertFloatEqual(t, 0, root.LayoutGetTop()) 375 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 376 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 377 | 378 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 379 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 380 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 381 | assertFloatEqual(t, 0, rootChild0.LayoutGetHeight()) 382 | 383 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetLeft()) 384 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 385 | assertFloatEqual(t, 100, rootChild0Child0.LayoutGetWidth()) 386 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetHeight()) 387 | } 388 | 389 | func TestFlex_grow_less_than_factor_one(t *testing.T) { 390 | config := NewConfig() 391 | 392 | root := NewNodeWithConfig(config) 393 | root.StyleSetWidth(200) 394 | root.StyleSetHeight(500) 395 | 396 | rootChild0 := NewNodeWithConfig(config) 397 | rootChild0.StyleSetFlexGrow(0.2) 398 | rootChild0.StyleSetFlexBasis(40) 399 | root.InsertChild(rootChild0, 0) 400 | 401 | rootChild1 := NewNodeWithConfig(config) 402 | rootChild1.StyleSetFlexGrow(0.2) 403 | root.InsertChild(rootChild1, 1) 404 | 405 | rootChild2 := NewNodeWithConfig(config) 406 | rootChild2.StyleSetFlexGrow(0.4) 407 | root.InsertChild(rootChild2, 2) 408 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 409 | 410 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 411 | assertFloatEqual(t, 0, root.LayoutGetTop()) 412 | assertFloatEqual(t, 200, root.LayoutGetWidth()) 413 | assertFloatEqual(t, 500, root.LayoutGetHeight()) 414 | 415 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 416 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 417 | assertFloatEqual(t, 200, rootChild0.LayoutGetWidth()) 418 | assertFloatEqual(t, 132, rootChild0.LayoutGetHeight()) 419 | 420 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 421 | assertFloatEqual(t, 132, rootChild1.LayoutGetTop()) 422 | assertFloatEqual(t, 200, rootChild1.LayoutGetWidth()) 423 | assertFloatEqual(t, 92, rootChild1.LayoutGetHeight()) 424 | 425 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 426 | assertFloatEqual(t, 224, rootChild2.LayoutGetTop()) 427 | assertFloatEqual(t, 200, rootChild2.LayoutGetWidth()) 428 | assertFloatEqual(t, 184, rootChild2.LayoutGetHeight()) 429 | 430 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 431 | 432 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 433 | assertFloatEqual(t, 0, root.LayoutGetTop()) 434 | assertFloatEqual(t, 200, root.LayoutGetWidth()) 435 | assertFloatEqual(t, 500, root.LayoutGetHeight()) 436 | 437 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 438 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 439 | assertFloatEqual(t, 200, rootChild0.LayoutGetWidth()) 440 | assertFloatEqual(t, 132, rootChild0.LayoutGetHeight()) 441 | 442 | assertFloatEqual(t, 0, rootChild1.LayoutGetLeft()) 443 | assertFloatEqual(t, 132, rootChild1.LayoutGetTop()) 444 | assertFloatEqual(t, 200, rootChild1.LayoutGetWidth()) 445 | assertFloatEqual(t, 92, rootChild1.LayoutGetHeight()) 446 | 447 | assertFloatEqual(t, 0, rootChild2.LayoutGetLeft()) 448 | assertFloatEqual(t, 224, rootChild2.LayoutGetTop()) 449 | assertFloatEqual(t, 200, rootChild2.LayoutGetWidth()) 450 | assertFloatEqual(t, 184, rootChild2.LayoutGetHeight()) 451 | } 452 | -------------------------------------------------------------------------------- /flex/had_overflow_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func newHadOverflowTests() (*Config, *Node) { 10 | config := NewConfig() 11 | root := NewNodeWithConfig(config) 12 | root.StyleSetWidth(200) 13 | root.StyleSetHeight(100) 14 | root.StyleSetFlexDirection(FlexDirectionColumn) 15 | root.StyleSetFlexWrap(WrapNoWrap) 16 | return config, root 17 | } 18 | 19 | func TestChildren_overflow_no_wrap_and_no_flex_children(t *testing.T) { 20 | config, root := newHadOverflowTests() 21 | child0 := NewNodeWithConfig(config) 22 | child0.StyleSetWidth(80) 23 | child0.StyleSetHeight(40) 24 | child0.StyleSetMargin(EdgeTop, 10) 25 | child0.StyleSetMargin(EdgeBottom, 15) 26 | root.InsertChild(child0, 0) 27 | child1 := NewNodeWithConfig(config) 28 | child1.StyleSetWidth(80) 29 | child1.StyleSetHeight(40) 30 | child1.StyleSetMargin(EdgeBottom, 5) 31 | root.InsertChild(child1, 1) 32 | 33 | CalculateLayout(root, 200, 100, DirectionLTR) 34 | 35 | assert.True(t, root.Layout.HadOverflow) 36 | } 37 | 38 | func TestSpacing_overflow_no_wrap_and_no_flex_children(t *testing.T) { 39 | config, root := newHadOverflowTests() 40 | child0 := NewNodeWithConfig(config) 41 | child0.StyleSetWidth(80) 42 | child0.StyleSetHeight(40) 43 | child0.StyleSetMargin(EdgeTop, 10) 44 | child0.StyleSetMargin(EdgeBottom, 10) 45 | root.InsertChild(child0, 0) 46 | child1 := NewNodeWithConfig(config) 47 | child1.StyleSetWidth(80) 48 | child1.StyleSetHeight(40) 49 | child1.StyleSetMargin(EdgeBottom, 5) 50 | root.InsertChild(child1, 1) 51 | 52 | CalculateLayout(root, 200, 100, DirectionLTR) 53 | 54 | assert.True(t, root.Layout.HadOverflow) 55 | } 56 | 57 | func TestNo_overflow_no_wrap_and_flex_children(t *testing.T) { 58 | config, root := newHadOverflowTests() 59 | child0 := NewNodeWithConfig(config) 60 | child0.StyleSetWidth(80) 61 | child0.StyleSetHeight(40) 62 | child0.StyleSetMargin(EdgeTop, 10) 63 | child0.StyleSetMargin(EdgeBottom, 10) 64 | root.InsertChild(child0, 0) 65 | child1 := NewNodeWithConfig(config) 66 | child1.StyleSetWidth(80) 67 | child1.StyleSetHeight(40) 68 | child1.StyleSetMargin(EdgeBottom, 5) 69 | child1.StyleSetFlexShrink(1) 70 | root.InsertChild(child1, 1) 71 | 72 | CalculateLayout(root, 200, 100, DirectionLTR) 73 | 74 | assert.False(t, root.Layout.HadOverflow) 75 | } 76 | 77 | func TestHadOverflow_gets_reset_if_not_logger_valid(t *testing.T) { 78 | config, root := newHadOverflowTests() 79 | child0 := NewNodeWithConfig(config) 80 | child0.StyleSetWidth(80) 81 | child0.StyleSetHeight(40) 82 | child0.StyleSetMargin(EdgeTop, 10) 83 | child0.StyleSetMargin(EdgeBottom, 10) 84 | root.InsertChild(child0, 0) 85 | child1 := NewNodeWithConfig(config) 86 | child1.StyleSetWidth(80) 87 | child1.StyleSetHeight(40) 88 | child1.StyleSetMargin(EdgeBottom, 5) 89 | root.InsertChild(child1, 1) 90 | 91 | CalculateLayout(root, 200, 100, DirectionLTR) 92 | 93 | assert.True(t, root.Layout.HadOverflow) 94 | 95 | child1.StyleSetFlexShrink(1) 96 | 97 | CalculateLayout(root, 200, 100, DirectionLTR) 98 | 99 | assert.False(t, root.Layout.HadOverflow) 100 | } 101 | 102 | func TestSpacing_overflow_in_nested_nodes(t *testing.T) { 103 | config, root := newHadOverflowTests() 104 | child0 := NewNodeWithConfig(config) 105 | child0.StyleSetWidth(80) 106 | child0.StyleSetHeight(40) 107 | child0.StyleSetMargin(EdgeTop, 10) 108 | child0.StyleSetMargin(EdgeBottom, 10) 109 | root.InsertChild(child0, 0) 110 | child1 := NewNodeWithConfig(config) 111 | child1.StyleSetWidth(80) 112 | child1.StyleSetHeight(40) 113 | root.InsertChild(child1, 1) 114 | child1_1 := NewNodeWithConfig(config) 115 | child1_1.StyleSetWidth(80) 116 | child1_1.StyleSetHeight(40) 117 | child1_1.StyleSetMargin(EdgeBottom, 5) 118 | child1.InsertChild(child1_1, 0) 119 | 120 | CalculateLayout(root, 200, 100, DirectionLTR) 121 | 122 | assert.True(t, root.Layout.HadOverflow) 123 | } 124 | -------------------------------------------------------------------------------- /flex/issue5_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestIssue5(t *testing.T) { 11 | config := NewConfig() 12 | config.Context = "test" 13 | 14 | // check that "padding" set with EdgeAll is printed out 15 | root := NewNodeWithConfig(config) 16 | root.StyleSetFlexDirection(FlexDirectionColumn) 17 | root.StyleSetHeightPercent(100) 18 | 19 | child := NewNodeWithConfig(config) 20 | child.StyleSetPadding(EdgeAll, 20) 21 | root.InsertChild(child, 0) 22 | 23 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 24 | 25 | w := &bytes.Buffer{} 26 | printer := NewNodePrinter(w, PrintOptionsLayout|PrintOptionsStyle|PrintOptionsChildren) 27 | printer.Print(root) 28 | got := w.String() 29 | exp := `
30 |
31 |
` 32 | assert.Equal(t, got, exp) 33 | } 34 | -------------------------------------------------------------------------------- /flex/math.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "math" 4 | 5 | // from https://github.com/rkusa/gm/blob/master/math32/bits.go 6 | 7 | const ( 8 | uvnan = 0x7FC00001 9 | ) 10 | 11 | var ( 12 | NAN = math.Float32frombits(uvnan) 13 | ) 14 | 15 | // NaN returns an IEEE 754 ``not-a-number'' value. 16 | func NaN() float32 { return math.Float32frombits(uvnan) } 17 | 18 | // IsNaN reports whether f is an IEEE 754 ``not-a-number'' value. 19 | func IsNaN(f float32) (is bool) { 20 | return f != f 21 | } 22 | 23 | func feq(a, b float32) bool { 24 | if IsNaN(a) && IsNaN(b) { 25 | return true 26 | } 27 | return a == b 28 | } 29 | 30 | // https://github.com/evanphx/ulysses-libc/blob/master/src/math/fmaxf.c 31 | func fmaxf(a float32, b float32) float32 { 32 | if IsNaN(a) { 33 | return b 34 | } 35 | if IsNaN(b) { 36 | return a 37 | } 38 | // TODO: signed zeros 39 | if a > b { 40 | return a 41 | } 42 | return b 43 | } 44 | 45 | // https://github.com/evanphx/ulysses-libc/blob/master/src/math/fminf.c 46 | func fminf(a float32, b float32) float32 { 47 | if IsNaN(a) { 48 | return b 49 | } 50 | if IsNaN(b) { 51 | return a 52 | } 53 | // TODO: signed zeros 54 | if a < b { 55 | return a 56 | } 57 | return b 58 | } 59 | 60 | func fabs(x float32) float32 { 61 | switch { 62 | case x < 0: 63 | return -x 64 | case x == 0: 65 | return 0 // return correctly abs(-0) 66 | } 67 | return x 68 | } 69 | 70 | func fmodf(x, y float32) float32 { 71 | res := math.Mod(float64(x), float64(y)) 72 | return float32(res) 73 | } 74 | -------------------------------------------------------------------------------- /flex/math_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNaN(t *testing.T) { 10 | isNan := IsNaN(NAN) 11 | assert.True(t, isNan) 12 | isNan = IsNaN(5) 13 | assert.False(t, isNan) 14 | } 15 | -------------------------------------------------------------------------------- /flex/measure_cache_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func measureMax(node *Node, width float32, widthMode MeasureMode, height float32, heightMode MeasureMode) Size { 10 | measureCount := node.Context.(int) 11 | measureCount++ 12 | node.Context = measureCount 13 | 14 | if widthMode == MeasureModeUndefined { 15 | width = 10 16 | } 17 | if heightMode == MeasureModeUndefined { 18 | height = 10 19 | } 20 | return Size{ 21 | Width: width, 22 | Height: height, 23 | } 24 | } 25 | 26 | func measureMin(node *Node, width float32, widthMode MeasureMode, height float32, heightMode MeasureMode) Size { 27 | measureCount := node.Context.(int) 28 | measureCount++ 29 | node.Context = measureCount 30 | 31 | if widthMode == MeasureModeUndefined || (widthMode == MeasureModeAtMost && width > 10) { 32 | width = 10 33 | } 34 | if heightMode == MeasureModeUndefined || (heightMode == MeasureModeAtMost && height > 10) { 35 | height = 10 36 | } 37 | return Size{ 38 | Width: width, 39 | Height: height, 40 | } 41 | } 42 | 43 | func measure8449(node *Node, width float32, widthMode MeasureMode, height float32, heightMode MeasureMode) Size { 44 | measureCount, ok := node.Context.(int) 45 | if ok { 46 | measureCount++ 47 | node.Context = measureCount 48 | } 49 | 50 | return Size{ 51 | Width: 84, 52 | Height: 49, 53 | } 54 | } 55 | 56 | func TestMeasure_once_single_flexible_child(t *testing.T) { 57 | root := NewNode() 58 | root.StyleSetFlexDirection(FlexDirectionRow) 59 | root.StyleSetAlignItems(AlignFlexStart) 60 | root.StyleSetWidth(100) 61 | root.StyleSetHeight(100) 62 | 63 | rootChild0 := NewNode() 64 | measureCount := 0 65 | rootChild0.Context = measureCount 66 | rootChild0.SetMeasureFunc(measureMax) 67 | rootChild0.StyleSetFlexGrow(1) 68 | root.InsertChild(rootChild0, 0) 69 | 70 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 71 | 72 | measureCount = rootChild0.Context.(int) 73 | assert.Equal(t, 1, measureCount) 74 | 75 | } 76 | 77 | func TestRemeasure_with_same_exact_width_larger_than_needed_height(t *testing.T) { 78 | root := NewNode() 79 | 80 | rootChild0 := NewNode() 81 | measureCount := 0 82 | rootChild0.Context = measureCount 83 | rootChild0.SetMeasureFunc(measureMin) 84 | root.InsertChild(rootChild0, 0) 85 | 86 | CalculateLayout(root, 100, 100, DirectionLTR) 87 | CalculateLayout(root, 100, 50, DirectionLTR) 88 | 89 | measureCount = rootChild0.Context.(int) 90 | assert.Equal(t, 1, measureCount) 91 | 92 | } 93 | 94 | func TestRemeasure_with_same_atmost_width_larger_than_needed_height(t *testing.T) { 95 | root := NewNode() 96 | root.StyleSetAlignItems(AlignFlexStart) 97 | 98 | rootChild0 := NewNode() 99 | measureCount := 0 100 | rootChild0.Context = measureCount 101 | rootChild0.SetMeasureFunc(measureMin) 102 | root.InsertChild(rootChild0, 0) 103 | 104 | CalculateLayout(root, 100, 100, DirectionLTR) 105 | CalculateLayout(root, 100, 50, DirectionLTR) 106 | 107 | measureCount = rootChild0.Context.(int) 108 | assert.Equal(t, 1, measureCount) 109 | 110 | } 111 | 112 | func TestRemeasure_with_computed_width_larger_than_needed_height(t *testing.T) { 113 | root := NewNode() 114 | root.StyleSetAlignItems(AlignFlexStart) 115 | 116 | rootChild0 := NewNode() 117 | measureCount := 0 118 | rootChild0.Context = measureCount 119 | rootChild0.SetMeasureFunc(measureMin) 120 | root.InsertChild(rootChild0, 0) 121 | 122 | CalculateLayout(root, 100, 100, DirectionLTR) 123 | root.StyleSetAlignItems(AlignStretch) 124 | CalculateLayout(root, 10, 50, DirectionLTR) 125 | 126 | measureCount = rootChild0.Context.(int) 127 | assert.Equal(t, 1, measureCount) 128 | 129 | } 130 | 131 | func TestRemeasure_with_atmost_computed_width_undefined_height(t *testing.T) { 132 | root := NewNode() 133 | root.StyleSetAlignItems(AlignFlexStart) 134 | 135 | rootChild0 := NewNode() 136 | measureCount := 0 137 | rootChild0.Context = measureCount 138 | rootChild0.SetMeasureFunc(measureMin) 139 | root.InsertChild(rootChild0, 0) 140 | 141 | CalculateLayout(root, 100, Undefined, DirectionLTR) 142 | CalculateLayout(root, 10, Undefined, DirectionLTR) 143 | 144 | measureCount = rootChild0.Context.(int) 145 | assert.Equal(t, 1, measureCount) 146 | } 147 | 148 | func TestRemeasure_with_already_measured_value_smaller_but_still_float_equal(t *testing.T) { 149 | measureCount := 0 150 | 151 | root := NewNode() 152 | root.StyleSetWidth(288) 153 | root.StyleSetHeight(288) 154 | root.StyleSetFlexDirection(FlexDirectionRow) 155 | 156 | rootChild0 := NewNode() 157 | rootChild0.StyleSetPadding(EdgeAll, 2.88) 158 | rootChild0.StyleSetFlexDirection(FlexDirectionRow) 159 | root.InsertChild(rootChild0, 0) 160 | 161 | rootChild0Child0 := NewNode() 162 | rootChild0Child0.Context = measureCount 163 | rootChild0Child0.SetMeasureFunc(measure8449) 164 | rootChild0.InsertChild(rootChild0Child0, 0) 165 | 166 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 167 | 168 | measureCount = rootChild0Child0.Context.(int) 169 | assert.Equal(t, 1, measureCount) 170 | } 171 | -------------------------------------------------------------------------------- /flex/measure_mode_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type measureConstraint struct { 10 | width float32 11 | widthMode MeasureMode 12 | height float32 13 | heightMode MeasureMode 14 | } 15 | 16 | type measureConstraintList struct { 17 | length int 18 | constraints []measureConstraint 19 | } 20 | 21 | func _measure2(node *Node, 22 | width float32, 23 | widthMode MeasureMode, 24 | height float32, 25 | heightMode MeasureMode) Size { 26 | constraintList := node.Context.(*measureConstraintList) 27 | constraints := constraintList.constraints 28 | currentIndex := constraintList.length 29 | (&constraints[currentIndex]).width = width 30 | (&constraints[currentIndex]).widthMode = widthMode 31 | (&constraints[currentIndex]).height = height 32 | (&constraints[currentIndex]).heightMode = heightMode 33 | constraintList.length = currentIndex + 1 34 | 35 | if widthMode == MeasureModeUndefined { 36 | width = 10 37 | } 38 | 39 | if heightMode == MeasureModeUndefined { 40 | height = 10 41 | } else { 42 | height = width // TODO:: is it a bug in tests ? 43 | } 44 | return Size{ 45 | Width: width, 46 | Height: height, 47 | } 48 | } 49 | 50 | func TestExactly_measure_stretched_child_column(t *testing.T) { 51 | constraintList := measureConstraintList{ 52 | constraints: make([]measureConstraint, 10), 53 | } 54 | 55 | root := NewNode() 56 | root.StyleSetWidth(100) 57 | root.StyleSetHeight(100) 58 | 59 | rootChild0 := NewNode() 60 | rootChild0.Context = &constraintList 61 | rootChild0.SetMeasureFunc(_measure2) 62 | root.InsertChild(rootChild0, 0) 63 | 64 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 65 | 66 | assert.Equal(t, 1, constraintList.length) 67 | 68 | assertFloatEqual(t, 100, constraintList.constraints[0].width) 69 | assert.Equal(t, MeasureModeExactly, constraintList.constraints[0].widthMode) 70 | } 71 | 72 | func TestExactly_measure_stretched_child_row(t *testing.T) { 73 | constraintList := measureConstraintList{ 74 | constraints: make([]measureConstraint, 10), 75 | } 76 | 77 | root := NewNode() 78 | root.StyleSetFlexDirection(FlexDirectionRow) 79 | root.StyleSetWidth(100) 80 | root.StyleSetHeight(100) 81 | 82 | rootChild0 := NewNode() 83 | rootChild0.Context = &constraintList 84 | rootChild0.SetMeasureFunc(_measure2) 85 | root.InsertChild(rootChild0, 0) 86 | 87 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 88 | 89 | assert.Equal(t, 1, constraintList.length) 90 | 91 | assertFloatEqual(t, 100, constraintList.constraints[0].height) 92 | assert.Equal(t, MeasureModeExactly, constraintList.constraints[0].heightMode) 93 | } 94 | 95 | func TestAt_most_main_axis_column(t *testing.T) { 96 | constraintList := measureConstraintList{ 97 | constraints: make([]measureConstraint, 10), 98 | } 99 | 100 | root := NewNode() 101 | root.StyleSetWidth(100) 102 | root.StyleSetHeight(100) 103 | 104 | rootChild0 := NewNode() 105 | rootChild0.Context = &constraintList 106 | rootChild0.SetMeasureFunc(_measure2) 107 | root.InsertChild(rootChild0, 0) 108 | 109 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 110 | 111 | assert.Equal(t, 1, constraintList.length) 112 | 113 | assertFloatEqual(t, 100, constraintList.constraints[0].height) 114 | assert.Equal(t, MeasureModeAtMost, constraintList.constraints[0].heightMode) 115 | } 116 | 117 | func TestAt_most_cross_axis_column(t *testing.T) { 118 | constraintList := measureConstraintList{ 119 | constraints: make([]measureConstraint, 10), 120 | } 121 | 122 | root := NewNode() 123 | root.StyleSetAlignItems(AlignFlexStart) 124 | root.StyleSetWidth(100) 125 | root.StyleSetHeight(100) 126 | 127 | rootChild0 := NewNode() 128 | rootChild0.Context = &constraintList 129 | rootChild0.SetMeasureFunc(_measure2) 130 | root.InsertChild(rootChild0, 0) 131 | 132 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 133 | 134 | assert.Equal(t, 1, constraintList.length) 135 | 136 | assertFloatEqual(t, 100, constraintList.constraints[0].width) 137 | assert.Equal(t, MeasureModeAtMost, constraintList.constraints[0].widthMode) 138 | } 139 | 140 | func TestAt_most_main_axis_row(t *testing.T) { 141 | constraintList := measureConstraintList{ 142 | constraints: make([]measureConstraint, 10), 143 | } 144 | 145 | root := NewNode() 146 | root.StyleSetFlexDirection(FlexDirectionRow) 147 | root.StyleSetWidth(100) 148 | root.StyleSetHeight(100) 149 | 150 | rootChild0 := NewNode() 151 | rootChild0.Context = &constraintList 152 | rootChild0.SetMeasureFunc(_measure2) 153 | root.InsertChild(rootChild0, 0) 154 | 155 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 156 | 157 | assert.Equal(t, 1, constraintList.length) 158 | 159 | assertFloatEqual(t, 100, constraintList.constraints[0].width) 160 | assert.Equal(t, MeasureModeAtMost, constraintList.constraints[0].widthMode) 161 | } 162 | 163 | func TestAt_most_cross_axis_row(t *testing.T) { 164 | constraintList := measureConstraintList{ 165 | constraints: make([]measureConstraint, 10), 166 | } 167 | 168 | root := NewNode() 169 | root.StyleSetFlexDirection(FlexDirectionRow) 170 | root.StyleSetAlignItems(AlignFlexStart) 171 | root.StyleSetWidth(100) 172 | root.StyleSetHeight(100) 173 | 174 | rootChild0 := NewNode() 175 | rootChild0.Context = &constraintList 176 | rootChild0.SetMeasureFunc(_measure2) 177 | root.InsertChild(rootChild0, 0) 178 | 179 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 180 | 181 | assert.Equal(t, 1, constraintList.length) 182 | 183 | assertFloatEqual(t, 100, constraintList.constraints[0].height) 184 | assert.Equal(t, MeasureModeAtMost, constraintList.constraints[0].heightMode) 185 | } 186 | 187 | func TestFlex_child(t *testing.T) { 188 | constraintList := measureConstraintList{ 189 | constraints: make([]measureConstraint, 10), 190 | } 191 | 192 | root := NewNode() 193 | root.StyleSetHeight(100) 194 | 195 | rootChild0 := NewNode() 196 | rootChild0.StyleSetFlexGrow(1) 197 | rootChild0.Context = &constraintList 198 | rootChild0.SetMeasureFunc(_measure2) 199 | root.InsertChild(rootChild0, 0) 200 | 201 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 202 | 203 | assert.Equal(t, 2, constraintList.length) 204 | 205 | assertFloatEqual(t, 100, constraintList.constraints[0].height) 206 | assert.Equal(t, MeasureModeAtMost, constraintList.constraints[0].heightMode) 207 | 208 | assertFloatEqual(t, 100, constraintList.constraints[1].height) 209 | assert.Equal(t, MeasureModeExactly, constraintList.constraints[1].heightMode) 210 | } 211 | 212 | func TestFlex_child_with_flex_basis(t *testing.T) { 213 | constraintList := measureConstraintList{ 214 | constraints: make([]measureConstraint, 10), 215 | } 216 | 217 | root := NewNode() 218 | root.StyleSetHeight(100) 219 | 220 | rootChild0 := NewNode() 221 | rootChild0.StyleSetFlexGrow(1) 222 | rootChild0.StyleSetFlexBasis(0) 223 | rootChild0.Context = &constraintList 224 | rootChild0.SetMeasureFunc(_measure2) 225 | root.InsertChild(rootChild0, 0) 226 | 227 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 228 | 229 | assert.Equal(t, 1, constraintList.length) 230 | 231 | assertFloatEqual(t, 100, constraintList.constraints[0].height) 232 | assert.Equal(t, MeasureModeExactly, constraintList.constraints[0].heightMode) 233 | } 234 | 235 | func TestOverflow_scroll_column(t *testing.T) { 236 | constraintList := measureConstraintList{ 237 | constraints: make([]measureConstraint, 10), 238 | } 239 | 240 | root := NewNode() 241 | root.StyleSetAlignItems(AlignFlexStart) 242 | root.StyleSetOverflow(OverflowScroll) 243 | root.StyleSetHeight(100) 244 | root.StyleSetWidth(100) 245 | 246 | rootChild0 := NewNode() 247 | rootChild0.Context = &constraintList 248 | rootChild0.SetMeasureFunc(_measure2) 249 | root.InsertChild(rootChild0, 0) 250 | 251 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 252 | 253 | assert.Equal(t, 1, constraintList.length) 254 | 255 | assertFloatEqual(t, 100, constraintList.constraints[0].width) 256 | assert.Equal(t, MeasureModeAtMost, constraintList.constraints[0].widthMode) 257 | 258 | assert.True(t, FloatIsUndefined(constraintList.constraints[0].height)) 259 | assert.Equal(t, MeasureModeUndefined, constraintList.constraints[0].heightMode) 260 | } 261 | 262 | func TestOverflow_scroll_row(t *testing.T) { 263 | constraintList := measureConstraintList{ 264 | constraints: make([]measureConstraint, 10), 265 | } 266 | 267 | root := NewNode() 268 | root.StyleSetAlignItems(AlignFlexStart) 269 | root.StyleSetFlexDirection(FlexDirectionRow) 270 | root.StyleSetOverflow(OverflowScroll) 271 | root.StyleSetHeight(100) 272 | root.StyleSetWidth(100) 273 | 274 | rootChild0 := NewNode() 275 | rootChild0.Context = &constraintList 276 | rootChild0.SetMeasureFunc(_measure2) 277 | root.InsertChild(rootChild0, 0) 278 | 279 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 280 | 281 | assert.Equal(t, 1, constraintList.length) 282 | 283 | assert.True(t, FloatIsUndefined(constraintList.constraints[0].width)) 284 | assert.Equal(t, MeasureModeUndefined, constraintList.constraints[0].widthMode) 285 | 286 | assertFloatEqual(t, 100, constraintList.constraints[0].height) 287 | assert.Equal(t, MeasureModeAtMost, constraintList.constraints[0].heightMode) 288 | } 289 | -------------------------------------------------------------------------------- /flex/node_child_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestReset_layout_when_child_removed(t *testing.T) { 10 | root := NewNode() 11 | 12 | rootChild0 := NewNode() 13 | rootChild0.StyleSetWidth(100) 14 | rootChild0.StyleSetHeight(100) 15 | root.InsertChild(rootChild0, 0) 16 | 17 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 18 | 19 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 20 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 21 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 22 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 23 | 24 | root.RemoveChild(rootChild0) 25 | 26 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 27 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 28 | assert.True(t, FloatIsUndefined(rootChild0.LayoutGetWidth())) 29 | assert.True(t, FloatIsUndefined(rootChild0.LayoutGetHeight())) 30 | } 31 | -------------------------------------------------------------------------------- /flex/padding_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestPadding_no_size(t *testing.T) { 6 | config := NewConfig() 7 | 8 | root := NewNodeWithConfig(config) 9 | root.StyleSetPadding(EdgeLeft, 10) 10 | root.StyleSetPadding(EdgeTop, 10) 11 | root.StyleSetPadding(EdgeRight, 10) 12 | root.StyleSetPadding(EdgeBottom, 10) 13 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 14 | 15 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 16 | assertFloatEqual(t, 0, root.LayoutGetTop()) 17 | assertFloatEqual(t, 20, root.LayoutGetWidth()) 18 | assertFloatEqual(t, 20, root.LayoutGetHeight()) 19 | 20 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 21 | 22 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 23 | assertFloatEqual(t, 0, root.LayoutGetTop()) 24 | assertFloatEqual(t, 20, root.LayoutGetWidth()) 25 | assertFloatEqual(t, 20, root.LayoutGetHeight()) 26 | } 27 | 28 | func TestPadding_container_match_child(t *testing.T) { 29 | config := NewConfig() 30 | 31 | root := NewNodeWithConfig(config) 32 | root.StyleSetPadding(EdgeLeft, 10) 33 | root.StyleSetPadding(EdgeTop, 10) 34 | root.StyleSetPadding(EdgeRight, 10) 35 | root.StyleSetPadding(EdgeBottom, 10) 36 | 37 | rootChild0 := NewNodeWithConfig(config) 38 | rootChild0.StyleSetWidth(10) 39 | rootChild0.StyleSetHeight(10) 40 | root.InsertChild(rootChild0, 0) 41 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 42 | 43 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 44 | assertFloatEqual(t, 0, root.LayoutGetTop()) 45 | assertFloatEqual(t, 30, root.LayoutGetWidth()) 46 | assertFloatEqual(t, 30, root.LayoutGetHeight()) 47 | 48 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 49 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 50 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 51 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 52 | 53 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 54 | 55 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 56 | assertFloatEqual(t, 0, root.LayoutGetTop()) 57 | assertFloatEqual(t, 30, root.LayoutGetWidth()) 58 | assertFloatEqual(t, 30, root.LayoutGetHeight()) 59 | 60 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 61 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 62 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 63 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 64 | } 65 | 66 | func TestPadding_flex_child(t *testing.T) { 67 | config := NewConfig() 68 | 69 | root := NewNodeWithConfig(config) 70 | root.StyleSetPadding(EdgeLeft, 10) 71 | root.StyleSetPadding(EdgeTop, 10) 72 | root.StyleSetPadding(EdgeRight, 10) 73 | root.StyleSetPadding(EdgeBottom, 10) 74 | root.StyleSetWidth(100) 75 | root.StyleSetHeight(100) 76 | 77 | rootChild0 := NewNodeWithConfig(config) 78 | rootChild0.StyleSetFlexGrow(1) 79 | rootChild0.StyleSetWidth(10) 80 | root.InsertChild(rootChild0, 0) 81 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 82 | 83 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 84 | assertFloatEqual(t, 0, root.LayoutGetTop()) 85 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 86 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 87 | 88 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 89 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 90 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 91 | assertFloatEqual(t, 80, rootChild0.LayoutGetHeight()) 92 | 93 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 94 | 95 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 96 | assertFloatEqual(t, 0, root.LayoutGetTop()) 97 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 98 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 99 | 100 | assertFloatEqual(t, 80, rootChild0.LayoutGetLeft()) 101 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 102 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 103 | assertFloatEqual(t, 80, rootChild0.LayoutGetHeight()) 104 | } 105 | 106 | func TestPadding_stretch_child(t *testing.T) { 107 | config := NewConfig() 108 | 109 | root := NewNodeWithConfig(config) 110 | root.StyleSetPadding(EdgeLeft, 10) 111 | root.StyleSetPadding(EdgeTop, 10) 112 | root.StyleSetPadding(EdgeRight, 10) 113 | root.StyleSetPadding(EdgeBottom, 10) 114 | root.StyleSetWidth(100) 115 | root.StyleSetHeight(100) 116 | 117 | rootChild0 := NewNodeWithConfig(config) 118 | rootChild0.StyleSetHeight(10) 119 | root.InsertChild(rootChild0, 0) 120 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 121 | 122 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 123 | assertFloatEqual(t, 0, root.LayoutGetTop()) 124 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 125 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 126 | 127 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 128 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 129 | assertFloatEqual(t, 80, rootChild0.LayoutGetWidth()) 130 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 131 | 132 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 133 | 134 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 135 | assertFloatEqual(t, 0, root.LayoutGetTop()) 136 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 137 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 138 | 139 | assertFloatEqual(t, 10, rootChild0.LayoutGetLeft()) 140 | assertFloatEqual(t, 10, rootChild0.LayoutGetTop()) 141 | assertFloatEqual(t, 80, rootChild0.LayoutGetWidth()) 142 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 143 | } 144 | 145 | func TestPadding_center_child(t *testing.T) { 146 | config := NewConfig() 147 | 148 | root := NewNodeWithConfig(config) 149 | root.StyleSetJustifyContent(JustifyCenter) 150 | root.StyleSetAlignItems(AlignCenter) 151 | root.StyleSetPadding(EdgeStart, 10) 152 | root.StyleSetPadding(EdgeEnd, 20) 153 | root.StyleSetPadding(EdgeBottom, 20) 154 | root.StyleSetWidth(100) 155 | root.StyleSetHeight(100) 156 | 157 | rootChild0 := NewNodeWithConfig(config) 158 | rootChild0.StyleSetWidth(10) 159 | rootChild0.StyleSetHeight(10) 160 | root.InsertChild(rootChild0, 0) 161 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 162 | 163 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 164 | assertFloatEqual(t, 0, root.LayoutGetTop()) 165 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 166 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 167 | 168 | assertFloatEqual(t, 40, rootChild0.LayoutGetLeft()) 169 | assertFloatEqual(t, 35, rootChild0.LayoutGetTop()) 170 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 171 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 172 | 173 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 174 | 175 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 176 | assertFloatEqual(t, 0, root.LayoutGetTop()) 177 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 178 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 179 | 180 | assertFloatEqual(t, 50, rootChild0.LayoutGetLeft()) 181 | assertFloatEqual(t, 35, rootChild0.LayoutGetTop()) 182 | assertFloatEqual(t, 10, rootChild0.LayoutGetWidth()) 183 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 184 | } 185 | 186 | func TestChild_with_padding_align_end(t *testing.T) { 187 | config := NewConfig() 188 | 189 | root := NewNodeWithConfig(config) 190 | root.StyleSetJustifyContent(JustifyFlexEnd) 191 | root.StyleSetAlignItems(AlignFlexEnd) 192 | root.StyleSetWidth(200) 193 | root.StyleSetHeight(200) 194 | 195 | rootChild0 := NewNodeWithConfig(config) 196 | rootChild0.StyleSetPadding(EdgeLeft, 20) 197 | rootChild0.StyleSetPadding(EdgeTop, 20) 198 | rootChild0.StyleSetPadding(EdgeRight, 20) 199 | rootChild0.StyleSetPadding(EdgeBottom, 20) 200 | rootChild0.StyleSetWidth(100) 201 | rootChild0.StyleSetHeight(100) 202 | root.InsertChild(rootChild0, 0) 203 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 204 | 205 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 206 | assertFloatEqual(t, 0, root.LayoutGetTop()) 207 | assertFloatEqual(t, 200, root.LayoutGetWidth()) 208 | assertFloatEqual(t, 200, root.LayoutGetHeight()) 209 | 210 | assertFloatEqual(t, 100, rootChild0.LayoutGetLeft()) 211 | assertFloatEqual(t, 100, rootChild0.LayoutGetTop()) 212 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 213 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 214 | 215 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 216 | 217 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 218 | assertFloatEqual(t, 0, root.LayoutGetTop()) 219 | assertFloatEqual(t, 200, root.LayoutGetWidth()) 220 | assertFloatEqual(t, 200, root.LayoutGetHeight()) 221 | 222 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 223 | assertFloatEqual(t, 100, rootChild0.LayoutGetTop()) 224 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 225 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 226 | } 227 | -------------------------------------------------------------------------------- /flex/print.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // NodePrinter node printer. 11 | type NodePrinter struct { 12 | writer io.Writer 13 | options PrintOptions 14 | } 15 | 16 | // NodePrint prints node to standard output. 17 | func NodePrint(node *Node, options PrintOptions) { 18 | printer := NewNodePrinter(os.Stdout, options) 19 | printer.Print(node) 20 | } 21 | 22 | // NewNodePrinter creates new node printer. 23 | func NewNodePrinter(writer io.Writer, options PrintOptions) *NodePrinter { 24 | return &NodePrinter{ 25 | writer: writer, 26 | options: options, 27 | } 28 | } 29 | 30 | // Print prints node. 31 | func (printer *NodePrinter) Print(node *Node) { 32 | printer.printNode(node, 0) 33 | } 34 | 35 | func (printer *NodePrinter) printNode(node *Node, level int) { 36 | printer.printIndent(level) 37 | printer.printf("
") 116 | 117 | childCount := len(node.Children) 118 | if printer.options&PrintOptionsChildren != 0 && childCount > 0 { 119 | for i := 0; i < childCount; i++ { 120 | printer.printf("\n") 121 | printer.printNode(node.Children[i], level+1) 122 | } 123 | printer.printIndent(level) 124 | printer.printf("\n") 125 | } 126 | if childCount != 0 { 127 | printer.printIndent(level) 128 | } 129 | printer.printf("
") 130 | } 131 | 132 | func (printer *NodePrinter) printEdges(node *Node, str string, edges []Value) { 133 | if fourValuesEqual(edges) { 134 | printer.printNumberIfNotZero(node, str, &edges[EdgeLeft]) 135 | // bugfix for issue #5 136 | // if we set EdgeAll, the values are 137 | // [{NaN 0} {NaN 0} {NaN 0} {NaN 0} {NaN 0} {NaN 0} {NaN 0} {NaN 0} {20 1}] 138 | // so EdgeLeft is not printed and we won't print padding 139 | // for simplicity, I assume that EdgeAll is exclusive with setting specific edges 140 | // so we can print both and only one should show up 141 | // C code has this bug: https://github.com/facebook/yoga/blob/26481a6553a33d9c005f2b8d24a7952fc58df32c/yoga/Yoga.c#L1036 142 | printer.printNumberIfNotZero(node, str, &edges[EdgeAll]) 143 | } else { 144 | for edge := EdgeLeft; edge < EdgeCount; edge++ { 145 | buf := fmt.Sprintf("%s-%s", str, EdgeToString(edge)) 146 | printer.printNumberIfNotZero(node, buf, &edges[edge]) 147 | } 148 | } 149 | } 150 | 151 | func (printer *NodePrinter) printEdgeIfNotUndefined(node *Node, str string, edges []Value, edge Edge) { 152 | printer.printNumberIfNotUndefined(node, str, computedEdgeValue(edges, edge, &ValueUndefined)) 153 | } 154 | 155 | func (printer *NodePrinter) printFloatIfNotUndefined(node *Node, str string, number float32) { 156 | if !FloatIsUndefined(number) { 157 | printer.printf("%s: %g; ", str, number) 158 | } 159 | } 160 | 161 | func (printer *NodePrinter) printNumberIfNotUndefined(node *Node, str string, number *Value) { 162 | if number.Unit != UnitUndefined { 163 | if number.Unit == UnitAuto { 164 | printer.printf("%s: auto; ", str) 165 | } else { 166 | unit := "%" 167 | 168 | if number.Unit == UnitPoint { 169 | unit = "px" 170 | } 171 | printer.printf("%s: %g%s; ", str, number.Value, unit) 172 | } 173 | } 174 | } 175 | 176 | func (printer *NodePrinter) printNumberIfNotAuto(node *Node, str string, number *Value) { 177 | if number.Unit != UnitAuto { 178 | printer.printNumberIfNotUndefined(node, str, number) 179 | } 180 | } 181 | 182 | func (printer *NodePrinter) printNumberIfNotZero(node *Node, str string, number *Value) { 183 | if !FloatsEqual(number.Value, 0) { 184 | printer.printNumberIfNotUndefined(node, str, number) 185 | } 186 | } 187 | 188 | func (printer *NodePrinter) printf(format string, args ...interface{}) { 189 | fmt.Fprintf(printer.writer, format, args...) 190 | } 191 | 192 | func (printer *NodePrinter) printIndent(n int) { 193 | printer.writer.Write([]byte(strings.Repeat(" ", n))) 194 | } 195 | 196 | func fourValuesEqual(four []Value) bool { 197 | return ValueEqual(four[0], four[1]) && ValueEqual(four[0], four[2]) && 198 | ValueEqual(four[0], four[3]) 199 | } 200 | -------------------------------------------------------------------------------- /flex/relayout_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestDont_cache_computed_flex_basis_between_layouts(t *testing.T) { 6 | config := NewConfig() 7 | config.SetExperimentalFeatureEnabled(ExperimentalFeatureWebFlexBasis, true) 8 | 9 | root := NewNodeWithConfig(config) 10 | root.StyleSetHeightPercent(100) 11 | root.StyleSetWidthPercent(100) 12 | 13 | rootChild0 := NewNodeWithConfig(config) 14 | rootChild0.StyleSetFlexBasisPercent(100) 15 | root.InsertChild(rootChild0, 0) 16 | 17 | CalculateLayout(root, 100, Undefined, DirectionLTR) 18 | CalculateLayout(root, 100, 100, DirectionLTR) 19 | 20 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 21 | } 22 | 23 | func TestRecalculate_resolvedDimonsion_onchange(t *testing.T) { 24 | root := NewNode() 25 | 26 | rootChild0 := NewNode() 27 | rootChild0.StyleSetMinHeight(10) 28 | rootChild0.StyleSetMaxHeight(10) 29 | root.InsertChild(rootChild0, 0) 30 | 31 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 32 | assertFloatEqual(t, 10, rootChild0.LayoutGetHeight()) 33 | 34 | rootChild0.StyleSetMinHeight(Undefined) 35 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 36 | 37 | assertFloatEqual(t, 0, rootChild0.LayoutGetHeight()) 38 | } 39 | -------------------------------------------------------------------------------- /flex/rounding_function_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestRounding_value(t *testing.T) { 6 | 7 | // Test that whole numbers are rounded to whole despite ceil/floor flags 8 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(6.000001, 2.0, false, false)) 9 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(6.000001, 2.0, true, false)) 10 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(6.000001, 2.0, false, true)) 11 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(5.999999, 2.0, false, false)) 12 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(5.999999, 2.0, true, false)) 13 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(5.999999, 2.0, false, true)) 14 | 15 | // Test that numbers with fraction are rounded correctly accounting for ceil/floor flags 16 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(6.01, 2.0, false, false)) 17 | assertFloatEqual(t, 6.5, roundValueToPixelGrid(6.01, 2.0, true, false)) 18 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(6.01, 2.0, false, true)) 19 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(5.99, 2.0, false, false)) 20 | assertFloatEqual(t, 6.0, roundValueToPixelGrid(5.99, 2.0, true, false)) 21 | assertFloatEqual(t, 5.5, roundValueToPixelGrid(5.99, 2.0, false, true)) 22 | } 23 | -------------------------------------------------------------------------------- /flex/rounding_measure_func_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func _measureFloor(node *Node, width float32, widthMode MeasureMode, height float32, heightMode MeasureMode) Size { 6 | return Size{ 7 | Width: 10.2, Height: 10.2, 8 | } 9 | } 10 | 11 | func _measureCeil(node *Node, width float32, widthMode MeasureMode, height float32, heightMode MeasureMode) Size { 12 | return Size{ 13 | Width: 10.5, Height: 10.5, 14 | } 15 | } 16 | 17 | func _measureFractial(node *Node, width float32, widthMode MeasureMode, height float32, heightMode MeasureMode) Size { 18 | return Size{ 19 | Width: 0.5, Height: 0.5, 20 | } 21 | } 22 | 23 | func TestRounding_feature_with_custom_measure_func_floor(t *testing.T) { 24 | config := NewConfig() 25 | root := NewNodeWithConfig(config) 26 | 27 | rootChild0 := NewNodeWithConfig(config) 28 | rootChild0.SetMeasureFunc(_measureFloor) 29 | root.InsertChild(rootChild0, 0) 30 | 31 | config.SetPointScaleFactor(0) 32 | 33 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 34 | 35 | assertFloatEqual(t, 10.2, rootChild0.LayoutGetWidth()) 36 | assertFloatEqual(t, 10.2, rootChild0.LayoutGetHeight()) 37 | 38 | config.SetPointScaleFactor(1) 39 | 40 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 41 | 42 | assertFloatEqual(t, 11, rootChild0.LayoutGetWidth()) 43 | assertFloatEqual(t, 11, rootChild0.LayoutGetHeight()) 44 | 45 | config.SetPointScaleFactor(2) 46 | 47 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 48 | 49 | assertFloatEqual(t, 10.5, rootChild0.LayoutGetWidth()) 50 | assertFloatEqual(t, 10.5, rootChild0.LayoutGetHeight()) 51 | 52 | config.SetPointScaleFactor(4) 53 | 54 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 55 | 56 | assertFloatEqual(t, 10.25, rootChild0.LayoutGetWidth()) 57 | assertFloatEqual(t, 10.25, rootChild0.LayoutGetHeight()) 58 | 59 | config.SetPointScaleFactor(float32(1) / float32(3)) 60 | 61 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 62 | 63 | assertFloatEqual(t, 12.0, rootChild0.LayoutGetWidth()) 64 | assertFloatEqual(t, 12.0, rootChild0.LayoutGetHeight()) 65 | } 66 | 67 | func TestRounding_feature_with_custom_measure_func_ceil(t *testing.T) { 68 | config := NewConfig() 69 | root := NewNodeWithConfig(config) 70 | 71 | rootChild0 := NewNodeWithConfig(config) 72 | rootChild0.SetMeasureFunc(_measureCeil) 73 | root.InsertChild(rootChild0, 0) 74 | 75 | config.SetPointScaleFactor(1) 76 | 77 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 78 | 79 | assertFloatEqual(t, 11, rootChild0.LayoutGetWidth()) 80 | assertFloatEqual(t, 11, rootChild0.LayoutGetHeight()) 81 | } 82 | 83 | func TestRounding_feature_with_custom_measure_and_fractial_matching_scale(t *testing.T) { 84 | config := NewConfig() 85 | root := NewNodeWithConfig(config) 86 | 87 | rootChild0 := NewNodeWithConfig(config) 88 | rootChild0.StyleSetPosition(EdgeLeft, 73.625) 89 | rootChild0.SetMeasureFunc(_measureFractial) 90 | root.InsertChild(rootChild0, 0) 91 | 92 | config.SetPointScaleFactor(2) 93 | 94 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 95 | 96 | assertFloatEqual(t, 0.5, rootChild0.LayoutGetWidth()) 97 | assertFloatEqual(t, 0.5, rootChild0.LayoutGetHeight()) 98 | assertFloatEqual(t, 73.5, rootChild0.LayoutGetLeft()) 99 | } 100 | -------------------------------------------------------------------------------- /flex/size_overflow_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import "testing" 4 | 5 | func TestNested_overflowing_child(t *testing.T) { 6 | config := NewConfig() 7 | 8 | root := NewNodeWithConfig(config) 9 | root.StyleSetWidth(100) 10 | root.StyleSetHeight(100) 11 | 12 | rootChild0 := NewNodeWithConfig(config) 13 | root.InsertChild(rootChild0, 0) 14 | 15 | rootChild0Child0 := NewNodeWithConfig(config) 16 | rootChild0Child0.StyleSetWidth(200) 17 | rootChild0Child0.StyleSetHeight(200) 18 | rootChild0.InsertChild(rootChild0Child0, 0) 19 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 20 | 21 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 22 | assertFloatEqual(t, 0, root.LayoutGetTop()) 23 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 24 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 25 | 26 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 27 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 28 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 29 | assertFloatEqual(t, 200, rootChild0.LayoutGetHeight()) 30 | 31 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetLeft()) 32 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 33 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetWidth()) 34 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetHeight()) 35 | 36 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 37 | 38 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 39 | assertFloatEqual(t, 0, root.LayoutGetTop()) 40 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 41 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 42 | 43 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 44 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 45 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 46 | assertFloatEqual(t, 200, rootChild0.LayoutGetHeight()) 47 | 48 | assertFloatEqual(t, -100, rootChild0Child0.LayoutGetLeft()) 49 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 50 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetWidth()) 51 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetHeight()) 52 | } 53 | 54 | func TestNested_overflowing_child_in_constraint_parent(t *testing.T) { 55 | config := NewConfig() 56 | 57 | root := NewNodeWithConfig(config) 58 | root.StyleSetWidth(100) 59 | root.StyleSetHeight(100) 60 | 61 | rootChild0 := NewNodeWithConfig(config) 62 | rootChild0.StyleSetWidth(100) 63 | rootChild0.StyleSetHeight(100) 64 | root.InsertChild(rootChild0, 0) 65 | 66 | rootChild0Child0 := NewNodeWithConfig(config) 67 | rootChild0Child0.StyleSetWidth(200) 68 | rootChild0Child0.StyleSetHeight(200) 69 | rootChild0.InsertChild(rootChild0Child0, 0) 70 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 71 | 72 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 73 | assertFloatEqual(t, 0, root.LayoutGetTop()) 74 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 75 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 76 | 77 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 78 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 79 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 80 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 81 | 82 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetLeft()) 83 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 84 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetWidth()) 85 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetHeight()) 86 | 87 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 88 | 89 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 90 | assertFloatEqual(t, 0, root.LayoutGetTop()) 91 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 92 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 93 | 94 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 95 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 96 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 97 | assertFloatEqual(t, 100, rootChild0.LayoutGetHeight()) 98 | 99 | assertFloatEqual(t, -100, rootChild0Child0.LayoutGetLeft()) 100 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 101 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetWidth()) 102 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetHeight()) 103 | } 104 | 105 | func TestParent_wrap_child_size_overflowing_parent(t *testing.T) { 106 | config := NewConfig() 107 | 108 | root := NewNodeWithConfig(config) 109 | root.StyleSetWidth(100) 110 | root.StyleSetHeight(100) 111 | 112 | rootChild0 := NewNodeWithConfig(config) 113 | rootChild0.StyleSetWidth(100) 114 | root.InsertChild(rootChild0, 0) 115 | 116 | rootChild0Child0 := NewNodeWithConfig(config) 117 | rootChild0Child0.StyleSetWidth(100) 118 | rootChild0Child0.StyleSetHeight(200) 119 | rootChild0.InsertChild(rootChild0Child0, 0) 120 | CalculateLayout(root, Undefined, Undefined, DirectionLTR) 121 | 122 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 123 | assertFloatEqual(t, 0, root.LayoutGetTop()) 124 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 125 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 126 | 127 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 128 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 129 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 130 | assertFloatEqual(t, 200, rootChild0.LayoutGetHeight()) 131 | 132 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetLeft()) 133 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 134 | assertFloatEqual(t, 100, rootChild0Child0.LayoutGetWidth()) 135 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetHeight()) 136 | 137 | CalculateLayout(root, Undefined, Undefined, DirectionRTL) 138 | 139 | assertFloatEqual(t, 0, root.LayoutGetLeft()) 140 | assertFloatEqual(t, 0, root.LayoutGetTop()) 141 | assertFloatEqual(t, 100, root.LayoutGetWidth()) 142 | assertFloatEqual(t, 100, root.LayoutGetHeight()) 143 | 144 | assertFloatEqual(t, 0, rootChild0.LayoutGetLeft()) 145 | assertFloatEqual(t, 0, rootChild0.LayoutGetTop()) 146 | assertFloatEqual(t, 100, rootChild0.LayoutGetWidth()) 147 | assertFloatEqual(t, 200, rootChild0.LayoutGetHeight()) 148 | 149 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetLeft()) 150 | assertFloatEqual(t, 0, rootChild0Child0.LayoutGetTop()) 151 | assertFloatEqual(t, 100, rootChild0Child0.LayoutGetWidth()) 152 | assertFloatEqual(t, 200, rootChild0Child0.LayoutGetHeight()) 153 | } 154 | -------------------------------------------------------------------------------- /flex/style_test.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCopy_style_same(t *testing.T) { 10 | node0 := NewNode() 11 | node1 := NewNode() 12 | assert.False(t, node0.IsDirty) 13 | 14 | NodeCopyStyle(node0, node1) 15 | assert.False(t, node0.IsDirty) 16 | } 17 | 18 | func TestCopy_style_modified(t *testing.T) { 19 | node0 := NewNode() 20 | assert.False(t, node0.IsDirty) 21 | assert.Equal(t, FlexDirectionColumn, node0.Style.FlexDirection) 22 | assert.False(t, node0.StyleGetMaxHeight().Unit != UnitUndefined) 23 | 24 | node1 := NewNode() 25 | node1.StyleSetFlexDirection(FlexDirectionRow) 26 | node1.StyleSetMaxHeight(10) 27 | 28 | NodeCopyStyle(node0, node1) 29 | assert.True(t, node0.IsDirty) 30 | assert.Equal(t, FlexDirectionRow, node0.Style.FlexDirection) 31 | assertFloatEqual(t, 10, node0.StyleGetMaxHeight().Value) 32 | } 33 | 34 | func TestCopy_style_modified_same(t *testing.T) { 35 | node0 := NewNode() 36 | node0.StyleSetFlexDirection(FlexDirectionRow) 37 | node0.StyleSetMaxHeight(10) 38 | CalculateLayout(node0, Undefined, Undefined, DirectionLTR) 39 | assert.False(t, node0.IsDirty) 40 | 41 | node1 := NewNode() 42 | node1.StyleSetFlexDirection(FlexDirectionRow) 43 | node1.StyleSetMaxHeight(10) 44 | 45 | NodeCopyStyle(node0, node1) 46 | assert.False(t, node0.IsDirty) 47 | } 48 | -------------------------------------------------------------------------------- /flex/yoga_h.go: -------------------------------------------------------------------------------- 1 | package flex 2 | 3 | var ( 4 | // Undefined defines undefined value 5 | Undefined = NAN 6 | ) 7 | 8 | // Size describes size 9 | type Size struct { 10 | Width float32 11 | Height float32 12 | } 13 | 14 | // Value describes value 15 | type Value struct { 16 | Value float32 17 | Unit Unit 18 | } 19 | 20 | var ( 21 | // ValueUndefined defines undefined YGValue 22 | ValueUndefined = Value{Undefined, UnitUndefined} 23 | // ValueAuto defines auto YGValue 24 | ValueAuto = Value{Undefined, UnitAuto} 25 | ) 26 | 27 | // MeasureFunc describes function for measuring 28 | type MeasureFunc func(node *Node, width float32, widthMode MeasureMode, height float32, heightMode MeasureMode) Size 29 | 30 | // BaselineFunc describes function for baseline 31 | type BaselineFunc func(node *Node, width float32, height float32) float32 32 | 33 | // PrintFunc defines function for printing 34 | type PrintFunc func(node *Node) 35 | 36 | // Logger defines logging function 37 | type Logger func(config *Config, node *Node, level LogLevel, format string, args ...interface{}) int 38 | -------------------------------------------------------------------------------- /fragment.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | // Fragment appends multiple components together. A fragment has no layout 4 | // implications, it is as if the set of components were appended directly to 5 | // the parent. 6 | func Fragment(c ...Component) Component { 7 | return &fragmentComponent{List: c} 8 | } 9 | 10 | type fragmentComponent struct { 11 | terminalComponent 12 | 13 | List []Component 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mitchellh/go-glint 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/cheggaaa/pb/v3 v3.0.5 7 | github.com/containerd/console v1.0.1 8 | github.com/gookit/color v1.3.1 9 | github.com/mitchellh/go-testing-interface v1.14.1 10 | github.com/mitchellh/go-wordwrap v1.0.1 11 | github.com/morikuni/aec v1.0.0 12 | github.com/stretchr/testify v1.6.1 13 | github.com/tj/go-spin v1.1.0 14 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= 2 | github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= 3 | github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE= 4 | github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= 5 | github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc= 6 | github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 10 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 11 | github.com/gookit/color v1.3.1 h1:PPD/C7sf8u2L8XQPdPgsWRoAiLQGZEZOzU3cf5IYYUk= 12 | github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= 13 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 14 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 15 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 16 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 17 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 18 | github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= 19 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 20 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= 21 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 22 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 23 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 24 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 25 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 26 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 27 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 28 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 30 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 31 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 32 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 33 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 34 | github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds= 35 | github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= 36 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 37 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= 38 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 39 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 40 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 44 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f h1:6Sc1XOXTulBN6imkqo6XoAXDEzoQ4/ro6xy7Vn8+rOM= 46 | golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 49 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 50 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 51 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 52 | -------------------------------------------------------------------------------- /internal/layout/builder.go: -------------------------------------------------------------------------------- 1 | package layout 2 | 3 | import ( 4 | "github.com/mitchellh/go-glint/flex" 5 | ) 6 | 7 | type SetFunc func(n *flex.Node) 8 | 9 | // Builder builds a set of styles to apply to a flex node. 10 | type Builder struct { 11 | f SetFunc 12 | } 13 | 14 | // Raw composes a SetFunc on the builder. This will call the previous 15 | // styles setters first and then call this function. 16 | func (l *Builder) Raw(f SetFunc) *Builder { 17 | return l.add(f) 18 | } 19 | 20 | // Apply sets the styles on the flex node. 21 | func (l *Builder) Apply(node *flex.Node) { 22 | if l == nil || l.f == nil { 23 | return 24 | } 25 | 26 | l.f(node) 27 | } 28 | 29 | // add is a helper to add the function to the call chain for this builder. 30 | // This will return a new builder. 31 | func (l *Builder) add(f func(*flex.Node)) *Builder { 32 | old := l.f 33 | new := func(n *flex.Node) { 34 | if old != nil { 35 | old(n) 36 | } 37 | 38 | f(n) 39 | } 40 | 41 | return &Builder{f: new} 42 | } 43 | -------------------------------------------------------------------------------- /layout.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/mitchellh/go-glint/flex" 7 | "github.com/mitchellh/go-glint/internal/layout" 8 | ) 9 | 10 | // Layout is used to set layout properties for the child components. 11 | // This can be used similarly to a "div" in HTML with "display: flex" set. 12 | // This component follows the builder pattern for setting layout properties 13 | // such as margins, paddings, etc. 14 | func Layout(inner ...Component) *LayoutComponent { 15 | return &LayoutComponent{inner: inner, builder: &layout.Builder{}} 16 | } 17 | 18 | // LayoutComponent is a component used for layout settings. See Layout. 19 | type LayoutComponent struct { 20 | inner []Component 21 | builder *layout.Builder 22 | } 23 | 24 | // Row sets the `flex-direction: row` property. 25 | func (c *LayoutComponent) Row() *LayoutComponent { 26 | c.builder = c.builder.Raw(func(n *flex.Node) { 27 | n.StyleSetFlexDirection(flex.FlexDirectionRow) 28 | }) 29 | return c 30 | } 31 | 32 | // MarginLeft sets the `margin-left` property. 33 | func (c *LayoutComponent) MarginLeft(x int) *LayoutComponent { 34 | c.builder = c.builder.Raw(func(n *flex.Node) { 35 | n.StyleSetMargin(flex.EdgeLeft, float32(x)) 36 | }) 37 | return c 38 | } 39 | 40 | // MarginRight sets the `margin-left` property. 41 | func (c *LayoutComponent) MarginRight(x int) *LayoutComponent { 42 | c.builder = c.builder.Raw(func(n *flex.Node) { 43 | n.StyleSetMargin(flex.EdgeRight, float32(x)) 44 | }) 45 | return c 46 | } 47 | 48 | // PaddingLeft sets the `margin-left` property. 49 | func (c *LayoutComponent) PaddingLeft(x int) *LayoutComponent { 50 | c.builder = c.builder.Raw(func(n *flex.Node) { 51 | n.StyleSetPadding(flex.EdgeLeft, float32(x)) 52 | }) 53 | return c 54 | } 55 | 56 | // PaddingRight sets the `margin-left` property. 57 | func (c *LayoutComponent) PaddingRight(x int) *LayoutComponent { 58 | c.builder = c.builder.Raw(func(n *flex.Node) { 59 | n.StyleSetPadding(flex.EdgeRight, float32(x)) 60 | }) 61 | return c 62 | } 63 | 64 | // Component implementation 65 | func (c *LayoutComponent) Body(context.Context) Component { 66 | return Fragment(c.inner...) 67 | } 68 | 69 | // componentLayout internal implementation. 70 | func (c *LayoutComponent) Layout() *layout.Builder { 71 | return c.builder 72 | } 73 | -------------------------------------------------------------------------------- /layout_test.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestLayout(t *testing.T) { 10 | t.Run("left margin", func(t *testing.T) { 11 | // single line 12 | require.Equal(t, " hello", TestRender(t, 13 | Layout(Text("hello")).MarginLeft(2), 14 | )) 15 | }) 16 | 17 | t.Run("right margin", func(t *testing.T) { 18 | // single line 19 | require.Equal(t, "hello ", TestRender(t, 20 | Layout(Text("hello")).MarginRight(2), 21 | )) 22 | }) 23 | 24 | t.Run("left margin multi-line", func(t *testing.T) { 25 | // single line 26 | require.Equal(t, " hello\n world", TestRender(t, 27 | Layout(Text("hello\nworld")).MarginLeft(2), 28 | )) 29 | }) 30 | 31 | t.Run("left padding", func(t *testing.T) { 32 | // single line 33 | require.Equal(t, " hello", TestRender(t, 34 | Layout(Text("hello")).PaddingLeft(2), 35 | )) 36 | }) 37 | 38 | t.Run("right padding", func(t *testing.T) { 39 | // single line 40 | require.Equal(t, "hello ", TestRender(t, 41 | Layout(Text("hello")).PaddingRight(2), 42 | )) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /measure.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "strings" 7 | "unicode/utf8" 8 | 9 | "github.com/mitchellh/go-glint/flex" 10 | "github.com/mitchellh/go-wordwrap" 11 | ) 12 | 13 | // TextNodeContext is the *flex.Node.Context set for all *TextComponent flex nodes. 14 | type TextNodeContext struct { 15 | // C is the TextComponent represented. 16 | C *TextComponent 17 | 18 | // The context at the time layout was done 19 | Context context.Context 20 | 21 | // Text is the rendered text. This is populated after MeasureTextNode 22 | // is called. Note that this may not fit in the final layout calculations 23 | // since it is populated on measurement. 24 | Text string 25 | 26 | // Size is the measurement size returned. This can be used to determine 27 | // if the text above fits in the final size. Text is guaranteed to fit 28 | // in this size. 29 | Size flex.Size 30 | } 31 | 32 | func (c *TextNodeContext) Component() Component { return c.C } 33 | 34 | // MeasureTextNode implements flex.MeasureFunc and returns the measurements 35 | // for the given node only if the node represents a TextComponent. This is 36 | // the MeasureFunc that is typically used for renderers since all component 37 | // trees terminate in a text node. 38 | // 39 | // The flex.Node must have Context set to TextNodeContext. After calling this, 40 | // fields such as Text and Size will be populated on the node. 41 | func MeasureTextNode( 42 | node *flex.Node, 43 | width float32, 44 | widthMode flex.MeasureMode, 45 | height float32, 46 | heightMode flex.MeasureMode, 47 | ) flex.Size { 48 | // If we have no context set then we use the full spacing. 49 | ctx, ok := node.Context.(*TextNodeContext) 50 | if !ok || ctx == nil { 51 | return flex.Size{Width: width, Height: height} 52 | } 53 | 54 | // Otherwise, we have to render this. 55 | ctx.Text = ctx.C.Render(uint(height), uint(width)) 56 | 57 | // Word wrap and truncate if we're beyond the width limit. 58 | if !math.IsNaN(float64(width)) && width > 0 { 59 | ctx.Text = clampTextWidth( 60 | wordwrap.WrapString(ctx.Text, uint(width)), 61 | int(width)) 62 | } 63 | 64 | // Truncate height if we have a limit. This is a no-op if it fits. 65 | if !math.IsNaN(float64(height)) && height > 0 { 66 | ctx.Text = truncateTextHeight(ctx.Text, int(height)) 67 | } 68 | 69 | // Calculate the size 70 | ctx.Size = flex.Size{ 71 | Width: float32(longestLine(ctx.Text)), 72 | Height: float32(countLines(ctx.Text)), 73 | } 74 | 75 | // We special case the empty-text case, since this is a height of 76 | // one and width of zero. If the user wanted no rendering at all they 77 | // should render nil. 78 | if ctx.Text == "" { 79 | ctx.Size.Height = 1 80 | } 81 | 82 | return ctx.Size 83 | } 84 | 85 | func countLines(s string) int { 86 | count := strings.Count(s, "\n") 87 | 88 | // If the last character isn't a newline, we have to add one since we'll 89 | // always have one more line than newline characters. 90 | if len(s) > 0 && s[len(s)-1] != '\n' { 91 | count++ 92 | } 93 | return count 94 | } 95 | 96 | func longestLine(s string) int { 97 | longest := 0 98 | for { 99 | idx := strings.IndexByte(s, '\n') 100 | if idx == -1 { 101 | break 102 | } 103 | 104 | current := utf8.RuneCountInString(s[:idx]) 105 | if current > longest { 106 | longest = current 107 | } 108 | 109 | s = s[idx+1:] 110 | } 111 | 112 | if longest == 0 { 113 | return utf8.RuneCountInString(s) 114 | } 115 | 116 | return longest 117 | } 118 | 119 | func truncateTextHeight(s string, height int) string { 120 | // The way this works is that we iterate through HEIGHT newlines 121 | // and return up to that point. If we either don't find a newline 122 | // or we've reached the end of the string, then the string is shorter 123 | // than the height limit and we return the whole thing. 124 | idx := 0 125 | for i := 0; i < height; i++ { 126 | next := strings.IndexByte(s[idx:], '\n') 127 | if next == -1 || idx >= len(s) { 128 | return s 129 | } 130 | 131 | idx += next + 1 132 | } 133 | 134 | // This can happen if height == 0 135 | if idx == 0 { 136 | return "" 137 | } 138 | 139 | // Subtract one here because the idx is the last "\n" char 140 | return s[:idx-1] 141 | } 142 | 143 | // clampTextWidth cuts off any lines in s that are longer than width 144 | // characters (not including the newline). 145 | func clampTextWidth(s string, width int) string { 146 | // If our width is zero just return empty 147 | if width == 0 { 148 | return "" 149 | } 150 | 151 | // NOTE(mitchellh): This loop is really horrible. It is unclear, weirdly 152 | // repetitive, and just aesthetically gross. But the tests pass and we have 153 | // good test cases on this. Let's fix this later. 154 | var b *strings.Builder 155 | total := 0 156 | original := s 157 | for { 158 | end := false 159 | idx := strings.IndexByte(s, '\n') 160 | if idx == -1 { 161 | idx = len(s) 162 | end = true 163 | } 164 | 165 | runeCount := utf8.RuneCountInString(s[:idx]) 166 | if runeCount > width { 167 | if b == nil { 168 | b = &strings.Builder{} 169 | if total > 0 { 170 | b.WriteString(original[:total]) 171 | b.WriteByte('\n') 172 | } 173 | } 174 | 175 | runes := []rune(s) 176 | b.WriteString(string(runes[:width])) 177 | if !end { 178 | b.WriteByte('\n') 179 | } 180 | } else if idx > 0 { 181 | if b != nil { 182 | b.WriteString(s[:idx]) 183 | } 184 | } 185 | 186 | if end { 187 | break 188 | } 189 | 190 | total += idx 191 | s = s[idx+1:] 192 | } 193 | 194 | if b == nil { 195 | return original 196 | } 197 | 198 | return b.String() 199 | } 200 | -------------------------------------------------------------------------------- /measure_test.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestLongestLine(t *testing.T) { 10 | cases := []struct { 11 | Name string 12 | Input string 13 | Expected int 14 | }{ 15 | { 16 | "empty", 17 | "", 18 | 0, 19 | }, 20 | 21 | { 22 | "no newline", 23 | "foo", 24 | 3, 25 | }, 26 | 27 | { 28 | "trailing newline", 29 | "foo\n", 30 | 3, 31 | }, 32 | 33 | { 34 | "middle line", 35 | "foo\nbarr\nbaz\n", 36 | 4, 37 | }, 38 | 39 | { 40 | "unicode character", 41 | "\u2584", 42 | 1, 43 | }, 44 | 45 | { 46 | "unicode character with newlines", 47 | "\u2584\n", 48 | 1, 49 | }, 50 | } 51 | 52 | for _, tt := range cases { 53 | t.Run(tt.Name, func(t *testing.T) { 54 | require := require.New(t) 55 | actual := longestLine(tt.Input) 56 | require.Equal(tt.Expected, actual) 57 | }) 58 | } 59 | } 60 | 61 | func TestTruncateTextHeight(t *testing.T) { 62 | cases := []struct { 63 | Name string 64 | Input string 65 | Height int 66 | Expected string 67 | }{ 68 | { 69 | "empty", 70 | "", 71 | 10, 72 | "", 73 | }, 74 | 75 | { 76 | "height zero", 77 | "hello\nworld\n", 78 | 0, 79 | "", 80 | }, 81 | 82 | { 83 | "shorter than limit", 84 | "foo\nbar", 85 | 5, 86 | "foo\nbar", 87 | }, 88 | 89 | { 90 | "greater than limit", 91 | "foo\nbar\nbaz\nqux", 92 | 3, 93 | "foo\nbar\nbaz", 94 | }, 95 | 96 | { 97 | "equal to limit", 98 | "foo\nbar\nbaz", 99 | 3, 100 | "foo\nbar\nbaz", 101 | }, 102 | 103 | { 104 | "equal to limit with trailing newline", 105 | "foo\nbar\n", 106 | 3, 107 | "foo\nbar\n", 108 | }, 109 | } 110 | 111 | for _, tt := range cases { 112 | t.Run(tt.Name, func(t *testing.T) { 113 | require := require.New(t) 114 | actual := truncateTextHeight(tt.Input, tt.Height) 115 | require.Equal(tt.Expected, actual) 116 | }) 117 | } 118 | } 119 | 120 | func TestClampTextWidth(t *testing.T) { 121 | cases := []struct { 122 | Name string 123 | Input string 124 | Width int 125 | Expected string 126 | }{ 127 | { 128 | "empty", 129 | "", 130 | 10, 131 | "", 132 | }, 133 | 134 | { 135 | "width zero", 136 | "hello\nworld\n", 137 | 0, 138 | "", 139 | }, 140 | 141 | { 142 | "width fits", 143 | "hello world\ni fit!", 144 | 100, 145 | "hello world\ni fit!", 146 | }, 147 | 148 | { 149 | "clamped one line", 150 | "hello world", 151 | 5, 152 | "hello", 153 | }, 154 | 155 | { 156 | "clamped one line ends in newline", 157 | "hello world\n", 158 | 5, 159 | "hello\n", 160 | }, 161 | 162 | { 163 | "fits ends in newline", 164 | "hello\n", 165 | 5, 166 | "hello\n", 167 | }, 168 | 169 | { 170 | "clamped both lines", 171 | "hello world\ni fit!", 172 | 5, 173 | "hello\ni fit", 174 | }, 175 | 176 | { 177 | "clamped first line but not second", 178 | "helloworld\nhello", 179 | 5, 180 | "hello\nhello", 181 | }, 182 | 183 | { 184 | "unicode multi-byte character", 185 | "\u2584", 186 | 1, 187 | "\u2584", 188 | }, 189 | 190 | { 191 | "unicode exceeds width", 192 | "\u2584\u2582", 193 | 1, 194 | "\u2584", 195 | }, 196 | } 197 | 198 | for _, tt := range cases { 199 | t.Run(tt.Name, func(t *testing.T) { 200 | require := require.New(t) 201 | actual := clampTextWidth(tt.Input, tt.Width) 202 | require.Equal(tt.Expected, actual) 203 | }) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /renderer.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/mitchellh/go-glint/flex" 7 | ) 8 | 9 | // Renderers are responsible for helping configure layout properties and 10 | // ultimately drawing components. 11 | // 12 | // Renderers may also optionally implement io.Closer. If a renderer implements 13 | // io.Closer, the Close method will be called. After this is called. the 14 | // Render methods will no longer be called again. This can be used to perform 15 | // final cleanups. 16 | type Renderer interface { 17 | // LayoutRoot returns the root node for the layout engine. This should 18 | // set any styling to restrict children such as width. If this returns nil 19 | // then rendering will do nothing. 20 | LayoutRoot() *flex.Node 21 | 22 | // RenderRoot is called to render the tree rooted at the given node. 23 | // This will always be called with the root node. In the future we plan 24 | // to support partial re-renders but this will be done via a separate call. 25 | // 26 | // The height of root is always greater than zero. RenderRoot won't be 27 | // called if the root has a zero height since this implies that nothing 28 | // has to be drawn. 29 | // 30 | // prev will be the previous root that was rendered. This can be used to 31 | // determine layout differences. This will be nil if this is the first 32 | // render. If the height of the previous node is zero then that means that 33 | // everything drawn was finalized. 34 | RenderRoot(root, prev *flex.Node) 35 | } 36 | 37 | // WithRenderer inserts the renderer into the context. This is done automatically 38 | // by Document for components. 39 | func WithRenderer(ctx context.Context, r Renderer) context.Context { 40 | return context.WithValue(ctx, rendererCtxKey, r) 41 | } 42 | 43 | // RendererFromContext returns the Renderer in the context or nil if no 44 | // Renderer is found. 45 | func RendererFromContext(ctx context.Context) Renderer { 46 | v, _ := ctx.Value(rendererCtxKey).(Renderer) 47 | return v 48 | } 49 | 50 | type glintCtxKey string 51 | 52 | const ( 53 | rendererCtxKey = glintCtxKey("renderer") 54 | ) 55 | -------------------------------------------------------------------------------- /renderer_string.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "strings" 8 | 9 | "github.com/mitchellh/go-glint/flex" 10 | ) 11 | 12 | // StringRenderer renders output to a string builder. This will clear 13 | // the builder on each frame render. The StringRenderer is primarily meant 14 | // for testing components. 15 | type StringRenderer struct { 16 | // Builder is the strings builder to write to. If this is nil then 17 | // it will be created on first render. 18 | Builder *strings.Builder 19 | 20 | // Width is a fixed width to set for the root node. If this isn't 21 | // set then a width of 80 is arbitrarily used. 22 | Width uint 23 | } 24 | 25 | func (r *StringRenderer) LayoutRoot() *flex.Node { 26 | width := r.Width 27 | if width == 0 { 28 | width = 80 29 | } 30 | 31 | node := flex.NewNode() 32 | node.StyleSetWidth(float32(width)) 33 | return node 34 | } 35 | 36 | func (r *StringRenderer) RenderRoot(root, prev *flex.Node) { 37 | if r.Builder == nil { 38 | r.Builder = &strings.Builder{} 39 | } 40 | 41 | // Reset our builder 42 | r.Builder.Reset() 43 | 44 | // Draw 45 | r.renderTree(r.Builder, root, -1, false) 46 | } 47 | 48 | func (r *StringRenderer) renderTree(final io.Writer, parent *flex.Node, lastRow int, color bool) { 49 | var buf bytes.Buffer 50 | for _, child := range parent.Children { 51 | // Ignore children with a zero height 52 | if child.LayoutGetHeight() == 0 { 53 | continue 54 | } 55 | 56 | // If we're on a different row than last time then we draw a newline. 57 | thisRow := int(child.LayoutGetTop()) 58 | if lastRow >= 0 && thisRow > lastRow { 59 | buf.WriteByte('\n') 60 | } 61 | lastRow = thisRow 62 | 63 | // Get our node context. If we don't have one then we're a container 64 | // and we render below. 65 | ctx, ok := child.Context.(*TextNodeContext) 66 | if !ok { 67 | r.renderTree(&buf, child, lastRow, color) 68 | } else { 69 | text := ctx.Text 70 | if color { 71 | text = styleRender(ctx.Context, text) 72 | } 73 | 74 | // Draw our text 75 | fmt.Fprint(&buf, text) 76 | } 77 | } 78 | 79 | // We've finished drawing our main content. If we have any paddings/margins 80 | // we have to draw these now into our buffer. 81 | leftMargin := int(parent.LayoutGetMargin(flex.EdgeLeft)) 82 | rightMargin := int(parent.LayoutGetMargin(flex.EdgeRight)) 83 | leftPadding := int(parent.LayoutGetPadding(flex.EdgeLeft)) 84 | rightPadding := int(parent.LayoutGetPadding(flex.EdgeRight)) 85 | 86 | // NOTE(mitchellh): this is not an optimal way to do this. This was a 87 | // get-it-done-fast implementation. We should swing back around at some 88 | // point and rewrite this with less allocations and copying. 89 | lines := bytes.Split(buf.Bytes(), newline) 90 | for i, line := range lines { 91 | final.Write(bytes.Repeat(space, leftMargin+leftPadding)) 92 | final.Write(line) 93 | final.Write(bytes.Repeat(space, rightMargin+rightPadding)) 94 | if i < len(lines)-1 { 95 | final.Write(newline) 96 | } 97 | } 98 | } 99 | 100 | var ( 101 | space = []byte(" ") 102 | newline = []byte("\n") 103 | ) 104 | -------------------------------------------------------------------------------- /renderer_string_test.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestStringRenderer(t *testing.T) { 10 | require := require.New(t) 11 | 12 | r := &StringRenderer{} 13 | d := New() 14 | d.SetRenderer(r) 15 | d.Append(Text("hello\nworld")) 16 | 17 | d.RenderFrame() 18 | require.Equal("hello\nworld", r.Builder.String()) 19 | 20 | // Second render should clear and rewrite 21 | d.RenderFrame() 22 | require.Equal("hello\nworld", r.Builder.String()) 23 | } 24 | 25 | func TestStringRenderer_blankText(t *testing.T) { 26 | require := require.New(t) 27 | 28 | r := &StringRenderer{} 29 | d := New() 30 | d.SetRenderer(r) 31 | d.Append(Text("")) 32 | d.Append(Text("hello")) 33 | 34 | d.RenderFrame() 35 | require.Equal("\nhello", r.Builder.String()) 36 | } 37 | -------------------------------------------------------------------------------- /renderer_term.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | "github.com/containerd/console" 10 | "github.com/gookit/color" 11 | "github.com/morikuni/aec" 12 | sshterm "golang.org/x/crypto/ssh/terminal" 13 | 14 | "github.com/mitchellh/go-glint/flex" 15 | ) 16 | 17 | // TerminalRenderer renders output to a terminal. It expects the Output set 18 | // to be a TTY. This will use ANSI escape codes to redraw. 19 | type TerminalRenderer struct { 20 | // Output is where to write to. This should be a TTY. 21 | Output io.Writer 22 | 23 | // Rows, Cols are the dimensions of the terminal. If these are not set 24 | // (zero), then we will auto-detect the size of the output if it is a TTY. 25 | // If the values are still zero, nothing will be rendered. 26 | Rows, Cols uint 27 | } 28 | 29 | func (r *TerminalRenderer) LayoutRoot() *flex.Node { 30 | // If we don't have a writer set, then don't render anything. 31 | if r.Output == nil { 32 | return nil 33 | } 34 | 35 | // Setup our dimensions 36 | cols := r.Cols 37 | rows := r.Rows 38 | if cols == 0 || rows == 0 { 39 | if f, ok := r.Output.(*os.File); ok && sshterm.IsTerminal(int(f.Fd())) { 40 | if c, err := console.ConsoleFromFile(f); err == nil { 41 | if sz, err := c.Size(); err == nil { 42 | rows = uint(sz.Height) 43 | cols = uint(sz.Width) 44 | } 45 | } 46 | } 47 | } 48 | 49 | // Render nothing if we're going to have any zero dimensions 50 | if cols == 0 || rows == 0 { 51 | return nil 52 | } 53 | 54 | // Setup our node 55 | node := flex.NewNode() 56 | node.StyleSetWidth(float32(cols)) 57 | node.Context = &termRootContext{ 58 | Rows: rows, 59 | Cols: cols, 60 | } 61 | 62 | return node 63 | } 64 | 65 | func (r *TerminalRenderer) RenderRoot(root, prev *flex.Node) { 66 | w := r.Output 67 | rootCtx := root.Context.(*termRootContext) 68 | 69 | // Draw into a buffer first. This minimizes the time we spend with 70 | // a blank screen. 71 | var buf bytes.Buffer 72 | var sr StringRenderer 73 | sr.renderTree(&buf, root, -1, color.IsSupportColor()) 74 | rootCtx.Buf = &buf 75 | 76 | if prev != nil { 77 | // If the previous draw was a terminal and the output was identical, 78 | // then we do nothing. 79 | prevCtx, ok := prev.Context.(*termRootContext) 80 | if ok && 81 | prevCtx != nil && 82 | prevCtx.Buf != nil && 83 | prevCtx.Rows == rootCtx.Rows && 84 | prevCtx.Cols == rootCtx.Cols && 85 | bytes.Equal(prevCtx.Buf.Bytes(), buf.Bytes()) { 86 | return 87 | } 88 | 89 | // Remove what we last drew. If what we last drew is greater than the number 90 | // of rows then we need to clear the screen. 91 | height := uint(prev.LayoutGetHeight()) 92 | if height == 0 { 93 | // If our previous render height is zero that means that everything 94 | // was finalized and we need to start on a new line. 95 | fmt.Fprintf(w, "\n") 96 | } else { 97 | if height <= rootCtx.Rows { 98 | // Delete current line 99 | fmt.Fprint(w, b.Column(0).EraseLine(aec.EraseModes.All).ANSI) 100 | 101 | // Delete n lines above 102 | for i := uint(0); i < height-1; i++ { 103 | fmt.Fprint(w, b.Up(1).Column(0).EraseLine(aec.EraseModes.All).ANSI) 104 | } 105 | } else { 106 | fmt.Fprint(w, b.EraseDisplay(aec.EraseModes.All).EraseDisplay(aec.EraseMode(3)).Position(0, 0).ANSI) 107 | } 108 | } 109 | } 110 | 111 | // Draw our current output. We wrap buf in a bytes.Reader so we don't 112 | // consume the bytes since we'll reuse them in a future call. 113 | io.Copy(w, bytes.NewReader(buf.Bytes())) 114 | } 115 | 116 | func (r *TerminalRenderer) Close() error { 117 | fmt.Fprintln(r.Output, "") 118 | return nil 119 | } 120 | 121 | type termRootContext struct { 122 | Rows, Cols uint 123 | Buf *bytes.Buffer 124 | } 125 | 126 | var b = aec.EmptyBuilder 127 | -------------------------------------------------------------------------------- /style.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gookit/color" 7 | ) 8 | 9 | // Style applies visual styles to this component and any children. This 10 | // can be used to set a foreground color, for example, to a set of components. 11 | func Style(inner Component, opts ...StyleOption) Component { 12 | c := &styleComponent{inner: inner} 13 | for _, opt := range opts { 14 | opt(c) 15 | } 16 | 17 | return c 18 | } 19 | 20 | // styleRender is used internally to apply styles to the given string. The 21 | // ctx should be the same context given when Body was called on this 22 | // component. 23 | func styleRender(ctx context.Context, v string) string { 24 | value, _ := ctx.Value(styleCtxKey).([]*styleComponent) 25 | for _, s := range value { 26 | v = s.render(v) 27 | } 28 | 29 | return v 30 | } 31 | 32 | type styleComponent struct { 33 | inner Component 34 | fgColor, bgColor colorizer 35 | style []color.Color 36 | } 37 | 38 | func (c *styleComponent) Body(ctx context.Context) Component { 39 | // Add our style to the list of styles. We have to use copy here 40 | // so we don't append to a parent. 41 | old, _ := ctx.Value(styleCtxKey).([]*styleComponent) 42 | value := make([]*styleComponent, len(old), len(old)+1) 43 | copy(value, old) 44 | value = append(value, c) 45 | return Context(c.inner, styleCtxKey, value) 46 | } 47 | 48 | func (c *styleComponent) render(v string) string { 49 | if c.bgColor != nil { 50 | v = c.bgColor.Sprint(v) 51 | } 52 | if c.fgColor != nil { 53 | v = c.fgColor.Sprint(v) 54 | } 55 | v = color.Style(c.style).Sprint(v) 56 | 57 | return v 58 | } 59 | 60 | type styleCtxKeyType struct{} 61 | 62 | var styleCtxKey = styleCtxKeyType{} 63 | 64 | // StyleOption is an option that can be set when creating Text components. 65 | type StyleOption func(t *styleComponent) 66 | 67 | // Color sets the color by name. The supported colors are listed below. 68 | // 69 | // black, red, green, yellow, blue, magenta, cyan, white, darkGray, 70 | // lightRed, lightGreen, lightYellow, lightBlue, lightMagenta, lightCyan, 71 | // lightWhite. 72 | func Color(name string) StyleOption { 73 | return func(t *styleComponent) { 74 | if c, ok := color.FgColors[name]; ok { 75 | t.fgColor = c 76 | } 77 | if c, ok := color.ExFgColors[name]; ok { 78 | t.fgColor = c 79 | } 80 | } 81 | } 82 | 83 | // ColorHex sets the foreground color by hex code. The value can be 84 | // in formats AABBCC, #AABBCC, 0xAABBCC. 85 | func ColorHex(v string) StyleOption { 86 | return func(t *styleComponent) { 87 | t.fgColor = color.HEX(v) 88 | } 89 | } 90 | 91 | // ColorRGB sets the foreground color by RGB values. 92 | func ColorRGB(r, g, b uint8) StyleOption { 93 | return func(t *styleComponent) { 94 | t.fgColor = color.RGB(r, g, b) 95 | } 96 | } 97 | 98 | // BGColor sets the background color by name. The supported colors are listed 99 | // below. 100 | // 101 | // black, red, green, yellow, blue, magenta, cyan, white, darkGray, 102 | // lightRed, lightGreen, lightYellow, lightBlue, lightMagenta, lightCyan, 103 | // lightWhite. 104 | func BGColor(name string) StyleOption { 105 | return func(t *styleComponent) { 106 | if c, ok := color.BgColors[name]; ok { 107 | t.bgColor = c 108 | } 109 | if c, ok := color.ExBgColors[name]; ok { 110 | t.bgColor = c 111 | } 112 | } 113 | } 114 | 115 | // BGColorHex sets the background color by hex code. The value can be 116 | // in formats AABBCC, #AABBCC, 0xAABBCC. 117 | func BGColorHex(v string) StyleOption { 118 | return func(t *styleComponent) { 119 | t.bgColor = color.HEX(v, true) 120 | } 121 | } 122 | 123 | // BGColorRGB sets the background color by RGB values. 124 | func BGColorRGB(r, g, b uint8) StyleOption { 125 | return func(t *styleComponent) { 126 | t.bgColor = color.RGB(r, g, b, true) 127 | } 128 | } 129 | 130 | // Bold sets the text to bold. 131 | func Bold() StyleOption { 132 | return func(t *styleComponent) { 133 | t.style = append(t.style, color.OpBold) 134 | } 135 | } 136 | 137 | // Italic sets the text to italic. 138 | func Italic() StyleOption { 139 | return func(t *styleComponent) { 140 | t.style = append(t.style, color.OpItalic) 141 | } 142 | } 143 | 144 | // Underline sets the text to be underlined. 145 | func Underline() StyleOption { 146 | return func(t *styleComponent) { 147 | t.style = append(t.style, color.OpUnderscore) 148 | } 149 | } 150 | 151 | type colorizer interface { 152 | Sprint(...interface{}) string 153 | } 154 | -------------------------------------------------------------------------------- /testing.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "github.com/mitchellh/go-testing-interface" 5 | ) 6 | 7 | // TestRender renders the component using the string renderer and returns 8 | // the string. This is a test helper function for writing components. 9 | func TestRender(t testing.T, c Component) string { 10 | // Note that nothing here fails at the moment so the t param above is 11 | // unneeded but we're gonna keep it around in case we need it in the future 12 | // so we don't have to break API. 13 | r := &StringRenderer{} 14 | d := New() 15 | d.SetRenderer(r) 16 | d.Append(c) 17 | d.RenderFrame() 18 | return r.Builder.String() 19 | } 20 | -------------------------------------------------------------------------------- /text.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // TextComponent is a Component that renders text. 8 | type TextComponent struct { 9 | terminalComponent 10 | f func(rows, cols uint) string 11 | } 12 | 13 | // Text creates a TextComponent for static text. The text here will be word 14 | // wrapped automatically based on the width of the terminal. 15 | func Text(v string) *TextComponent { 16 | return TextFunc(func(rows, cols uint) string { return v }) 17 | } 18 | 19 | // TextFunc creates a TextComponent for text that is dependent on the 20 | // size of the draw area. 21 | func TextFunc(f func(rows, cols uint) string) *TextComponent { 22 | return &TextComponent{ 23 | f: f, 24 | } 25 | } 26 | 27 | func (el *TextComponent) Body(context.Context) Component { 28 | return nil 29 | } 30 | 31 | func (el *TextComponent) Render(rows, cols uint) string { 32 | if el.f == nil { 33 | return "" 34 | } 35 | 36 | return el.f(rows, cols) 37 | } 38 | -------------------------------------------------------------------------------- /tree.go: -------------------------------------------------------------------------------- 1 | package glint 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/mitchellh/go-glint/flex" 7 | ) 8 | 9 | func tree( 10 | ctx context.Context, 11 | parent *flex.Node, 12 | c Component, 13 | finalize bool, 14 | ) { 15 | // Don't do anything with no component 16 | if c == nil { 17 | return 18 | } 19 | 20 | // Fragments don't create a node 21 | switch c := c.(type) { 22 | case *contextComponent: 23 | for i := 0; i < len(c.pairs); i += 2 { 24 | ctx = context.WithValue(ctx, c.pairs[i], c.pairs[i+1]) 25 | } 26 | 27 | tree(ctx, parent, c.inner, finalize) 28 | return 29 | 30 | case *fragmentComponent: 31 | for _, c := range c.List { 32 | tree(ctx, parent, c, finalize) 33 | } 34 | 35 | return 36 | } 37 | 38 | // Setup our node 39 | node := flex.NewNodeWithConfig(parent.Config) 40 | parent.InsertChild(node, len(parent.Children)) 41 | 42 | // Setup our default context 43 | parentCtx := &parentContext{C: c} 44 | node.Context = parentCtx 45 | 46 | // Check if we're finalized and note it 47 | if _, ok := c.(*finalizedComponent); ok { 48 | parentCtx.Finalized = true 49 | } 50 | 51 | // Finalize 52 | if finalize { 53 | if c, ok := c.(ComponentFinalizer); ok { 54 | c.Finalize() 55 | } 56 | } 57 | 58 | // Setup a custom layout 59 | if c, ok := c.(componentLayout); ok { 60 | c.Layout().Apply(node) 61 | } 62 | 63 | switch c := c.(type) { 64 | case *TextComponent: 65 | node.Context = &TextNodeContext{C: c, Context: ctx} 66 | node.StyleSetFlexShrink(1) 67 | node.StyleSetFlexGrow(0) 68 | node.StyleSetFlexDirection(flex.FlexDirectionRow) 69 | node.SetMeasureFunc(MeasureTextNode) 70 | 71 | default: 72 | // If this is not terminal then we nest. 73 | tree(ctx, node, c.Body(ctx), finalize) 74 | } 75 | 76 | } 77 | 78 | type parentContext struct { 79 | C Component 80 | Finalized bool 81 | } 82 | 83 | func (c *parentContext) Component() Component { return c.C } 84 | 85 | type treeContext interface { 86 | Component() Component 87 | } 88 | --------------------------------------------------------------------------------