diff --git a/CHANGELOG.md b/CHANGELOG.md index 2713e0e..29ce85d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - the `button` widget allows users to specify multiple trigger keys. - the `button` widget can now be drawn without the shadow and the pressing the animation. +- the `button` widget can now be drawn without horizontal padding around its + text. ## [0.13.0] - 17-Nov-2020 diff --git a/widgets/button/button.go b/widgets/button/button.go index d66c880..9e9fa22 100644 --- a/widgets/button/button.go +++ b/widgets/button/button.go @@ -143,7 +143,8 @@ func (b *Button) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error { return err } - textAr := image.Rect(buttonAr.Min.X+1, buttonAr.Min.Y, buttonAr.Dx()-1, buttonAr.Max.Y) + pad := b.opts.textHorizontalPadding + textAr := image.Rect(buttonAr.Min.X+pad, buttonAr.Min.Y, buttonAr.Dx()-pad, buttonAr.Max.Y) start, err := alignfor.Text(textAr, b.text, align.HorizontalCenter, align.VerticalMiddle) if err != nil { return err @@ -223,7 +224,7 @@ func (b *Button) shadowWidth() int { func (b *Button) Options() widgetapi.Options { // No need to lock, as the height and width get fixed when New is called. - width := b.opts.width + b.shadowWidth() + width := b.opts.width + b.shadowWidth() + 2*b.opts.textHorizontalPadding height := b.opts.height + b.shadowWidth() return widgetapi.Options{ MinimumSize: image.Point{width, height}, diff --git a/widgets/button/button_test.go b/widgets/button/button_test.go index 8225ca1..733d4df 100644 --- a/widgets/button/button_test.go +++ b/widgets/button/button_test.go @@ -115,6 +115,15 @@ func TestButton(t *testing.T) { canvas: image.Rect(0, 0, 1, 1), wantNewErr: true, }, + { + desc: "New fails with negative textHorizontalPadding", + callback: &callbackTracker{}, + opts: []Option{ + TextHorizontalPadding(-1), + }, + canvas: image.Rect(0, 0, 1, 1), + wantNewErr: true, + }, { desc: "draw fails on canvas too small", callback: &callbackTracker{}, @@ -934,11 +943,25 @@ func TestOptions(t *testing.T) { }, }, { - desc: "custom width specified", + desc: "custom width specified with default padding", text: "hello", opts: []Option{ Width(10), }, + want: widgetapi.Options{ + MinimumSize: image.Point{13, 4}, + MaximumSize: image.Point{13, 4}, + WantKeyboard: widgetapi.KeyScopeNone, + WantMouse: widgetapi.MouseScopeGlobal, + }, + }, + { + desc: "custom width specified with custom padding", + text: "hello", + opts: []Option{ + Width(10), + TextHorizontalPadding(0), + }, want: widgetapi.Options{ MinimumSize: image.Point{11, 4}, MaximumSize: image.Point{11, 4}, @@ -946,7 +969,21 @@ func TestOptions(t *testing.T) { WantMouse: widgetapi.MouseScopeGlobal, }, }, - + { + desc: "without shadow or padding", + text: "hello", + opts: []Option{ + Width(10), + TextHorizontalPadding(0), + DisableShadow(), + }, + want: widgetapi.Options{ + MinimumSize: image.Point{10, 3}, + MaximumSize: image.Point{10, 3}, + WantKeyboard: widgetapi.KeyScopeNone, + WantMouse: widgetapi.MouseScopeGlobal, + }, + }, { desc: "doesn't want keyboard by default", text: "hello", diff --git a/widgets/button/formdemo/formdemo.go b/widgets/button/formdemo/formdemo.go index 83a9ac6..37e1716 100644 --- a/widgets/button/formdemo/formdemo.go +++ b/widgets/button/formdemo/formdemo.go @@ -60,6 +60,7 @@ func main() { button.GlobalKeys('s', 'S'), button.DisableShadow(), button.Height(1), + button.TextHorizontalPadding(0), ) if err != nil { panic(err) @@ -75,6 +76,7 @@ func main() { button.GlobalKeys('c', 'C'), button.DisableShadow(), button.Height(1), + button.TextHorizontalPadding(0), ) if err != nil { panic(err) diff --git a/widgets/button/options.go b/widgets/button/options.go index e22812f..a5c139d 100644 --- a/widgets/button/options.go +++ b/widgets/button/options.go @@ -42,19 +42,23 @@ func (o option) set(opts *options) { // options holds the provided options. type options struct { - fillColor cell.Color - textColor cell.Color - shadowColor cell.Color - disableShadow bool - height int - width int - keys map[keyboard.Key]bool - keyScope widgetapi.KeyScope - keyUpDelay time.Duration + fillColor cell.Color + textColor cell.Color + textHorizontalPadding int + shadowColor cell.Color + disableShadow bool + height int + width int + keys map[keyboard.Key]bool + keyScope widgetapi.KeyScope + keyUpDelay time.Duration } // validate validates the provided options. func (o *options) validate() error { + if min := 0; o.textHorizontalPadding < min { + return fmt.Errorf("invalid textHorizontalPadding %d, must be %d <= textHorizontalPadding", o.textHorizontalPadding, min) + } if min := 1; o.height < min { return fmt.Errorf("invalid height %d, must be %d <= height", o.height, min) } @@ -76,13 +80,14 @@ type keyScope struct { // newOptions returns options with the default values set. func newOptions(text string) *options { return &options{ - fillColor: cell.ColorNumber(117), - textColor: cell.ColorBlack, - shadowColor: cell.ColorNumber(240), - height: DefaultHeight, - width: widthFor(text), - keyUpDelay: DefaultKeyUpDelay, - keys: map[keyboard.Key]bool{}, + fillColor: cell.ColorNumber(117), + textColor: cell.ColorBlack, + textHorizontalPadding: DefaultTextHorizontalPadding, + shadowColor: cell.ColorNumber(240), + height: DefaultHeight, + width: widthFor(text), + keyUpDelay: DefaultKeyUpDelay, + keys: map[keyboard.Key]bool{}, } } @@ -122,6 +127,8 @@ func Height(cells int) Option { // Width sets the width of the button in cells. // Must be a positive non-zero integer. // Defaults to the auto-width based on the length of the text label. +// Not all the width may be available to the text if TextHorizontalPadding is +// set to a non-zero integer. func Width(cells int) Option { return option(func(opts *options) { opts.width = cells @@ -211,7 +218,18 @@ func DisableShadow() Option { }) } +// DefaultTextHorizontalPadding is the default value for the HorizontalPadding option. +const DefaultTextHorizontalPadding = 1 + +// TextHorizontalPadding sets padding on the left and right side of the +// button's text as the amount of cells. +func TextHorizontalPadding(p int) Option { + return option(func(opts *options) { + opts.textHorizontalPadding = p + }) +} + // widthFor returns the required width for the specified text. func widthFor(text string) int { - return runewidth.StringWidth(text) + 2 // One empty cell at each side. + return runewidth.StringWidth(text) }