251 lines
7.0 KiB
Go
251 lines
7.0 KiB
Go
package world
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
)
|
|
|
|
// fakeClipRect describes one clip rectangle in canvas pixel coordinates.
|
|
type fakeClipRect struct {
|
|
X, Y float64
|
|
W, H float64
|
|
}
|
|
|
|
// fakeDrawerState stores the active fake drawing state.
|
|
// The state is copied on Save and restored on Restore.
|
|
type fakeDrawerState struct {
|
|
StrokeColor color.RGBA
|
|
FillColor color.RGBA
|
|
LineWidth float64
|
|
Dashes []float64
|
|
DashOffset float64
|
|
Clips []fakeClipRect
|
|
}
|
|
|
|
// clone returns a deep copy of the state.
|
|
func (s fakeDrawerState) clone() fakeDrawerState {
|
|
out := s
|
|
out.Dashes = append([]float64(nil), s.Dashes...)
|
|
out.Clips = append([]fakeClipRect(nil), s.Clips...)
|
|
return out
|
|
}
|
|
|
|
// fakeDrawerCommand is one recorded drawer call together with a snapshot
|
|
// of the active fake drawing state at the moment of the call.
|
|
type fakeDrawerCommand struct {
|
|
Name string
|
|
Args []float64
|
|
StrokeColor color.RGBA
|
|
FillColor color.RGBA
|
|
LineWidth float64
|
|
Dashes []float64
|
|
DashOffset float64
|
|
Clips []fakeClipRect
|
|
}
|
|
|
|
// String returns a compact debug representation useful in assertion failures.
|
|
func (c fakeDrawerCommand) String() string {
|
|
return fmt.Sprintf(
|
|
"%s args=%v stroke=%v fill=%v lineWidth=%v dashes=%v dashOffset=%v clips=%v",
|
|
c.Name,
|
|
c.Args,
|
|
c.StrokeColor,
|
|
c.FillColor,
|
|
c.LineWidth,
|
|
c.Dashes,
|
|
c.DashOffset,
|
|
c.Clips,
|
|
)
|
|
}
|
|
|
|
// fakePrimitiveDrawer is a reusable PrimitiveDrawer test double.
|
|
// It records all calls and emulates stateful behavior, including nested
|
|
// Save/Restore and clip reset semantics.
|
|
type fakePrimitiveDrawer struct {
|
|
commands []fakeDrawerCommand
|
|
state fakeDrawerState
|
|
stack []fakeDrawerState
|
|
}
|
|
|
|
// Ensure fakePrimitiveDrawer implements PrimitiveDrawer.
|
|
var _ PrimitiveDrawer = (*fakePrimitiveDrawer)(nil)
|
|
|
|
// rgbaColor converts any color.Color into a comparable RGBA value.
|
|
func rgbaColor(c color.Color) color.RGBA {
|
|
if c == nil {
|
|
return color.RGBA{}
|
|
}
|
|
return color.RGBAModel.Convert(c).(color.RGBA)
|
|
}
|
|
|
|
// snapshotCommand records one command together with the current state snapshot.
|
|
func (d *fakePrimitiveDrawer) snapshotCommand(name string, args ...float64) {
|
|
cmd := fakeDrawerCommand{
|
|
Name: name,
|
|
Args: append([]float64(nil), args...),
|
|
StrokeColor: d.state.StrokeColor,
|
|
FillColor: d.state.FillColor,
|
|
LineWidth: d.state.LineWidth,
|
|
Dashes: append([]float64(nil), d.state.Dashes...),
|
|
DashOffset: d.state.DashOffset,
|
|
Clips: append([]fakeClipRect(nil), d.state.Clips...),
|
|
}
|
|
d.commands = append(d.commands, cmd)
|
|
}
|
|
|
|
// Save stores the current fake state.
|
|
func (d *fakePrimitiveDrawer) Save() {
|
|
d.stack = append(d.stack, d.state.clone())
|
|
d.snapshotCommand("Save")
|
|
}
|
|
|
|
// Restore restores the most recently saved fake state.
|
|
func (d *fakePrimitiveDrawer) Restore() {
|
|
if len(d.stack) == 0 {
|
|
panic("fakePrimitiveDrawer: Restore without matching Save")
|
|
}
|
|
|
|
d.state = d.stack[len(d.stack)-1]
|
|
d.stack = d.stack[:len(d.stack)-1]
|
|
d.snapshotCommand("Restore")
|
|
}
|
|
|
|
// ResetClip clears the current fake clip stack.
|
|
func (d *fakePrimitiveDrawer) ResetClip() {
|
|
d.state.Clips = nil
|
|
d.snapshotCommand("ResetClip")
|
|
}
|
|
|
|
// ClipRect appends one clip rectangle to the current fake state.
|
|
func (d *fakePrimitiveDrawer) ClipRect(x, y, w, h float64) {
|
|
d.state.Clips = append(d.state.Clips, fakeClipRect{X: x, Y: y, W: w, H: h})
|
|
d.snapshotCommand("ClipRect", x, y, w, h)
|
|
}
|
|
|
|
// SetStrokeColor sets the current fake stroke color.
|
|
func (d *fakePrimitiveDrawer) SetStrokeColor(c color.Color) {
|
|
d.state.StrokeColor = rgbaColor(c)
|
|
d.snapshotCommand("SetStrokeColor")
|
|
}
|
|
|
|
// SetFillColor sets the current fake fill color.
|
|
func (d *fakePrimitiveDrawer) SetFillColor(c color.Color) {
|
|
d.state.FillColor = rgbaColor(c)
|
|
d.snapshotCommand("SetFillColor")
|
|
}
|
|
|
|
// SetLineWidth sets the current fake line width.
|
|
func (d *fakePrimitiveDrawer) SetLineWidth(width float64) {
|
|
d.state.LineWidth = width
|
|
d.snapshotCommand("SetLineWidth", width)
|
|
}
|
|
|
|
// SetDash sets the current fake dash pattern.
|
|
func (d *fakePrimitiveDrawer) SetDash(dashes ...float64) {
|
|
d.state.Dashes = append([]float64(nil), dashes...)
|
|
d.snapshotCommand("SetDash", dashes...)
|
|
}
|
|
|
|
// SetDashOffset sets the current fake dash offset.
|
|
func (d *fakePrimitiveDrawer) SetDashOffset(offset float64) {
|
|
d.state.DashOffset = offset
|
|
d.snapshotCommand("SetDashOffset", offset)
|
|
}
|
|
|
|
// AddPoint records a point path append command.
|
|
func (d *fakePrimitiveDrawer) AddPoint(x, y, r float64) {
|
|
d.snapshotCommand("AddPoint", x, y, r)
|
|
}
|
|
|
|
// AddLine records a line path append command.
|
|
func (d *fakePrimitiveDrawer) AddLine(x1, y1, x2, y2 float64) {
|
|
d.snapshotCommand("AddLine", x1, y1, x2, y2)
|
|
}
|
|
|
|
// AddCircle records a circle path append command.
|
|
func (d *fakePrimitiveDrawer) AddCircle(cx, cy, r float64) {
|
|
d.snapshotCommand("AddCircle", cx, cy, r)
|
|
}
|
|
|
|
// Stroke records a stroke finalization command.
|
|
func (d *fakePrimitiveDrawer) Stroke() {
|
|
d.snapshotCommand("Stroke")
|
|
}
|
|
|
|
// Fill records a fill finalization command.
|
|
func (d *fakePrimitiveDrawer) Fill() {
|
|
d.snapshotCommand("Fill")
|
|
}
|
|
|
|
// Commands returns a defensive copy of the recorded command log.
|
|
func (d *fakePrimitiveDrawer) Commands() []fakeDrawerCommand {
|
|
out := make([]fakeDrawerCommand, len(d.commands))
|
|
copy(out, d.commands)
|
|
return out
|
|
}
|
|
|
|
// CommandNames returns only command names in call order.
|
|
func (d *fakePrimitiveDrawer) CommandNames() []string {
|
|
out := make([]string, 0, len(d.commands))
|
|
for _, cmd := range d.commands {
|
|
out = append(out, cmd.Name)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// CommandsByName returns all commands with the given name.
|
|
func (d *fakePrimitiveDrawer) CommandsByName(name string) []fakeDrawerCommand {
|
|
var out []fakeDrawerCommand
|
|
for _, cmd := range d.commands {
|
|
if cmd.Name == name {
|
|
out = append(out, cmd)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// LastCommand returns the last recorded command and whether it exists.
|
|
func (d *fakePrimitiveDrawer) LastCommand() (fakeDrawerCommand, bool) {
|
|
if len(d.commands) == 0 {
|
|
return fakeDrawerCommand{}, false
|
|
}
|
|
return d.commands[len(d.commands)-1], true
|
|
}
|
|
|
|
// CurrentState returns a defensive copy of the current fake state.
|
|
func (d *fakePrimitiveDrawer) CurrentState() fakeDrawerState {
|
|
return d.state.clone()
|
|
}
|
|
|
|
// SaveDepth returns the current Save/Restore nesting depth.
|
|
func (d *fakePrimitiveDrawer) SaveDepth() int {
|
|
return len(d.stack)
|
|
}
|
|
|
|
// ResetLog clears only the command log and keeps the current state intact.
|
|
func (d *fakePrimitiveDrawer) ResetLog() {
|
|
d.commands = nil
|
|
}
|
|
|
|
func (d *fakePrimitiveDrawer) CopyShift(dx, dy int) {
|
|
d.snapshotCommand("CopyShift", float64(dx), float64(dy))
|
|
}
|
|
|
|
func (d *fakePrimitiveDrawer) ClearAllTo(_ color.Color) {
|
|
// Store as a command; tests usually only care that it was called.
|
|
d.snapshotCommand("ClearAllTo")
|
|
}
|
|
|
|
func (d *fakePrimitiveDrawer) ClearRectTo(x, y, w, h int, _ color.Color) {
|
|
d.snapshotCommand("ClearRectTo", float64(x), float64(y), float64(w), float64(h))
|
|
}
|
|
|
|
func (d *fakePrimitiveDrawer) DrawImage(_ image.Image, x, y int) {
|
|
d.snapshotCommand("DrawImage", float64(x), float64(y))
|
|
}
|
|
|
|
func (d *fakePrimitiveDrawer) DrawImageScaled(_ image.Image, x, y, w, h int) {
|
|
d.snapshotCommand("DrawImageScaled", float64(x), float64(y), float64(w), float64(h))
|
|
}
|