1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-25 13:48:50 +08:00

Button's support for cell options on each text cell.

This commit is contained in:
Jakub Sobon 2020-11-25 02:28:05 -05:00
parent 73644716a5
commit 0643120697
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
3 changed files with 159 additions and 12 deletions

View File

@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
animation.
- the `button` widget can now be drawn without horizontal padding around its
text.
- the `button`widget now allows specifying cell options for each cell of the
displayed text.
## [0.13.0] - 17-Nov-2020

View File

@ -18,7 +18,9 @@ package button
import (
"errors"
"fmt"
"image"
"strings"
"sync"
"time"
@ -26,6 +28,7 @@ import (
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/mouse"
"github.com/mum4k/termdash/private/alignfor"
"github.com/mum4k/termdash/private/attrrange"
"github.com/mum4k/termdash/private/button"
"github.com/mum4k/termdash/private/canvas"
"github.com/mum4k/termdash/private/draw"
@ -45,6 +48,20 @@ import (
// termdash.ErrorHandler.
type CallbackFn func() error
// TextChunk is a part of or the full text displayed in the button.
type TextChunk struct {
text string
wOpts *writeOptions
}
// NewChunk creates a new text chunk. Each chunk of text can have its own cell options.
func NewChunk(text string, wOpts ...WriteOption) *TextChunk {
return &TextChunk{
text: text,
wOpts: newWriteOptions(wOpts...),
}
}
// Button can be pressed using a mouse click or a configured keyboard key.
//
// Upon each press, the button invokes a callback provided by the user.
@ -52,7 +69,12 @@ type CallbackFn func() error
// Implements widgetapi.Widget. This object is thread-safe.
type Button struct {
// text in the text label displayed in the button.
text string
text strings.Builder
// givenWOpts are write options given for the content of text.
givenWOpts []*writeOptions
// wOptsTracker tracks the positions in a text to which the givenWOpts apply.
wOptsTracker *attrrange.Tracker
// mouseFSM tracks left mouse clicks.
mouseFSM *button.FSM
@ -78,19 +100,54 @@ type Button struct {
// New returns a new Button that will display the provided text.
// Each press of the button will invoke the callback function.
func New(text string, cFn CallbackFn, opts ...Option) (*Button, error) {
return NewFromChunks([]*TextChunk{NewChunk(text)}, cFn, opts...)
}
// NewFromChunks is like New, but allows specifying write options for
// individual chunks of text displayed in the button.
func NewFromChunks(chunks []*TextChunk, cFn CallbackFn, opts ...Option) (*Button, error) {
if cFn == nil {
return nil, errors.New("the CallbackFn argument cannot be nil")
}
opt := newOptions(text)
if len(chunks) == 0 {
return nil, errors.New("at least one text chunk must be specified")
}
var (
text strings.Builder
givenWOpts []*writeOptions
)
wOptsTracker := attrrange.NewTracker()
for i, tc := range chunks {
if tc.text == "" {
return nil, fmt.Errorf("text chunk[%d] is empty, all chunks must contains some text", i)
}
pos := text.Len()
givenWOpts = append(givenWOpts, tc.wOpts)
wOptsIdx := len(givenWOpts) - 1
if err := wOptsTracker.Add(pos, pos+len(tc.text), wOptsIdx); err != nil {
return nil, err
}
text.WriteString(tc.text)
}
opt := newOptions(text.String())
for _, o := range opts {
o.set(opt)
}
if err := opt.validate(); err != nil {
return nil, err
}
for _, wOpts := range givenWOpts {
wOpts.setDefaultFgColor(opt.textColor)
}
return &Button{
text: text,
givenWOpts: givenWOpts,
wOptsTracker: wOptsTracker,
mouseFSM: button.NewFSM(mouse.ButtonLeft, image.ZR),
callback: cFn,
opts: opt,
@ -145,15 +202,40 @@ func (b *Button) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error {
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)
start, err := alignfor.Text(textAr, b.text.String(), align.HorizontalCenter, align.VerticalMiddle)
if err != nil {
return err
}
return draw.Text(cvs, b.text, start,
draw.TextOverrunMode(draw.OverrunModeThreeDot),
draw.TextMaxX(buttonAr.Max.X),
draw.TextCellOpts(cell.FgColor(b.opts.textColor)),
)
maxCells := buttonAr.Max.X - start.X
trimmed, err := draw.TrimText(b.text.String(), maxCells, draw.OverrunModeThreeDot)
if err != nil {
return err
}
optRange, err := b.wOptsTracker.ForPosition(0) // Text options for the current byte.
if err != nil {
return err
}
cur := start
for i, r := range trimmed {
if i >= optRange.High { // Get the next write options.
or, err := b.wOptsTracker.ForPosition(i)
if err != nil {
return err
}
optRange = or
}
wOpts := b.givenWOpts[optRange.AttrIdx]
cells, err := cvs.SetCell(cur, r, wOpts.cellOpts...)
if err != nil {
return err
}
cur = image.Point{cur.X + cells, cur.Y}
}
return nil
}
// activated asserts whether the keyboard event activated the button.

View File

@ -0,0 +1,63 @@
// Copyright 2020 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package button
// write_options.go contains options used when writing content to the widget.
import "github.com/mum4k/termdash/cell"
// WriteOption is used to provide options to NewFromChunks().
type WriteOption interface {
// set sets the provided option.
set(*writeOptions)
}
// writeOptions stores the provided options.
type writeOptions struct {
cellOpts []cell.Option
}
// setDefaultFgColor configures a default color for text if one isn't specified
// in the write options.
func (wo *writeOptions) setDefaultFgColor(c cell.Color) {
wo.cellOpts = append(
[]cell.Option{cell.FgColor(c)},
wo.cellOpts...,
)
}
// newWriteOptions returns new writeOptions instance.
func newWriteOptions(wOpts ...WriteOption) *writeOptions {
wo := &writeOptions{}
for _, o := range wOpts {
o.set(wo)
}
return wo
}
// writeOption implements WriteOption.
type writeOption func(*writeOptions)
// set implements WriteOption.set.
func (wo writeOption) set(wOpts *writeOptions) {
wo(wOpts)
}
// WriteCellOpts sets options on the cells that contain the text.
func WriteCellOpts(opts ...cell.Option) WriteOption {
return writeOption(func(wOpts *writeOptions) {
wOpts.cellOpts = opts
})
}