github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/framework/context.go (about)

     1  package framework
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	gkey "github.com/gop9/olt/gio/io/key"
     7  	"github.com/gop9/olt/gio/io/pointer"
     8  	"image"
     9  	"image/draw"
    10  	"io"
    11  	"time"
    12  
    13  	"github.com/gop9/olt/framework/command"
    14  	"github.com/gop9/olt/framework/rect"
    15  	"github.com/gop9/olt/framework/style"
    16  )
    17  
    18  const perfUpdate = false
    19  const dumpFrame = false
    20  
    21  var UnknownCommandErr = errors.New("unknown command")
    22  
    23  type context struct {
    24  	mw             MasterWindow
    25  	Input          Input
    26  	Style          style.Style
    27  	Windows        []*Window
    28  	DockedWindows  dockedTree
    29  	changed        int32
    30  	activateEditor *TextEditor
    31  	cmds           []command.Command
    32  	trashFrame     bool
    33  	autopos        image.Point
    34  
    35  	finalCmds command.Buffer
    36  
    37  	dockedWindowFocus int
    38  	floatWindowFocus  int
    39  	scrollwheelFocus  int
    40  	dockedCnt         int
    41  
    42  	cmdstim []time.Duration // contains timing for all commands
    43  }
    44  
    45  func contextAllCommands(ctx *context) {
    46  	ctx.cmds = ctx.cmds[:0]
    47  	for i, w := range ctx.Windows {
    48  		ctx.cmds = append(ctx.cmds, w.cmds.Commands...)
    49  		if i == 0 {
    50  			ctx.DockedWindows.Walk(func(w *Window) *Window {
    51  				ctx.cmds = append(ctx.cmds, w.cmds.Commands...)
    52  				return w
    53  			})
    54  		}
    55  	}
    56  	ctx.cmds = append(ctx.cmds, ctx.finalCmds.Commands...)
    57  }
    58  
    59  func (ctx *context) setupMasterWindow(layout *panel, updatefn UpdateFn) {
    60  	ctx.Windows = append(ctx.Windows, createWindow(ctx, ""))
    61  	ctx.Windows[0].idx = 0
    62  	ctx.Windows[0].layout = layout
    63  	ctx.Windows[0].flags = layout.Flags | WindowNonmodal
    64  	ctx.Windows[0].updateFn = updatefn
    65  }
    66  
    67  func (ctx *context) Update() {
    68  	for count := 0; count < 2; count++ {
    69  		contextBegin(ctx, ctx.Windows[0].layout)
    70  		for i := 0; i < len(ctx.Windows); i++ {
    71  			ctx.Windows[i].began = false
    72  		}
    73  		ctx.Restack()
    74  		ctx.FindFocus()
    75  		for i := 0; i < len(ctx.Windows); i++ { // this must not use range or tooltips won't work
    76  			ctx.updateWindow(ctx.Windows[i])
    77  			if i == 0 {
    78  				t := ctx.DockedWindows.Update(ctx.Windows[0].Bounds, ctx.Style.Scaling)
    79  				if t != nil {
    80  					ctx.DockedWindows = *t
    81  				}
    82  			}
    83  		}
    84  		contextEnd(ctx)
    85  		if !ctx.trashFrame {
    86  			break
    87  		} else {
    88  			ctx.Reset()
    89  		}
    90  	}
    91  }
    92  
    93  func (ctx *context) updateWindow(win *Window) {
    94  	if win.updateFn != nil {
    95  		win.specialPanelBegin()
    96  		win.updateFn(win)
    97  	}
    98  
    99  	if !win.began {
   100  		win.close = true
   101  		return
   102  	}
   103  
   104  	if win.title == tooltipWindowTitle {
   105  		win.close = true
   106  	}
   107  
   108  	if win.flags&windowPopup != 0 {
   109  		panelEnd(ctx, win)
   110  	}
   111  }
   112  
   113  func (ctx *context) processKeyEvent(e gkey.Event, textbuffer *bytes.Buffer) {
   114  	if e.Direction == gkey.DirRelease {
   115  		return
   116  	}
   117  
   118  	evinNotext := func() {
   119  		for _, k := range ctx.Input.Keyboard.Keys {
   120  			if k.Code == e.Code {
   121  				k.Modifiers |= e.Modifiers
   122  				return
   123  			}
   124  		}
   125  		ctx.Input.Keyboard.Keys = append(ctx.Input.Keyboard.Keys, e)
   126  	}
   127  	evinText := func() {
   128  		if e.Modifiers == 0 || e.Modifiers == gkey.ModShift {
   129  			io.WriteString(textbuffer, string(e.Rune))
   130  		}
   131  
   132  		evinNotext()
   133  	}
   134  
   135  	switch {
   136  	case e.Code == gkey.CodeUnknown:
   137  		if e.Rune > 0 {
   138  			evinText()
   139  		}
   140  	case (e.Code >= gkey.CodeA && e.Code <= gkey.Code0) || e.Code == gkey.CodeSpacebar || e.Code == gkey.CodeHyphenMinus || e.Code == gkey.CodeEqualSign || e.Code == gkey.CodeLeftSquareBracket || e.Code == gkey.CodeRightSquareBracket || e.Code == gkey.CodeBackslash || e.Code == gkey.CodeSemicolon || e.Code == gkey.CodeApostrophe || e.Code == gkey.CodeGraveAccent || e.Code == gkey.CodeComma || e.Code == gkey.CodeFullStop || e.Code == gkey.CodeSlash || (e.Code >= gkey.CodeKeypadSlash && e.Code <= gkey.CodeKeypadPlusSign) || (e.Code >= gkey.CodeKeypad1 && e.Code <= gkey.CodeKeypadEqualSign):
   141  		evinText()
   142  	case e.Code == gkey.CodeTab:
   143  		e.Rune = '\t'
   144  		evinText()
   145  	case e.Code == gkey.CodeReturnEnter || e.Code == gkey.CodeKeypadEnter:
   146  		e.Rune = '\n'
   147  		evinText()
   148  	default:
   149  		evinNotext()
   150  	}
   151  }
   152  
   153  func contextBegin(ctx *context, layout *panel) {
   154  	for _, w := range ctx.Windows {
   155  		w.usingSub = false
   156  		w.curNode = w.rootNode
   157  		w.close = false
   158  		w.widgets.reset()
   159  		w.cmds.Reset()
   160  	}
   161  	ctx.finalCmds.Reset()
   162  	ctx.DockedWindows.Walk(func(w *Window) *Window {
   163  		w.usingSub = false
   164  		w.curNode = w.rootNode
   165  		w.close = false
   166  		w.widgets.reset()
   167  		w.cmds.Reset()
   168  		return w
   169  	})
   170  
   171  	ctx.trashFrame = false
   172  	ctx.Windows[0].layout = layout
   173  	panelBegin(ctx, ctx.Windows[0], "")
   174  	layout.Offset = &ctx.Windows[0].Scrollbar
   175  }
   176  
   177  func contextEnd(ctx *context) {
   178  	panelEnd(ctx, ctx.Windows[0])
   179  }
   180  
   181  func (ctx *context) Reset() {
   182  	prevNumWindows := len(ctx.Windows)
   183  	for i := 0; i < len(ctx.Windows); i++ {
   184  		if ctx.Windows[i].close {
   185  			if i != len(ctx.Windows)-1 {
   186  				copy(ctx.Windows[i:], ctx.Windows[i+1:])
   187  				i--
   188  			}
   189  			ctx.Windows = ctx.Windows[:len(ctx.Windows)-1]
   190  		}
   191  	}
   192  	for i := range ctx.Windows {
   193  		ctx.Windows[i].idx = i
   194  	}
   195  	if prevNumWindows == 2 && len(ctx.Windows) == 1 && ctx.Input.Mouse.valid {
   196  		ctx.DockedWindows.Walk(func(w *Window) *Window {
   197  			if w.flags&windowDocked == 0 {
   198  				return w
   199  			}
   200  			for _, b := range []pointer.Buttons{pointer.ButtonLeft, pointer.ButtonRight, pointer.ButtonMiddle} {
   201  				btn := ctx.Input.Mouse.Buttons[b]
   202  				if btn.Clicked && w.Bounds.Contains(btn.ClickedPos) {
   203  					ctx.dockedWindowFocus = w.idx
   204  					return w
   205  				}
   206  			}
   207  			return w
   208  		})
   209  	}
   210  	ctx.activateEditor = nil
   211  	in := &ctx.Input
   212  	in.Mouse.Buttons[pointer.ButtonLeft].Clicked = false
   213  	in.Mouse.Buttons[pointer.ButtonMiddle].Clicked = false
   214  	in.Mouse.Buttons[pointer.ButtonRight].Clicked = false
   215  	in.Mouse.ScrollDelta = 0
   216  	in.Mouse.Prev.X = in.Mouse.Pos.X
   217  	in.Mouse.Prev.Y = in.Mouse.Pos.Y
   218  	in.Mouse.Delta = image.Point{}
   219  	in.Keyboard.Keys = in.Keyboard.Keys[0:0]
   220  }
   221  
   222  func (ctx *context) Restack() {
   223  	clicked := false
   224  	for _, b := range []pointer.Buttons{pointer.ButtonLeft, pointer.ButtonRight, pointer.ButtonMiddle} {
   225  		if ctx.Input.Mouse.Buttons[b].Clicked && ctx.Input.Mouse.Buttons[b].Down {
   226  			clicked = true
   227  			break
   228  		}
   229  	}
   230  	if !clicked {
   231  		return
   232  	}
   233  	ctx.dockedWindowFocus = 0
   234  	nonmodalToplevel := false
   235  	var toplevelIdx int
   236  	for i := len(ctx.Windows) - 1; i >= 0; i-- {
   237  		if ctx.Windows[i].flags&windowTooltip == 0 {
   238  			toplevelIdx = i
   239  			nonmodalToplevel = ctx.Windows[i].flags&WindowNonmodal != 0
   240  			break
   241  		}
   242  	}
   243  	if !nonmodalToplevel {
   244  		return
   245  	}
   246  	// toplevel window is non-modal, proceed to change the stacking order if
   247  	// the user clicked outside of it
   248  	restacked := false
   249  	found := false
   250  	for i := len(ctx.Windows) - 1; i > 0; i-- {
   251  		if ctx.Windows[i].flags&windowTooltip != 0 {
   252  			continue
   253  		}
   254  		if ctx.restackClick(ctx.Windows[i]) {
   255  			found = true
   256  			if toplevelIdx != i {
   257  				newToplevel := ctx.Windows[i]
   258  				copy(ctx.Windows[i:toplevelIdx], ctx.Windows[i+1:toplevelIdx+1])
   259  				ctx.Windows[toplevelIdx] = newToplevel
   260  				restacked = true
   261  			}
   262  			break
   263  		}
   264  	}
   265  	if restacked {
   266  		for i := range ctx.Windows {
   267  			ctx.Windows[i].idx = i
   268  		}
   269  	}
   270  	if found {
   271  		return
   272  	}
   273  	ctx.DockedWindows.Walk(func(w *Window) *Window {
   274  		if ctx.restackClick(w) && (w.flags&windowDocked != 0) {
   275  			ctx.dockedWindowFocus = w.idx
   276  		}
   277  		return w
   278  	})
   279  }
   280  
   281  func (ctx *context) FindFocus() {
   282  	ctx.floatWindowFocus = 0
   283  	for i := len(ctx.Windows) - 1; i >= 0; i-- {
   284  		if ctx.Windows[i].flags&windowTooltip == 0 {
   285  			ctx.floatWindowFocus = i
   286  			break
   287  		}
   288  	}
   289  	ctx.scrollwheelFocus = 0
   290  	for i := len(ctx.Windows) - 1; i > 0; i-- {
   291  		if ctx.Windows[i].Bounds.Contains(ctx.Input.Mouse.Pos) {
   292  			ctx.scrollwheelFocus = i
   293  			break
   294  		}
   295  	}
   296  	if ctx.scrollwheelFocus == 0 {
   297  		ctx.DockedWindows.Walk(func(w *Window) *Window {
   298  			if w.Bounds.Contains(ctx.Input.Mouse.Pos) {
   299  				ctx.scrollwheelFocus = w.idx
   300  			}
   301  			return w
   302  		})
   303  	}
   304  }
   305  
   306  func (ctx *context) Walk(fn WindowWalkFn) {
   307  	fn(ctx.Windows[0].title, ctx.Windows[0].Data, false, 0, ctx.Windows[0].Bounds)
   308  	ctx.DockedWindows.walkExt(func(t *dockedTree) {
   309  		switch t.Type {
   310  		case dockedNodeHoriz:
   311  			fn("", nil, true, t.Split.Size, rect.Rect{})
   312  		case dockedNodeVert:
   313  			fn("", nil, true, -t.Split.Size, rect.Rect{})
   314  		case dockedNodeLeaf:
   315  			if t.W == nil {
   316  				fn("", nil, true, 0, rect.Rect{})
   317  			} else {
   318  				fn(t.W.title, t.W.Data, true, 0, t.W.Bounds)
   319  			}
   320  		}
   321  	})
   322  	for _, win := range ctx.Windows[1:] {
   323  		if win.flags&WindowNonmodal != 0 {
   324  			fn(win.title, win.Data, false, 0, win.Bounds)
   325  		}
   326  	}
   327  }
   328  
   329  func (ctx *context) restackClick(w *Window) bool {
   330  	if !ctx.Input.Mouse.valid {
   331  		return false
   332  	}
   333  	for _, b := range []pointer.Buttons{pointer.ButtonLeft, pointer.ButtonRight, pointer.ButtonMiddle} {
   334  		btn := ctx.Input.Mouse.Buttons[b]
   335  		if btn.Clicked && btn.Down && w.Bounds.Contains(btn.ClickedPos) {
   336  			return true
   337  		}
   338  	}
   339  	return false
   340  }
   341  
   342  type dockedNodeType uint8
   343  
   344  const (
   345  	dockedNodeLeaf dockedNodeType = iota
   346  	dockedNodeVert
   347  	dockedNodeHoriz
   348  )
   349  
   350  type dockedTree struct {
   351  	Type  dockedNodeType
   352  	Split ScalableSplit
   353  	Child [2]*dockedTree
   354  	W     *Window
   355  }
   356  
   357  func (t *dockedTree) Update(bounds rect.Rect, scaling float64) *dockedTree {
   358  	if t == nil {
   359  		return nil
   360  	}
   361  	switch t.Type {
   362  	case dockedNodeVert:
   363  		b0, b1, _ := t.Split.verticalnw(bounds, scaling)
   364  		t.Child[0] = t.Child[0].Update(b0, scaling)
   365  		t.Child[1] = t.Child[1].Update(b1, scaling)
   366  	case dockedNodeHoriz:
   367  		b0, b1, _ := t.Split.horizontalnw(bounds, scaling)
   368  		t.Child[0] = t.Child[0].Update(b0, scaling)
   369  		t.Child[1] = t.Child[1].Update(b1, scaling)
   370  	case dockedNodeLeaf:
   371  		if t.W != nil {
   372  			t.W.Bounds = bounds
   373  			t.W.ctx.updateWindow(t.W)
   374  			if t.W == nil {
   375  				return nil
   376  			}
   377  			if t.W.close {
   378  				t.W = nil
   379  				return nil
   380  			}
   381  			return t
   382  		}
   383  		return nil
   384  	}
   385  	if t.Child[0] == nil {
   386  		return t.Child[1]
   387  	}
   388  	if t.Child[1] == nil {
   389  		return t.Child[0]
   390  	}
   391  	return t
   392  }
   393  
   394  func (t *dockedTree) walkExt(fn func(t *dockedTree)) {
   395  	if t == nil {
   396  		return
   397  	}
   398  	switch t.Type {
   399  	case dockedNodeVert, dockedNodeHoriz:
   400  		fn(t)
   401  		t.Child[0].walkExt(fn)
   402  		t.Child[1].walkExt(fn)
   403  	case dockedNodeLeaf:
   404  		fn(t)
   405  	}
   406  }
   407  
   408  func (t *dockedTree) Walk(fn func(t *Window) *Window) {
   409  	t.walkExt(func(t *dockedTree) {
   410  		if t.Type == dockedNodeLeaf && t.W != nil {
   411  			t.W = fn(t.W)
   412  		}
   413  	})
   414  }
   415  
   416  func newDockedLeaf(win *Window) *dockedTree {
   417  	r := &dockedTree{Type: dockedNodeLeaf, W: win}
   418  	r.Split.MinSize = 40
   419  	return r
   420  }
   421  
   422  func (t *dockedTree) Dock(win *Window, pos image.Point, bounds rect.Rect, scaling float64) (bool, rect.Rect) {
   423  	if t == nil {
   424  		return false, rect.Rect{}
   425  	}
   426  	switch t.Type {
   427  	case dockedNodeVert:
   428  		b0, b1, _ := t.Split.verticalnw(bounds, scaling)
   429  		canDock, r := t.Child[0].Dock(win, pos, b0, scaling)
   430  		if canDock {
   431  			return canDock, r
   432  		}
   433  		canDock, r = t.Child[1].Dock(win, pos, b1, scaling)
   434  		if canDock {
   435  			return canDock, r
   436  		}
   437  	case dockedNodeHoriz:
   438  		b0, b1, _ := t.Split.horizontalnw(bounds, scaling)
   439  		canDock, r := t.Child[0].Dock(win, pos, b0, scaling)
   440  		if canDock {
   441  			return canDock, r
   442  		}
   443  		canDock, r = t.Child[1].Dock(win, pos, b1, scaling)
   444  		if canDock {
   445  			return canDock, r
   446  		}
   447  	case dockedNodeLeaf:
   448  		v := percentages(bounds, 0.03)
   449  		for i := range v {
   450  			if v[i].Contains(pos) {
   451  				if t.W == nil {
   452  					if win != nil {
   453  						t.W = win
   454  						win.ctx.dockWindow(win)
   455  					}
   456  					return true, bounds
   457  				}
   458  				w := percentages(bounds, 0.5)
   459  				if win != nil {
   460  					if i < 2 {
   461  						// horizontal split
   462  						t.Type = dockedNodeHoriz
   463  						t.Split.Size = int(float64(w[0].H) / scaling)
   464  						t.Child[i] = newDockedLeaf(win)
   465  						t.Child[-i+1] = newDockedLeaf(t.W)
   466  					} else {
   467  						// vertical split
   468  						t.Type = dockedNodeVert
   469  						t.Split.Size = int(float64(w[2].W) / scaling)
   470  						t.Child[i-2] = newDockedLeaf(win)
   471  						t.Child[-(i-2)+1] = newDockedLeaf(t.W)
   472  					}
   473  
   474  					t.W = nil
   475  					win.ctx.dockWindow(win)
   476  				}
   477  				return true, w[i]
   478  			}
   479  		}
   480  	}
   481  	return false, rect.Rect{}
   482  }
   483  
   484  func (ctx *context) dockWindow(win *Window) {
   485  	win.undockedSz = image.Point{win.Bounds.W, win.Bounds.H}
   486  	win.flags |= windowDocked
   487  	win.layout.Flags |= windowDocked
   488  	ctx.dockedCnt--
   489  	win.idx = ctx.dockedCnt
   490  	for i := range ctx.Windows {
   491  		if ctx.Windows[i] == win {
   492  			if i+1 < len(ctx.Windows) {
   493  				copy(ctx.Windows[i:], ctx.Windows[i+1:])
   494  			}
   495  			ctx.Windows = ctx.Windows[:len(ctx.Windows)-1]
   496  			return
   497  		}
   498  	}
   499  }
   500  
   501  func (t *dockedTree) Undock(win *Window) {
   502  	t.Walk(func(w *Window) *Window {
   503  		if w == win {
   504  			return nil
   505  		}
   506  		return w
   507  	})
   508  	win.flags &= ^windowDocked
   509  	win.layout.Flags &= ^windowDocked
   510  	win.Bounds.H = win.undockedSz.Y
   511  	win.Bounds.W = win.undockedSz.X
   512  	win.idx = len(win.ctx.Windows)
   513  	win.ctx.Windows = append(win.ctx.Windows, win)
   514  }
   515  
   516  func (t *dockedTree) Scale(win *Window, delta image.Point, scaling float64) image.Point {
   517  	if t == nil || (delta.X == 0 && delta.Y == 0) {
   518  		return image.Point{}
   519  	}
   520  	switch t.Type {
   521  	case dockedNodeVert:
   522  		d0 := t.Child[0].Scale(win, delta, scaling)
   523  		if d0.X != 0 {
   524  			t.Split.Size += int(float64(d0.X) / scaling)
   525  			if t.Split.Size <= t.Split.MinSize {
   526  				t.Split.Size = t.Split.MinSize
   527  			}
   528  			d0.X = 0
   529  		}
   530  		if d0 != image.ZP {
   531  			return d0
   532  		}
   533  		return t.Child[1].Scale(win, delta, scaling)
   534  	case dockedNodeHoriz:
   535  		d0 := t.Child[0].Scale(win, delta, scaling)
   536  		if d0.Y != 0 {
   537  			t.Split.Size += int(float64(d0.Y) / scaling)
   538  			if t.Split.Size <= t.Split.MinSize {
   539  				t.Split.Size = t.Split.MinSize
   540  			}
   541  			d0.Y = 0
   542  		}
   543  		if d0 != image.ZP {
   544  			return d0
   545  		}
   546  		return t.Child[1].Scale(win, delta, scaling)
   547  	case dockedNodeLeaf:
   548  		if t.W == win {
   549  			return delta
   550  		}
   551  	}
   552  	return image.Point{}
   553  }
   554  
   555  func (ctx *context) ResetWindows() *DockSplit {
   556  	ctx.DockedWindows = dockedTree{}
   557  	ctx.Windows = ctx.Windows[:1]
   558  	ctx.dockedCnt = 0
   559  	return &DockSplit{ctx, &ctx.DockedWindows}
   560  }
   561  
   562  type DockSplit struct {
   563  	ctx  *context
   564  	node *dockedTree
   565  }
   566  
   567  func (ds *DockSplit) Split(horiz bool, size int) (left, right *DockSplit) {
   568  	if horiz {
   569  		ds.node.Type = dockedNodeHoriz
   570  	} else {
   571  		ds.node.Type = dockedNodeVert
   572  	}
   573  	ds.node.Split.Size = size
   574  	ds.node.Child[0] = &dockedTree{Type: dockedNodeLeaf, Split: ScalableSplit{MinSize: 40}}
   575  	ds.node.Child[1] = &dockedTree{Type: dockedNodeLeaf, Split: ScalableSplit{MinSize: 40}}
   576  	return &DockSplit{ds.ctx, ds.node.Child[0]}, &DockSplit{ds.ctx, ds.node.Child[1]}
   577  }
   578  
   579  func (ds *DockSplit) Open(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn) {
   580  	ds.ctx.popupOpen(title, flags, rect, scale, updateFn)
   581  	ds.node.Type = dockedNodeLeaf
   582  	ds.node.W = ds.ctx.Windows[len(ds.ctx.Windows)-1]
   583  	ds.ctx.dockWindow(ds.node.W)
   584  }
   585  
   586  func percentages(bounds rect.Rect, f float64) (r [4]rect.Rect) {
   587  	pw := int(float64(bounds.W) * f)
   588  	ph := int(float64(bounds.H) * f)
   589  	// horizontal split
   590  	r[0] = bounds
   591  	r[0].H = ph
   592  	r[1] = bounds
   593  	r[1].Y += r[1].H - ph
   594  	r[1].H = ph
   595  
   596  	// vertical split
   597  	r[2] = bounds
   598  	r[2].W = pw
   599  	r[3] = bounds
   600  	r[3].X += r[3].W - pw
   601  	r[3].W = pw
   602  	return
   603  }
   604  
   605  func clip(dst *image.RGBA, r *image.Rectangle, src image.Image, sp *image.Point) {
   606  	orig := r.Min
   607  	*r = r.Intersect(dst.Bounds())
   608  	*r = r.Intersect(src.Bounds().Add(orig.Sub(*sp)))
   609  	dx := r.Min.X - orig.X
   610  	dy := r.Min.Y - orig.Y
   611  	if dx == 0 && dy == 0 {
   612  		return
   613  	}
   614  	sp.X += dx
   615  	sp.Y += dy
   616  }
   617  
   618  func drawFill(dst *image.RGBA, r image.Rectangle, src *image.Uniform, sp image.Point, op draw.Op) {
   619  	clip(dst, &r, src, &sp)
   620  	if r.Empty() {
   621  		return
   622  	}
   623  	sr, sg, sb, sa := src.RGBA()
   624  	switch op {
   625  	case draw.Over:
   626  		drawFillOver(dst, r, sr, sg, sb, sa)
   627  	case draw.Src:
   628  		drawFillSrc(dst, r, sr, sg, sb, sa)
   629  	default:
   630  		draw.Draw(dst, r, src, sp, op)
   631  	}
   632  }
   633  
   634  func drawFillSrc(dst *image.RGBA, r image.Rectangle, sr, sg, sb, sa uint32) {
   635  	sr8 := uint8(sr >> 8)
   636  	sg8 := uint8(sg >> 8)
   637  	sb8 := uint8(sb >> 8)
   638  	sa8 := uint8(sa >> 8)
   639  	// The built-in copy function is faster than a straightforward for loop to fill the destination with
   640  	// the color, but copy requires a slice source. We therefore use a for loop to fill the first row, and
   641  	// then use the first row as the slice source for the remaining rows.
   642  	i0 := dst.PixOffset(r.Min.X, r.Min.Y)
   643  	i1 := i0 + r.Dx()*4
   644  	for i := i0; i < i1; i += 4 {
   645  		dst.Pix[i+0] = sr8
   646  		dst.Pix[i+1] = sg8
   647  		dst.Pix[i+2] = sb8
   648  		dst.Pix[i+3] = sa8
   649  	}
   650  	firstRow := dst.Pix[i0:i1]
   651  	for y := r.Min.Y + 1; y < r.Max.Y; y++ {
   652  		i0 += dst.Stride
   653  		i1 += dst.Stride
   654  		copy(dst.Pix[i0:i1], firstRow)
   655  	}
   656  }