You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
NoiseTorch/vendor/github.com/aarzilli/nucular/context.go

658 lines
15 KiB
Go

package nucular
import (
"bytes"
"errors"
"image"
"image/draw"
"io"
"time"
"github.com/aarzilli/nucular/command"
"github.com/aarzilli/nucular/rect"
nstyle "github.com/aarzilli/nucular/style"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/mouse"
)
const perfUpdate = false
const dumpFrame = false
var UnknownCommandErr = errors.New("unknown command")
type context struct {
mw MasterWindow
Input Input
Style nstyle.Style
Windows []*Window
DockedWindows dockedTree
changed int32
activateEditor *TextEditor
cmds []command.Command
trashFrame bool
autopos image.Point
finalCmds command.Buffer
dockedWindowFocus int
floatWindowFocus int
scrollwheelFocus int
dockedCnt int
cmdstim []time.Duration // contains timing for all commands
}
func contextAllCommands(ctx *context) {
ctx.cmds = ctx.cmds[:0]
for i, w := range ctx.Windows {
ctx.cmds = append(ctx.cmds, w.cmds.Commands...)
if i == 0 {
ctx.DockedWindows.Walk(func(w *Window) *Window {
ctx.cmds = append(ctx.cmds, w.cmds.Commands...)
return w
})
}
}
ctx.cmds = append(ctx.cmds, ctx.finalCmds.Commands...)
}
func (ctx *context) setupMasterWindow(layout *panel, updatefn UpdateFn) {
ctx.Windows = append(ctx.Windows, createWindow(ctx, ""))
ctx.Windows[0].idx = 0
ctx.Windows[0].layout = layout
ctx.Windows[0].flags = layout.Flags | WindowNonmodal
ctx.Windows[0].updateFn = updatefn
}
func (ctx *context) Update() {
for count := 0; count < 2; count++ {
contextBegin(ctx, ctx.Windows[0].layout)
for i := 0; i < len(ctx.Windows); i++ {
ctx.Windows[i].began = false
}
ctx.Restack()
ctx.FindFocus()
for i := 0; i < len(ctx.Windows); i++ { // this must not use range or tooltips won't work
ctx.updateWindow(ctx.Windows[i])
if i == 0 {
t := ctx.DockedWindows.Update(ctx.Windows[0].Bounds, ctx.Style.Scaling)
if t != nil {
ctx.DockedWindows = *t
}
}
}
contextEnd(ctx)
if !ctx.trashFrame {
break
} else {
ctx.Reset()
}
}
}
func (ctx *context) updateWindow(win *Window) {
if win.updateFn != nil {
win.specialPanelBegin()
win.updateFn(win)
}
if !win.began {
win.close = true
return
}
if win.title == tooltipWindowTitle {
win.close = true
}
if win.flags&windowPopup != 0 {
panelEnd(ctx, win)
}
}
func (ctx *context) processKeyEvent(e key.Event, textbuffer *bytes.Buffer) {
if e.Direction == key.DirRelease {
return
}
evinNotext := func() {
for _, k := range ctx.Input.Keyboard.Keys {
if k.Code == e.Code {
k.Modifiers |= e.Modifiers
return
}
}
ctx.Input.Keyboard.Keys = append(ctx.Input.Keyboard.Keys, e)
}
evinText := func() {
if e.Modifiers == 0 || e.Modifiers == key.ModShift {
io.WriteString(textbuffer, string(e.Rune))
}
evinNotext()
}
switch {
case e.Code == key.CodeUnknown:
if e.Rune > 0 {
evinText()
}
case (e.Code >= key.CodeA && e.Code <= key.Code0) || e.Code == key.CodeSpacebar || e.Code == key.CodeHyphenMinus || e.Code == key.CodeEqualSign || e.Code == key.CodeLeftSquareBracket || e.Code == key.CodeRightSquareBracket || e.Code == key.CodeBackslash || e.Code == key.CodeSemicolon || e.Code == key.CodeApostrophe || e.Code == key.CodeGraveAccent || e.Code == key.CodeComma || e.Code == key.CodeFullStop || e.Code == key.CodeSlash || (e.Code >= key.CodeKeypadSlash && e.Code <= key.CodeKeypadPlusSign) || (e.Code >= key.CodeKeypad1 && e.Code <= key.CodeKeypadEqualSign):
evinText()
case e.Code == key.CodeTab:
e.Rune = '\t'
evinText()
case e.Code == key.CodeReturnEnter || e.Code == key.CodeKeypadEnter:
e.Rune = '\n'
evinText()
default:
evinNotext()
}
}
func contextBegin(ctx *context, layout *panel) {
for _, w := range ctx.Windows {
w.usingSub = false
w.curNode = w.rootNode
w.close = false
w.widgets.reset()
w.cmds.Reset()
}
ctx.finalCmds.Reset()
ctx.DockedWindows.Walk(func(w *Window) *Window {
w.usingSub = false
w.curNode = w.rootNode
w.close = false
w.widgets.reset()
w.cmds.Reset()
return w
})
ctx.trashFrame = false
ctx.Windows[0].layout = layout
panelBegin(ctx, ctx.Windows[0], "")
layout.Offset = &ctx.Windows[0].Scrollbar
}
func contextEnd(ctx *context) {
panelEnd(ctx, ctx.Windows[0])
}
func (ctx *context) Reset() {
prevNumWindows := len(ctx.Windows)
for i := 0; i < len(ctx.Windows); i++ {
if ctx.Windows[i].close {
if i != len(ctx.Windows)-1 {
copy(ctx.Windows[i:], ctx.Windows[i+1:])
i--
}
ctx.Windows = ctx.Windows[:len(ctx.Windows)-1]
}
}
for i := range ctx.Windows {
ctx.Windows[i].idx = i
}
if prevNumWindows == 2 && len(ctx.Windows) == 1 && ctx.Input.Mouse.valid {
ctx.DockedWindows.Walk(func(w *Window) *Window {
if w.flags&windowDocked == 0 {
return w
}
for _, b := range []mouse.Button{mouse.ButtonLeft, mouse.ButtonRight, mouse.ButtonMiddle} {
btn := ctx.Input.Mouse.Buttons[b]
if btn.Clicked && w.Bounds.Contains(btn.ClickedPos) {
ctx.dockedWindowFocus = w.idx
return w
}
}
return w
})
}
ctx.activateEditor = nil
in := &ctx.Input
in.Mouse.Buttons[mouse.ButtonLeft].Clicked = false
in.Mouse.Buttons[mouse.ButtonMiddle].Clicked = false
in.Mouse.Buttons[mouse.ButtonRight].Clicked = false
in.Mouse.ScrollDelta = 0
in.Mouse.Prev.X = in.Mouse.Pos.X
in.Mouse.Prev.Y = in.Mouse.Pos.Y
in.Mouse.Delta = image.Point{}
in.Keyboard.Keys = in.Keyboard.Keys[0:0]
}
func (ctx *context) Restack() {
clicked := false
for _, b := range []mouse.Button{mouse.ButtonLeft, mouse.ButtonRight, mouse.ButtonMiddle} {
if ctx.Input.Mouse.Buttons[b].Clicked && ctx.Input.Mouse.Buttons[b].Down {
clicked = true
break
}
}
if !clicked {
return
}
ctx.dockedWindowFocus = 0
nonmodalToplevel := false
var toplevelIdx int
for i := len(ctx.Windows) - 1; i >= 0; i-- {
if ctx.Windows[i].flags&windowTooltip == 0 {
toplevelIdx = i
nonmodalToplevel = ctx.Windows[i].flags&WindowNonmodal != 0
break
}
}
if !nonmodalToplevel {
return
}
// toplevel window is non-modal, proceed to change the stacking order if
// the user clicked outside of it
restacked := false
found := false
for i := len(ctx.Windows) - 1; i > 0; i-- {
if ctx.Windows[i].flags&windowTooltip != 0 {
continue
}
if ctx.restackClick(ctx.Windows[i]) {
found = true
if toplevelIdx != i {
newToplevel := ctx.Windows[i]
copy(ctx.Windows[i:toplevelIdx], ctx.Windows[i+1:toplevelIdx+1])
ctx.Windows[toplevelIdx] = newToplevel
restacked = true
}
break
}
}
if restacked {
for i := range ctx.Windows {
ctx.Windows[i].idx = i
}
}
if found {
return
}
ctx.DockedWindows.Walk(func(w *Window) *Window {
if ctx.restackClick(w) && (w.flags&windowDocked != 0) {
ctx.dockedWindowFocus = w.idx
}
return w
})
}
func (ctx *context) FindFocus() {
ctx.floatWindowFocus = 0
for i := len(ctx.Windows) - 1; i >= 0; i-- {
if ctx.Windows[i].flags&windowTooltip == 0 {
ctx.floatWindowFocus = i
break
}
}
ctx.scrollwheelFocus = 0
for i := len(ctx.Windows) - 1; i > 0; i-- {
if ctx.Windows[i].Bounds.Contains(ctx.Input.Mouse.Pos) {
ctx.scrollwheelFocus = i
break
}
}
if ctx.scrollwheelFocus == 0 {
ctx.DockedWindows.Walk(func(w *Window) *Window {
if w.Bounds.Contains(ctx.Input.Mouse.Pos) {
ctx.scrollwheelFocus = w.idx
}
return w
})
}
}
func (ctx *context) Walk(fn WindowWalkFn) {
fn(ctx.Windows[0].title, ctx.Windows[0].Data, false, 0, ctx.Windows[0].Bounds)
ctx.DockedWindows.walkExt(func(t *dockedTree) {
switch t.Type {
case dockedNodeHoriz:
fn("", nil, true, t.Split.Size, rect.Rect{})
case dockedNodeVert:
fn("", nil, true, -t.Split.Size, rect.Rect{})
case dockedNodeLeaf:
if t.W == nil {
fn("", nil, true, 0, rect.Rect{})
} else {
fn(t.W.title, t.W.Data, true, 0, t.W.Bounds)
}
}
})
for _, win := range ctx.Windows[1:] {
if win.flags&WindowNonmodal != 0 {
fn(win.title, win.Data, false, 0, win.Bounds)
}
}
}
func (ctx *context) restackClick(w *Window) bool {
if !ctx.Input.Mouse.valid {
return false
}
for _, b := range []mouse.Button{mouse.ButtonLeft, mouse.ButtonRight, mouse.ButtonMiddle} {
btn := ctx.Input.Mouse.Buttons[b]
if btn.Clicked && btn.Down && w.Bounds.Contains(btn.ClickedPos) {
return true
}
}
return false
}
type dockedNodeType uint8
const (
dockedNodeLeaf dockedNodeType = iota
dockedNodeVert
dockedNodeHoriz
)
type dockedTree struct {
Type dockedNodeType
Split ScalableSplit
Child [2]*dockedTree
W *Window
}
func (t *dockedTree) Update(bounds rect.Rect, scaling float64) *dockedTree {
if t == nil {
return nil
}
switch t.Type {
case dockedNodeVert:
b0, b1, _ := t.Split.verticalnw(bounds, scaling)
t.Child[0] = t.Child[0].Update(b0, scaling)
t.Child[1] = t.Child[1].Update(b1, scaling)
case dockedNodeHoriz:
b0, b1, _ := t.Split.horizontalnw(bounds, scaling)
t.Child[0] = t.Child[0].Update(b0, scaling)
t.Child[1] = t.Child[1].Update(b1, scaling)
case dockedNodeLeaf:
if t.W != nil {
t.W.Bounds = bounds
t.W.ctx.updateWindow(t.W)
if t.W == nil {
return nil
}
if t.W.close {
t.W = nil
return nil
}
return t
}
return nil
}
if t.Child[0] == nil {
return t.Child[1]
}
if t.Child[1] == nil {
return t.Child[0]
}
return t
}
func (t *dockedTree) walkExt(fn func(t *dockedTree)) {
if t == nil {
return
}
switch t.Type {
case dockedNodeVert, dockedNodeHoriz:
fn(t)
t.Child[0].walkExt(fn)
t.Child[1].walkExt(fn)
case dockedNodeLeaf:
fn(t)
}
}
func (t *dockedTree) Walk(fn func(t *Window) *Window) {
t.walkExt(func(t *dockedTree) {
if t.Type == dockedNodeLeaf && t.W != nil {
t.W = fn(t.W)
}
})
}
func newDockedLeaf(win *Window) *dockedTree {
r := &dockedTree{Type: dockedNodeLeaf, W: win}
r.Split.MinSize = 40
return r
}
func (t *dockedTree) Dock(win *Window, pos image.Point, bounds rect.Rect, scaling float64) (bool, rect.Rect) {
if t == nil {
return false, rect.Rect{}
}
switch t.Type {
case dockedNodeVert:
b0, b1, _ := t.Split.verticalnw(bounds, scaling)
canDock, r := t.Child[0].Dock(win, pos, b0, scaling)
if canDock {
return canDock, r
}
canDock, r = t.Child[1].Dock(win, pos, b1, scaling)
if canDock {
return canDock, r
}
case dockedNodeHoriz:
b0, b1, _ := t.Split.horizontalnw(bounds, scaling)
canDock, r := t.Child[0].Dock(win, pos, b0, scaling)
if canDock {
return canDock, r
}
canDock, r = t.Child[1].Dock(win, pos, b1, scaling)
if canDock {
return canDock, r
}
case dockedNodeLeaf:
v := percentages(bounds, 0.03)
for i := range v {
if v[i].Contains(pos) {
if t.W == nil {
if win != nil {
t.W = win
win.ctx.dockWindow(win)
}
return true, bounds
}
w := percentages(bounds, 0.5)
if win != nil {
if i < 2 {
// horizontal split
t.Type = dockedNodeHoriz
t.Split.Size = int(float64(w[0].H) / scaling)
t.Child[i] = newDockedLeaf(win)
t.Child[-i+1] = newDockedLeaf(t.W)
} else {
// vertical split
t.Type = dockedNodeVert
t.Split.Size = int(float64(w[2].W) / scaling)
t.Child[i-2] = newDockedLeaf(win)
t.Child[-(i-2)+1] = newDockedLeaf(t.W)
}
t.W = nil
win.ctx.dockWindow(win)
}
return true, w[i]
}
}
}
return false, rect.Rect{}
}
func (ctx *context) dockWindow(win *Window) {
win.undockedSz = image.Point{win.Bounds.W, win.Bounds.H}
win.flags |= windowDocked
win.layout.Flags |= windowDocked
ctx.dockedCnt--
win.idx = ctx.dockedCnt
for i := range ctx.Windows {
if ctx.Windows[i] == win {
if i+1 < len(ctx.Windows) {
copy(ctx.Windows[i:], ctx.Windows[i+1:])
}
ctx.Windows = ctx.Windows[:len(ctx.Windows)-1]
return
}
}
}
func (t *dockedTree) Undock(win *Window) {
t.Walk(func(w *Window) *Window {
if w == win {
return nil
}
return w
})
win.flags &= ^windowDocked
win.layout.Flags &= ^windowDocked
win.Bounds.H = win.undockedSz.Y
win.Bounds.W = win.undockedSz.X
win.idx = len(win.ctx.Windows)
win.ctx.Windows = append(win.ctx.Windows, win)
}
func (t *dockedTree) Scale(win *Window, delta image.Point, scaling float64) image.Point {
if t == nil || (delta.X == 0 && delta.Y == 0) {
return image.Point{}
}
switch t.Type {
case dockedNodeVert:
d0 := t.Child[0].Scale(win, delta, scaling)
if d0.X != 0 {
t.Split.Size += int(float64(d0.X) / scaling)
if t.Split.Size <= t.Split.MinSize {
t.Split.Size = t.Split.MinSize
}
d0.X = 0
}
if d0 != image.ZP {
return d0
}
return t.Child[1].Scale(win, delta, scaling)
case dockedNodeHoriz:
d0 := t.Child[0].Scale(win, delta, scaling)
if d0.Y != 0 {
t.Split.Size += int(float64(d0.Y) / scaling)
if t.Split.Size <= t.Split.MinSize {
t.Split.Size = t.Split.MinSize
}
d0.Y = 0
}
if d0 != image.ZP {
return d0
}
return t.Child[1].Scale(win, delta, scaling)
case dockedNodeLeaf:
if t.W == win {
return delta
}
}
return image.Point{}
}
func (ctx *context) ResetWindows() *DockSplit {
ctx.DockedWindows = dockedTree{}
ctx.Windows = ctx.Windows[:1]
ctx.dockedCnt = 0
return &DockSplit{ctx, &ctx.DockedWindows}
}
type DockSplit struct {
ctx *context
node *dockedTree
}
func (ds *DockSplit) Split(horiz bool, size int) (left, right *DockSplit) {
if horiz {
ds.node.Type = dockedNodeHoriz
} else {
ds.node.Type = dockedNodeVert
}
ds.node.Split.Size = size
ds.node.Child[0] = &dockedTree{Type: dockedNodeLeaf, Split: ScalableSplit{MinSize: 40}}
ds.node.Child[1] = &dockedTree{Type: dockedNodeLeaf, Split: ScalableSplit{MinSize: 40}}
return &DockSplit{ds.ctx, ds.node.Child[0]}, &DockSplit{ds.ctx, ds.node.Child[1]}
}
func (ds *DockSplit) Open(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn) {
ds.ctx.popupOpen(title, flags, rect, scale, updateFn)
ds.node.Type = dockedNodeLeaf
ds.node.W = ds.ctx.Windows[len(ds.ctx.Windows)-1]
ds.ctx.dockWindow(ds.node.W)
}
func percentages(bounds rect.Rect, f float64) (r [4]rect.Rect) {
pw := int(float64(bounds.W) * f)
ph := int(float64(bounds.H) * f)
// horizontal split
r[0] = bounds
r[0].H = ph
r[1] = bounds
r[1].Y += r[1].H - ph
r[1].H = ph
// vertical split
r[2] = bounds
r[2].W = pw
r[3] = bounds
r[3].X += r[3].W - pw
r[3].W = pw
return
}
func clip(dst *image.RGBA, r *image.Rectangle, src image.Image, sp *image.Point) {
orig := r.Min
*r = r.Intersect(dst.Bounds())
*r = r.Intersect(src.Bounds().Add(orig.Sub(*sp)))
dx := r.Min.X - orig.X
dy := r.Min.Y - orig.Y
if dx == 0 && dy == 0 {
return
}
sp.X += dx
sp.Y += dy
}
func drawFill(dst *image.RGBA, r image.Rectangle, src *image.Uniform, sp image.Point, op draw.Op) {
clip(dst, &r, src, &sp)
if r.Empty() {
return
}
sr, sg, sb, sa := src.RGBA()
switch op {
case draw.Over:
drawFillOver(dst, r, sr, sg, sb, sa)
case draw.Src:
drawFillSrc(dst, r, sr, sg, sb, sa)
default:
draw.Draw(dst, r, src, sp, op)
}
}
func drawFillSrc(dst *image.RGBA, r image.Rectangle, sr, sg, sb, sa uint32) {
sr8 := uint8(sr >> 8)
sg8 := uint8(sg >> 8)
sb8 := uint8(sb >> 8)
sa8 := uint8(sa >> 8)
// The built-in copy function is faster than a straightforward for loop to fill the destination with
// the color, but copy requires a slice source. We therefore use a for loop to fill the first row, and
// then use the first row as the slice source for the remaining rows.
i0 := dst.PixOffset(r.Min.X, r.Min.Y)
i1 := i0 + r.Dx()*4
for i := i0; i < i1; i += 4 {
dst.Pix[i+0] = sr8
dst.Pix[i+1] = sg8
dst.Pix[i+2] = sb8
dst.Pix[i+3] = sa8
}
firstRow := dst.Pix[i0:i1]
for y := r.Min.Y + 1; y < r.Max.Y; y++ {
i0 += dst.Stride
i1 += dst.Stride
copy(dst.Pix[i0:i1], firstRow)
}
}