Skip to content

Commit

Permalink
Merge ceab87b into 4356b6f
Browse files Browse the repository at this point in the history
  • Loading branch information
mum4k committed Nov 28, 2020
2 parents 4356b6f + ceab87b commit 7e74f52
Show file tree
Hide file tree
Showing 24 changed files with 414 additions and 154 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Breaking API changes

- The `widgetapi.Widget.Keyboard` and `widgetapi.Widget.Mouse` methods now
accepts a second argument which provides widgets with additional metadata.
All widgets implemented outside of the `termdash` repository will need to be
similarly to the `Barchart` example below. Change the original method
signatures:
```go
func (*BarChart) Keyboard(k *terminalapi.Keyboard) error { ... }

func (*BarChart) Mouse(m *terminalapi.Mouse) error { ... }

```

By adding the new `*widgetapi.EventMeta` argument as follows:
```go
func (*BarChart) Keyboard(k *terminalapi.Keyboard, meta *widgetapi.EventMeta) error { ... }

func (*BarChart) Mouse(m *terminalapi.Mouse, meta *widgetapi.EventMeta) error { ... }
```

### Added

- ability to configure keyboard keys that move focus to the next or the
Expand Down
69 changes: 46 additions & 23 deletions container/container.go
Expand Up @@ -320,24 +320,20 @@ func (c *Container) prepareEvTargets(ev terminalapi.Event) (func() error, error)
}
return func() error {
for _, mt := range targets {
if err := mt.widget.Mouse(mt.ev); err != nil {
if err := mt.widget.Mouse(mt.ev, mt.meta); err != nil {
return err
}
}
return nil
}, nil

case *terminalapi.Keyboard:
targets := c.keyEvTargets()

// Update the focused container based on the pressed key.
// Done after collecting "targets" above. If the key changes which
// container is focused, they key press itself should go to the widget
// that was focused when the key was pressed.
c.updateFocusFromKeyboard(ev.(*terminalapi.Keyboard))

targets := c.keyEvTargets()
return func() error {
for _, w := range targets {
if err := w.Keyboard(e); err != nil {
for _, kt := range targets {
if err := kt.widget.Keyboard(e, kt.meta); err != nil {
return err
}
}
Expand All @@ -349,55 +345,79 @@ func (c *Container) prepareEvTargets(ev terminalapi.Event) (func() error, error)
}
}

// keyEvTarget contains a widget that should receive an event and the metadata
// for the event.
type keyEvTarget struct {
// widget is the widget that should receive the keyboard event.
widget widgetapi.Widget
// meta is the metadata about the event.
meta *widgetapi.EventMeta
}

// newKeyEvTarget returns a new keyEvTarget.
func newKeyEvTarget(w widgetapi.Widget, meta *widgetapi.EventMeta) *keyEvTarget {
return &keyEvTarget{
widget: w,
meta: meta,
}
}

// keyEvTargets returns those widgets found in the container that should
// receive this keyboard event.
// Caller must hold c.mu.
func (c *Container) keyEvTargets() []widgetapi.Widget {
func (c *Container) keyEvTargets() []*keyEvTarget {
var (
errStr string
widgets []widgetapi.Widget
targets []*keyEvTarget
)

// All the widgets that should receive this event.
// All the targets that should receive this event.
// For now stable ordering (preOrder).
preOrder(c, &errStr, visitFunc(func(cur *Container) error {
if !cur.hasWidget() {
return nil
}

focused := cur.focusTracker.isActive(cur)
meta := &widgetapi.EventMeta{
Focused: focused,
}
wOpt := cur.opts.widget.Options()
switch wOpt.WantKeyboard {
case widgetapi.KeyScopeNone:
// Widget doesn't want any keyboard events.
return nil

case widgetapi.KeyScopeFocused:
if cur.focusTracker.isActive(cur) {
widgets = append(widgets, cur.opts.widget)
if focused {
targets = append(targets, newKeyEvTarget(cur.opts.widget, meta))
}

case widgetapi.KeyScopeGlobal:
widgets = append(widgets, cur.opts.widget)
targets = append(targets, newKeyEvTarget(cur.opts.widget, meta))
}
return nil
}))
return widgets
return targets
}

// mouseEvTarget contains a mouse event adjusted relative to the widget's area
// and the widget that should receive it.
// mouseEvTarget contains a mouse event adjusted relative to the widget's area,
// the widget that should receive it and metadata about the event.
type mouseEvTarget struct {
// widget is the widget that should receive the mouse event.
widget widgetapi.Widget
// ev is the adjusted mouse event.
ev *terminalapi.Mouse
// meta is the metadata about the event.
meta *widgetapi.EventMeta
}

// newMouseEvTarget returns a new newMouseEvTarget.
func newMouseEvTarget(w widgetapi.Widget, wArea image.Rectangle, ev *terminalapi.Mouse) *mouseEvTarget {
// newMouseEvTarget returns a new mouseEvTarget.
func newMouseEvTarget(w widgetapi.Widget, wArea image.Rectangle, ev *terminalapi.Mouse, meta *widgetapi.EventMeta) *mouseEvTarget {
return &mouseEvTarget{
widget: w,
ev: adjustMouseEv(ev, wArea),
meta: meta,
}
}

Expand All @@ -423,6 +443,9 @@ func (c *Container) mouseEvTargets(m *terminalapi.Mouse) ([]*mouseEvTarget, erro
return err
}

meta := &widgetapi.EventMeta{
Focused: cur.focusTracker.isActive(cur),
}
switch wOpts.WantMouse {
case widgetapi.MouseScopeNone:
// Widget doesn't want any mouse events.
Expand All @@ -431,18 +454,18 @@ func (c *Container) mouseEvTargets(m *terminalapi.Mouse) ([]*mouseEvTarget, erro
case widgetapi.MouseScopeWidget:
// Only if the event falls inside of the widget's canvas.
if m.Position.In(wa) {
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m, meta))
}

case widgetapi.MouseScopeContainer:
// Only if the event falls inside the widget's parent container.
if m.Position.In(cur.area) {
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m, meta))
}

case widgetapi.MouseScopeGlobal:
// Widget wants all mouse events.
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m, meta))
}
return nil
}))
Expand Down

0 comments on commit 7e74f52

Please sign in to comment.