Projects STRLCPY termdash Commits 9a82474a
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■
    CHANGELOG.md
    skipped 9 lines
    10 10  ### Added
    11 11   
    12 12  - The `TextInput` widget, an input field allowing interactive text input.
     13 +- The `Donut` widget can now display an optional text label under the donut.
    13 14   
    14 15  ### Changed
    15 16   
    skipped 260 lines
  • ■ ■ ■ ■ ■ ■
    internal/area/area.go
    skipped 100 lines
    101 101   return left, right, nil
    102 102  }
    103 103   
     104 +// HSplitCells returns two new areas created by splitting the provided area
     105 +// after the specified amount of cells of its height. The number of cells must
     106 +// be a zero or a positive integer. Providing a zero returns top=image.ZR,
     107 +// bottom=area. Providing a number equal or larger to area's height returns
     108 +// top=area, bottom=image.ZR.
     109 +func HSplitCells(area image.Rectangle, cells int) (top image.Rectangle, bottom image.Rectangle, err error) {
     110 + if min := 0; cells < min {
     111 + return image.ZR, image.ZR, fmt.Errorf("invalid cells %d, must be a positive integer", cells)
     112 + }
     113 + if cells == 0 {
     114 + return image.ZR, area, nil
     115 + }
     116 + 
     117 + height := area.Dy()
     118 + if cells >= height {
     119 + return area, image.ZR, nil
     120 + }
     121 + 
     122 + top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+cells)
     123 + bottom = image.Rect(area.Min.X, area.Min.Y+cells, area.Max.X, area.Max.Y)
     124 + return top, bottom, nil
     125 +}
     126 + 
    104 127  // ExcludeBorder returns a new area created by subtracting a border around the
    105 128  // provided area. Return the zero area if there isn't enough space to exclude
    106 129  // the border.
    skipped 98 lines
  • ■ ■ ■ ■ ■ ■
    internal/area/area_test.go
    skipped 366 lines
    367 367   }
    368 368  }
    369 369   
     370 +func TestHSplitCells(t *testing.T) {
     371 + tests := []struct {
     372 + desc string
     373 + area image.Rectangle
     374 + cells int
     375 + wantTop image.Rectangle
     376 + wantBottom image.Rectangle
     377 + wantErr bool
     378 + }{
     379 + {
     380 + desc: "fails on negative cells",
     381 + area: image.Rect(1, 1, 2, 2),
     382 + cells: -1,
     383 + wantErr: true,
     384 + },
     385 + {
     386 + desc: "returns area as top on cells too large",
     387 + area: image.Rect(1, 1, 2, 2),
     388 + cells: 2,
     389 + wantTop: image.Rect(1, 1, 2, 2),
     390 + wantBottom: image.ZR,
     391 + },
     392 + {
     393 + desc: "returns area as top on cells equal area width",
     394 + area: image.Rect(1, 1, 2, 2),
     395 + cells: 1,
     396 + wantTop: image.Rect(1, 1, 2, 2),
     397 + wantBottom: image.ZR,
     398 + },
     399 + {
     400 + desc: "returns area as bottom on zero cells",
     401 + area: image.Rect(1, 1, 2, 2),
     402 + cells: 0,
     403 + wantBottom: image.Rect(1, 1, 2, 2),
     404 + wantTop: image.ZR,
     405 + },
     406 + {
     407 + desc: "zero area to begin with",
     408 + area: image.ZR,
     409 + cells: 0,
     410 + wantTop: image.ZR,
     411 + wantBottom: image.ZR,
     412 + },
     413 + {
     414 + desc: "splits area with even height",
     415 + area: image.Rect(1, 1, 3, 3),
     416 + cells: 1,
     417 + wantTop: image.Rect(1, 1, 3, 2),
     418 + wantBottom: image.Rect(1, 2, 3, 3),
     419 + },
     420 + {
     421 + desc: "splits area with odd width",
     422 + area: image.Rect(1, 1, 4, 4),
     423 + cells: 1,
     424 + wantTop: image.Rect(1, 1, 4, 2),
     425 + wantBottom: image.Rect(1, 2, 4, 4),
     426 + },
     427 + {
     428 + desc: "splits to unequal areas",
     429 + area: image.Rect(0, 0, 4, 4),
     430 + cells: 3,
     431 + wantTop: image.Rect(0, 0, 4, 3),
     432 + wantBottom: image.Rect(0, 3, 4, 4),
     433 + },
     434 + }
     435 + 
     436 + for _, tc := range tests {
     437 + t.Run(tc.desc, func(t *testing.T) {
     438 + gotTop, gotBottom, err := HSplitCells(tc.area, tc.cells)
     439 + if (err != nil) != tc.wantErr {
     440 + t.Errorf("HSplitCells => unexpected error:%v, wantErr:%v", err, tc.wantErr)
     441 + }
     442 + if err != nil {
     443 + return
     444 + }
     445 + if diff := pretty.Compare(tc.wantTop, gotTop); diff != "" {
     446 + t.Errorf("HSplitCells => left value unexpected diff (-want, +got):\n%s", diff)
     447 + }
     448 + if diff := pretty.Compare(tc.wantBottom, gotBottom); diff != "" {
     449 + t.Errorf("HSplitCells => right value unexpected diff (-want, +got):\n%s", diff)
     450 + }
     451 + })
     452 + }
     453 +}
     454 + 
    370 455  func TestExcludeBorder(t *testing.T) {
    371 456   tests := []struct {
    372 457   desc string
    skipped 417 lines
  • ■ ■ ■ ■ ■
    widgets/donut/donut.go
    skipped 24 lines
    25 25   
    26 26   "github.com/mum4k/termdash/align"
    27 27   "github.com/mum4k/termdash/internal/alignfor"
     28 + "github.com/mum4k/termdash/internal/area"
    28 29   "github.com/mum4k/termdash/internal/canvas"
    29 30   "github.com/mum4k/termdash/internal/canvas/braille"
    30 31   "github.com/mum4k/termdash/internal/draw"
    skipped 134 lines
    165 166  // drawText draws the text label showing the progress.
    166 167  // The text is only drawn if the radius of the donut "hole" is large enough to
    167 168  // accommodate it.
    168  -func (d *Donut) drawText(cvs *canvas.Canvas, mid image.Point, holeR int) error {
     169 +// The mid point addresses coordinates in pixels on a braille canvas.
     170 +// The donutAr is the cell area for the donut itself.
     171 +func (d *Donut) drawText(cvs *canvas.Canvas, donutAr image.Rectangle, mid image.Point, holeR int) error {
    169 172   cells, first := availableCells(mid, holeR)
    170 173   t := d.progressText()
    171 174   needCells := runewidth.StringWidth(t)
    skipped 1 lines
    173 176   return nil
    174 177   }
    175 178   
     179 + if donutAr.Min.Y > 0 {
     180 + // donutAr is what the braille canvas is created from, mid is relative
     181 + // to it.
     182 + // donutAr might have non-zero Y coordinate if we are displaying a text
     183 + // label.
     184 + first.Y += donutAr.Min.Y
     185 + }
    176 186   ar := image.Rect(first.X, first.Y, first.X+cells+2, first.Y+1)
    177 187   start, err := alignfor.Text(ar, t, align.HorizontalCenter, align.VerticalMiddle)
    178 188   if err != nil {
    skipped 5 lines
    184 194   return nil
    185 195  }
    186 196   
     197 +// drawLabel draws the text label in the area.
     198 +func (d *Donut) drawLabel(cvs *canvas.Canvas, labelAr image.Rectangle) error {
     199 + start, err := alignfor.Text(labelAr, d.opts.label, d.opts.labelAlign, align.VerticalMiddle)
     200 + if err != nil {
     201 + return err
     202 + }
     203 + if err := draw.Text(
     204 + cvs, d.opts.label, start,
     205 + draw.TextOverrunMode(draw.OverrunModeThreeDot),
     206 + draw.TextMaxX(labelAr.Max.X),
     207 + draw.TextCellOpts(d.opts.labelCellOpts...),
     208 + ); err != nil {
     209 + return err
     210 + }
     211 + return nil
     212 +}
     213 + 
    187 214  // Draw draws the Donut widget onto the canvas.
    188 215  // Implements widgetapi.Widget.Draw.
    189 216  func (d *Donut) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error {
    190 217   d.mu.Lock()
    191 218   defer d.mu.Unlock()
    192 219   
    193  - bc, err := braille.New(cvs.Area())
    194  - if err != nil {
    195  - return fmt.Errorf("braille.New => %v", err)
    196  - }
    197  - 
    198 220   startA, endA := startEndAngles(d.current, d.total, d.opts.startAngle, d.opts.direction)
    199 221   if startA == endA {
    200 222   // No progress recorded, so nothing to do.
    201 223   return nil
    202 224   }
    203 225   
     226 + var donutAr, labelAr image.Rectangle
     227 + if len(d.opts.label) > 0 {
     228 + d, l, err := donutAndLabel(cvs.Area())
     229 + if err != nil {
     230 + return err
     231 + }
     232 + donutAr = d
     233 + labelAr = l
     234 + 
     235 + } else {
     236 + donutAr = cvs.Area()
     237 + }
     238 + 
     239 + if donutAr.Dx() < minSize.X || donutAr.Dy() < minSize.Y {
     240 + // Reserving area for the label might have resulted in donutAr being
     241 + // too small.
     242 + return draw.ResizeNeeded(cvs)
     243 + }
     244 + 
     245 + bc, err := braille.New(donutAr)
     246 + if err != nil {
     247 + return fmt.Errorf("braille.New => %v", err)
     248 + }
     249 + 
    204 250   mid, r := midAndRadius(bc.Area())
    205 251   if err := draw.BrailleCircle(bc, mid, r,
    206 252   draw.BrailleCircleFilled(),
    skipped 17 lines
    224 270   }
    225 271   
    226 272   if !d.opts.hideTextProgress {
    227  - return d.drawText(cvs, mid, holeR)
     273 + if err := d.drawText(cvs, donutAr, mid, holeR); err != nil {
     274 + return err
     275 + }
     276 + }
     277 + 
     278 + if !labelAr.Empty() {
     279 + if err := d.drawLabel(cvs, labelAr); err != nil {
     280 + return err
     281 + }
    228 282   }
    229 283   return nil
    230 284  }
    skipped 8 lines
    239 293   return errors.New("the Donut widget doesn't support mouse events")
    240 294  }
    241 295   
     296 +// minSize is the smallest area we can draw donut on.
     297 +var minSize = image.Point{3, 3}
     298 + 
    242 299  // Options implements widgetapi.Widget.Options.
    243 300  func (d *Donut) Options() widgetapi.Options {
    244 301   return widgetapi.Options{
    skipped 2 lines
    247 304   Ratio: image.Point{braille.RowMult, braille.ColMult},
    248 305   
    249 306   // The smallest circle that "looks" like a circle on the canvas.
    250  - MinimumSize: image.Point{3, 3},
     307 + MinimumSize: minSize,
    251 308   WantKeyboard: widgetapi.KeyScopeNone,
    252 309   WantMouse: widgetapi.MouseScopeNone,
    253 310   }
    254 311  }
    255 312   
     313 +// donutAndLabel splits the canvas area into square area for the donut and an
     314 +// area under the donut for the text label.
     315 +func donutAndLabel(cvsAr image.Rectangle) (donAr, labelAr image.Rectangle, err error) {
     316 + height := cvsAr.Dy()
     317 + // One line for the text label at the bottom.
     318 + top, labelAr, err := area.HSplitCells(cvsAr, height-1)
     319 + if err != nil {
     320 + return image.ZR, image.ZR, err
     321 + }
     322 + 
     323 + // Remove one line from the top too so the donut area remains square.
     324 + // When using braille, this effectively removes 4 pixels from both the top
     325 + // and the bottom. See braille.RowMult.
     326 + donAr, err = area.Shrink(top, 1, 0, 0, 0)
     327 + if err != nil {
     328 + return image.ZR, image.ZR, err
     329 + }
     330 + return donAr, labelAr, nil
     331 +}
     332 + 
  • ■ ■ ■ ■ ■ ■
    widgets/donut/donut_test.go
    skipped 18 lines
    19 19   "testing"
    20 20   
    21 21   "github.com/kylelemons/godebug/pretty"
     22 + "github.com/mum4k/termdash/align"
    22 23   "github.com/mum4k/termdash/cell"
    23 24   "github.com/mum4k/termdash/internal/canvas"
    24 25   "github.com/mum4k/termdash/internal/canvas/braille/testbraille"
    skipped 118 lines
    143 144   update: func(d *Donut) error {
    144 145   return d.Percent(100)
    145 146   },
    146  - canvas: image.Rect(0, 0, 1, 1),
    147  - wantDrawErr: true,
     147 + canvas: image.Rect(0, 0, 1, 1),
     148 + want: func(size image.Point) *faketerm.Terminal {
     149 + ft := faketerm.MustNew(size)
     150 + cvs := testcanvas.MustNew(ft.Area())
     151 + testdraw.MustResizeNeeded(cvs)
     152 + testcanvas.MustApply(cvs, ft)
     153 + return ft
     154 + },
    148 155   },
    149 156   {
    150 157   desc: "smallest valid donut, 100% progress",
    skipped 12 lines
    163 170   },
    164 171   },
    165 172   {
     173 + desc: "adding label to the smallest canvas makes it too small",
     174 + opts: []Option{
     175 + Label("hi"),
     176 + },
     177 + canvas: image.Rect(0, 0, 3, 3),
     178 + update: func(d *Donut) error {
     179 + return d.Percent(100)
     180 + },
     181 + want: func(size image.Point) *faketerm.Terminal {
     182 + ft := faketerm.MustNew(size)
     183 + cvs := testcanvas.MustNew(ft.Area())
     184 + testdraw.MustResizeNeeded(cvs)
     185 + testcanvas.MustApply(cvs, ft)
     186 + return ft
     187 + },
     188 + },
     189 + {
    166 190   desc: "New sets donut options",
    167 191   opts: []Option{
    168 192   CellOpts(
    skipped 132 lines
    301 325   },
    302 326   },
    303 327   {
     328 + desc: "draws hole and label",
     329 + opts: []Option{
     330 + Label("hi"),
     331 + },
     332 + canvas: image.Rect(0, 0, 6, 6),
     333 + update: func(d *Donut) error {
     334 + return d.Percent(100, HolePercent(50))
     335 + },
     336 + want: func(size image.Point) *faketerm.Terminal {
     337 + ft := faketerm.MustNew(size)
     338 + c := testcanvas.MustNew(ft.Area())
     339 + bc := testbraille.MustNew(ft.Area())
     340 + 
     341 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5, draw.BrailleCircleFilled())
     342 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 3,
     343 + draw.BrailleCircleFilled(),
     344 + draw.BrailleCircleClearPixels(),
     345 + )
     346 + testbraille.MustCopyTo(bc, c)
     347 + 
     348 + testdraw.MustText(c, "hi", image.Point{2, 5})
     349 + 
     350 + testcanvas.MustApply(c, ft)
     351 + return ft
     352 + },
     353 + },
     354 + {
    304 355   desc: "hole as large as donut",
    305 356   canvas: image.Rect(0, 0, 6, 6),
    306 357   update: func(d *Donut) error {
    skipped 265 lines
    572 623   testbraille.MustCopyTo(bc, c)
    573 624   
    574 625   testdraw.MustText(c, "1/10", image.Point{2, 4})
     626 + 
     627 + testcanvas.MustApply(c, ft)
     628 + return ft
     629 + },
     630 + },
     631 + {
     632 + desc: "displays text label under the donut",
     633 + opts: []Option{
     634 + Label("hi"),
     635 + },
     636 + canvas: image.Rect(0, 0, 7, 7),
     637 + update: func(d *Donut) error {
     638 + return d.Percent(100, HolePercent(80))
     639 + },
     640 + want: func(size image.Point) *faketerm.Terminal {
     641 + ft := faketerm.MustNew(size)
     642 + c := testcanvas.MustNew(ft.Area())
     643 + bc := testbraille.MustNew(c.Area())
     644 + 
     645 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
     646 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
     647 + draw.BrailleCircleFilled(),
     648 + draw.BrailleCircleClearPixels(),
     649 + )
     650 + testbraille.MustCopyTo(bc, c)
     651 + 
     652 + testdraw.MustText(c, "100%", image.Point{2, 3})
     653 + 
     654 + testdraw.MustText(c, "hi", image.Point{2, 6})
     655 + 
     656 + testcanvas.MustApply(c, ft)
     657 + return ft
     658 + },
     659 + },
     660 + {
     661 + desc: "aligns text label center with option",
     662 + opts: []Option{
     663 + Label("hi"),
     664 + LabelAlign(align.HorizontalCenter),
     665 + },
     666 + canvas: image.Rect(0, 0, 7, 7),
     667 + update: func(d *Donut) error {
     668 + return d.Percent(100, HolePercent(80))
     669 + },
     670 + want: func(size image.Point) *faketerm.Terminal {
     671 + ft := faketerm.MustNew(size)
     672 + c := testcanvas.MustNew(ft.Area())
     673 + bc := testbraille.MustNew(c.Area())
     674 + 
     675 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
     676 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
     677 + draw.BrailleCircleFilled(),
     678 + draw.BrailleCircleClearPixels(),
     679 + )
     680 + testbraille.MustCopyTo(bc, c)
     681 + 
     682 + testdraw.MustText(c, "100%", image.Point{2, 3})
     683 + 
     684 + testdraw.MustText(c, "hi", image.Point{2, 6})
     685 + 
     686 + testcanvas.MustApply(c, ft)
     687 + return ft
     688 + },
     689 + },
     690 + {
     691 + desc: "aligns text label left",
     692 + opts: []Option{
     693 + Label("hi"),
     694 + LabelAlign(align.HorizontalLeft),
     695 + },
     696 + canvas: image.Rect(0, 0, 7, 7),
     697 + update: func(d *Donut) error {
     698 + return d.Percent(100, HolePercent(80))
     699 + },
     700 + want: func(size image.Point) *faketerm.Terminal {
     701 + ft := faketerm.MustNew(size)
     702 + c := testcanvas.MustNew(ft.Area())
     703 + bc := testbraille.MustNew(c.Area())
     704 + 
     705 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
     706 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
     707 + draw.BrailleCircleFilled(),
     708 + draw.BrailleCircleClearPixels(),
     709 + )
     710 + testbraille.MustCopyTo(bc, c)
     711 + 
     712 + testdraw.MustText(c, "100%", image.Point{2, 3})
     713 + 
     714 + testdraw.MustText(c, "hi", image.Point{0, 6})
     715 + 
     716 + testcanvas.MustApply(c, ft)
     717 + return ft
     718 + },
     719 + },
     720 + {
     721 + desc: "aligns text label right",
     722 + opts: []Option{
     723 + Label("hi"),
     724 + LabelAlign(align.HorizontalRight),
     725 + },
     726 + canvas: image.Rect(0, 0, 7, 7),
     727 + update: func(d *Donut) error {
     728 + return d.Percent(100, HolePercent(80))
     729 + },
     730 + want: func(size image.Point) *faketerm.Terminal {
     731 + ft := faketerm.MustNew(size)
     732 + c := testcanvas.MustNew(ft.Area())
     733 + bc := testbraille.MustNew(c.Area())
     734 + 
     735 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
     736 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
     737 + draw.BrailleCircleFilled(),
     738 + draw.BrailleCircleClearPixels(),
     739 + )
     740 + testbraille.MustCopyTo(bc, c)
     741 + 
     742 + testdraw.MustText(c, "100%", image.Point{2, 3})
     743 + 
     744 + testdraw.MustText(c, "hi", image.Point{5, 6})
     745 + 
     746 + testcanvas.MustApply(c, ft)
     747 + return ft
     748 + },
     749 + },
     750 + {
     751 + desc: "sets cell options on text label",
     752 + opts: []Option{
     753 + Label(
     754 + "hi",
     755 + cell.FgColor(cell.ColorRed),
     756 + cell.BgColor(cell.ColorBlue),
     757 + ),
     758 + },
     759 + canvas: image.Rect(0, 0, 7, 7),
     760 + update: func(d *Donut) error {
     761 + return d.Percent(100, HolePercent(80))
     762 + },
     763 + want: func(size image.Point) *faketerm.Terminal {
     764 + ft := faketerm.MustNew(size)
     765 + c := testcanvas.MustNew(ft.Area())
     766 + bc := testbraille.MustNew(c.Area())
     767 + 
     768 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
     769 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
     770 + draw.BrailleCircleFilled(),
     771 + draw.BrailleCircleClearPixels(),
     772 + )
     773 + testbraille.MustCopyTo(bc, c)
     774 + 
     775 + testdraw.MustText(c, "100%", image.Point{2, 3})
     776 + 
     777 + testdraw.MustText(
     778 + c,
     779 + "hi",
     780 + image.Point{2, 6},
     781 + draw.TextCellOpts(
     782 + cell.FgColor(cell.ColorRed),
     783 + cell.BgColor(cell.ColorBlue),
     784 + ),
     785 + )
     786 + 
     787 + testcanvas.MustApply(c, ft)
     788 + return ft
     789 + },
     790 + },
     791 + {
     792 + desc: "text label too long, gets trimmed",
     793 + opts: []Option{
     794 + Label(
     795 + "hello world",
     796 + ),
     797 + },
     798 + canvas: image.Rect(0, 0, 7, 7),
     799 + update: func(d *Donut) error {
     800 + return d.Percent(100, HolePercent(80))
     801 + },
     802 + want: func(size image.Point) *faketerm.Terminal {
     803 + ft := faketerm.MustNew(size)
     804 + c := testcanvas.MustNew(ft.Area())
     805 + bc := testbraille.MustNew(c.Area())
     806 + 
     807 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 6, draw.BrailleCircleFilled())
     808 + testdraw.MustBrailleCircle(bc, image.Point{6, 13}, 5,
     809 + draw.BrailleCircleFilled(),
     810 + draw.BrailleCircleClearPixels(),
     811 + )
     812 + testbraille.MustCopyTo(bc, c)
     813 + 
     814 + testdraw.MustText(c, "100%", image.Point{2, 3})
     815 + 
     816 + testdraw.MustText(c, "hello …", image.Point{0, 6})
    575 817   
    576 818   testcanvas.MustApply(c, ft)
    577 819   return ft
    skipped 100 lines
  • ■ ■ ■ ■
    widgets/donut/donutdemo/donutdemo.go
    skipped 85 lines
    86 86   defer t.Close()
    87 87   
    88 88   ctx, cancel := context.WithCancel(context.Background())
    89  - green, err := donut.New(donut.CellOpts(cell.FgColor(cell.ColorGreen)))
     89 + green, err := donut.New(
     90 + donut.CellOpts(cell.FgColor(cell.ColorGreen)),
     91 + donut.Label("text label", cell.FgColor(cell.ColorGreen)),
     92 + )
    90 93   if err != nil {
    91 94   panic(err)
    92 95   }
    skipped 54 lines
  • ■ ■ ■ ■ ■ ■
    widgets/donut/options.go
    skipped 18 lines
    19 19  import (
    20 20   "fmt"
    21 21   
     22 + "github.com/mum4k/termdash/align"
    22 23   "github.com/mum4k/termdash/cell"
    23 24  )
    24 25   
    skipped 18 lines
    43 44   
    44 45   textCellOpts []cell.Option
    45 46   cellOpts []cell.Option
     47 + 
     48 + labelCellOpts []cell.Option
     49 + labelAlign align.Horizontal
     50 + label string
    46 51   
    47 52   // The angle in degrees that represents 0 and 100% of the progress.
    48 53   startAngle int
    skipped 25 lines
    74 79   cell.FgColor(cell.ColorDefault),
    75 80   cell.BgColor(cell.ColorDefault),
    76 81   },
     82 + labelAlign: DefaultLabelAlign,
    77 83   }
    78 84  }
    79 85   
    skipped 78 lines
    158 164   })
    159 165  }
    160 166   
     167 +// Label sets a text label to be displayed under the donut.
     168 +func Label(text string, cOpts ...cell.Option) Option {
     169 + return option(func(opts *options) {
     170 + opts.label = text
     171 + opts.labelCellOpts = cOpts
     172 + })
     173 +}
     174 + 
     175 +// DefaultLabelAlign is the default value for the LabelAlign option.
     176 +const DefaultLabelAlign = align.HorizontalCenter
     177 + 
     178 +// LabelAlign sets the alignment of the label under the donut.
     179 +func LabelAlign(la align.Horizontal) Option {
     180 + return option(func(opts *options) {
     181 + opts.labelAlign = la
     182 + })
     183 +}
     184 + 
Please wait...
Page is in error, reload to recover