Wallpapers and Bounded Levels
Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
This commit is contained in:
parent
b4a366baa9
commit
bca848d534
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ maps/
|
||||||
bin/
|
bin/
|
||||||
screenshot-*.png
|
screenshot-*.png
|
||||||
map-*.json
|
map-*.json
|
||||||
|
pkg/wallpaper/*.png
|
||||||
|
|
BIN
assets/wallpapers/notebook.png
Normal file
BIN
assets/wallpapers/notebook.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
|
@ -35,3 +35,7 @@ like `#FF00FF99` for 153 ($99) on the alpha channel.
|
||||||
* `D_SCROLL_SPEED=8`: Canvas scroll speed when using the keyboard arrows
|
* `D_SCROLL_SPEED=8`: Canvas scroll speed when using the keyboard arrows
|
||||||
in the Editor Mode, in pixels per tick.
|
in the Editor Mode, in pixels per tick.
|
||||||
* `D_DOODAD_SIZE=100`: Default size when creating a new Doodad.
|
* `D_DOODAD_SIZE=100`: Default size when creating a new Doodad.
|
||||||
|
|
||||||
|
Development booleans for unit tests (set to any non-empty value):
|
||||||
|
|
||||||
|
* `T_WALLPAPER_PNG` for pkg/wallpaper to output PNG images.
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"git.kirsle.net/apps/doodle"
|
"git.kirsle.net/apps/doodle"
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
"git.kirsle.net/apps/doodle/render/sdl"
|
"git.kirsle.net/apps/doodle/render/sdl"
|
||||||
|
|
||||||
|
_ "image/png"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build number is the git commit hash.
|
// Build number is the git commit hash.
|
||||||
|
|
66
docs/Doodad Scripts.md
Normal file
66
docs/Doodad Scripts.md
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# Doodad Scripting Engine
|
||||||
|
|
||||||
|
Some ideas for the scripting engine for Doodads inside your level.
|
||||||
|
|
||||||
|
# Architecture
|
||||||
|
|
||||||
|
The script will be an "attached file" in the Doodad format as a special file
|
||||||
|
named "index.js" as the entry point.
|
||||||
|
|
||||||
|
Each Doodad will have its `index.js` script loaded into an isolated JS
|
||||||
|
environment where it can't access any data about other Doodads or anything
|
||||||
|
user specific. The `main()` function is called so the Doodad script can
|
||||||
|
set itself up.
|
||||||
|
|
||||||
|
The `main()` function should:
|
||||||
|
|
||||||
|
* Initialize any state variables the Doodad wants to use in its script.
|
||||||
|
* Subscribe to callback events that the Doodad is interested in catching.
|
||||||
|
|
||||||
|
The script interacts with the Doodle application through an API broker object
|
||||||
|
(a Go surface area of functions).
|
||||||
|
|
||||||
|
# API Broker Interface
|
||||||
|
|
||||||
|
```go
|
||||||
|
type API interface {
|
||||||
|
// "Self" functions.
|
||||||
|
SetFrame(frame int) // Set the currently visible frame in this Doodad.
|
||||||
|
MoveTo(render.Point)
|
||||||
|
|
||||||
|
// Game functions.k
|
||||||
|
EndLevel() // Exit the current level with a victory
|
||||||
|
|
||||||
|
/************************************
|
||||||
|
* Event Handler Callback Functions *
|
||||||
|
************************************/
|
||||||
|
|
||||||
|
// When we become visible on screen or disappear off the screen.
|
||||||
|
OnVisible()
|
||||||
|
OnHidden()
|
||||||
|
|
||||||
|
// OnEnter: the other Doodad has ENTIRELY entered our box. Or if the other
|
||||||
|
// doodad is bigger, they have ENTIRELY enveloped ours.
|
||||||
|
OnEnter(func(other Doodad))
|
||||||
|
|
||||||
|
// OnCollide: when we bump into another Doodad.
|
||||||
|
OnCollide(func(other Doodad))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mockup Script
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function main() {
|
||||||
|
console.log("hello world");
|
||||||
|
|
||||||
|
// Register event callbacks.
|
||||||
|
Doodle.OnEnter(onEnter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// onEnter: handle when another Doodad (like the player) completely enters
|
||||||
|
// the bounding box of our Doodad. Example: a level exit.
|
||||||
|
function onEnter(other) {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
|
@ -24,6 +24,7 @@ type EditorScene struct {
|
||||||
DoodadSize int
|
DoodadSize int
|
||||||
|
|
||||||
UI *EditorUI
|
UI *EditorUI
|
||||||
|
d *Doodle
|
||||||
|
|
||||||
// The current level or doodad object being edited, based on the
|
// The current level or doodad object being edited, based on the
|
||||||
// DrawingType.
|
// DrawingType.
|
||||||
|
@ -43,6 +44,7 @@ func (s *EditorScene) Name() string {
|
||||||
func (s *EditorScene) Setup(d *Doodle) error {
|
func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
// Initialize the user interface. It references the palette and such so it
|
// Initialize the user interface. It references the palette and such so it
|
||||||
// must be initialized after those things.
|
// must be initialized after those things.
|
||||||
|
s.d = d
|
||||||
s.UI = NewEditorUI(d, s)
|
s.UI = NewEditorUI(d, s)
|
||||||
|
|
||||||
// Were we given configuration data?
|
// Were we given configuration data?
|
||||||
|
@ -57,7 +59,7 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
case enum.LevelDrawing:
|
case enum.LevelDrawing:
|
||||||
if s.Level != nil {
|
if s.Level != nil {
|
||||||
log.Debug("EditorScene.Setup: received level from scene caller")
|
log.Debug("EditorScene.Setup: received level from scene caller")
|
||||||
s.UI.Canvas.LoadLevel(s.Level)
|
s.UI.Canvas.LoadLevel(d.Engine, s.Level)
|
||||||
} else if s.filename != "" && s.OpenFile {
|
} else if s.filename != "" && s.OpenFile {
|
||||||
log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename)
|
log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename)
|
||||||
if err := s.LoadLevel(s.filename); err != nil {
|
if err := s.LoadLevel(s.filename); err != nil {
|
||||||
|
@ -70,7 +72,7 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
log.Debug("EditorScene.Setup: initializing a new Level")
|
log.Debug("EditorScene.Setup: initializing a new Level")
|
||||||
s.Level = level.New()
|
s.Level = level.New()
|
||||||
s.Level.Palette = level.DefaultPalette()
|
s.Level.Palette = level.DefaultPalette()
|
||||||
s.UI.Canvas.LoadLevel(s.Level)
|
s.UI.Canvas.LoadLevel(d.Engine, s.Level)
|
||||||
s.UI.Canvas.ScrollTo(render.Origin)
|
s.UI.Canvas.ScrollTo(render.Origin)
|
||||||
s.UI.Canvas.Scrollable = true
|
s.UI.Canvas.Scrollable = true
|
||||||
}
|
}
|
||||||
|
@ -153,7 +155,7 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
||||||
|
|
||||||
s.DrawingType = enum.LevelDrawing
|
s.DrawingType = enum.LevelDrawing
|
||||||
s.Level = level
|
s.Level = level
|
||||||
s.UI.Canvas.LoadLevel(s.Level)
|
s.UI.Canvas.LoadLevel(s.d.Engine, s.Level)
|
||||||
|
|
||||||
// TODO: debug
|
// TODO: debug
|
||||||
for i, actor := range level.Actors {
|
for i, actor := range level.Actors {
|
||||||
|
|
|
@ -48,6 +48,11 @@ func LoadJSON(filename string) (*Level, error) {
|
||||||
return m, fmt.Errorf("level.LoadJSON: JSON decode error: %s", err)
|
return m, fmt.Errorf("level.LoadJSON: JSON decode error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fill in defaults.
|
||||||
|
if m.Wallpaper == "" {
|
||||||
|
m.Wallpaper = DefaultWallpaper
|
||||||
|
}
|
||||||
|
|
||||||
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||||
m.Chunker.Inflate(m.Palette)
|
m.Chunker.Inflate(m.Palette)
|
||||||
m.Actors.Inflate()
|
m.Actors.Inflate()
|
||||||
|
|
32
level/page_type.go
Normal file
32
level/page_type.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package level
|
||||||
|
|
||||||
|
// PageType configures the bounds and wallpaper behavior of a Level.
|
||||||
|
type PageType int
|
||||||
|
|
||||||
|
// PageType values.
|
||||||
|
const (
|
||||||
|
// Unbounded means the map can grow freely in any direction.
|
||||||
|
// - Only the repeat texture is used for the wallpaper.
|
||||||
|
Unbounded PageType = iota
|
||||||
|
|
||||||
|
// NoNegativeSpace means the map is bounded at the top left edges.
|
||||||
|
// - Can't scroll or visit any pixels in negative X,Y coordinates.
|
||||||
|
// - Wallpaper shows the Corner at 0,0
|
||||||
|
// - Wallpaper repeats the Top along the Y=0 plane
|
||||||
|
// - Wallpaper repeats the Left along the X=0 plane
|
||||||
|
// - The repeat texture fills the rest of the level.
|
||||||
|
NoNegativeSpace
|
||||||
|
|
||||||
|
// Bounded is the same as NoNegativeSpace but the level is imposing a
|
||||||
|
// maximum cap on the width and height of the level.
|
||||||
|
// - Can't scroll below X,Y origin at 0,0
|
||||||
|
// - Can't scroll past the bounded width and height of the level
|
||||||
|
Bounded
|
||||||
|
|
||||||
|
// Bordered is like Bounded except the corner textures are wrapped
|
||||||
|
// around the other edges of the level too.
|
||||||
|
// - The wallpaper hoz mirrors Left along the X=Width plane
|
||||||
|
// - The wallpaper vert mirrors Top along the Y=Width plane
|
||||||
|
// - The wallpaper 180 rotates the Corner for opposite corners
|
||||||
|
Bordered
|
||||||
|
)
|
|
@ -8,6 +8,11 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Useful variables.
|
||||||
|
var (
|
||||||
|
DefaultWallpaper = "notebook.png"
|
||||||
|
)
|
||||||
|
|
||||||
// Base provides the common struct keys that are shared between Levels and
|
// Base provides the common struct keys that are shared between Levels and
|
||||||
// Doodads.
|
// Doodads.
|
||||||
type Base struct {
|
type Base struct {
|
||||||
|
@ -33,6 +38,12 @@ type Level struct {
|
||||||
// properties (solid, fire, slippery, etc.)
|
// properties (solid, fire, slippery, etc.)
|
||||||
Palette *Palette `json:"palette"`
|
Palette *Palette `json:"palette"`
|
||||||
|
|
||||||
|
// Page boundaries and wallpaper settings.
|
||||||
|
PageType PageType `json:"pageType"`
|
||||||
|
MaxWidth int64 `json:"boundedWidth"` // only if bounded or bordered
|
||||||
|
MaxHeight int64 `json:"boundedHeight"`
|
||||||
|
Wallpaper string `json:"wallpaper"`
|
||||||
|
|
||||||
// Actors keep a list of the doodad instances in this map.
|
// Actors keep a list of the doodad instances in this map.
|
||||||
Actors ActorMap `json:"actors"`
|
Actors ActorMap `json:"actors"`
|
||||||
}
|
}
|
||||||
|
@ -46,6 +57,11 @@ func New() *Level {
|
||||||
Chunker: NewChunker(balance.ChunkSize),
|
Chunker: NewChunker(balance.ChunkSize),
|
||||||
Palette: &Palette{},
|
Palette: &Palette{},
|
||||||
Actors: ActorMap{},
|
Actors: ActorMap{},
|
||||||
|
|
||||||
|
PageType: NoNegativeSpace,
|
||||||
|
Wallpaper: DefaultWallpaper,
|
||||||
|
MaxWidth: 2550,
|
||||||
|
MaxHeight: 3300,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
76
pkg/wallpaper/texture.go
Normal file
76
pkg/wallpaper/texture.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package wallpaper
|
||||||
|
|
||||||
|
// The methods that deal in cached Textures for Doodle.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"golang.org/x/image/bmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CornerTexture returns the Texture.
|
||||||
|
func (wp *Wallpaper) CornerTexture(e render.Engine) (render.Texturer, error) {
|
||||||
|
fmt.Println("CornerTex")
|
||||||
|
if wp.tex.corner == nil {
|
||||||
|
tex, err := texture(e, wp.corner, wp.Name+"c")
|
||||||
|
wp.tex.corner = tex
|
||||||
|
return tex, err
|
||||||
|
}
|
||||||
|
return wp.tex.corner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TopTexture returns the Texture.
|
||||||
|
func (wp *Wallpaper) TopTexture(e render.Engine) (render.Texturer, error) {
|
||||||
|
if wp.tex.top == nil {
|
||||||
|
tex, err := texture(e, wp.top, wp.Name+"t")
|
||||||
|
wp.tex.top = tex
|
||||||
|
return tex, err
|
||||||
|
}
|
||||||
|
return wp.tex.top, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeftTexture returns the Texture.
|
||||||
|
func (wp *Wallpaper) LeftTexture(e render.Engine) (render.Texturer, error) {
|
||||||
|
if wp.tex.left == nil {
|
||||||
|
tex, err := texture(e, wp.left, wp.Name+"l")
|
||||||
|
wp.tex.left = tex
|
||||||
|
return tex, err
|
||||||
|
}
|
||||||
|
return wp.tex.left, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepeatTexture returns the Texture.
|
||||||
|
func (wp *Wallpaper) RepeatTexture(e render.Engine) (render.Texturer, error) {
|
||||||
|
if wp.tex.repeat == nil {
|
||||||
|
tex, err := texture(e, wp.repeat, wp.Name+"x")
|
||||||
|
wp.tex.repeat = tex
|
||||||
|
return tex, err
|
||||||
|
}
|
||||||
|
return wp.tex.repeat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func texture(e render.Engine, img *image.RGBA, name string) (render.Texturer, error) {
|
||||||
|
filename := userdir.CacheFilename("wallpaper", name+".bmp")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
fh, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CornerTexture: %s", err.Error())
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
err = bmp.Encode(fh, img)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CornerTexture: bmp.Encode: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
texture, err := e.NewBitmap(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CornerTexture: NewBitmap(%s): %s", filename, err.Error())
|
||||||
|
}
|
||||||
|
return texture, nil
|
||||||
|
}
|
143
pkg/wallpaper/wallpaper.go
Normal file
143
pkg/wallpaper/wallpaper.go
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package wallpaper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wallpaper is a repeatable background image to go behind levels.
|
||||||
|
type Wallpaper struct {
|
||||||
|
Name string
|
||||||
|
Format string // image file format
|
||||||
|
Image *image.RGBA
|
||||||
|
|
||||||
|
// Parsed values.
|
||||||
|
quarterWidth int
|
||||||
|
quarterHeight int
|
||||||
|
|
||||||
|
// The four parsed images.
|
||||||
|
corner *image.RGBA // Top Left corner
|
||||||
|
top *image.RGBA // Top repeating
|
||||||
|
left *image.RGBA // Left repeating
|
||||||
|
repeat *image.RGBA // Main repeating
|
||||||
|
|
||||||
|
// Cached textures.
|
||||||
|
tex struct {
|
||||||
|
corner render.Texturer
|
||||||
|
top render.Texturer
|
||||||
|
left render.Texturer
|
||||||
|
repeat render.Texturer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromImage creates a Wallpaper from an image.Image.
|
||||||
|
// If the renger.Engine is nil it will compute images but not pre-cache any
|
||||||
|
// textures yet.
|
||||||
|
func FromImage(e render.Engine, img *image.RGBA, name string) (*Wallpaper, error) {
|
||||||
|
wp := &Wallpaper{
|
||||||
|
Name: name,
|
||||||
|
Image: img,
|
||||||
|
}
|
||||||
|
wp.cache(e)
|
||||||
|
return wp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromFile creates a Wallpaper from a file on disk.
|
||||||
|
// If the renger.Engine is nil it will compute images but not pre-cache any
|
||||||
|
// textures yet.
|
||||||
|
func FromFile(e render.Engine, filename string) (*Wallpaper, error) {
|
||||||
|
fh, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img, format, err := image.Decode(fh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ugly hack: make it an image.RGBA because the thing we get tends to be
|
||||||
|
// an image.Paletted, UGH!
|
||||||
|
var b = img.Bounds()
|
||||||
|
rgba := image.NewRGBA(b)
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
rgba.Set(x, y, img.At(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wp := &Wallpaper{
|
||||||
|
Name: strings.Split(filepath.Base(filename), ".")[0],
|
||||||
|
Format: format,
|
||||||
|
Image: rgba,
|
||||||
|
}
|
||||||
|
wp.cache(e)
|
||||||
|
return wp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache the bitmap images.
|
||||||
|
func (wp *Wallpaper) cache(e render.Engine) {
|
||||||
|
// Zero-bound the rect cuz an image.Rect doesn't necessarily contain 0,0
|
||||||
|
var rect = wp.Image.Bounds()
|
||||||
|
if rect.Min.X < 0 {
|
||||||
|
rect.Max.X += rect.Min.X
|
||||||
|
rect.Min.X = 0
|
||||||
|
}
|
||||||
|
if rect.Min.Y < 0 {
|
||||||
|
rect.Max.Y += rect.Min.Y
|
||||||
|
rect.Min.Y = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our quarter rect size.
|
||||||
|
wp.quarterWidth = int(float64((rect.Max.X - rect.Min.X) / 2))
|
||||||
|
wp.quarterHeight = int(float64((rect.Max.Y - rect.Min.Y) / 2))
|
||||||
|
quarter := image.Rect(0, 0, wp.quarterWidth, wp.quarterHeight)
|
||||||
|
|
||||||
|
// Slice the image into the four corners.
|
||||||
|
slice := func(dx, dy int) *image.RGBA {
|
||||||
|
slice := image.NewRGBA(quarter)
|
||||||
|
draw.Draw(
|
||||||
|
slice,
|
||||||
|
image.Rect(0, 0, wp.quarterWidth, wp.quarterHeight),
|
||||||
|
wp.Image,
|
||||||
|
image.Point{dx, dy},
|
||||||
|
draw.Over,
|
||||||
|
)
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
wp.corner = slice(0, 0)
|
||||||
|
wp.top = slice(wp.quarterWidth, 0)
|
||||||
|
wp.left = slice(0, wp.quarterHeight)
|
||||||
|
wp.repeat = slice(wp.quarterWidth, wp.quarterHeight)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuarterSize returns the width and height of the quarter images.
|
||||||
|
func (wp *Wallpaper) QuarterSize() (int, int) {
|
||||||
|
return wp.quarterWidth, wp.quarterHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Corner returns the top left corner image.
|
||||||
|
func (wp *Wallpaper) Corner() *image.RGBA {
|
||||||
|
return wp.corner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top returns the top repeating image.
|
||||||
|
func (wp *Wallpaper) Top() *image.RGBA {
|
||||||
|
return wp.top
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left returns the left repeating image.
|
||||||
|
func (wp *Wallpaper) Left() *image.RGBA {
|
||||||
|
return wp.left
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repeat returns the main repeating image.
|
||||||
|
func (wp *Wallpaper) Repeat() *image.RGBA {
|
||||||
|
return wp.repeat
|
||||||
|
}
|
111
pkg/wallpaper/wallpaper_test.go
Normal file
111
pkg/wallpaper/wallpaper_test.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package wallpaper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWallpaper(t *testing.T) {
|
||||||
|
var testFunc = func(width, height int) {
|
||||||
|
var (
|
||||||
|
qWidth = width / 2
|
||||||
|
qHeight = height / 2
|
||||||
|
red = color.RGBA{255, 0, 0, 255}
|
||||||
|
green = color.RGBA{0, 255, 0, 255}
|
||||||
|
blue = color.RGBA{0, 0, 255, 255}
|
||||||
|
pink = color.RGBA{255, 0, 255, 255}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a dummy image that is width*height and has the four
|
||||||
|
// quadrants laid out as solid colors:
|
||||||
|
// Red | Green
|
||||||
|
// Blue | Pink
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
draw.Draw(
|
||||||
|
// Corner: red
|
||||||
|
img, // dst Image
|
||||||
|
image.Rect(0, 0, qWidth, qHeight), // r Rectangle
|
||||||
|
image.NewUniform(red), // src Image
|
||||||
|
image.Point{0, 0}, // sp Point
|
||||||
|
draw.Over, // op Op
|
||||||
|
)
|
||||||
|
draw.Draw(
|
||||||
|
// Top: green
|
||||||
|
img,
|
||||||
|
image.Rect(qWidth, 0, width, qHeight),
|
||||||
|
image.NewUniform(green),
|
||||||
|
image.Point{qWidth, 0},
|
||||||
|
draw.Over,
|
||||||
|
)
|
||||||
|
draw.Draw(
|
||||||
|
// Left: blue
|
||||||
|
img,
|
||||||
|
image.Rect(0, qHeight, qWidth, height),
|
||||||
|
image.NewUniform(blue),
|
||||||
|
image.Point{0, qHeight},
|
||||||
|
draw.Over,
|
||||||
|
)
|
||||||
|
draw.Draw(
|
||||||
|
// Repeat: pink
|
||||||
|
img,
|
||||||
|
image.Rect(qWidth, qHeight, width, height),
|
||||||
|
image.NewUniform(pink),
|
||||||
|
image.Point{qWidth, qHeight},
|
||||||
|
draw.Over,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Output as png to disk if you wanna see what's in it.
|
||||||
|
if os.Getenv("T_WALLPAPER_PNG") != "" {
|
||||||
|
fn := fmt.Sprintf("test-%dx%d.png", width, height)
|
||||||
|
if fh, err := os.Create(fn); err == nil {
|
||||||
|
defer fh.Close()
|
||||||
|
if err := png.Encode(fh, img); err != nil {
|
||||||
|
t.Errorf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wp, err := FromImage(nil, img, "dummy")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't create FromImage: %s", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the quarter size is what we expected.
|
||||||
|
w, h := wp.QuarterSize()
|
||||||
|
if w != qWidth || h != qHeight {
|
||||||
|
t.Errorf(
|
||||||
|
"Got wrong quarter size: expected %dx%d but got %dx%d",
|
||||||
|
qWidth, qHeight,
|
||||||
|
w, h,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the colors.
|
||||||
|
testColor := func(name string, img *image.RGBA, expect color.RGBA) {
|
||||||
|
if actual := img.At(5, 5); actual != expect {
|
||||||
|
t.Errorf(
|
||||||
|
"%s: expected color %v but got %v",
|
||||||
|
name,
|
||||||
|
expect,
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testColor("Corner", wp.Corner(), red)
|
||||||
|
testColor("Top", wp.Top(), green)
|
||||||
|
testColor("Left", wp.Left(), blue)
|
||||||
|
testColor("Repeat", wp.Repeat(), pink)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFunc(128, 128)
|
||||||
|
testFunc(128, 64)
|
||||||
|
testFunc(64, 128)
|
||||||
|
testFunc(12, 12)
|
||||||
|
testFunc(57, 39)
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ type PlayScene struct {
|
||||||
Level *level.Level
|
Level *level.Level
|
||||||
|
|
||||||
// Private variables.
|
// Private variables.
|
||||||
|
d *Doodle
|
||||||
drawing *uix.Canvas
|
drawing *uix.Canvas
|
||||||
|
|
||||||
// Player character
|
// Player character
|
||||||
|
@ -31,6 +32,7 @@ func (s *PlayScene) Name() string {
|
||||||
|
|
||||||
// Setup the play scene.
|
// Setup the play scene.
|
||||||
func (s *PlayScene) Setup(d *Doodle) error {
|
func (s *PlayScene) Setup(d *Doodle) error {
|
||||||
|
s.d = d
|
||||||
s.drawing = uix.NewCanvas(balance.ChunkSize, false)
|
s.drawing = uix.NewCanvas(balance.ChunkSize, false)
|
||||||
s.drawing.MoveTo(render.Origin)
|
s.drawing.MoveTo(render.Origin)
|
||||||
s.drawing.Resize(render.NewRect(int32(d.width), int32(d.height)))
|
s.drawing.Resize(render.NewRect(int32(d.width), int32(d.height)))
|
||||||
|
@ -39,7 +41,7 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
||||||
// Given a filename or map data to play?
|
// Given a filename or map data to play?
|
||||||
if s.Level != nil {
|
if s.Level != nil {
|
||||||
log.Debug("PlayScene.Setup: received level from scene caller")
|
log.Debug("PlayScene.Setup: received level from scene caller")
|
||||||
s.drawing.LoadLevel(s.Level)
|
s.drawing.LoadLevel(d.Engine, s.Level)
|
||||||
} else if s.Filename != "" {
|
} else if s.Filename != "" {
|
||||||
log.Debug("PlayScene.Setup: loading map from file %s", s.Filename)
|
log.Debug("PlayScene.Setup: loading map from file %s", s.Filename)
|
||||||
s.LoadLevel(s.Filename)
|
s.LoadLevel(s.Filename)
|
||||||
|
@ -50,7 +52,7 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
||||||
if s.Level == nil {
|
if s.Level == nil {
|
||||||
log.Debug("PlayScene.Setup: no grid given, initializing empty grid")
|
log.Debug("PlayScene.Setup: no grid given, initializing empty grid")
|
||||||
s.Level = level.New()
|
s.Level = level.New()
|
||||||
s.drawing.LoadLevel(s.Level)
|
s.drawing.LoadLevel(d.Engine, s.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
|
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
|
||||||
|
@ -140,7 +142,7 @@ func (s *PlayScene) LoadLevel(filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Level = level
|
s.Level = level
|
||||||
s.drawing.LoadLevel(s.Level)
|
s.drawing.LoadLevel(s.d.Engine, s.Level)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
69
render/functions.go
Normal file
69
render/functions.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package render
|
||||||
|
|
||||||
|
// TrimBox helps with Engine.Copy() to trim a destination box so that it
|
||||||
|
// won't overflow with the parent container.
|
||||||
|
func TrimBox(src, dst *Rect, p Point, S Rect, thickness int32) {
|
||||||
|
// Constrain source width to not bigger than Canvas width.
|
||||||
|
if src.W > S.W {
|
||||||
|
src.W = S.W
|
||||||
|
}
|
||||||
|
if src.H > S.H {
|
||||||
|
src.H = S.H
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the destination width will cause it to overflow the widget
|
||||||
|
// box, trim off the right edge of the destination rect.
|
||||||
|
//
|
||||||
|
// Keep in mind we're dealing with chunks here, and a chunk is
|
||||||
|
// a small part of the image. Example:
|
||||||
|
// - Canvas is 800x600 (S.W=800 S.H=600)
|
||||||
|
// - Chunk wants to render at 790,0 width 100,100 or whatever
|
||||||
|
// dst={790, 0, 100, 100}
|
||||||
|
// - Chunk box would exceed 800px width (X=790 + W=100 == 890)
|
||||||
|
// - Find the delta how much it exceeds as negative (800 - 890 == -90)
|
||||||
|
// - Lower the Source and Dest rects by that delta size so they
|
||||||
|
// stay proportional and don't scale or anything dumb.
|
||||||
|
if dst.X+src.W > p.X+S.W {
|
||||||
|
// NOTE: delta is a negative number,
|
||||||
|
// so it will subtract from the width.
|
||||||
|
delta := (p.X + S.W - thickness) - (dst.W + dst.X)
|
||||||
|
src.W += delta
|
||||||
|
dst.W += delta
|
||||||
|
}
|
||||||
|
if dst.Y+src.H > p.Y+S.H {
|
||||||
|
// NOTE: delta is a negative number
|
||||||
|
delta := (p.Y + S.H - thickness) - (dst.H + dst.Y)
|
||||||
|
src.H += delta
|
||||||
|
dst.H += delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// The same for the top left edge, so the drawings don't overlap
|
||||||
|
// menu bars or left side toolbars.
|
||||||
|
// - Canvas was placed 80px from the left of the screen.
|
||||||
|
// Canvas.MoveTo(80, 0)
|
||||||
|
// - A texture wants to draw at 60, 0 which would cause it to
|
||||||
|
// overlap 20 pixels into the left toolbar. It needs to be cropped.
|
||||||
|
// - The delta is: p.X=80 - dst.X=60 == 20
|
||||||
|
// - Set destination X to p.X to constrain it there: 20
|
||||||
|
// - Subtract the delta from destination W so we don't scale it.
|
||||||
|
// - Add 20 to X of the source: the left edge of source is not visible
|
||||||
|
if dst.X < p.X {
|
||||||
|
// NOTE: delta is a positive number,
|
||||||
|
// so it will add to the destination coordinates.
|
||||||
|
delta := p.X - dst.X
|
||||||
|
dst.X = p.X + thickness
|
||||||
|
dst.W -= delta
|
||||||
|
src.X += delta
|
||||||
|
}
|
||||||
|
if dst.Y < p.Y {
|
||||||
|
delta := p.Y - dst.Y
|
||||||
|
dst.Y = p.Y + thickness
|
||||||
|
dst.H -= delta
|
||||||
|
src.Y += delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the destination width so it doesn't overlap the Canvas border.
|
||||||
|
if dst.W >= S.W-thickness {
|
||||||
|
dst.W = S.W - thickness
|
||||||
|
}
|
||||||
|
}
|
10
shell.go
10
shell.go
|
@ -206,6 +206,11 @@ func (s *Shell) Parse(input string) Command {
|
||||||
|
|
||||||
// Draw the shell.
|
// Draw the shell.
|
||||||
func (s *Shell) Draw(d *Doodle, ev *events.State) error {
|
func (s *Shell) Draw(d *Doodle, ev *events.State) error {
|
||||||
|
// Compute the line height we can draw.
|
||||||
|
lineHeight := balance.ShellFontSize + int(balance.ShellPadding)
|
||||||
|
|
||||||
|
// If the console is open, draw the console.
|
||||||
|
if s.Open {
|
||||||
if ev.EscapeKey.Read() {
|
if ev.EscapeKey.Read() {
|
||||||
s.Close()
|
s.Close()
|
||||||
return nil
|
return nil
|
||||||
|
@ -246,11 +251,6 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the line height we can draw.
|
|
||||||
lineHeight := balance.ShellFontSize + int(balance.ShellPadding)
|
|
||||||
|
|
||||||
// If the console is open, draw the console.
|
|
||||||
if s.Open {
|
|
||||||
// Cursor flip?
|
// Cursor flip?
|
||||||
if d.ticks > s.cursorFlip {
|
if d.ticks > s.cursorFlip {
|
||||||
s.cursorFlip = d.ticks + s.cursorRate
|
s.cursorFlip = d.ticks + s.cursorRate
|
||||||
|
|
|
@ -2,6 +2,7 @@ package uix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/level"
|
"git.kirsle.net/apps/doodle/level"
|
||||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/wallpaper"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"git.kirsle.net/apps/doodle/ui"
|
"git.kirsle.net/apps/doodle/ui"
|
||||||
)
|
)
|
||||||
|
@ -32,6 +34,10 @@ type Canvas struct {
|
||||||
// to remove the mask.
|
// to remove the mask.
|
||||||
MaskColor render.Color
|
MaskColor render.Color
|
||||||
|
|
||||||
|
// Debug tools
|
||||||
|
// NoLimitScroll suppresses the scroll limit for bounded levels.
|
||||||
|
NoLimitScroll bool
|
||||||
|
|
||||||
// Underlying chunk data for the drawing.
|
// Underlying chunk data for the drawing.
|
||||||
chunks *level.Chunker
|
chunks *level.Chunker
|
||||||
|
|
||||||
|
@ -39,6 +45,9 @@ type Canvas struct {
|
||||||
actor *level.Actor // if this canvas IS an actor
|
actor *level.Actor // if this canvas IS an actor
|
||||||
actors []*Actor
|
actors []*Actor
|
||||||
|
|
||||||
|
// Wallpaper settings.
|
||||||
|
wallpaper *Wallpaper
|
||||||
|
|
||||||
// When the Canvas wants to delete Actors, but ultimately it is upstream
|
// When the Canvas wants to delete Actors, but ultimately it is upstream
|
||||||
// that controls the actors. Upstream should delete them and then reinstall
|
// that controls the actors. Upstream should delete them and then reinstall
|
||||||
// the actor list from scratch.
|
// the actor list from scratch.
|
||||||
|
@ -70,6 +79,7 @@ func NewCanvas(size int, editable bool) *Canvas {
|
||||||
Palette: level.NewPalette(),
|
Palette: level.NewPalette(),
|
||||||
chunks: level.NewChunker(size),
|
chunks: level.NewChunker(size),
|
||||||
actors: make([]*Actor, 0),
|
actors: make([]*Actor, 0),
|
||||||
|
wallpaper: &Wallpaper{},
|
||||||
}
|
}
|
||||||
w.setup()
|
w.setup()
|
||||||
w.IDFunc(func() string {
|
w.IDFunc(func() string {
|
||||||
|
@ -101,8 +111,27 @@ func (w *Canvas) Load(p *level.Palette, g *level.Chunker) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadLevel initializes a Canvas from a Level object.
|
// LoadLevel initializes a Canvas from a Level object.
|
||||||
func (w *Canvas) LoadLevel(level *level.Level) {
|
func (w *Canvas) LoadLevel(e render.Engine, level *level.Level) {
|
||||||
w.Load(level.Palette, level.Chunker)
|
w.Load(level.Palette, level.Chunker)
|
||||||
|
|
||||||
|
// TODO: wallpaper paths
|
||||||
|
filename := "assets/wallpapers/" + level.Wallpaper
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
log.Error("LoadLevel: %s", err)
|
||||||
|
filename = "assets/wallpapers/notebook.png" // XXX TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
wp, err := wallpaper.FromFile(e, filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("wallpaper FromFile(%s): %s", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.wallpaper.maxWidth = level.MaxWidth
|
||||||
|
w.wallpaper.maxHeight = level.MaxHeight
|
||||||
|
err = w.wallpaper.Load(e, level.PageType, wp)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("wallpaper Load: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadDoodad initializes a Canvas from a Doodad object.
|
// LoadDoodad initializes a Canvas from a Doodad object.
|
||||||
|
@ -274,6 +303,44 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
|
||||||
H: S.H - w.BoxThickness(2),
|
H: S.H - w.BoxThickness(2),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Constrain the scroll view if the level is bounded.
|
||||||
|
if w.Scrollable && !w.NoLimitScroll {
|
||||||
|
// Constrain the top and left edges.
|
||||||
|
if w.wallpaper.pageType > level.Unbounded {
|
||||||
|
if w.Scroll.X > 0 {
|
||||||
|
w.Scroll.X = 0
|
||||||
|
}
|
||||||
|
if w.Scroll.Y > 0 {
|
||||||
|
w.Scroll.Y = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constrain the bottom and right for limited world sizes.
|
||||||
|
if w.wallpaper.maxWidth > 0 && w.wallpaper.maxHeight > 0 {
|
||||||
|
var (
|
||||||
|
// TODO: downcast from int64!
|
||||||
|
mw = int32(w.wallpaper.maxWidth)
|
||||||
|
mh = int32(w.wallpaper.maxHeight)
|
||||||
|
)
|
||||||
|
if Viewport.W > mw {
|
||||||
|
delta := Viewport.W - mw
|
||||||
|
w.Scroll.X += delta
|
||||||
|
}
|
||||||
|
if Viewport.H > mh {
|
||||||
|
delta := Viewport.H - mh
|
||||||
|
w.Scroll.Y += delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the wallpaper.
|
||||||
|
if w.wallpaper.Valid() {
|
||||||
|
err := w.PresentWallpaper(e, p)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the chunks in the viewport and cache their textures.
|
// Get the chunks in the viewport and cache their textures.
|
||||||
for coord := range w.chunks.IterViewportChunks(Viewport) {
|
for coord := range w.chunks.IterViewportChunks(Viewport) {
|
||||||
if chunk, ok := w.chunks.GetChunk(coord); ok {
|
if chunk, ok := w.chunks.GetChunk(coord); ok {
|
||||||
|
@ -310,6 +377,8 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
|
||||||
H: src.H,
|
H: src.H,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: all this shit is in TrimBox(), make it DRY
|
||||||
|
|
||||||
// If the destination width will cause it to overflow the widget
|
// If the destination width will cause it to overflow the widget
|
||||||
// box, trim off the right edge of the destination rect.
|
// box, trim off the right edge of the destination rect.
|
||||||
//
|
//
|
||||||
|
|
175
uix/canvas_wallpaper.go
Normal file
175
uix/canvas_wallpaper.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package uix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/level"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/wallpaper"
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wallpaper configures the wallpaper in a Canvas.
|
||||||
|
type Wallpaper struct {
|
||||||
|
pageType level.PageType
|
||||||
|
maxWidth int64
|
||||||
|
maxHeight int64
|
||||||
|
corner render.Texturer
|
||||||
|
top render.Texturer
|
||||||
|
left render.Texturer
|
||||||
|
repeat render.Texturer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns whether the Wallpaper is configured. Only Levels should
|
||||||
|
// have wallpapers and Doodads will have nil ones.
|
||||||
|
func (wp *Wallpaper) Valid() bool {
|
||||||
|
return wp.repeat != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresentWallpaper draws the wallpaper.
|
||||||
|
func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
||||||
|
var (
|
||||||
|
wp = w.wallpaper
|
||||||
|
S = w.Size()
|
||||||
|
size = wp.corner.Size()
|
||||||
|
Viewport = w.ViewportRelative()
|
||||||
|
origin = render.Point{
|
||||||
|
X: p.X + w.Scroll.X + w.BoxThickness(1),
|
||||||
|
Y: p.Y + w.Scroll.Y + w.BoxThickness(1),
|
||||||
|
}
|
||||||
|
limit = render.Point{
|
||||||
|
// NOTE: we add + the texture size so we would actually draw one
|
||||||
|
// full extra texture out-of-bounds for the repeating backgrounds.
|
||||||
|
// This is cuz for scrolling we offset the draw spot on a loop.
|
||||||
|
X: origin.X + S.W - w.BoxThickness(1) + size.W,
|
||||||
|
Y: origin.Y + S.H - w.BoxThickness(1) + size.H,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// For tiled textures, compute the offset amount. If we are scrolled away
|
||||||
|
// from the Origin (0,0) we find out by how far (subtract full tile sizes)
|
||||||
|
// and use the remainder as an offset for drawing the tiles.
|
||||||
|
var dx, dy int32
|
||||||
|
if origin.X > 0 {
|
||||||
|
for origin.X > 0 && origin.X > size.W {
|
||||||
|
origin.X -= size.W
|
||||||
|
}
|
||||||
|
dx = origin.X
|
||||||
|
origin.X = 0
|
||||||
|
}
|
||||||
|
if origin.Y > 0 {
|
||||||
|
for origin.Y > 0 && origin.Y > size.H {
|
||||||
|
origin.Y -= size.H
|
||||||
|
}
|
||||||
|
dy = origin.Y
|
||||||
|
origin.Y = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// And capping the scroll delta in the other direction.
|
||||||
|
if limit.X < S.W {
|
||||||
|
limit.X = S.W
|
||||||
|
}
|
||||||
|
if limit.Y < S.H {
|
||||||
|
// TODO: slight flicker on bottom edge when scrolling down
|
||||||
|
limit.Y = S.H
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tile the repeat texture.
|
||||||
|
for x := origin.X - size.W; x < limit.X; x += size.W {
|
||||||
|
for y := origin.Y - size.H; y < limit.Y; y += size.H {
|
||||||
|
src := render.Rect{
|
||||||
|
W: size.W,
|
||||||
|
H: size.H,
|
||||||
|
}
|
||||||
|
dst := render.Rect{
|
||||||
|
X: x + dx,
|
||||||
|
Y: y + dy,
|
||||||
|
W: src.W,
|
||||||
|
H: src.H,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the edges of the destination box, like in canvas.go#Present
|
||||||
|
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||||
|
|
||||||
|
e.Copy(wp.repeat, src, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The left edge corner tiled along the left edge.
|
||||||
|
if wp.pageType > level.Unbounded {
|
||||||
|
for y := origin.Y; y < limit.Y; y += size.H {
|
||||||
|
src := render.Rect{
|
||||||
|
W: size.W,
|
||||||
|
H: size.H,
|
||||||
|
}
|
||||||
|
dst := render.Rect{
|
||||||
|
X: origin.X,
|
||||||
|
Y: y + dy,
|
||||||
|
W: src.W,
|
||||||
|
H: src.H,
|
||||||
|
}
|
||||||
|
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||||
|
e.Copy(wp.left, src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The top edge tiled along the top edge.
|
||||||
|
for x := origin.X; x < limit.X; x += size.W {
|
||||||
|
src := render.Rect{
|
||||||
|
W: size.W,
|
||||||
|
H: size.H,
|
||||||
|
}
|
||||||
|
dst := render.Rect{
|
||||||
|
X: x,
|
||||||
|
Y: origin.Y,
|
||||||
|
W: src.W,
|
||||||
|
H: src.H,
|
||||||
|
}
|
||||||
|
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||||
|
e.Copy(wp.top, src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The top left corner for all page types except Unbounded.
|
||||||
|
if Viewport.Intersects(size) {
|
||||||
|
src := render.Rect{
|
||||||
|
W: size.W,
|
||||||
|
H: size.H,
|
||||||
|
}
|
||||||
|
dst := render.Rect{
|
||||||
|
X: origin.X,
|
||||||
|
Y: origin.Y,
|
||||||
|
W: src.W,
|
||||||
|
H: src.H,
|
||||||
|
}
|
||||||
|
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||||
|
e.Copy(wp.corner, src, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the wallpaper settings from a level.
|
||||||
|
func (wp *Wallpaper) Load(e render.Engine, pageType level.PageType, v *wallpaper.Wallpaper) error {
|
||||||
|
wp.pageType = pageType
|
||||||
|
if tex, err := v.CornerTexture(e); err == nil {
|
||||||
|
wp.corner = tex
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tex, err := v.TopTexture(e); err == nil {
|
||||||
|
wp.top = tex
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tex, err := v.LeftTexture(e); err == nil {
|
||||||
|
wp.left = tex
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tex, err := v.RepeatTexture(e); err == nil {
|
||||||
|
wp.repeat = tex
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user