1// Copyright 2019 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package donut
16
17// options.go contains configurable options for Donut.
18
19import (
20	"fmt"
21
22	"github.com/mum4k/termdash/align"
23	"github.com/mum4k/termdash/cell"
24)
25
26// Option is used to provide options.
27type Option interface {
28	// set sets the provided option.
29	set(*options)
30}
31
32// option implements Option.
33type option func(*options)
34
35// set implements Option.set.
36func (o option) set(opts *options) {
37	o(opts)
38}
39
40// options holds the provided options.
41type options struct {
42	donutHolePercent int
43	hideTextProgress bool
44
45	textCellOpts []cell.Option
46	cellOpts     []cell.Option
47
48	labelCellOpts []cell.Option
49	labelAlign    align.Horizontal
50	label         string
51
52	// The angle in degrees that represents 0 and 100% of the progress.
53	startAngle int
54	// The direction in which the donut completes as progress increases.
55	// Positive for counter-clockwise, negative for clockwise.
56	direction int
57}
58
59// validate validates the provided options.
60func (o *options) validate() error {
61	if min, max := 0, 100; o.donutHolePercent < min || o.donutHolePercent > max {
62		return fmt.Errorf("invalid donut hole percent %d, must be in range %d <= p <= %d", o.donutHolePercent, min, max)
63	}
64
65	if min, max := 0, 360; o.startAngle < min || o.startAngle >= max {
66		return fmt.Errorf("invalid start angle %d, must be in range %d <= angle < %d", o.startAngle, min, max)
67	}
68
69	return nil
70}
71
72// newOptions returns options with the default values set.
73func newOptions() *options {
74	return &options{
75		donutHolePercent: DefaultHolePercent,
76		startAngle:       DefaultStartAngle,
77		direction:        -1,
78		textCellOpts: []cell.Option{
79			cell.FgColor(cell.ColorDefault),
80			cell.BgColor(cell.ColorDefault),
81		},
82		labelAlign: DefaultLabelAlign,
83	}
84}
85
86// DefaultHolePercent is the default value for the HolePercent
87// option.
88const DefaultHolePercent = 35
89
90// HolePercent sets the size of the "hole" inside the donut as a
91// percentage of the donut's radius.
92// Setting this to zero disables the hole so that the donut will become just a
93// circle. Valid range is 0 <= p <= 100.
94func HolePercent(p int) Option {
95	return option(func(opts *options) {
96		opts.donutHolePercent = p
97	})
98}
99
100// ShowTextProgress configures the Gauge so that it also displays a text
101// enumerating the progress. This is the default behavior.
102// If the progress is set by a call to Percent(), the displayed text will show
103// the percentage, e.g. "50%". If the progress is set by a call to Absolute(),
104// the displayed text will those the absolute numbers, e.g. "5/10".
105//
106// The progress is only displayed if there is enough space for it in the middle
107// of the drawn donut.
108//
109// Providing this option also sets HolePercent to its default value.
110func ShowTextProgress() Option {
111	return option(func(opts *options) {
112		opts.hideTextProgress = false
113	})
114}
115
116// HideTextProgress disables the display of a text enumerating the progress.
117func HideTextProgress() Option {
118	return option(func(opts *options) {
119		opts.hideTextProgress = true
120	})
121}
122
123// TextCellOpts sets cell options on cells that contain the displayed text
124// progress.
125func TextCellOpts(cOpts ...cell.Option) Option {
126	return option(func(opts *options) {
127		opts.textCellOpts = cOpts
128	})
129}
130
131// CellOpts sets cell options on cells that contain the donut.
132func CellOpts(cOpts ...cell.Option) Option {
133	return option(func(opts *options) {
134		opts.cellOpts = cOpts
135	})
136}
137
138// DefaultStartAngle is the default value for the StartAngle option.
139const DefaultStartAngle = 90
140
141// StartAngle sets the starting angle in degrees, i.e. the point that will
142// represent both 0% and 100% of progress.
143// Valid values are in range 0 <= angle < 360.
144// Angles start at the X axis and grow counter-clockwise.
145func StartAngle(angle int) Option {
146	return option(func(opts *options) {
147		opts.startAngle = angle
148	})
149}
150
151// Clockwise sets the donut widget for a progression in the clockwise
152// direction. This is the default option.
153func Clockwise() Option {
154	return option(func(opts *options) {
155		opts.direction = -1
156	})
157}
158
159// CounterClockwise sets the donut widget for a progression in the counter-clockwise
160// direction.
161func CounterClockwise() Option {
162	return option(func(opts *options) {
163		opts.direction = 1
164	})
165}
166
167// Label sets a text label to be displayed under the donut.
168func 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.
176const DefaultLabelAlign = align.HorizontalCenter
177
178// LabelAlign sets the alignment of the label under the donut.
179func LabelAlign(la align.Horizontal) Option {
180	return option(func(opts *options) {
181		opts.labelAlign = la
182	})
183}
184