func (w *Widget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool {
	res := false
	if _, ok := ev.(gowid.CopyModeEvent); ok {
		if app.CopyModeClaimedAt() >= app.CopyLevel() && app.CopyModeClaimedAt() < app.CopyLevel()+len(w.Layers())+1 {
			app.CopyModeClaimedBy(w)
			res = true
		} else {
			cl := app.CopyLevel()
			app.CopyLevel(cl + len(w.Layers()) + 1)  
			res = w.w.UserInput(ev, size, focus, app)
			app.CopyLevel(cl)
			if !res {
				app.CopyModeClaimedAt(app.CopyLevel() + len(w.Layers()))
				app.CopyModeClaimedBy(w)
			}
		}
	} else if evc, ok := ev.(gowid.CopyModeClipsEvent); ok && (app.CopyModeClaimedAt() >= app.CopyLevel() && app.CopyModeClaimedAt() < app.CopyLevel()+len(w.Layers())+1) {
		evc.Action.Collect(w.Clips(app))
		res = true
	} else {
		cur := w.Position()
		res = w.w.UserInput(ev, size, focus, app)
		if res {
			newpos := w.Position()
			if newpos != cur {
				gowid.RunWidgetCallbacks(w.Callbacks, PositionChangedCB{}, app, w)
			}
		}
	}
	return res
}
Reject tab because I want it to switch views. Not intended to be transferable.
package hexdumper
import (
	"encoding/hex"
	"fmt"
	"unicode"
	"github.com/gcla/gowid"
	"github.com/gcla/gowid/widgets/button"
	"github.com/gcla/gowid/widgets/columns"
	"github.com/gcla/gowid/widgets/palettemap"
	"github.com/gcla/gowid/widgets/pile"
	"github.com/gcla/gowid/widgets/styled"
	"github.com/gcla/gowid/widgets/text"
	"github.com/gcla/termshark/v2"
	"github.com/gcla/termshark/v2/format"
	"github.com/gcla/termshark/v2/widgets/renderfocused"
	"github.com/gdamore/tcell"
	"github.com/pkg/errors"
)
type LayerStyler struct {
	Start         int
	End           int
	ColUnselected string
	ColSelected   string
}
type PositionChangedCB struct{}
type boxedText struct {
	width int
	gowid.IWidget
}
func (h boxedText) String() string {
	return fmt.Sprintf("[hacktext %v]", h.IWidget)
}
func (h boxedText) RenderSize(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.IRenderBox {
	return gowid.RenderBox{C: h.width, R: 1}
}
type Options struct {
	StyledLayers      []LayerStyler
	CursorUnselected  string
	CursorSelected    string
	LineNumUnselected string
	LineNumSelected   string
	PaletteIfCopying  string
}
type Widget struct {
	w                 gowid.IWidget
	data              []byte
	layers            []LayerStyler
	chrs              []boxedText
	cursorUnselected  string
	cursorSelected    string
	lineNumUnselected string
	lineNumSelected   string
	paletteIfCopying  string
	gowid.AddressProvidesID
	styled.UsePaletteIfSelectedForCopy
	Callbacks *gowid.Callbacks
	gowid.IsSelectable
}
var _ gowid.IWidget = (*Widget)(nil)
var _ gowid.IIdentityWidget = (*Widget)(nil)
var _ gowid.IClipboard = (*Widget)(nil)
var _ gowid.IClipboardSelected = (*Widget)(nil)
func New(data []byte, opts ...Options) *Widget {
	var opt Options
	if len(opts) > 0 {
		opt = opts[0]
	}
	res := &Widget{
		data:                        data,
		layers:                      opt.StyledLayers,
		cursorUnselected:            opt.CursorUnselected,
		cursorSelected:              opt.CursorSelected,
		lineNumUnselected:           opt.LineNumUnselected,
		lineNumSelected:             opt.LineNumSelected,
		paletteIfCopying:            opt.PaletteIfCopying,
		UsePaletteIfSelectedForCopy: styled.UsePaletteIfSelectedForCopy{Entry: opt.PaletteIfCopying},
		Callbacks:                   gowid.NewCallbacks(),
	}
	res.chrs = make([]boxedText, 256)
	for i := 0; i < 256; i++ {
		if unicode.IsPrint(rune(i)) {
			res.chrs[i] = boxedText{
				width:   1,
				IWidget: text.NewCopyable(string(rune(i)), hexChrsId{i}, styled.UsePaletteIfSelectedForCopy{Entry: opt.PaletteIfCopying}),
			}
		}
	}
	res.w = res.Build(0)
	return res
}
func (w *Widget) OnPositionChanged(f gowid.IWidgetChangedCallback) {
	gowid.AddWidgetCallback(w.Callbacks, PositionChangedCB{}, f)
}
func (w *Widget) RemoveOnPositionChanged(f gowid.IIdentity) {
	gowid.RemoveWidgetCallback(w.Callbacks, PositionChangedCB{}, f)
}
func (w *Widget) String() string {
	return "hexdump"
}
func (w *Widget) CursorUnselected() string {
	return w.cursorUnselected
}
func (w *Widget) CursorSelected() string {
	return w.cursorSelected
}
func (w *Widget) LineNumUnselected() string {
	return w.lineNumUnselected
}
func (w *Widget) LineNumSelected() string {
	return w.lineNumSelected
}
func (w *Widget) Layers() []LayerStyler {
	return w.layers
}
func (w *Widget) SetLayers(layers []LayerStyler, app gowid.IApp) {
	w.layers = layers
	pos := w.Position()
	inhex := w.InHex()
	w.w = w.Build(pos)
	w.SetInHex(inhex, app)
	w.SetPosition(pos, app)
}
func (w *Widget) Data() []byte {
	return w.data
}
func (w *Widget) SetData(data []byte, app gowid.IApp) {
	w.data = data
	pos := w.Position()
	inhex := w.InHex()
	w.w = w.Build(pos)
	w.SetInHex(inhex, app)
	w.SetPosition(pos, app)
}
func (w *Widget) InHex() bool {
	fp := gowid.FocusPath(w.w)
	if len(fp) < 3 {
		panic(errors.WithStack(gowid.WithKVs(termshark.BadState, map[string]interface{}{"focus path": fp})))
	}
	return fp[0] == 3
}
func (w *Widget) SetInHex(val bool, app gowid.IApp) {
	fp := gowid.FocusPath(w.w)
	if len(fp) < 3 {
		panic(errors.WithStack(gowid.WithKVs(termshark.BadState, map[string]interface{}{"focus path": fp})))
	}
	if val {
		if fp[0].(int) == 3 {
			return
		}
		fp[0] = 3
		x := fp[2].(int)
		if x > 7 {
			fp[2] = (x * 2) - 1
		} else {
			fp[2] = x * 2
		}
	} else {
		if fp[0].(int) == 7 {
			return
		}
		fp[0] = 7
		x := fp[2].(int)
		if x > 14 {
			fp[2] = (x + 1) / 2
		} else {
			fp[2] = x / 2
		}
	}
	gowid.SetFocusPath(w.w, fp, app)
}
func (w *Widget) Position() int {
	fp := gowid.FocusPath(w.w)
	if len(fp) < 3 {
		panic(gowid.WithKVs(termshark.BadState, map[string]interface{}{"focus path": fp}))
	}
	if fp[0] == 3 {
		x := fp[2].(int)
		if x > 14 {
			return (fp[1].(int) * 16) + (x / 2)  
		} else {
			return (fp[1].(int) * 16) + (x / 2)
		}
	} else {
		x := fp[2].(int)
		if x > 7 {
			return (fp[1].(int) * 16) + (x - 1)
		} else {
			return (fp[1].(int) * 16) + x
		}
	}
}
func (w *Widget) SetPosition(pos int, app gowid.IApp) {
	fp := gowid.FocusPath(w.w)
	if len(fp) < 3 {
		panic(gowid.WithKVs(termshark.BadState, map[string]interface{}{"focus path": fp}))
	}
	curpos := w.Position()
	fp[1] = pos / 16
	if fp[0] == 3 {
		if pos%16 > 7 {
			fp[2] = ((pos % 16) * 2) + 1
		} else {
			fp[2] = (pos % 16) * 2
		}
	} else {
		if pos%16 > 7 {
			fp[2] = pos%16 + 1
		} else {
			fp[2] = pos % 16
		}
	}
	gowid.SetFocusPath(w.w, fp, app)
	if curpos != pos {
		gowid.RunWidgetCallbacks(w.Callbacks, PositionChangedCB{}, app, w)
	}
}
type viewSwitchFn func(ev *tcell.EventKey) bool
type viewSwitch struct {
	w  *Widget
	fn viewSwitchFn
}
func (v viewSwitch) SwitchView(ev *tcell.EventKey, app gowid.IApp) bool {
	if v.fn(ev) {
		v.w.SetInHex(!v.w.InHex(), app)
		return true
	}
	return false
}
func (w *Widget) OnKey(fn viewSwitchFn) viewSwitch {
	return viewSwitch{
		w:  w,
		fn: fn,
	}
}
func (w *Widget) RenderSize(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.IRenderBox {
	return gowid.RenderSize(w.w, size, focus, app)
}
type privateId struct {
	*Widget
}
func (d privateId) ID() interface{} {
	return d
}
func (d privateId) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas {
	return d.w.Render(size, focus, app)
}
func (w *Widget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas {
	if app.InCopyMode() && app.CopyModeClaimedBy().ID() == w.ID() && focus.Focus {
		var wa gowid.IWidget
		diff := app.CopyModeClaimedAt() - app.CopyLevel()
		if diff == 0 {
			wa = w.AlterWidget(privateId{w}, app)  
		} else {
			layerConv := make(map[string]string)
			for i := diff - 1; i < len(w.Layers()); i++ {
				layerConv[w.layers[i].ColSelected] = "copy-mode"  
			}
			wa = palettemap.New(privateId{w}, layerConv, layerConv)
		}
		return wa.Render(size, focus, app)
	} else {
		return w.w.Render(size, focus, app)
	}
}
func clipsForBytes(data []byte, start int, end int) []gowid.ICopyResult {
	dump := hex.Dump(data[start:end])
	dump2 := format.MakeEscapedString(data[start:end])
	dump3 := format.MakePrintableString(data[start:end])
	dump4 := format.MakeHexStream(data[start:end])
	return []gowid.ICopyResult{
		gowid.CopyResult{
			Name: fmt.Sprintf("Copy bytes %d-%d as hex + ascii", start, end),
			Val:  dump,
		},
		gowid.CopyResult{
			Name: fmt.Sprintf("Copy bytes %d-%d as escaped string", start, end),
			Val:  dump2,
		},
		gowid.CopyResult{
			Name: fmt.Sprintf("Copy bytes %d-%d as printable string", start, end),
			Val:  dump3,
		},
		gowid.CopyResult{
			Name: fmt.Sprintf("Copy bytes %d-%d as hex stream", start, end),
			Val:  dump4,
		},
	}
}
func (w *Widget) Clips(app gowid.IApp) []gowid.ICopyResult {
	diff := app.CopyModeClaimedAt() - app.CopyLevel()
	if diff == 0 {
		return clipsForBytes(w.Data(), 0, len(w.Data()))
	} else {
		return clipsForBytes(w.Data(), w.layers[diff-1].Start, w.layers[diff-1].End)
	}
}