Projects STRLCPY termdash Commits 38e4dcef
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■
    CHANGELOG.md
    skipped 11 lines
    12 12  - Added `time.Duration` based `ValueFormatter` for the `LineChart` Y-axis labels.
    13 13  - Added round and suffix `ValueFormatter` for the `LineChart` Y-axis labels.
    14 14  - Added decimal and suffix `ValueFormatter` for the `LineChart` Y-axis labels.
    15  -- Added an option that allows fixed size container splits.
     15 +- Added a `container.SplitOption` that allows fixed size container splits.
     16 +- Added `grid` functions that allow fixed size rows and columns.
    16 17   
    17 18  ### Changed
    18 19   
    skipped 290 lines
  • ■ ■ ■ ■ ■
    container/grid/grid.go
    skipped 34 lines
    35 35  // Add adds the specified elements.
    36 36  // The subElements can be either a single Widget or any combination of Rows and
    37 37  // Columns.
    38  -// Rows are created using RowHeightPerc() and Columns are created using
    39  -// ColWidthPerc().
     38 +// Rows are created using functions with the RowHeight prefix and Columns are
     39 +// created using functions with the ColWidth prefix
    40 40  // Can be called repeatedly, e.g. to add multiple Rows or Columns.
    41 41  func (b *Builder) Add(subElements ...Element) {
    42 42   b.elems = append(b.elems, subElements...)
    skipped 2 lines
    45 45  // Build builds the grid layout and returns the corresponding container
    46 46  // options.
    47 47  func (b *Builder) Build() ([]container.Option, error) {
    48  - if err := validate(b.elems); err != nil {
     48 + if err := validate(b.elems /* fixedSizeParent = */, false); err != nil {
    49 49   return nil, err
    50 50   }
    51 51   return build(b.elems, 100, 100), nil
    skipped 6 lines
    58 58  // Each individual width or height is in the range 0 < v < 100.
    59 59  // The sum of all widths is <= 100.
    60 60  // The sum of all heights is <= 100.
    61  -func validate(elems []Element) error {
    62  - heightSum := 0
    63  - widthSum := 0
     61 +// Argument fixedSizeParent indicates if any of the parent elements uses fixed
     62 +// size splitType.
     63 +func validate(elems []Element, fixedSizeParent bool) error {
     64 + heightPercSum := 0
     65 + widthPercSum := 0
    64 66   for _, elem := range elems {
    65 67   switch e := elem.(type) {
    66 68   case *row:
    67  - if min, max := 0, 100; e.heightPerc <= min || e.heightPerc >= max {
    68  - return fmt.Errorf("invalid row heightPerc(%d), must be a value in the range %d < v < %d", e.heightPerc, min, max)
     69 + if e.splitType == splitTypeRelative {
     70 + if min, max := 0, 100; e.heightPerc <= min || e.heightPerc >= max {
     71 + return fmt.Errorf("invalid row %v, must be a value in the range %d < v < %d", e, min, max)
     72 + }
     73 + }
     74 + heightPercSum += e.heightPerc
     75 + 
     76 + if fixedSizeParent && e.splitType == splitTypeRelative {
     77 + return fmt.Errorf("row %v cannot use relative height when one of its parent elements uses fixed height", e)
    69 78   }
    70  - heightSum += e.heightPerc
    71  - if err := validate(e.subElem); err != nil {
     79 + 
     80 + isFixed := fixedSizeParent || e.splitType == splitTypeFixed
     81 + if err := validate(e.subElem, isFixed); err != nil {
    72 82   return err
    73 83   }
    74 84   
    75 85   case *col:
    76  - if min, max := 0, 100; e.widthPerc <= min || e.widthPerc >= max {
    77  - return fmt.Errorf("invalid column widthPerc(%d), must be a value in the range %d < v < %d", e.widthPerc, min, max)
     86 + if e.splitType == splitTypeRelative {
     87 + if min, max := 0, 100; e.widthPerc <= min || e.widthPerc >= max {
     88 + return fmt.Errorf("invalid column %v, must be a value in the range %d < v < %d", e, min, max)
     89 + }
    78 90   }
    79  - widthSum += e.widthPerc
    80  - if err := validate(e.subElem); err != nil {
     91 + widthPercSum += e.widthPerc
     92 + 
     93 + if fixedSizeParent && e.splitType == splitTypeRelative {
     94 + return fmt.Errorf("column %v cannot use relative width when one of its parent elements uses fixed height", e)
     95 + }
     96 + 
     97 + isFixed := fixedSizeParent || e.splitType == splitTypeFixed
     98 + if err := validate(e.subElem, isFixed); err != nil {
    81 99   return err
    82 100   }
    83 101   
    skipped 4 lines
    88 106   }
    89 107   }
    90 108   
    91  - if max := 100; heightSum > max || widthSum > max {
    92  - return fmt.Errorf("the sum of all height percentages(%d) and width percentages(%d) at one element level cannot be larger than %d", heightSum, widthSum, max)
     109 + if max := 100; heightPercSum > max || widthPercSum > max {
     110 + return fmt.Errorf("the sum of all height percentages(%d) and width percentages(%d) at one element level cannot be larger than %d", heightPercSum, widthPercSum, max)
    93 111   }
    94 112   return nil
    95 113  }
    skipped 13 lines
    109 127   
    110 128   switch e := elem.(type) {
    111 129   case *row:
    112  - 
    113 130   if len(elems) > 0 {
    114 131   perc := innerPerc(e.heightPerc, parentHeightPerc)
    115 132   childHeightPerc := parentHeightPerc - e.heightPerc
     133 + 
     134 + var splitOpts []container.SplitOption
     135 + if e.splitType == splitTypeRelative {
     136 + splitOpts = append(splitOpts, container.SplitPercent(perc))
     137 + } else {
     138 + splitOpts = append(splitOpts, container.SplitFixed(e.heightFixed))
     139 + }
     140 + 
    116 141   return []container.Option{
    117 142   container.SplitHorizontal(
    118 143   container.Top(append(e.cOpts, build(e.subElem, 100, parentWidthPerc)...)...),
    119 144   container.Bottom(build(elems, childHeightPerc, parentWidthPerc)...),
    120  - container.SplitPercent(perc),
     145 + splitOpts...,
    121 146   ),
    122 147   }
    123 148   }
    skipped 3 lines
    127 152   if len(elems) > 0 {
    128 153   perc := innerPerc(e.widthPerc, parentWidthPerc)
    129 154   childWidthPerc := parentWidthPerc - e.widthPerc
     155 + 
     156 + var splitOpts []container.SplitOption
     157 + if e.splitType == splitTypeRelative {
     158 + splitOpts = append(splitOpts, container.SplitPercent(perc))
     159 + } else {
     160 + splitOpts = append(splitOpts, container.SplitFixed(e.widthFixed))
     161 + }
     162 + 
    130 163   return []container.Option{
    131 164   container.SplitVertical(
    132 165   container.Left(append(e.cOpts, build(e.subElem, parentHeightPerc, 100)...)...),
    133 166   container.Right(build(elems, parentHeightPerc, childWidthPerc)...),
    134  - container.SplitPercent(perc),
     167 + splitOpts...,
    135 168   ),
    136 169   }
    137 170   }
    skipped 41 lines
    179 212   isElement()
    180 213  }
    181 214   
     215 +// splitType represents
     216 +type splitType int
     217 + 
     218 +// String implements fmt.Stringer()
     219 +func (st splitType) String() string {
     220 + if n, ok := splitTypeNames[st]; ok {
     221 + return n
     222 + }
     223 + return "splitTypeUnknown"
     224 +}
     225 + 
     226 +// splitTypeNames maps splitType values to human readable names.
     227 +var splitTypeNames = map[splitType]string{
     228 + splitTypeRelative: "splitTypeRelative",
     229 + splitTypeFixed: "splitTypeFixed",
     230 +}
     231 + 
     232 +const (
     233 + splitTypeRelative splitType = iota
     234 + splitTypeFixed
     235 +)
     236 + 
    182 237  // row is a row in the grid.
    183 238  // row implements Element.
    184 239  type row struct {
     240 + // splitType identifies how the size of the split is determined.
     241 + splitType splitType
     242 + 
    185 243   // heightPerc is the height percentage this row occupies.
     244 + // Only set when splitType is splitTypeRelative.
    186 245   heightPerc int
     246 + 
     247 + // heightFixed is the height in cells this row occupies.
     248 + // Only set when splitType is splitTypeFixed.
     249 + heightFixed int
    187 250   
    188 251   // subElem are the sub Rows or Columns or a single widget.
    189 252   subElem []Element
    skipped 7 lines
    197 260   
    198 261  // String implements fmt.Stringer.
    199 262  func (r *row) String() string {
    200  - return fmt.Sprintf("row{height:%d, sub:%v}", r.heightPerc, r.subElem)
     263 + return fmt.Sprintf("row{splitType:%v, heightPerc:%d, heightFixed:%d, sub:%v}", r.splitType, r.heightPerc, r.heightFixed, r.subElem)
    201 264  }
    202 265   
    203 266  // col is a column in the grid.
    204 267  // col implements Element.
    205 268  type col struct {
     269 + // splitType identifies how the size of the split is determined.
     270 + splitType splitType
     271 + 
    206 272   // widthPerc is the width percentage this column occupies.
     273 + // Only set when splitType is splitTypeRelative.
    207 274   widthPerc int
    208 275   
     276 + // widthFixed is the width in cells thiw column occupies.
     277 + // Only set when splitType is splitTypeRelative.
     278 + widthFixed int
     279 + 
    209 280   // subElem are the sub Rows or Columns or a single widget.
    210 281   subElem []Element
    211 282   
    skipped 6 lines
    218 289   
    219 290  // String implements fmt.Stringer.
    220 291  func (c *col) String() string {
    221  - return fmt.Sprintf("col{width:%d, sub:%v}", c.widthPerc, c.subElem)
     292 + return fmt.Sprintf("col{splitType:%v, widthPerc:%d, widthFixed:%d, sub:%v}", c.splitType, c.widthPerc, c.widthFixed, c.subElem)
    222 293  }
    223 294   
    224 295  // widget is a widget placed into the grid.
    skipped 13 lines
    238 309  // isElement implements Element.isElement.
    239 310  func (widget) isElement() {}
    240 311   
    241  -// RowHeightPerc creates a row of the specified height.
     312 +// RowHeightPerc creates a row of the specified relative height.
    242 313  // The height is supplied as height percentage of the parent element.
    243 314  // The sum of all heights at the same level cannot be larger than 100%. If it
    244 315  // is less that 100%, the last element stretches to the edge of the screen.
    skipped 1 lines
    246 317  // Columns.
    247 318  func RowHeightPerc(heightPerc int, subElements ...Element) Element {
    248 319   return &row{
     320 + splitType: splitTypeRelative,
    249 321   heightPerc: heightPerc,
    250 322   subElem: subElements,
    251 323   }
    252 324  }
    253 325   
     326 +// RowHeightFixed creates a row of the specified fixed height.
     327 +// The height is supplied as a number of cells on the terminal.
     328 +// If the actual terminal size leaves the container with less than the
     329 +// specified amount of cells, the container will be created with zero cells and
     330 +// won't be drawn until the terminal size increases. If the sum of all the
     331 +// heights is less than 100% of the screen height, the last element stretches
     332 +// to the edge of the screen.
     333 +// The subElements can be either a single Widget or any combination of Rows and
     334 +// Columns.
     335 +// A row with fixed height cannot contain any sub-elements with relative size.
     336 +func RowHeightFixed(heightCells int, subElements ...Element) Element {
     337 + return &row{
     338 + splitType: splitTypeFixed,
     339 + heightFixed: heightCells,
     340 + subElem: subElements,
     341 + }
     342 +}
     343 + 
    254 344  // RowHeightPercWithOpts is like RowHeightPerc, but also allows to apply
    255 345  // additional options to the container that represents the row.
    256 346  func RowHeightPercWithOpts(heightPerc int, cOpts []container.Option, subElements ...Element) Element {
    257 347   return &row{
     348 + splitType: splitTypeRelative,
    258 349   heightPerc: heightPerc,
    259 350   subElem: subElements,
    260 351   cOpts: cOpts,
    261 352   }
    262 353  }
    263 354   
    264  -// ColWidthPerc creates a column of the specified width.
     355 +// RowHeightFixedWithOpts is like RowHeightFixed, but also allows to apply
     356 +// additional options to the container that represents the row.
     357 +func RowHeightFixedWithOpts(heightCells int, cOpts []container.Option, subElements ...Element) Element {
     358 + return &row{
     359 + splitType: splitTypeFixed,
     360 + heightFixed: heightCells,
     361 + subElem: subElements,
     362 + cOpts: cOpts,
     363 + }
     364 +}
     365 + 
     366 +// ColWidthPerc creates a column of the specified relative width.
    265 367  // The width is supplied as width percentage of the parent element.
    266 368  // The sum of all widths at the same level cannot be larger than 100%. If it
    267 369  // is less that 100%, the last element stretches to the edge of the screen.
    skipped 1 lines
    269 371  // Columns.
    270 372  func ColWidthPerc(widthPerc int, subElements ...Element) Element {
    271 373   return &col{
     374 + splitType: splitTypeRelative,
    272 375   widthPerc: widthPerc,
    273 376   subElem: subElements,
    274 377   }
    275 378  }
    276 379   
     380 +// ColWidthFixed creates a column of the specified fixed width.
     381 +// The width is supplied as a number of cells on the terminal.
     382 +// If the actual terminal size leaves the container with less than the
     383 +// specified amount of cells, the container will be created with zero cells and
     384 +// won't be drawn until the terminal size increases. If the sum of all the
     385 +// widths is less than 100% of the screen width, the last element stretches
     386 +// to the edge of the screen.
     387 +// The subElements can be either a single Widget or any combination of Rows and
     388 +// Columns.
     389 +// A column with fixed width cannot contain any sub-elements with relative size.
     390 +func ColWidthFixed(widthCells int, subElements ...Element) Element {
     391 + return &col{
     392 + splitType: splitTypeFixed,
     393 + widthFixed: widthCells,
     394 + subElem: subElements,
     395 + }
     396 +}
     397 + 
    277 398  // ColWidthPercWithOpts is like ColWidthPerc, but also allows to apply
    278 399  // additional options to the container that represents the column.
    279 400  func ColWidthPercWithOpts(widthPerc int, cOpts []container.Option, subElements ...Element) Element {
    280 401   return &col{
     402 + splitType: splitTypeRelative,
    281 403   widthPerc: widthPerc,
    282 404   subElem: subElements,
    283 405   cOpts: cOpts,
     406 + }
     407 +}
     408 + 
     409 +// ColWidthFixedWithOpts is like ColWidthFixed, but also allows to apply
     410 +// additional options to the container that represents the column.
     411 +func ColWidthFixedWithOpts(widthCells int, cOpts []container.Option, subElements ...Element) Element {
     412 + return &col{
     413 + splitType: splitTypeFixed,
     414 + widthFixed: widthCells,
     415 + subElem: subElements,
     416 + cOpts: cOpts,
    284 417   }
    285 418  }
    286 419   
    skipped 10 lines
  • ■ ■ ■ ■ ■ ■
    container/grid/grid_test.go
    skipped 234 lines
    235 235   wantErr: true,
    236 236   },
    237 237   {
     238 + desc: "fails when Row heightPerc used under Row heightFixed",
     239 + termSize: image.Point{10, 10},
     240 + builder: func() *Builder {
     241 + b := New()
     242 + b.Add(
     243 + RowHeightFixed(
     244 + 5,
     245 + RowHeightPerc(10),
     246 + ),
     247 + )
     248 + return b
     249 + }(),
     250 + wantErr: true,
     251 + },
     252 + {
     253 + desc: "fails when Row heightPerc used under Col widthFixed",
     254 + termSize: image.Point{10, 10},
     255 + builder: func() *Builder {
     256 + b := New()
     257 + b.Add(
     258 + ColWidthFixed(
     259 + 5,
     260 + RowHeightPerc(10),
     261 + ),
     262 + )
     263 + return b
     264 + }(),
     265 + wantErr: true,
     266 + },
     267 + {
    238 268   desc: "fails when Col widthPerc is too low at top level",
    239 269   termSize: image.Point{10, 10},
    240 270   builder: func() *Builder {
    skipped 48 lines
    289 319   wantErr: true,
    290 320   },
    291 321   {
     322 + desc: "fails when Col widthPerc used under Col widthFixed",
     323 + termSize: image.Point{10, 10},
     324 + builder: func() *Builder {
     325 + b := New()
     326 + b.Add(
     327 + ColWidthFixed(
     328 + 5,
     329 + ColWidthPerc(10),
     330 + ),
     331 + )
     332 + return b
     333 + }(),
     334 + wantErr: true,
     335 + },
     336 + {
     337 + desc: "fails when Col widthPerc used under Row heightFixed",
     338 + termSize: image.Point{10, 10},
     339 + builder: func() *Builder {
     340 + b := New()
     341 + b.Add(
     342 + RowHeightFixed(
     343 + 5,
     344 + ColWidthPerc(10),
     345 + ),
     346 + )
     347 + return b
     348 + }(),
     349 + wantErr: true,
     350 + },
     351 + {
    292 352   desc: "fails when height sum is too large at top level",
    293 353   termSize: image.Point{10, 10},
    294 354   builder: func() *Builder {
    skipped 95 lines
    390 450   },
    391 451   },
    392 452   {
     453 + desc: "two equal rows, fixed size",
     454 + termSize: image.Point{10, 10},
     455 + builder: func() *Builder {
     456 + b := New()
     457 + b.Add(RowHeightFixed(5, Widget(mirror())))
     458 + b.Add(RowHeightFixed(5, Widget(mirror())))
     459 + return b
     460 + }(),
     461 + want: func(size image.Point) *faketerm.Terminal {
     462 + ft := faketerm.MustNew(size)
     463 + top, bot := mustHSplit(ft.Area(), 50)
     464 + fakewidget.MustDraw(ft, testcanvas.MustNew(top), &widgetapi.Meta{}, widgetapi.Options{})
     465 + fakewidget.MustDraw(ft, testcanvas.MustNew(bot), &widgetapi.Meta{}, widgetapi.Options{})
     466 + return ft
     467 + },
     468 + },
     469 + {
    393 470   desc: "two equal rows with options",
    394 471   termSize: image.Point{10, 10},
    395 472   builder: func() *Builder {
    skipped 33 lines
    429 506   },
    430 507   },
    431 508   {
     509 + desc: "two equal rows with options, fixed size",
     510 + termSize: image.Point{10, 10},
     511 + builder: func() *Builder {
     512 + b := New()
     513 + b.Add(RowHeightFixedWithOpts(
     514 + 5,
     515 + []container.Option{
     516 + container.Border(linestyle.Double),
     517 + },
     518 + Widget(mirror()),
     519 + ))
     520 + b.Add(RowHeightFixedWithOpts(
     521 + 5,
     522 + []container.Option{
     523 + container.Border(linestyle.Double),
     524 + },
     525 + Widget(mirror()),
     526 + ))
     527 + return b
     528 + }(),
     529 + want: func(size image.Point) *faketerm.Terminal {
     530 + ft := faketerm.MustNew(size)
     531 + 
     532 + top, bot := mustHSplit(ft.Area(), 50)
     533 + topCvs := testcanvas.MustNew(top)
     534 + botCvs := testcanvas.MustNew(bot)
     535 + testdraw.MustBorder(topCvs, topCvs.Area(), draw.BorderLineStyle(linestyle.Double))
     536 + testdraw.MustBorder(botCvs, botCvs.Area(), draw.BorderLineStyle(linestyle.Double))
     537 + testcanvas.MustApply(topCvs, ft)
     538 + testcanvas.MustApply(botCvs, ft)
     539 + 
     540 + topWidget := testcanvas.MustNew(area.ExcludeBorder(top))
     541 + botWidget := testcanvas.MustNew(area.ExcludeBorder(bot))
     542 + fakewidget.MustDraw(ft, topWidget, &widgetapi.Meta{}, widgetapi.Options{})
     543 + fakewidget.MustDraw(ft, botWidget, &widgetapi.Meta{}, widgetapi.Options{})
     544 + return ft
     545 + },
     546 + },
     547 + {
    432 548   desc: "two unequal rows",
    433 549   termSize: image.Point{10, 10},
    434 550   builder: func() *Builder {
    skipped 11 lines
    446 562   },
    447 563   },
    448 564   {
     565 + desc: "two unequal rows, fixed size",
     566 + termSize: image.Point{10, 10},
     567 + builder: func() *Builder {
     568 + b := New()
     569 + b.Add(RowHeightFixed(2, Widget(mirror())))
     570 + b.Add(RowHeightFixed(8, Widget(mirror())))
     571 + return b
     572 + }(),
     573 + want: func(size image.Point) *faketerm.Terminal {
     574 + ft := faketerm.MustNew(size)
     575 + top, bot := mustHSplit(ft.Area(), 20)
     576 + fakewidget.MustDraw(ft, testcanvas.MustNew(top), &widgetapi.Meta{}, widgetapi.Options{})
     577 + fakewidget.MustDraw(ft, testcanvas.MustNew(bot), &widgetapi.Meta{}, widgetapi.Options{})
     578 + return ft
     579 + },
     580 + },
     581 + {
     582 + desc: "two equal columns",
     583 + termSize: image.Point{20, 10},
     584 + builder: func() *Builder {
     585 + b := New()
     586 + b.Add(ColWidthPerc(50, Widget(mirror())))
     587 + b.Add(ColWidthPerc(50, Widget(mirror())))
     588 + return b
     589 + }(),
     590 + want: func(size image.Point) *faketerm.Terminal {
     591 + ft := faketerm.MustNew(size)
     592 + left, right := mustVSplit(ft.Area(), 50)
     593 + fakewidget.MustDraw(ft, testcanvas.MustNew(left), &widgetapi.Meta{}, widgetapi.Options{})
     594 + fakewidget.MustDraw(ft, testcanvas.MustNew(right), &widgetapi.Meta{}, widgetapi.Options{})
     595 + return ft
     596 + },
     597 + },
     598 + {
     599 + desc: "two equal columns, fixed size",
     600 + termSize: image.Point{20, 10},
     601 + builder: func() *Builder {
     602 + b := New()
     603 + b.Add(ColWidthFixed(10, Widget(mirror())))
     604 + b.Add(ColWidthFixed(10, Widget(mirror())))
     605 + return b
     606 + }(),
     607 + want: func(size image.Point) *faketerm.Terminal {
     608 + ft := faketerm.MustNew(size)
     609 + left, right := mustVSplit(ft.Area(), 50)
     610 + fakewidget.MustDraw(ft, testcanvas.MustNew(left), &widgetapi.Meta{}, widgetapi.Options{})
     611 + fakewidget.MustDraw(ft, testcanvas.MustNew(right), &widgetapi.Meta{}, widgetapi.Options{})
     612 + return ft
     613 + },
     614 + },
     615 + {
    449 616   desc: "two equal columns with options",
    450 617   termSize: image.Point{20, 10},
    451 618   builder: func() *Builder {
    skipped 33 lines
    485 652   },
    486 653   },
    487 654   {
    488  - desc: "two equal columns",
     655 + desc: "two equal columns with options, fixed size",
    489 656   termSize: image.Point{20, 10},
    490 657   builder: func() *Builder {
    491 658   b := New()
    492  - b.Add(ColWidthPerc(50, Widget(mirror())))
    493  - b.Add(ColWidthPerc(50, Widget(mirror())))
     659 + b.Add(ColWidthFixedWithOpts(
     660 + 10,
     661 + []container.Option{
     662 + container.Border(linestyle.Double),
     663 + },
     664 + Widget(mirror()),
     665 + ))
     666 + b.Add(ColWidthFixedWithOpts(
     667 + 10,
     668 + []container.Option{
     669 + container.Border(linestyle.Double),
     670 + },
     671 + Widget(mirror()),
     672 + ))
    494 673   return b
    495 674   }(),
    496 675   want: func(size image.Point) *faketerm.Terminal {
    497 676   ft := faketerm.MustNew(size)
     677 + 
    498 678   left, right := mustVSplit(ft.Area(), 50)
    499  - fakewidget.MustDraw(ft, testcanvas.MustNew(left), &widgetapi.Meta{}, widgetapi.Options{})
    500  - fakewidget.MustDraw(ft, testcanvas.MustNew(right), &widgetapi.Meta{}, widgetapi.Options{})
     679 + leftCvs := testcanvas.MustNew(left)
     680 + rightCvs := testcanvas.MustNew(right)
     681 + testdraw.MustBorder(leftCvs, leftCvs.Area(), draw.BorderLineStyle(linestyle.Double))
     682 + testdraw.MustBorder(rightCvs, rightCvs.Area(), draw.BorderLineStyle(linestyle.Double))
     683 + testcanvas.MustApply(leftCvs, ft)
     684 + testcanvas.MustApply(rightCvs, ft)
     685 + 
     686 + leftWidget := testcanvas.MustNew(area.ExcludeBorder(left))
     687 + rightWidget := testcanvas.MustNew(area.ExcludeBorder(right))
     688 + fakewidget.MustDraw(ft, leftWidget, &widgetapi.Meta{}, widgetapi.Options{})
     689 + fakewidget.MustDraw(ft, rightWidget, &widgetapi.Meta{}, widgetapi.Options{})
    501 690   return ft
    502 691   },
    503 692   },
    skipped 4 lines
    508 697   b := New()
    509 698   b.Add(ColWidthPerc(20, Widget(mirror())))
    510 699   b.Add(ColWidthPerc(80, Widget(mirror())))
     700 + return b
     701 + }(),
     702 + want: func(size image.Point) *faketerm.Terminal {
     703 + ft := faketerm.MustNew(size)
     704 + left, right := mustVSplit(ft.Area(), 20)
     705 + fakewidget.MustDraw(ft, testcanvas.MustNew(left), &widgetapi.Meta{}, widgetapi.Options{})
     706 + fakewidget.MustDraw(ft, testcanvas.MustNew(right), &widgetapi.Meta{}, widgetapi.Options{})
     707 + return ft
     708 + },
     709 + },
     710 + {
     711 + desc: "two unequal columns, fixed size",
     712 + termSize: image.Point{40, 10},
     713 + builder: func() *Builder {
     714 + b := New()
     715 + b.Add(ColWidthFixed(8, Widget(mirror())))
     716 + b.Add(ColWidthFixed(32, Widget(mirror())))
    511 717   return b
    512 718   }(),
    513 719   want: func(size image.Point) *faketerm.Terminal {
    skipped 46 lines
    560 766   20,
    561 767   ColWidthPerc(20, Widget(mirror())),
    562 768   ColWidthPerc(80, Widget(mirror())),
     769 + ),
     770 + RowHeightPerc(
     771 + 80,
     772 + ColWidthPerc(80, Widget(mirror())),
     773 + ColWidthPerc(20, Widget(mirror())),
     774 + ),
     775 + )
     776 + return b
     777 + }(),
     778 + want: func(size image.Point) *faketerm.Terminal {
     779 + ft := faketerm.MustNew(size)
     780 + top, bot := mustHSplit(ft.Area(), 20)
     781 + 
     782 + topLeft, topRight := mustVSplit(top, 20)
     783 + botLeft, botRight := mustVSplit(bot, 80)
     784 + fakewidget.MustDraw(ft, testcanvas.MustNew(topLeft), &widgetapi.Meta{}, widgetapi.Options{})
     785 + fakewidget.MustDraw(ft, testcanvas.MustNew(topRight), &widgetapi.Meta{}, widgetapi.Options{})
     786 + fakewidget.MustDraw(ft, testcanvas.MustNew(botLeft), &widgetapi.Meta{}, widgetapi.Options{})
     787 + fakewidget.MustDraw(ft, testcanvas.MustNew(botRight), &widgetapi.Meta{}, widgetapi.Options{})
     788 + return ft
     789 + },
     790 + },
     791 + {
     792 + desc: "rows with columns (unequal), fixed and relative sizes mixed",
     793 + termSize: image.Point{40, 20},
     794 + builder: func() *Builder {
     795 + b := New()
     796 + b.Add(
     797 + RowHeightFixed(
     798 + 4,
     799 + ColWidthFixed(8, Widget(mirror())),
     800 + ColWidthFixed(32, Widget(mirror())),
    563 801   ),
    564 802   RowHeightPerc(
    565 803   80,
    skipped 234 lines
    800 1038   
    801 1039   gridOpts, err := tc.builder.Build()
    802 1040   if (err != nil) != tc.wantErr {
    803  - t.Errorf("tc.builder => unexpected error:%v, wantErr:%v", err, tc.wantErr)
     1041 + t.Errorf("tc.builder => unexpected error: %v, wantErr:%v", err, tc.wantErr)
    804 1042   }
    805 1043   if err != nil {
    806 1044   return
    skipped 27 lines
Please wait...
Page is in error, reload to recover