mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-25 13:48:50 +08:00
Merge branch 'devel' into text-input
This commit is contained in:
commit
afe70553e5
16
CHANGELOG.md
16
CHANGELOG.md
@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
executed.
|
||||
- The SegmentDisplay widget now has a method that returns the observed character
|
||||
capacity the last time Draw was called.
|
||||
- The grid.Builder API now allows users to specify options for intermediate
|
||||
containers, i.e. containers that don't have widgets, but represent rows and
|
||||
columns.
|
||||
- Line chart widget now allows `math.NaN` values to represent "no value" (values
|
||||
that will not be rendered) in the values slice.
|
||||
|
||||
#### Breaking API changes
|
||||
|
||||
@ -82,10 +87,10 @@ identifiers shouldn't be used externally.
|
||||
- The draw.LineStyle enum was refactored into its own package
|
||||
linestyle.LineStyle. Users will have to replace:
|
||||
|
||||
- draw.LineStyleNone -> linestyle.None
|
||||
- draw.LineStyleLight -> linestyle.Light
|
||||
- draw.LineStyleDouble -> linestyle.Double
|
||||
- draw.LineStyleRound -> linestyle.Round
|
||||
- draw.LineStyleNone -> linestyle.None
|
||||
- draw.LineStyleLight -> linestyle.Light
|
||||
- draw.LineStyleDouble -> linestyle.Double
|
||||
- draw.LineStyleRound -> linestyle.Round
|
||||
|
||||
## [0.7.0] - 24-Feb-2019
|
||||
|
||||
@ -119,7 +124,6 @@ identifiers shouldn't be used externally.
|
||||
- The Text widget now has a Write option that atomically replaces the entire
|
||||
text content.
|
||||
|
||||
|
||||
#### Improvements to the infrastructure
|
||||
|
||||
- A function that draws text vertically.
|
||||
@ -248,7 +252,7 @@ identifiers shouldn't be used externally.
|
||||
- The Gauge widget.
|
||||
- The Text widget.
|
||||
|
||||
[Unreleased]: https://github.com/mum4k/termdash/compare/v0.8.0...devel
|
||||
[unreleased]: https://github.com/mum4k/termdash/compare/v0.8.0...devel
|
||||
[0.8.0]: https://github.com/mum4k/termdash/compare/v0.7.2...v0.8.0
|
||||
[0.7.2]: https://github.com/mum4k/termdash/compare/v0.7.1...v0.7.2
|
||||
[0.7.1]: https://github.com/mum4k/termdash/compare/v0.7.0...v0.7.1
|
||||
|
@ -109,18 +109,19 @@ func build(elems []Element, parentHeightPerc, parentWidthPerc int) []container.O
|
||||
|
||||
switch e := elem.(type) {
|
||||
case *row:
|
||||
|
||||
if len(elems) > 0 {
|
||||
perc := innerPerc(e.heightPerc, parentHeightPerc)
|
||||
childHeightPerc := parentHeightPerc - e.heightPerc
|
||||
return []container.Option{
|
||||
container.SplitHorizontal(
|
||||
container.Top(build(e.subElem, 100, parentWidthPerc)...),
|
||||
container.Top(append(e.cOpts, build(e.subElem, 100, parentWidthPerc)...)...),
|
||||
container.Bottom(build(elems, childHeightPerc, parentWidthPerc)...),
|
||||
container.SplitPercent(perc),
|
||||
),
|
||||
}
|
||||
}
|
||||
return build(e.subElem, 100, parentWidthPerc)
|
||||
return append(e.cOpts, build(e.subElem, 100, parentWidthPerc)...)
|
||||
|
||||
case *col:
|
||||
if len(elems) > 0 {
|
||||
@ -128,13 +129,13 @@ func build(elems []Element, parentHeightPerc, parentWidthPerc int) []container.O
|
||||
childWidthPerc := parentWidthPerc - e.widthPerc
|
||||
return []container.Option{
|
||||
container.SplitVertical(
|
||||
container.Left(build(e.subElem, parentHeightPerc, 100)...),
|
||||
container.Left(append(e.cOpts, build(e.subElem, parentHeightPerc, 100)...)...),
|
||||
container.Right(build(elems, parentHeightPerc, childWidthPerc)...),
|
||||
container.SplitPercent(perc),
|
||||
),
|
||||
}
|
||||
}
|
||||
return build(e.subElem, parentHeightPerc, 100)
|
||||
return append(e.cOpts, build(e.subElem, parentHeightPerc, 100)...)
|
||||
|
||||
case *widget:
|
||||
opts := e.cOpts
|
||||
@ -186,6 +187,9 @@ type row struct {
|
||||
|
||||
// subElem are the sub Rows or Columns or a single widget.
|
||||
subElem []Element
|
||||
|
||||
// cOpts are the options for the row's container.
|
||||
cOpts []container.Option
|
||||
}
|
||||
|
||||
// isElement implements Element.isElement.
|
||||
@ -204,6 +208,9 @@ type col struct {
|
||||
|
||||
// subElem are the sub Rows or Columns or a single widget.
|
||||
subElem []Element
|
||||
|
||||
// cOpts are the options for the column's container.
|
||||
cOpts []container.Option
|
||||
}
|
||||
|
||||
// isElement implements Element.isElement.
|
||||
@ -244,6 +251,16 @@ func RowHeightPerc(heightPerc int, subElements ...Element) Element {
|
||||
}
|
||||
}
|
||||
|
||||
// RowHeightPercWithOpts is like RowHeightPerc, but also allows to apply
|
||||
// additional options to the container that represents the row.
|
||||
func RowHeightPercWithOpts(heightPerc int, cOpts []container.Option, subElements ...Element) Element {
|
||||
return &row{
|
||||
heightPerc: heightPerc,
|
||||
subElem: subElements,
|
||||
cOpts: cOpts,
|
||||
}
|
||||
}
|
||||
|
||||
// ColWidthPerc creates a column of the specified width.
|
||||
// The width is supplied as width percentage of the parent element.
|
||||
// The sum of all widths at the same level cannot be larger than 100%. If it
|
||||
@ -257,6 +274,16 @@ func ColWidthPerc(widthPerc int, subElements ...Element) Element {
|
||||
}
|
||||
}
|
||||
|
||||
// ColWidthPercWithOpts is like ColWidthPerc, but also allows to apply
|
||||
// additional options to the container that represents the column.
|
||||
func ColWidthPercWithOpts(widthPerc int, cOpts []container.Option, subElements ...Element) Element {
|
||||
return &col{
|
||||
widthPerc: widthPerc,
|
||||
subElem: subElements,
|
||||
cOpts: cOpts,
|
||||
}
|
||||
}
|
||||
|
||||
// Widget adds a widget into the Row or Column.
|
||||
// The options will be applied to the container that directly holds this
|
||||
// widget.
|
||||
|
@ -389,6 +389,45 @@ func TestBuilder(t *testing.T) {
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "two equal rows with options",
|
||||
termSize: image.Point{10, 10},
|
||||
builder: func() *Builder {
|
||||
b := New()
|
||||
b.Add(RowHeightPercWithOpts(
|
||||
50,
|
||||
[]container.Option{
|
||||
container.Border(linestyle.Double),
|
||||
},
|
||||
Widget(mirror()),
|
||||
))
|
||||
b.Add(RowHeightPercWithOpts(
|
||||
50,
|
||||
[]container.Option{
|
||||
container.Border(linestyle.Double),
|
||||
},
|
||||
Widget(mirror()),
|
||||
))
|
||||
return b
|
||||
}(),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
|
||||
top, bot := mustHSplit(ft.Area(), 50)
|
||||
topCvs := testcanvas.MustNew(top)
|
||||
botCvs := testcanvas.MustNew(bot)
|
||||
testdraw.MustBorder(topCvs, topCvs.Area(), draw.BorderLineStyle(linestyle.Double))
|
||||
testdraw.MustBorder(botCvs, botCvs.Area(), draw.BorderLineStyle(linestyle.Double))
|
||||
testcanvas.MustApply(topCvs, ft)
|
||||
testcanvas.MustApply(botCvs, ft)
|
||||
|
||||
topWidget := testcanvas.MustNew(area.ExcludeBorder(top))
|
||||
botWidget := testcanvas.MustNew(area.ExcludeBorder(bot))
|
||||
fakewidget.MustDraw(ft, topWidget, &widgetapi.Meta{}, widgetapi.Options{})
|
||||
fakewidget.MustDraw(ft, botWidget, &widgetapi.Meta{}, widgetapi.Options{})
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "two unequal rows",
|
||||
termSize: image.Point{10, 10},
|
||||
@ -406,6 +445,45 @@ func TestBuilder(t *testing.T) {
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "two equal columns with options",
|
||||
termSize: image.Point{20, 10},
|
||||
builder: func() *Builder {
|
||||
b := New()
|
||||
b.Add(ColWidthPercWithOpts(
|
||||
50,
|
||||
[]container.Option{
|
||||
container.Border(linestyle.Double),
|
||||
},
|
||||
Widget(mirror()),
|
||||
))
|
||||
b.Add(ColWidthPercWithOpts(
|
||||
50,
|
||||
[]container.Option{
|
||||
container.Border(linestyle.Double),
|
||||
},
|
||||
Widget(mirror()),
|
||||
))
|
||||
return b
|
||||
}(),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
|
||||
left, right := mustVSplit(ft.Area(), 50)
|
||||
leftCvs := testcanvas.MustNew(left)
|
||||
rightCvs := testcanvas.MustNew(right)
|
||||
testdraw.MustBorder(leftCvs, leftCvs.Area(), draw.BorderLineStyle(linestyle.Double))
|
||||
testdraw.MustBorder(rightCvs, rightCvs.Area(), draw.BorderLineStyle(linestyle.Double))
|
||||
testcanvas.MustApply(leftCvs, ft)
|
||||
testcanvas.MustApply(rightCvs, ft)
|
||||
|
||||
leftWidget := testcanvas.MustNew(area.ExcludeBorder(left))
|
||||
rightWidget := testcanvas.MustNew(area.ExcludeBorder(right))
|
||||
fakewidget.MustDraw(ft, leftWidget, &widgetapi.Meta{}, widgetapi.Options{})
|
||||
fakewidget.MustDraw(ft, rightWidget, &widgetapi.Meta{}, widgetapi.Options{})
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "two equal columns",
|
||||
termSize: image.Point{20, 10},
|
||||
|
@ -109,14 +109,21 @@ func Round(x float64) float64 {
|
||||
|
||||
// MinMax returns the smallest and the largest value among the provided values.
|
||||
// Returns (0, 0) if there are no values.
|
||||
// Ignores NaN values. Allowing NaN values could lead to a corner case where all
|
||||
// values can be NaN, in this case the function will return NaN as min and max.
|
||||
func MinMax(values []float64) (min, max float64) {
|
||||
if len(values) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
min = math.MaxFloat64
|
||||
max = -1 * math.MaxFloat64
|
||||
|
||||
allNaN := true
|
||||
for _, v := range values {
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
allNaN = false
|
||||
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
@ -124,6 +131,11 @@ func MinMax(values []float64) (min, max float64) {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
|
||||
if allNaN {
|
||||
return math.NaN(), math.NaN()
|
||||
}
|
||||
|
||||
return min, max
|
||||
}
|
||||
|
||||
|
@ -215,13 +215,28 @@ func TestMinMax(t *testing.T) {
|
||||
wantMin: -11.3,
|
||||
wantMax: 22.5,
|
||||
},
|
||||
{
|
||||
desc: "min and max among negative, positive, zero and NaN values",
|
||||
values: []float64{1.1, 0, 1.3, math.NaN(), -11.3, 22.5},
|
||||
wantMin: -11.3,
|
||||
wantMax: 22.5,
|
||||
},
|
||||
{
|
||||
desc: "all NaN values",
|
||||
values: []float64{math.NaN(), math.NaN(), math.NaN(), math.NaN()},
|
||||
wantMin: math.NaN(),
|
||||
wantMax: math.NaN(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
gotMin, gotMax := MinMax(tc.values)
|
||||
if gotMin != tc.wantMin || gotMax != tc.wantMax {
|
||||
t.Errorf("MinMax => (%v, %v), want (%v, %v)", gotMin, gotMax, tc.wantMin, tc.wantMax)
|
||||
if diff := pretty.Compare(tc.wantMin, gotMin); diff != "" {
|
||||
t.Errorf("MinMax => unexpected min, diff (-want, +got):\n %s", diff)
|
||||
}
|
||||
if diff := pretty.Compare(tc.wantMax, gotMax); diff != "" {
|
||||
t.Errorf("MinMax => unexpected max, diff (-want, +got):\n %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -166,16 +166,18 @@ func gridLayout(w *widgets, lt layoutType) ([]container.Option, error) {
|
||||
container.BorderTitle("A rolling text"),
|
||||
),
|
||||
),
|
||||
grid.RowHeightPerc(50,
|
||||
grid.Widget(w.spGreen,
|
||||
container.Border(linestyle.Light),
|
||||
container.BorderTitle("Green SparkLine"),
|
||||
grid.ColWidthPerc(50,
|
||||
grid.RowHeightPerc(50,
|
||||
grid.Widget(w.spGreen,
|
||||
container.Border(linestyle.Light),
|
||||
container.BorderTitle("Green SparkLine"),
|
||||
),
|
||||
),
|
||||
),
|
||||
grid.RowHeightPerc(50,
|
||||
grid.Widget(w.spRed,
|
||||
container.Border(linestyle.Light),
|
||||
container.BorderTitle("Red SparkLine"),
|
||||
grid.RowHeightPerc(50,
|
||||
grid.Widget(w.spRed,
|
||||
container.Border(linestyle.Light),
|
||||
container.BorderTitle("Red SparkLine"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
@ -56,7 +57,7 @@ func newSeriesValues(values []float64) *seriesValues {
|
||||
v := make([]float64, len(values))
|
||||
copy(v, values)
|
||||
|
||||
min, max := numbers.MinMax(v)
|
||||
min, max := minMax(v)
|
||||
return &seriesValues{
|
||||
values: v,
|
||||
min: min,
|
||||
@ -175,8 +176,9 @@ func (lc *LineChart) yMinMax() (float64, float64) {
|
||||
maximums = append(maximums, lc.opts.yAxisCustomScale.max)
|
||||
}
|
||||
|
||||
min, _ := numbers.MinMax(minimums)
|
||||
_, max := numbers.MinMax(maximums)
|
||||
min, _ := minMax(minimums)
|
||||
_, max := minMax(maximums)
|
||||
|
||||
return min, max
|
||||
}
|
||||
|
||||
@ -196,6 +198,8 @@ func (lc *LineChart) ValueCapacity() int {
|
||||
|
||||
// Series sets the values that should be displayed as the line chart with the
|
||||
// provided label.
|
||||
// The values that should not be displayed on the line chart should be represented
|
||||
// as math.NaN values on the values slice.
|
||||
// Subsequent calls with the same label replace any previously provided values.
|
||||
func (lc *LineChart) Series(label string, values []float64, opts ...SeriesOption) error {
|
||||
if label == "" {
|
||||
@ -364,6 +368,7 @@ func (lc *LineChart) graphAr(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YDe
|
||||
// drawSeries draws the graph representing the stored series.
|
||||
// Returns XDetails that might be adjusted to not start at zero value if some
|
||||
// of the series didn't fit the graphs and XAxisUnscaled was provided.
|
||||
// If the series has NaN values they will be ignored and not draw on the graph.
|
||||
func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YDetails) (*axes.XDetails, error) {
|
||||
graphAr := lc.graphAr(cvs, xd, yd)
|
||||
bc, err := braille.New(graphAr)
|
||||
@ -406,7 +411,14 @@ func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.
|
||||
|
||||
var prev float64
|
||||
for i := 1; i < len(sv.values); i++ {
|
||||
v := sv.values[i]
|
||||
prev = sv.values[i-1]
|
||||
|
||||
// Skip the values that are missing.
|
||||
if math.IsNaN(v) || math.IsNaN(prev) {
|
||||
continue
|
||||
}
|
||||
|
||||
if i < int(xdZoomed.Scale.Min.Value)+1 || i > int(xdZoomed.Scale.Max.Value) {
|
||||
// Don't draw lines for values that aren't supposed to be visible.
|
||||
// These are either values outside of the current zoom or
|
||||
@ -429,7 +441,7 @@ func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure for series %v[%d] on scale %v, yd.Scale.ValueToPixel(%v) => %v", name, i-1, yd.Scale, prev, err)
|
||||
}
|
||||
v := sv.values[i]
|
||||
|
||||
endY, err := yd.Scale.ValueToPixel(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure for series %v[%d] on scale %v, yd.Scale.ValueToPixel(%v) => %v", name, i, yd.Scale, v, err)
|
||||
@ -519,3 +531,17 @@ func (lc *LineChart) maxXValue() int {
|
||||
}
|
||||
return maxLen - 1
|
||||
}
|
||||
|
||||
// minMax is a wrapper around numbers.MinMax that controls
|
||||
// the output if the values are NaN and sets defaults if it's
|
||||
// the case.
|
||||
func minMax(values []float64) (x, y float64) {
|
||||
min, max := numbers.MinMax(values)
|
||||
if math.IsNaN(min) {
|
||||
min = 0
|
||||
}
|
||||
if math.IsNaN(max) {
|
||||
max = 0
|
||||
}
|
||||
return min, max
|
||||
}
|
||||
|
@ -1523,6 +1523,119 @@ func TestLineChartDraws(t *testing.T) {
|
||||
testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{26, 0})
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "all NaN values",
|
||||
canvas: image.Rect(0, 0, 20, 10),
|
||||
writes: func(lc *LineChart) error {
|
||||
return lc.Series("first", []float64{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()})
|
||||
},
|
||||
wantCapacity: 36,
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
// Y and X axis.
|
||||
lines := []draw.HVLine{
|
||||
{Start: image.Point{1, 0}, End: image.Point{1, 8}},
|
||||
{Start: image.Point{1, 8}, End: image.Point{19, 8}},
|
||||
}
|
||||
testdraw.MustHVLines(c, lines)
|
||||
|
||||
// Value labels.
|
||||
testdraw.MustText(c, "0", image.Point{0, 7})
|
||||
testdraw.MustText(c, "0", image.Point{2, 9})
|
||||
testdraw.MustText(c, "1", image.Point{6, 9})
|
||||
testdraw.MustText(c, "2", image.Point{10, 9})
|
||||
testdraw.MustText(c, "3", image.Point{14, 9})
|
||||
testdraw.MustText(c, "4", image.Point{18, 9})
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "first and last NaN values",
|
||||
canvas: image.Rect(0, 0, 28, 10),
|
||||
writes: func(lc *LineChart) error {
|
||||
return lc.Series("first", []float64{math.NaN(), math.NaN(), 100, 150, math.NaN()})
|
||||
},
|
||||
wantCapacity: 44,
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
// Y and X axis.
|
||||
lines := []draw.HVLine{
|
||||
{Start: image.Point{5, 0}, End: image.Point{5, 8}},
|
||||
{Start: image.Point{5, 8}, End: image.Point{27, 8}},
|
||||
}
|
||||
testdraw.MustHVLines(c, lines)
|
||||
|
||||
// Value labels.
|
||||
testdraw.MustText(c, "0", image.Point{4, 7})
|
||||
testdraw.MustText(c, "77.44", image.Point{0, 3})
|
||||
testdraw.MustText(c, "0", image.Point{6, 9})
|
||||
testdraw.MustText(c, "1", image.Point{11, 9})
|
||||
testdraw.MustText(c, "2", image.Point{16, 9})
|
||||
testdraw.MustText(c, "3", image.Point{22, 9})
|
||||
testdraw.MustText(c, "4", image.Point{27, 9})
|
||||
|
||||
graphAr := image.Rect(6, 0, 25, 8)
|
||||
bc := testbraille.MustNew(graphAr)
|
||||
testdraw.MustBrailleLine(bc, image.Point{21, 10}, image.Point{32, 0})
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "more values than capacity, X rescales with NaN values ignored",
|
||||
canvas: image.Rect(0, 0, 11, 10),
|
||||
writes: func(lc *LineChart) error {
|
||||
return lc.Series("first", []float64{0, 1, 2, 3, 4, 5, 6, 7, math.NaN(), math.NaN(), math.NaN(), math.NaN(), 12, 13, 14, 15, 16, 17, 18, 19})
|
||||
},
|
||||
wantCapacity: 12,
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
// Y and X axis.
|
||||
lines := []draw.HVLine{
|
||||
{Start: image.Point{4, 0}, End: image.Point{4, 8}},
|
||||
{Start: image.Point{4, 8}, End: image.Point{10, 8}},
|
||||
}
|
||||
testdraw.MustHVLines(c, lines)
|
||||
|
||||
// Value labels.
|
||||
testdraw.MustText(c, "0", image.Point{3, 7})
|
||||
testdraw.MustText(c, "9.92", image.Point{0, 3})
|
||||
testdraw.MustText(c, "0", image.Point{5, 9})
|
||||
testdraw.MustText(c, "14", image.Point{9, 9})
|
||||
|
||||
// Braille line.
|
||||
graphAr := image.Rect(5, 0, 11, 8)
|
||||
bc := testbraille.MustNew(graphAr)
|
||||
testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{1, 29})
|
||||
testdraw.MustBrailleLine(bc, image.Point{1, 29}, image.Point{1, 28})
|
||||
testdraw.MustBrailleLine(bc, image.Point{1, 28}, image.Point{2, 26})
|
||||
testdraw.MustBrailleLine(bc, image.Point{2, 26}, image.Point{2, 25})
|
||||
testdraw.MustBrailleLine(bc, image.Point{2, 25}, image.Point{3, 23})
|
||||
testdraw.MustBrailleLine(bc, image.Point{3, 23}, image.Point{3, 21})
|
||||
testdraw.MustBrailleLine(bc, image.Point{3, 21}, image.Point{4, 20})
|
||||
testdraw.MustBrailleLine(bc, image.Point{7, 12}, image.Point{8, 10})
|
||||
testdraw.MustBrailleLine(bc, image.Point{8, 10}, image.Point{8, 8})
|
||||
testdraw.MustBrailleLine(bc, image.Point{8, 8}, image.Point{9, 7})
|
||||
testdraw.MustBrailleLine(bc, image.Point{9, 7}, image.Point{9, 5})
|
||||
testdraw.MustBrailleLine(bc, image.Point{9, 5}, image.Point{10, 4})
|
||||
testdraw.MustBrailleLine(bc, image.Point{10, 4}, image.Point{10, 2})
|
||||
testdraw.MustBrailleLine(bc, image.Point{10, 2}, image.Point{11, 0})
|
||||
testbraille.MustCopyTo(bc, c)
|
||||
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user