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.
658 lines
15 KiB
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)
|
|
}
|
|
}
|