diff options
Diffstat (limited to 'examples/go-dashboard/src/github.com/mum4k/termdash/private/button/button.go')
-rw-r--r-- | examples/go-dashboard/src/github.com/mum4k/termdash/private/button/button.go | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/button/button.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/button/button.go new file mode 100644 index 000000000..d4e0601b8 --- /dev/null +++ b/examples/go-dashboard/src/github.com/mum4k/termdash/private/button/button.go @@ -0,0 +1,135 @@ +// Copyright 2019 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 implements a state machine that tracks mouse button clicks. +package button + +import ( + "image" + + "github.com/mum4k/termdash/mouse" + "github.com/mum4k/termdash/terminal/terminalapi" +) + +// State represents the state of the mouse button. +type State int + +// String implements fmt.Stringer() +func (s State) String() string { + if n, ok := stateNames[s]; ok { + return n + } + return "StateUnknown" +} + +// stateNames maps State values to human readable names. +var stateNames = map[State]string{ + Up: "StateUp", + Down: "StateDown", +} + +const ( + // Up is the default idle state of the mouse button. + Up State = iota + + // Down is a state where the mouse button is pressed down and held. + Down +) + +// FSM implements a finite-state machine that tracks mouse clicks within an +// area. +// +// Simplifies tracking of mouse button clicks, i.e. when the caller wants to +// perform an action only if both the button press and release happen within +// the specified area. +// +// This object is not thread-safe. +type FSM struct { + // button is the mouse button whose state this FSM tracks. + button mouse.Button + + // area is the area provided to NewFSM. + area image.Rectangle + + // state is the current state of the FSM. + state stateFn +} + +// NewFSM creates a new FSM instance that tracks the state of the specified +// mouse button through button events that fall within the provided area. +func NewFSM(button mouse.Button, area image.Rectangle) *FSM { + return &FSM{ + button: button, + area: area, + state: wantPress, + } +} + +// Event is used to forward mouse events to the state machine. +// Only events related to the button specified on a call to NewFSM are +// processed. +// +// Returns a bool indicating if an action guarded by the button should be +// performed and the state of the button after the provided event. +// The bool is true if the button click should take an effect, i.e. if the +// FSM saw both the button click and its release. +func (fsm *FSM) Event(m *terminalapi.Mouse) (bool, State) { + clicked, bs, next := fsm.state(fsm, m) + fsm.state = next + return clicked, bs +} + +// UpdateArea informs FSM of an area change. +// This method is idempotent. +func (fsm *FSM) UpdateArea(area image.Rectangle) { + fsm.area = area +} + +// stateFn is a single state in the state machine. +// Returns bool indicating if a click happened, the state of the button and the +// next state of the FSM. +type stateFn func(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn) + +// wantPress is the initial state, expecting a button press inside the area. +func wantPress(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn) { + if m.Button != fsm.button || !m.Position.In(fsm.area) { + return false, Up, wantPress + } + return false, Down, wantRelease +} + +// wantRelease waits for a mouse button release in the same area as +// the press. +func wantRelease(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn) { + switch m.Button { + case fsm.button: + if m.Position.In(fsm.area) { + // Remain in the same state, since termbox reports move of mouse with + // button held down as a series of clicks, one per position. + return false, Down, wantRelease + } + return false, Up, wantPress + + case mouse.ButtonRelease: + if m.Position.In(fsm.area) { + // Seen both press and release, report a click. + return true, Up, wantPress + } + // Release the button even if the release event happened outside of the area. + return false, Up, wantPress + + default: + return false, Up, wantPress + } +} |