Compare commits
4 Commits
1cf13b0ec9
...
cd5ca3f04f
Author | SHA1 | Date | |
---|---|---|---|
cd5ca3f04f | |||
b14f79a61a | |||
44aef25d34 | |||
5c0824bafb |
56
animation.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package tins2020
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Animation struct {
|
||||||
|
active bool
|
||||||
|
start time.Time
|
||||||
|
interval time.Duration
|
||||||
|
lastUpdate time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnimation(interval time.Duration) Animation {
|
||||||
|
return Animation{
|
||||||
|
active: true,
|
||||||
|
start: time.Now(),
|
||||||
|
interval: interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnimationPtr(interval time.Duration) *Animation {
|
||||||
|
ani := NewAnimation(interval)
|
||||||
|
return &ani
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animation) Animate() bool {
|
||||||
|
since := time.Since(a.start)
|
||||||
|
if !a.active || since < a.lastUpdate+a.interval {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a.lastUpdate += a.interval
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animation) AnimateFn(fn func()) {
|
||||||
|
since := time.Since(a.start)
|
||||||
|
for a.active && since > a.lastUpdate+a.interval {
|
||||||
|
fn()
|
||||||
|
a.lastUpdate += a.interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animation) Pause() {
|
||||||
|
a.active = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animation) Run() {
|
||||||
|
if a.active {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.active = true
|
||||||
|
a.start = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animation) SetInterval(interval time.Duration) {
|
||||||
|
a.interval = interval
|
||||||
|
}
|
57
buttonbar.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package tins2020
|
||||||
|
|
||||||
|
import "github.com/veandco/go-sdl2/sdl"
|
||||||
|
|
||||||
|
type ButtonBar struct {
|
||||||
|
Container
|
||||||
|
|
||||||
|
Background sdl.Color
|
||||||
|
Orientation Orientation
|
||||||
|
Buttons []Control
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonBarWidth = 96
|
||||||
|
|
||||||
|
func (b *ButtonBar) Init(ctx *Context) error {
|
||||||
|
for i := range b.Buttons {
|
||||||
|
b.AddChild(b.Buttons[i])
|
||||||
|
}
|
||||||
|
return b.Container.Init(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ButtonBar) Arrange(ctx *Context, bounds Rectangle) {
|
||||||
|
b.Container.Arrange(ctx, bounds)
|
||||||
|
switch b.Orientation {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
length := bounds.H
|
||||||
|
offset := bounds.X
|
||||||
|
for i := range b.Buttons {
|
||||||
|
b.Buttons[i].Arrange(ctx, RectSize(offset, bounds.Y, length, length))
|
||||||
|
offset += length
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
length := bounds.W
|
||||||
|
offset := bounds.Y
|
||||||
|
for i := range b.Buttons {
|
||||||
|
b.Buttons[i].Arrange(ctx, RectSize(bounds.X, offset, length, length))
|
||||||
|
offset += length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ButtonBar) Handle(ctx *Context, event sdl.Event) {
|
||||||
|
b.Container.Handle(ctx, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ButtonBar) Render(ctx *Context) {
|
||||||
|
ctx.Renderer.SetDrawColor(b.Background.R, b.Background.G, b.Background.B, b.Background.A)
|
||||||
|
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
|
||||||
|
b.Container.Render(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Orientation int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OrientationVertical Orientation = iota
|
||||||
|
OrientationHorizontal
|
||||||
|
)
|
95
buyflowerbutton.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package tins2020
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BuyFlowerButton struct {
|
||||||
|
IconButton
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Price int
|
||||||
|
Description string
|
||||||
|
|
||||||
|
hoverAnimation *Animation
|
||||||
|
hoverOffset int32
|
||||||
|
hoverTexture *Texture
|
||||||
|
priceTexture *Texture
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBuyFlowerButton(icon, iconDisabled, name string, price int, description string, isDisabled bool, onClick EventFn) *BuyFlowerButton {
|
||||||
|
return &BuyFlowerButton{
|
||||||
|
IconButton: *NewIconButtonConfig(icon, onClick, func(b *IconButton) {
|
||||||
|
b.IconDisabled = iconDisabled
|
||||||
|
b.IsDisabled = isDisabled
|
||||||
|
}),
|
||||||
|
Name: name,
|
||||||
|
Price: price,
|
||||||
|
Description: description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BuyFlowerButton) animate() {
|
||||||
|
b.hoverOffset++
|
||||||
|
if b.hoverOffset > b.hoverTexture.Size().X+b.Bounds.W {
|
||||||
|
b.hoverOffset = b.priceTexture.Size().X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BuyFlowerButton) Init(ctx *Context) error {
|
||||||
|
text := fmt.Sprintf("%s - %s - %s", FmtMoney(b.Price), b.Name, b.Description)
|
||||||
|
font := ctx.Fonts.Font("small")
|
||||||
|
color := MustHexColor("#ffffff")
|
||||||
|
texture, err := font.Render(ctx.Renderer, text, color)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.hoverTexture = texture
|
||||||
|
texture, err = font.Render(ctx.Renderer, FmtMoney(b.Price), color)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.priceTexture = texture
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BuyFlowerButton) Handle(ctx *Context, event sdl.Event) {
|
||||||
|
b.IconButton.Handle(ctx, event)
|
||||||
|
if b.IsMouseOver && b.hoverAnimation == nil {
|
||||||
|
b.hoverAnimation = NewAnimationPtr(10 * time.Millisecond)
|
||||||
|
b.hoverOffset = b.priceTexture.Size().X
|
||||||
|
} else if !b.IsMouseOver {
|
||||||
|
b.hoverAnimation = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BuyFlowerButton) Render(ctx *Context) {
|
||||||
|
iconTexture := b.activeTexture(ctx)
|
||||||
|
mouseOverTexture := ctx.Textures.Texture("control-hover")
|
||||||
|
|
||||||
|
pos := Pt(b.Bounds.X, b.Bounds.Y)
|
||||||
|
iconTexture.CopyResize(ctx.Renderer, RectSize(pos.X, pos.Y-40, buttonBarWidth, 120))
|
||||||
|
if b.IsMouseOver && !b.IsDisabled {
|
||||||
|
mouseOverTexture.Copy(ctx.Renderer, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.hoverAnimation != nil {
|
||||||
|
b.hoverAnimation.AnimateFn(b.animate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.IsMouseOver {
|
||||||
|
left := buttonBarWidth - 8 - b.hoverOffset
|
||||||
|
top := pos.Y + buttonBarWidth - 20
|
||||||
|
if left < 0 {
|
||||||
|
part := Rect(-left, 0, b.hoverTexture.Size().X, b.hoverTexture.Size().Y)
|
||||||
|
b.hoverTexture.CopyPart(ctx.Renderer, part, Pt(pos.X, top))
|
||||||
|
} else {
|
||||||
|
b.hoverTexture.Copy(ctx.Renderer, Pt(pos.X+left, top))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.priceTexture.Copy(ctx.Renderer, Pt(pos.X+buttonBarWidth-8-b.priceTexture.Size().X, pos.Y+buttonBarWidth-20))
|
||||||
|
}
|
||||||
|
}
|
34
cmd/imadapt/color.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/tins2020/img"
|
||||||
|
)
|
||||||
|
|
||||||
|
func colorImage(path string, col color.Color) error {
|
||||||
|
src, err := img.DecodeImage(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
colNRGBA := color.NRGBAModel.Convert(col).(color.NRGBA)
|
||||||
|
bounds := src.Bounds()
|
||||||
|
dst := image.NewRGBA(bounds)
|
||||||
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||||
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||||
|
c := src.At(x, y)
|
||||||
|
srcCol := color.NRGBAModel.Convert(c).(color.NRGBA)
|
||||||
|
if srcCol.A > 0 {
|
||||||
|
dstCol := colNRGBA
|
||||||
|
dstCol.A = srcCol.A
|
||||||
|
dst.Set(x, y, dstCol)
|
||||||
|
} else {
|
||||||
|
dst.Set(x, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return img.EncodePNG(path, dst)
|
||||||
|
return nil
|
||||||
|
}
|
@ -5,10 +5,11 @@ import (
|
|||||||
"image/draw"
|
"image/draw"
|
||||||
|
|
||||||
"github.com/nfnt/resize"
|
"github.com/nfnt/resize"
|
||||||
|
"opslag.de/schobers/tins2020/img"
|
||||||
)
|
)
|
||||||
|
|
||||||
func crop(path string, crop rect, size *point) error {
|
func crop(path string, crop rect, size *point) error {
|
||||||
src, err := decodeImage(path)
|
src, err := img.DecodeImage(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -17,9 +18,9 @@ func crop(path string, crop rect, size *point) error {
|
|||||||
dst := image.NewRGBA(dstRect)
|
dst := image.NewRGBA(dstRect)
|
||||||
draw.Draw(dst, dstRect, src, srcRect.Min, draw.Src)
|
draw.Draw(dst, dstRect, src, srcRect.Min, draw.Src)
|
||||||
if size == nil {
|
if size == nil {
|
||||||
return encodePNG(path, dst)
|
return img.EncodePNG(path, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
resized := resize.Resize(uint(size.x), uint(size.y), dst, resize.Bilinear)
|
resized := resize.Resize(uint(size.x), uint(size.y), dst, resize.Bilinear)
|
||||||
return encodePNG(path, resized)
|
return img.EncodePNG(path, resized)
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/tins2020/img"
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertToGray(path string) error {
|
func convertToGray(path string) error {
|
||||||
src, err := decodeImage(path)
|
src, err := img.DecodeImage(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -25,5 +27,5 @@ func convertToGray(path string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return encodePNG(path, dst)
|
return img.EncodePNG(path, dst)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"opslag.de/schobers/tins2020/img"
|
||||||
)
|
)
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
@ -73,6 +75,17 @@ func run() error {
|
|||||||
return fmt.Errorf("couldn't crop '%s'; error: %v", path, err)
|
return fmt.Errorf("couldn't crop '%s'; error: %v", path, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "color":
|
||||||
|
flags := flag.NewFlagSet("color", flag.ContinueOnError)
|
||||||
|
var col string
|
||||||
|
flags.StringVar(&col, "color", "#ff0000", "target color")
|
||||||
|
flags.Parse(args[1:])
|
||||||
|
for _, path := range flags.Args() {
|
||||||
|
err := colorImage(path, img.MustHexColor(col))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't convert to grayscale of '%s'; error: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"opslag.de/schobers/tins2020/img"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setAlpha(path string, alpha int) error {
|
func setAlpha(path string, alpha int) error {
|
||||||
src, err := decodeImage(path)
|
src, err := img.DecodeImage(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -21,5 +23,5 @@ func setAlpha(path string, alpha int) error {
|
|||||||
dst.Set(x, y, c)
|
dst.Set(x, y, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return encodePNG(path, dst)
|
return img.EncodePNG(path, dst)
|
||||||
}
|
}
|
||||||
|
BIN
cmd/tins2020/res/images/basket.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
cmd/tins2020/res/images/fastForward.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
cmd/tins2020/res/images/fastForward_disabled.png
Normal file
After Width: | Height: | Size: 373 B |
BIN
cmd/tins2020/res/images/forward.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
cmd/tins2020/res/images/forward_disabled.png
Normal file
After Width: | Height: | Size: 346 B |
BIN
cmd/tins2020/res/images/gear.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
cmd/tins2020/res/images/genericItem_color_022.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
cmd/tins2020/res/images/genericItem_color_111.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
cmd/tins2020/res/images/pause.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
cmd/tins2020/res/images/pause_disabled.png
Normal file
After Width: | Height: | Size: 246 B |
BIN
cmd/tins2020/res/images/power.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
cmd/tins2020/res/images/save.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -1,4 +1,18 @@
|
|||||||
game-control-hover: images/game_control_hover.png
|
control-hover: images/game_control_hover.png
|
||||||
|
control-shovel: images/genericItem_color_022.png
|
||||||
|
control-research: images/genericItem_color_111.png
|
||||||
|
|
||||||
|
control-settings: images/gear.png
|
||||||
|
control-save: images/save.png
|
||||||
|
control-load: images/basket.png
|
||||||
|
control-quit: images/power.png
|
||||||
|
|
||||||
|
control-pause: images/pause.png
|
||||||
|
control-pause-disabled: images/pause_disabled.png
|
||||||
|
control-run: images/forward.png
|
||||||
|
control-run-disabled: images/forward_disabled.png
|
||||||
|
control-run-fast: images/fastForward.png
|
||||||
|
control-run-fast-disabled: images/fastForward_disabled.png
|
||||||
|
|
||||||
tile-dirt: images/tile_dirt.png
|
tile-dirt: images/tile_dirt.png
|
||||||
tile-grass: images/tile_grass.png
|
tile-grass: images/tile_grass.png
|
||||||
|
@ -42,16 +42,10 @@ func run() error {
|
|||||||
defer ctx.Destroy()
|
defer ctx.Destroy()
|
||||||
|
|
||||||
if ctx.Settings.Window.Location == nil {
|
if ctx.Settings.Window.Location == nil {
|
||||||
ctx.Settings.Window.Location = &tins2020.Point{
|
ctx.Settings.Window.Location = tins2020.PtPtr(sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED)
|
||||||
X: sdl.WINDOWPOS_UNDEFINED,
|
|
||||||
Y: sdl.WINDOWPOS_UNDEFINED,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ctx.Settings.Window.Size == nil {
|
if ctx.Settings.Window.Size == nil {
|
||||||
ctx.Settings.Window.Size = &tins2020.Point{
|
ctx.Settings.Window.Size = tins2020.PtPtr(800, 600)
|
||||||
X: 800,
|
|
||||||
Y: 600,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
|
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
|
||||||
@ -73,8 +67,10 @@ func run() error {
|
|||||||
ctx.Init(renderer)
|
ctx.Init(renderer)
|
||||||
|
|
||||||
err = ctx.Fonts.LoadDesc(
|
err = ctx.Fonts.LoadDesc(
|
||||||
tins2020.FontDescriptor{Name: "debug", Path: "fonts/OpenSans-Regular.ttf", Size: 12},
|
tins2020.FontDescriptor{Name: "debug", Path: "fonts/FiraMono-Regular.ttf", Size: 12},
|
||||||
tins2020.FontDescriptor{Name: "default", Path: "fonts/FiraMono-Regular.ttf", Size: 10},
|
tins2020.FontDescriptor{Name: "default", Path: "fonts/OpenSans-Regular.ttf", Size: 16},
|
||||||
|
tins2020.FontDescriptor{Name: "small", Path: "fonts/OpenSans-Regular.ttf", Size: 12},
|
||||||
|
tins2020.FontDescriptor{Name: "balance", Path: "fonts/OpenSans-Bold.ttf", Size: 40},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -91,7 +87,7 @@ func run() error {
|
|||||||
|
|
||||||
app := tins2020.NewContainer()
|
app := tins2020.NewContainer()
|
||||||
overlays := tins2020.NewContainer()
|
overlays := tins2020.NewContainer()
|
||||||
gameControls := tins2020.NewGameControls()
|
gameControls := tins2020.NewGameControls(game)
|
||||||
overlays.AddChild(gameControls)
|
overlays.AddChild(gameControls)
|
||||||
overlays.AddChild(&tins2020.FPS{})
|
overlays.AddChild(&tins2020.FPS{})
|
||||||
content := tins2020.NewContainer()
|
content := tins2020.NewContainer()
|
||||||
@ -103,6 +99,9 @@ func run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w, h := window.GetSize()
|
||||||
|
app.Arrange(ctx, tins2020.Rect(0, 0, w, h))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
@ -113,10 +112,11 @@ func run() error {
|
|||||||
switch e.Event {
|
switch e.Event {
|
||||||
case sdl.WINDOWEVENT_MOVED:
|
case sdl.WINDOWEVENT_MOVED:
|
||||||
x, y := window.GetPosition()
|
x, y := window.GetPosition()
|
||||||
ctx.Settings.Window.Location = &tins2020.Point{X: x, Y: y}
|
ctx.Settings.Window.Location = tins2020.PtPtr(x, y)
|
||||||
case sdl.WINDOWEVENT_SIZE_CHANGED:
|
case sdl.WINDOWEVENT_SIZE_CHANGED:
|
||||||
w, h := window.GetSize()
|
w, h := window.GetSize()
|
||||||
ctx.Settings.Window.Size = &tins2020.Point{X: w, Y: h}
|
app.Arrange(ctx, tins2020.Rect(0, 0, w, h))
|
||||||
|
ctx.Settings.Window.Size = tins2020.PtPtr(w, h)
|
||||||
}
|
}
|
||||||
case *sdl.KeyboardEvent:
|
case *sdl.KeyboardEvent:
|
||||||
switch e.Keysym.Sym {
|
switch e.Keysym.Sym {
|
||||||
|
50
color.go
@ -1,55 +1,9 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"opslag.de/schobers/tins2020/img"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var hexColorRE = regexp.MustCompile(`^#?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})?$`)
|
func MustHexColor(s string) sdl.Color { return sdl.Color(img.MustHexColor(s)) }
|
||||||
|
|
||||||
func HexColor(s string) (sdl.Color, error) {
|
|
||||||
match := hexColorRE.FindStringSubmatch(s)
|
|
||||||
if match == nil {
|
|
||||||
return sdl.Color{}, errors.New("invalid color format")
|
|
||||||
}
|
|
||||||
values, err := HexToInts(match[1:]...)
|
|
||||||
if err != nil {
|
|
||||||
return sdl.Color{}, err
|
|
||||||
}
|
|
||||||
a := 255
|
|
||||||
if len(match[4]) > 0 {
|
|
||||||
a = values[3]
|
|
||||||
}
|
|
||||||
return sdl.Color{R: uint8(values[0]), G: uint8(values[1]), B: uint8(values[2]), A: uint8(a)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func HexToInt(s string) (int, error) {
|
|
||||||
var i int
|
|
||||||
for _, c := range s {
|
|
||||||
i *= 16
|
|
||||||
if c >= '0' && c <= '9' {
|
|
||||||
i += int(c - '0')
|
|
||||||
} else if c >= 'A' && c <= 'F' {
|
|
||||||
i += int(c - 'A' + 10)
|
|
||||||
} else if c >= 'a' && c <= 'f' {
|
|
||||||
i += int(c - 'a' + 10)
|
|
||||||
} else {
|
|
||||||
return 0, errors.New("hex digit not supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func HexToInts(s ...string) ([]int, error) {
|
|
||||||
ints := make([]int, len(s))
|
|
||||||
for i, s := range s {
|
|
||||||
value, err := HexToInt(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ints[i] = value
|
|
||||||
}
|
|
||||||
return ints, nil
|
|
||||||
}
|
|
||||||
|
26
container.go
@ -5,6 +5,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
|
ControlBase
|
||||||
|
|
||||||
Children []Control
|
Children []Control
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,19 +18,23 @@ func (c *Container) AddChild(child Control) {
|
|||||||
c.Children = append(c.Children, child)
|
c.Children = append(c.Children, child)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) Arrange(ctx *Context, bounds Rectangle) {
|
||||||
|
c.ControlBase.Arrange(ctx, bounds)
|
||||||
|
for _, child := range c.Children {
|
||||||
|
child.Arrange(ctx, bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Container) Handle(ctx *Context, event sdl.Event) {
|
func (c *Container) Handle(ctx *Context, event sdl.Event) {
|
||||||
|
c.ControlBase.Handle(ctx, event)
|
||||||
for _, child := range c.Children {
|
for _, child := range c.Children {
|
||||||
child.Handle(ctx, event)
|
child.Handle(ctx, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Render(ctx *Context) {
|
|
||||||
for _, child := range c.Children {
|
|
||||||
child.Render(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Init(ctx *Context) error {
|
func (c *Container) Init(ctx *Context) error {
|
||||||
|
c.ControlBase.Init(ctx)
|
||||||
|
|
||||||
for _, child := range c.Children {
|
for _, child := range c.Children {
|
||||||
err := child.Init(ctx)
|
err := child.Init(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -37,3 +43,11 @@ func (c *Container) Init(ctx *Context) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) Render(ctx *Context) {
|
||||||
|
c.ControlBase.Render(ctx)
|
||||||
|
|
||||||
|
for _, child := range c.Children {
|
||||||
|
child.Render(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
38
control.go
@ -4,15 +4,47 @@ import "github.com/veandco/go-sdl2/sdl"
|
|||||||
|
|
||||||
type Control interface {
|
type Control interface {
|
||||||
Init(*Context) error
|
Init(*Context) error
|
||||||
|
Arrange(*Context, Rectangle)
|
||||||
Handle(*Context, sdl.Event)
|
Handle(*Context, sdl.Event)
|
||||||
Render(*Context)
|
Render(*Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ControlBase struct {
|
type EventFn func(*Context)
|
||||||
|
|
||||||
|
type EmptyEventFn func()
|
||||||
|
|
||||||
|
func EmptyEvent(fn EmptyEventFn) EventFn {
|
||||||
|
return func(*Context) { fn() }
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ControlBase) Handle(*Context, sdl.Event) {}
|
type ControlBase struct {
|
||||||
|
Bounds Rectangle
|
||||||
|
|
||||||
func (b *ControlBase) Render(*Context) {}
|
IsMouseOver bool
|
||||||
|
|
||||||
|
OnLeftMouseButtonClick EventFn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds }
|
||||||
|
|
||||||
func (b *ControlBase) Init(*Context) error { return nil }
|
func (b *ControlBase) Init(*Context) error { return nil }
|
||||||
|
|
||||||
|
func (b *ControlBase) Handle(ctx *Context, event sdl.Event) {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *sdl.MouseMotionEvent:
|
||||||
|
b.IsMouseOver = b.Bounds.IsPointInside(e.X, e.Y)
|
||||||
|
case *sdl.MouseButtonEvent:
|
||||||
|
if b.IsMouseOver && e.Button == sdl.BUTTON_LEFT && e.Type == sdl.MOUSEBUTTONDOWN {
|
||||||
|
b.Invoke(ctx, b.OnLeftMouseButtonClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ControlBase) Invoke(ctx *Context, fn EventFn) {
|
||||||
|
if fn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ControlBase) Render(*Context) {}
|
||||||
|
13
flower.go
@ -34,13 +34,14 @@ type FlowerResistance struct {
|
|||||||
// NewPoppyTraits creates the traits of a poppy, a very generic flower that thrives in a moderate climate.
|
// NewPoppyTraits creates the traits of a poppy, a very generic flower that thrives in a moderate climate.
|
||||||
func NewPoppyTraits() FlowerTraits {
|
func NewPoppyTraits() FlowerTraits {
|
||||||
return FlowerTraits{
|
return FlowerTraits{
|
||||||
Spread: 0.0007,
|
// Spread: 0.0011,
|
||||||
Life: 0.99991,
|
Spread: 0.0011,
|
||||||
|
Life: 0.99993,
|
||||||
Resistance: FlowerResistance{
|
Resistance: FlowerResistance{
|
||||||
Cold: 0.3,
|
Cold: 0.5,
|
||||||
Hot: 0.3,
|
Hot: 0.5,
|
||||||
Dry: 0.3,
|
Dry: 0.5,
|
||||||
Wet: 0.3,
|
Wet: 0.5,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
fonts.go
@ -20,7 +20,7 @@ type Font struct {
|
|||||||
*ttf.Font
|
*ttf.Font
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) Render(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) (*Texture, error) {
|
func (f *Font) Render(renderer *sdl.Renderer, text string, color sdl.Color) (*Texture, error) {
|
||||||
surface, err := f.RenderUTF8Blended(text, color)
|
surface, err := f.RenderUTF8Blended(text, color)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -34,31 +34,25 @@ func (f *Font) Render(renderer *sdl.Renderer, text string, pos Point, color sdl.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) RenderCopyAlign(renderer *sdl.Renderer, text string, pos Point, color sdl.Color, align TextAlignment) error {
|
func (f *Font) RenderCopyAlign(renderer *sdl.Renderer, text string, pos Point, color sdl.Color, align TextAlignment) error {
|
||||||
texture, err := f.Render(renderer, text, pos, color)
|
texture, err := f.Render(renderer, text, color)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer texture.Destroy()
|
defer texture.Destroy()
|
||||||
rect := texture.Rect()
|
size := texture.Size()
|
||||||
switch align {
|
switch align {
|
||||||
case TextAlignmentLeft:
|
case TextAlignmentLeft:
|
||||||
texture.Copy(renderer, &sdl.Rect{X: pos.X, Y: pos.Y, W: rect.W, H: rect.H})
|
texture.Copy(renderer, Pt(pos.X, pos.Y-size.Y))
|
||||||
case TextAlignmentCenter:
|
case TextAlignmentCenter:
|
||||||
texture.Copy(renderer, &sdl.Rect{X: pos.X - (rect.W / 2), Y: pos.Y, W: rect.W, H: rect.H})
|
texture.Copy(renderer, Pt(pos.X-(size.X/2), pos.Y-size.Y))
|
||||||
case TextAlignmentRight:
|
case TextAlignmentRight:
|
||||||
texture.Copy(renderer, &sdl.Rect{X: pos.X - rect.W, Y: pos.Y, W: rect.W, H: rect.H})
|
texture.Copy(renderer, Pt(pos.X-size.X, pos.Y-size.Y))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) RenderCopy(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) error {
|
func (f *Font) RenderCopy(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) error {
|
||||||
texture, err := f.Render(renderer, text, pos, color)
|
return f.RenderCopyAlign(renderer, text, pos, color, TextAlignmentLeft)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer texture.Destroy()
|
|
||||||
texture.Copy(renderer, texture.RectOffset(pos))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fonts struct {
|
type Fonts struct {
|
||||||
|
@ -37,5 +37,5 @@ func (f *FPS) Render(ctx *Context) {
|
|||||||
f.ticks[f.slot]++
|
f.ticks[f.slot]++
|
||||||
|
|
||||||
font := ctx.Fonts.Font("debug")
|
font := ctx.Fonts.Font("debug")
|
||||||
font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 5), sdl.Color{R: 255, G: 255, B: 255, A: 255})
|
font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 17), sdl.Color{R: 255, G: 255, B: 255, A: 255})
|
||||||
}
|
}
|
||||||
|
53
game.go
@ -6,13 +6,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
Money int
|
Balance int
|
||||||
|
Speed GameSpeed
|
||||||
Terrain *Map
|
Terrain *Map
|
||||||
|
|
||||||
start time.Time
|
simulation Animation
|
||||||
lastUpdate time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GameSpeed string
|
||||||
|
|
||||||
|
const (
|
||||||
|
GameSpeedNormal GameSpeed = "normal"
|
||||||
|
GameSpeedFast = "fast"
|
||||||
|
GameSpeedPaused = "paused"
|
||||||
|
)
|
||||||
|
|
||||||
type Map struct {
|
type Map struct {
|
||||||
Temp NoiseMap
|
Temp NoiseMap
|
||||||
Humid NoiseMap
|
Humid NoiseMap
|
||||||
@ -36,6 +44,9 @@ func (m *Map) NewFlower(pos Point, traits FlowerTraits) Flower {
|
|||||||
return flower
|
return flower
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const simulationInterval = 120 * time.Millisecond
|
||||||
|
const fastSimulationInterval = 40 * time.Millisecond
|
||||||
|
|
||||||
func NewGame() *Game {
|
func NewGame() *Game {
|
||||||
terrain := &Map{
|
terrain := &Map{
|
||||||
Temp: NewNoiseMap(rand.Int63()),
|
Temp: NewNoiseMap(rand.Int63()),
|
||||||
@ -47,18 +58,11 @@ func NewGame() *Game {
|
|||||||
}
|
}
|
||||||
terrain.AddFlower(Pt(0, 0), NewPoppyTraits())
|
terrain.AddFlower(Pt(0, 0), NewPoppyTraits())
|
||||||
return &Game{
|
return &Game{
|
||||||
Money: 100,
|
Speed: GameSpeedNormal,
|
||||||
|
Balance: 100,
|
||||||
Terrain: terrain,
|
Terrain: terrain,
|
||||||
|
|
||||||
start: time.Now(),
|
simulation: NewAnimation(time.Millisecond * 10),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Game) Update() {
|
|
||||||
update := time.Since(g.start)
|
|
||||||
for g.lastUpdate < update {
|
|
||||||
g.tick()
|
|
||||||
g.lastUpdate += time.Millisecond * 10
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,3 +96,26 @@ func (g *Game) tick() {
|
|||||||
}
|
}
|
||||||
g.Terrain.Flowers = flowers
|
g.Terrain.Flowers = flowers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Game) Pause() {
|
||||||
|
g.Speed = GameSpeedPaused
|
||||||
|
g.simulation.Pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Run() {
|
||||||
|
g.Speed = GameSpeedNormal
|
||||||
|
g.simulation.SetInterval(simulationInterval)
|
||||||
|
g.simulation.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) RunFast() {
|
||||||
|
g.Speed = GameSpeedFast
|
||||||
|
g.simulation.SetInterval(fastSimulationInterval)
|
||||||
|
g.simulation.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Update() {
|
||||||
|
for g.simulation.Animate() {
|
||||||
|
g.tick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
149
gamecontrols.go
@ -1,113 +1,90 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GameControls struct {
|
type GameControls struct {
|
||||||
ControlBase
|
Container
|
||||||
|
|
||||||
|
game *Game
|
||||||
|
|
||||||
menu ButtonBar
|
menu ButtonBar
|
||||||
|
top ButtonBar
|
||||||
flowers ButtonBar
|
flowers ButtonBar
|
||||||
|
|
||||||
|
pause *IconButton
|
||||||
|
run *IconButton
|
||||||
|
runFast *IconButton
|
||||||
}
|
}
|
||||||
|
|
||||||
type ButtonBar struct {
|
func NewGameControls(game *Game) *GameControls {
|
||||||
Top int32
|
return &GameControls{game: game}
|
||||||
Left int32
|
|
||||||
Bottom int32
|
|
||||||
Hover int
|
|
||||||
Buttons []Button
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Button struct {
|
func (c *GameControls) updateSpeedControls() {
|
||||||
Icon string
|
disable := func(b *IconButton, speed GameSpeed) {
|
||||||
Disabled string
|
b.IsDisabled = speed == c.game.Speed
|
||||||
|
|
||||||
IsDisabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttonBarWidth = 96
|
|
||||||
|
|
||||||
func (b *ButtonBar) Handle(ctx *Context, event sdl.Event) {
|
|
||||||
switch e := event.(type) {
|
|
||||||
case *sdl.MouseMotionEvent:
|
|
||||||
if e.X > b.Left && e.X < b.Left+buttonBarWidth {
|
|
||||||
button := int(e.Y-b.Top) / buttonBarWidth
|
|
||||||
if button < 0 || button >= len(b.Buttons) || b.Buttons[button].IsDisabled {
|
|
||||||
button = -1
|
|
||||||
}
|
|
||||||
b.Hover = button
|
|
||||||
} else {
|
|
||||||
b.Hover = -1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
disable(c.pause, GameSpeedPaused)
|
||||||
|
disable(c.run, GameSpeedNormal)
|
||||||
|
disable(c.runFast, GameSpeedFast)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ButtonBar) Render(ctx *Context) {
|
func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) {
|
||||||
ctx.Renderer.FillRect(&sdl.Rect{X: b.Left, Y: b.Top, W: b.Left + buttonBarWidth, H: b.Bottom})
|
c.Bounds = bounds
|
||||||
texture := func(b Button) *Texture {
|
c.menu.Arrange(ctx, RectSize(bounds.X, bounds.Y, buttonBarWidth, bounds.H))
|
||||||
if b.IsDisabled {
|
c.top.Arrange(ctx, Rect(bounds.X+bounds.W/2+8, bounds.Y, bounds.Right(), bounds.Y+64))
|
||||||
texture := ctx.Textures.Texture(b.Disabled)
|
c.flowers.Arrange(ctx, RectSize(bounds.Right()-buttonBarWidth, bounds.Y, buttonBarWidth, bounds.H))
|
||||||
if texture != nil {
|
|
||||||
return texture
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ctx.Textures.Texture(b.Icon)
|
|
||||||
}
|
|
||||||
hoverTexture := ctx.Textures.Texture("game-control-hover")
|
|
||||||
for i, button := range b.Buttons {
|
|
||||||
pos := Pt(b.Left, b.Top+int32(i)*buttonBarWidth)
|
|
||||||
texture := texture(button)
|
|
||||||
texture.Copy(ctx.Renderer, &sdl.Rect{X: pos.X, Y: pos.Y - 40, W: buttonBarWidth, H: 120})
|
|
||||||
if b.Hover == i {
|
|
||||||
hoverTexture.Copy(ctx.Renderer, hoverTexture.RectOffset(pos))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGameControls() *GameControls {
|
func (c *GameControls) buyPoppy(ctx *Context) {
|
||||||
return &GameControls{}
|
c.game.Balance -= 10
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GameControls) Init(ctx *Context) error {
|
func (c *GameControls) Init(ctx *Context) error {
|
||||||
c.flowers.Hover = -1
|
c.flowers.Background = MustHexColor("#356dad") // brown alternative? #4ac69a
|
||||||
c.flowers.Buttons = []Button{
|
c.flowers.Buttons = []Control{
|
||||||
Button{Icon: "flower-poppy-1", Disabled: "flower-poppy-disabled"},
|
NewBuyFlowerButton("flower-poppy-1", "flower-poppy-disabled", "Poppy", 10, "A very generic flower that thrives in a moderate climate.", false, c.buyPoppy),
|
||||||
Button{Icon: "flower-red-c-1", Disabled: "flower-red-c-disabled", IsDisabled: true},
|
NewBuyFlowerButton("flower-red-c-1", "flower-poppy-disabled", "Unknown", 100, "Traits are not known yet.", true, nil),
|
||||||
}
|
}
|
||||||
return c.updateBarPositions(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *GameControls) Handle(ctx *Context, event sdl.Event) {
|
c.top.Orientation = OrientationHorizontal
|
||||||
c.menu.Handle(ctx, event)
|
c.pause = NewIconButtonConfig("control-pause", EmptyEvent(func() {
|
||||||
c.flowers.Handle(ctx, event)
|
c.game.Pause()
|
||||||
|
c.updateSpeedControls()
|
||||||
|
}), func(b *IconButton) {
|
||||||
|
b.IconDisabled = "control-pause-disabled"
|
||||||
|
})
|
||||||
|
c.run = NewIconButtonConfig("control-run", EmptyEvent(func() {
|
||||||
|
c.game.Run()
|
||||||
|
c.updateSpeedControls()
|
||||||
|
}), func(b *IconButton) {
|
||||||
|
b.IconDisabled = "control-run-disabled"
|
||||||
|
})
|
||||||
|
c.runFast = NewIconButtonConfig("control-run-fast", EmptyEvent(func() {
|
||||||
|
c.game.RunFast()
|
||||||
|
c.updateSpeedControls()
|
||||||
|
}), func(b *IconButton) {
|
||||||
|
b.IconDisabled = "control-run-fast-disabled"
|
||||||
|
})
|
||||||
|
c.updateSpeedControls()
|
||||||
|
c.top.Buttons = []Control{c.pause, c.run, c.runFast}
|
||||||
|
|
||||||
switch e := event.(type) {
|
c.menu.Background = MustHexColor("#356dad")
|
||||||
case *sdl.WindowEvent:
|
c.menu.Buttons = []Control{
|
||||||
switch e.Event {
|
NewIconButton("control-settings", EmptyEvent(func() {})),
|
||||||
case sdl.WINDOWEVENT_RESIZED:
|
NewIconButton("control-save", EmptyEvent(func() {})),
|
||||||
err := c.updateBarPositions(ctx)
|
NewIconButton("control-load", EmptyEvent(func() {})),
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.Container.AddChild(&c.menu)
|
||||||
|
c.Container.AddChild(&c.top)
|
||||||
|
c.Container.AddChild(&c.flowers)
|
||||||
|
return c.Container.Init(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GameControls) Render(ctx *Context) {
|
func (c *GameControls) Render(ctx *Context) {
|
||||||
ctx.Renderer.SetDrawColor(74, 198, 154, 255)
|
topBar := MustHexColor("#0000007f")
|
||||||
c.menu.Render(ctx)
|
ctx.Renderer.SetDrawColor(topBar.R, topBar.G, topBar.B, topBar.A)
|
||||||
c.flowers.Render(ctx)
|
ctx.Renderer.FillRect(Rect(c.menu.Bounds.Right(), 0, c.flowers.Bounds.X, 64).SDLPtr())
|
||||||
}
|
ctx.Fonts.Font("balance").RenderCopyAlign(ctx.Renderer, FmtMoney(c.game.Balance), Pt(c.top.Bounds.X-8, 58), MustHexColor("#4AC69A"), TextAlignmentRight)
|
||||||
|
|
||||||
func (c *GameControls) updateBarPositions(ctx *Context) error {
|
c.Container.Render(ctx)
|
||||||
w, h, err := ctx.Renderer.GetOutputSize()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.menu.Top, c.menu.Left, c.menu.Bottom = 0, 0, h
|
|
||||||
c.flowers.Top, c.flowers.Left, c.flowers.Bottom = 0, w-buttonBarWidth, h
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
62
iconbutton.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package tins2020
|
||||||
|
|
||||||
|
type IconButton struct {
|
||||||
|
ControlBase
|
||||||
|
|
||||||
|
Icon string
|
||||||
|
IconDisabled string
|
||||||
|
IconScale Scale
|
||||||
|
IconWidth int32
|
||||||
|
|
||||||
|
IsDisabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Scale int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScaleCenter Scale = iota
|
||||||
|
ScaleStretch
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewIconButton(icon string, onClick EventFn) *IconButton {
|
||||||
|
return &IconButton{
|
||||||
|
ControlBase: ControlBase{
|
||||||
|
OnLeftMouseButtonClick: onClick,
|
||||||
|
},
|
||||||
|
Icon: icon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIconButtonConfig(icon string, onClick EventFn, configure func(*IconButton)) *IconButton {
|
||||||
|
button := NewIconButton(icon, onClick)
|
||||||
|
configure(button)
|
||||||
|
return button
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *IconButton) activeTexture(ctx *Context) *Texture {
|
||||||
|
if b.IsDisabled {
|
||||||
|
texture := ctx.Textures.Texture(b.IconDisabled)
|
||||||
|
if texture != nil {
|
||||||
|
return texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx.Textures.Texture(b.Icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *IconButton) Render(ctx *Context) {
|
||||||
|
iconTexture := b.activeTexture(ctx)
|
||||||
|
mouseOverTexture := ctx.Textures.Texture("control-hover")
|
||||||
|
|
||||||
|
if b.IconScale == ScaleCenter {
|
||||||
|
size := iconTexture.Size()
|
||||||
|
if b.IconWidth != 0 {
|
||||||
|
size = Pt(b.IconWidth, b.IconWidth*size.Y/size.X)
|
||||||
|
}
|
||||||
|
iconTexture.CopyResize(ctx.Renderer, RectSize(b.Bounds.X+(b.Bounds.W-size.X)/2, b.Bounds.Y+(b.Bounds.H-size.Y)/2, size.X, size.Y))
|
||||||
|
} else {
|
||||||
|
iconTexture.CopyResize(ctx.Renderer, b.Bounds)
|
||||||
|
}
|
||||||
|
if b.IsMouseOver && !b.IsDisabled {
|
||||||
|
mouseOverTexture.CopyResize(ctx.Renderer, b.Bounds)
|
||||||
|
}
|
||||||
|
}
|
62
img/color.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package img
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image/color"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hexColorRE = regexp.MustCompile(`^#?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})-?([0-9A-Fa-f]{2})?$`)
|
||||||
|
|
||||||
|
func HexColor(s string) (color.RGBA, error) {
|
||||||
|
match := hexColorRE.FindStringSubmatch(s)
|
||||||
|
if match == nil {
|
||||||
|
return color.RGBA{}, errors.New("invalid color format")
|
||||||
|
}
|
||||||
|
values, err := HexToInts(match[1:]...)
|
||||||
|
if err != nil {
|
||||||
|
return color.RGBA{}, err
|
||||||
|
}
|
||||||
|
a := 255
|
||||||
|
if len(match[4]) > 0 {
|
||||||
|
a = values[3]
|
||||||
|
}
|
||||||
|
return color.RGBA{R: uint8(values[0]), G: uint8(values[1]), B: uint8(values[2]), A: uint8(a)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustHexColor(s string) color.RGBA {
|
||||||
|
color, err := HexColor(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
func HexToInt(s string) (int, error) {
|
||||||
|
var i int
|
||||||
|
for _, c := range s {
|
||||||
|
i *= 16
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
i += int(c - '0')
|
||||||
|
} else if c >= 'A' && c <= 'F' {
|
||||||
|
i += int(c - 'A' + 10)
|
||||||
|
} else if c >= 'a' && c <= 'f' {
|
||||||
|
i += int(c - 'a' + 10)
|
||||||
|
} else {
|
||||||
|
return 0, errors.New("hex digit not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HexToInts(s ...string) ([]int, error) {
|
||||||
|
ints := make([]int, len(s))
|
||||||
|
for i, s := range s {
|
||||||
|
value, err := HexToInt(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ints[i] = value
|
||||||
|
}
|
||||||
|
return ints, nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package img
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func encodePNG(path string, im image.Image) error {
|
func EncodePNG(path string, im image.Image) error {
|
||||||
dst, err := os.Create(path)
|
dst, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -15,7 +15,7 @@ func encodePNG(path string, im image.Image) error {
|
|||||||
return png.Encode(dst, im)
|
return png.Encode(dst, im)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeImage(path string) (image.Image, error) {
|
func DecodeImage(path string) (image.Image, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
14
math.go
@ -20,6 +20,13 @@ func Ceil32(x float32) float32 { return float32(math.Ceil(float64(x))) }
|
|||||||
|
|
||||||
func Floor32(x float32) float32 { return float32(math.Floor(float64(x))) }
|
func Floor32(x float32) float32 { return float32(math.Floor(float64(x))) }
|
||||||
|
|
||||||
|
func Max(a, b int32) int32 {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func Max32(a, b float32) float32 {
|
func Max32(a, b float32) float32 {
|
||||||
if a > b {
|
if a > b {
|
||||||
return a
|
return a
|
||||||
@ -27,6 +34,13 @@ func Max32(a, b float32) float32 {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Min(a, b int32) int32 {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func Min32(a, b float32) float32 {
|
func Min32(a, b float32) float32 {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
|
5
money.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package tins2020
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func FmtMoney(amount int) string { return fmt.Sprintf("$ %d", amount) }
|
32
point.go
@ -1,18 +1,40 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
|
import "github.com/veandco/go-sdl2/sdl"
|
||||||
|
|
||||||
type Point struct {
|
type Point struct {
|
||||||
X, Y int32
|
sdl.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Point) Add(q Point) Point { return Pt(p.X+q.X, p.Y+q.Y) }
|
func (p Point) Add(q Point) Point { return Pt(p.X+q.X, p.Y+q.Y) }
|
||||||
|
|
||||||
|
func (p Point) In(r Rectangle) bool { return r.IsPointInsidePt(p) }
|
||||||
|
|
||||||
|
func (p Point) ToPtF() PointF { return PtF(float32(p.X), float32(p.Y)) }
|
||||||
|
|
||||||
type PointF struct {
|
type PointF struct {
|
||||||
X, Y float32
|
sdl.FPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PointF) Add(q PointF) PointF { return PtF(p.X+q.X, p.Y+q.Y) }
|
func (p PointF) Add(q PointF) PointF {
|
||||||
|
return PtF(p.X+q.X, p.Y+q.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PointF) Mul(f float32) PointF {
|
||||||
|
return PtF(f*p.X, f*p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
func (p PointF) Sub(q PointF) PointF { return PtF(p.X-q.X, p.Y-q.Y) }
|
func (p PointF) Sub(q PointF) PointF { return PtF(p.X-q.X, p.Y-q.Y) }
|
||||||
|
|
||||||
func Pt(x, y int32) Point { return Point{x, y} }
|
func Pt(x, y int32) Point { return Point{sdl.Point{X: x, Y: y}} }
|
||||||
func PtF(x, y float32) PointF { return PointF{x, y} }
|
func PtF(x, y float32) PointF { return PointF{sdl.FPoint{X: x, Y: y}} }
|
||||||
|
|
||||||
|
func PtPtr(x, y int32) *Point {
|
||||||
|
p := Pt(x, y)
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
|
func PtFPtr(x, y float32) *PointF {
|
||||||
|
p := PtF(x, y)
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
@ -11,7 +11,8 @@ type projection struct {
|
|||||||
zoom float32
|
zoom float32
|
||||||
zoomInv float32
|
zoomInv float32
|
||||||
|
|
||||||
windowRect sdl.Rect
|
windowInteractRect Rectangle
|
||||||
|
windowVisibleRect Rectangle
|
||||||
tileScreenDelta PointF
|
tileScreenDelta PointF
|
||||||
tileScreenDeltaInv PointF
|
tileScreenDeltaInv PointF
|
||||||
tileScreenOffset Point
|
tileScreenOffset Point
|
||||||
@ -30,7 +31,7 @@ func (p *projection) mapToScreen(x, y int32) Point {
|
|||||||
|
|
||||||
func (p *projection) mapToScreenF(x, y float32) Point {
|
func (p *projection) mapToScreenF(x, y float32) Point {
|
||||||
translated := PtF(x-p.center.X, y-p.center.Y)
|
translated := PtF(x-p.center.X, y-p.center.Y)
|
||||||
return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*64*p.zoomInv), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoomInv))
|
return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*64*p.zoom), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoom))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projection) screenToMap(x, y int32) PointF {
|
func (p *projection) screenToMap(x, y int32) PointF {
|
||||||
@ -39,49 +40,51 @@ func (p *projection) screenToMap(x, y int32) PointF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *projection) screenToMapRel(x, y int32) PointF {
|
func (p *projection) screenToMapRel(x, y int32) PointF {
|
||||||
normX := p.zoom * float32(x)
|
normX := p.zoomInv * float32(x)
|
||||||
normY := p.zoom * float32(y)
|
normY := p.zoomInv * float32(y)
|
||||||
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
|
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projection) screenToTileFitRect(pos Point) *sdl.Rect {
|
func (p *projection) screenToTileFitRect(pos Point) Rectangle {
|
||||||
return &sdl.Rect{X: pos.X - p.tileFitScreenSize.X, Y: pos.Y - p.tileFitScreenSize.Y, W: 2 * p.tileFitScreenSize.X, H: 2 * p.tileFitScreenSize.Y}
|
return RectSize(pos.X-p.tileFitScreenSize.X, pos.Y-p.tileFitScreenSize.Y, 2*p.tileFitScreenSize.X, 2*p.tileFitScreenSize.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projection) screenToTileRect(pos Point) *sdl.Rect {
|
func (p *projection) screenToTileRect(pos Point) Rectangle {
|
||||||
return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y}
|
return RectSize(pos.X-p.tileScreenOffset.X, pos.Y-p.tileScreenOffset.Y, p.tileScreenSize.X, p.tileScreenSize.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projection) update(renderer *sdl.Renderer) {
|
func (p *projection) update(renderer *sdl.Renderer) {
|
||||||
p.zoomInv = 1 / p.zoom
|
p.zoomInv = 1 / p.zoom
|
||||||
|
|
||||||
p.tileScreenOffset = Pt(int32(p.zoomInv*64), int32(p.zoomInv*112))
|
p.tileScreenOffset = Pt(int32(p.zoom*64), int32(p.zoom*112))
|
||||||
p.tileScreenSize = Pt(int32(p.zoomInv*128), int32(p.zoomInv*160))
|
p.tileScreenSize = Pt(int32(p.zoom*128), int32(p.zoom*160))
|
||||||
p.tileFitScreenSize = Pt(int32(p.zoomInv*64), int32(p.zoomInv*32))
|
p.tileFitScreenSize = Pt(int32(p.zoom*64), int32(p.zoom*32))
|
||||||
|
|
||||||
windowW, windowH, err := renderer.GetOutputSize()
|
windowW, windowH, err := renderer.GetOutputSize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
p.windowCenter = Pt(windowW/2, windowH/2)
|
p.windowCenter = Pt(windowW/2, windowH/2)
|
||||||
p.windowRect = sdl.Rect{X: buttonBarWidth, Y: 0, W: windowW - 2*buttonBarWidth, H: windowH - 0}
|
p.windowInteractRect = Rect(buttonBarWidth, 64, windowW-buttonBarWidth, windowH)
|
||||||
|
p.windowVisibleRect = Rect(buttonBarWidth, 0, windowW-buttonBarWidth, windowH+p.tileScreenSize.Y) // Adding a tile height to the bottom for trees that stick out from the cells below.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projection) visibleTiles(action func(int32, int32, Point)) {
|
func (p *projection) visibleTiles(action func(int32, int32, Point)) {
|
||||||
topLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y)
|
visible := p.windowVisibleRect
|
||||||
topRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y)
|
topLeft := p.screenToMap(visible.X, visible.Y)
|
||||||
bottomLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y+p.windowRect.H)
|
topRight := p.screenToMap(visible.X+visible.W, visible.Y)
|
||||||
bottomRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y+p.windowRect.H)
|
bottomLeft := p.screenToMap(visible.X, visible.Y+visible.H)
|
||||||
|
bottomRight := p.screenToMap(visible.X+visible.W, visible.Y+visible.H)
|
||||||
minY, maxY := int32(Floor32(topRight.Y)), int32(Ceil32(bottomLeft.Y))
|
minY, maxY := int32(Floor32(topRight.Y)), int32(Ceil32(bottomLeft.Y))
|
||||||
minX, maxX := int32(Floor32(topLeft.X)), int32(Ceil32(bottomRight.X))
|
minX, maxX := int32(Floor32(topLeft.X)), int32(Ceil32(bottomRight.X))
|
||||||
for y := minY; y <= maxY; y++ {
|
for y := minY; y <= maxY; y++ {
|
||||||
for x := minX; x <= maxX; x++ {
|
for x := minX; x <= maxX; x++ {
|
||||||
pos := p.mapToScreen(x, y)
|
pos := p.mapToScreen(x, y)
|
||||||
rectFit := p.screenToTileFitRect(pos)
|
rectFit := p.screenToTileFitRect(pos)
|
||||||
if rectFit.X+rectFit.W < p.windowRect.X || rectFit.Y+rectFit.H < p.windowRect.Y {
|
if rectFit.X+rectFit.W < visible.X || rectFit.Y+rectFit.H < visible.Y {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if rectFit.X > p.windowRect.X+p.windowRect.W || rectFit.Y > p.windowRect.Y+p.windowRect.H {
|
if rectFit.X > visible.X+visible.W || rectFit.Y > visible.Y+visible.H {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
action(x, y, pos)
|
action(x, y, pos)
|
||||||
|
32
rect.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package tins2020
|
||||||
|
|
||||||
|
import "github.com/veandco/go-sdl2/sdl"
|
||||||
|
|
||||||
|
type Rectangle struct {
|
||||||
|
sdl.Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rect(x1, y1, x2, y2 int32) Rectangle {
|
||||||
|
if x1 > x2 {
|
||||||
|
x1, x2 = x2, x1
|
||||||
|
}
|
||||||
|
if y1 > y2 {
|
||||||
|
y1, y2 = y2, y1
|
||||||
|
}
|
||||||
|
return Rectangle{sdl.Rect{X: x1, Y: y1, W: x2 - x1, H: y2 - y1}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RectSize(x, y, w, h int32) Rectangle { return Rectangle{sdl.Rect{X: x, Y: y, W: w, H: h}} }
|
||||||
|
|
||||||
|
func (r Rectangle) Bottom() int32 { return r.Y + r.H }
|
||||||
|
|
||||||
|
func (r Rectangle) IsPointInside(x, y int32) bool {
|
||||||
|
return x >= r.X && x < r.X+r.W && y >= r.Y && y < r.Y+r.H
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rectangle) IsPointInsidePt(p Point) bool { return r.IsPointInside(p.X, p.Y) }
|
||||||
|
|
||||||
|
func (r Rectangle) Right() int32 { return r.X + r.W }
|
||||||
|
|
||||||
|
func (r Rectangle) SDL() sdl.Rect { return r.Rect }
|
||||||
|
func (r Rectangle) SDLPtr() *sdl.Rect { return &r.Rect }
|
@ -24,6 +24,10 @@ func NewTerrainRenderer(terrain *Map) Control {
|
|||||||
return &terrainRenderer{terrain: terrain, project: newProjection()}
|
return &terrainRenderer{terrain: terrain, project: newProjection()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *terrainRenderer) Arrange(ctx *Context, _ Rectangle) {
|
||||||
|
r.project.update(ctx.Renderer)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *terrainRenderer) Init(ctx *Context) error {
|
func (r *terrainRenderer) Init(ctx *Context) error {
|
||||||
r.project.update(ctx.Renderer)
|
r.project.update(ctx.Renderer)
|
||||||
return nil
|
return nil
|
||||||
@ -32,47 +36,46 @@ func (r *terrainRenderer) Init(ctx *Context) error {
|
|||||||
func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
|
func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *sdl.MouseButtonEvent:
|
case *sdl.MouseButtonEvent:
|
||||||
if e.Button == sdl.BUTTON_LEFT {
|
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
|
||||||
r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN
|
if e.Button == sdl.BUTTON_LEFT {
|
||||||
if r.interact.mouseLeftDown && r.interact.mouseDrag == nil {
|
r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN
|
||||||
r.interact.mouseDrag = &Point{e.X, e.Y}
|
if r.interact.mouseLeftDown && r.interact.mouseDrag == nil {
|
||||||
} else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil {
|
r.interact.mouseDrag = PtPtr(e.X, e.Y)
|
||||||
r.interact.mouseDrag = nil
|
} else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil {
|
||||||
|
r.interact.mouseDrag = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *sdl.MouseMotionEvent:
|
case *sdl.MouseMotionEvent:
|
||||||
if insideRect(e.X, e.Y, &r.project.windowRect) {
|
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
|
||||||
hover := r.project.screenToMap(e.X, e.Y)
|
hover := r.project.screenToMap(e.X, e.Y)
|
||||||
r.hover = &Point{X: int32(Round32(hover.X)), Y: int32(Round32(hover.Y))}
|
r.hover = PtPtr(int32(Round32(hover.X)), int32(Round32(hover.Y)))
|
||||||
} else {
|
} else {
|
||||||
r.hover = nil
|
r.hover = nil
|
||||||
}
|
}
|
||||||
if r.interact.mouseDrag != nil {
|
if r.interact.mouseDrag != nil {
|
||||||
r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y))
|
r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y))
|
||||||
r.project.update(ctx.Renderer)
|
r.project.update(ctx.Renderer)
|
||||||
r.interact.mouseDrag = &Point{e.X, e.Y}
|
r.interact.mouseDrag = PtPtr(e.X, e.Y)
|
||||||
}
|
}
|
||||||
case *sdl.MouseWheelEvent:
|
case *sdl.MouseWheelEvent:
|
||||||
if e.Y > 0 && r.project.zoom > .5 {
|
if r.hover != nil {
|
||||||
r.project.zoom *= .5
|
zoom := r.project.zoom
|
||||||
r.project.update(ctx.Renderer)
|
if e.Y < 0 && r.project.zoom > .25 {
|
||||||
} else if e.Y < 0 && r.project.zoom < 4 {
|
zoom *= .5
|
||||||
r.project.zoom *= 2
|
} else if e.Y > 0 && r.project.zoom < 2 {
|
||||||
r.project.update(ctx.Renderer)
|
zoom *= 2
|
||||||
}
|
}
|
||||||
case *sdl.WindowEvent:
|
if zoom != r.project.zoom {
|
||||||
if e.Event == sdl.WINDOWEVENT_RESIZED {
|
hover := r.hover.ToPtF()
|
||||||
r.project.update(ctx.Renderer)
|
r.project.center = hover.Sub(hover.Sub(r.project.center).Mul(r.project.zoom / zoom))
|
||||||
|
r.project.zoom = zoom
|
||||||
|
r.project.update(ctx.Renderer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func insideRect(x, y int32, rect *sdl.Rect) bool {
|
|
||||||
return x >= rect.X && x < rect.X+rect.W && y >= rect.Y && y < rect.Y+rect.H
|
|
||||||
}
|
|
||||||
|
|
||||||
func insideRectPt(p Point, rect *sdl.Rect) bool { return insideRect(p.X, p.Y, rect) }
|
|
||||||
|
|
||||||
func (r *terrainRenderer) Render(ctx *Context) {
|
func (r *terrainRenderer) Render(ctx *Context) {
|
||||||
toTileTexture := func(x, y int32) *Texture {
|
toTileTexture := func(x, y int32) *Texture {
|
||||||
temp := r.terrain.Temp.Value(x, y)
|
temp := r.terrain.Temp.Value(x, y)
|
||||||
@ -156,10 +159,10 @@ func (r *terrainRenderer) Render(ctx *Context) {
|
|||||||
r.project.visibleTiles(func(x, y int32, pos Point) {
|
r.project.visibleTiles(func(x, y int32, pos Point) {
|
||||||
text := toTileTexture(x, y)
|
text := toTileTexture(x, y)
|
||||||
rect := r.project.screenToTileRect(pos)
|
rect := r.project.screenToTileRect(pos)
|
||||||
text.Copy(ctx.Renderer, rect)
|
text.CopyResize(ctx.Renderer, rect)
|
||||||
|
|
||||||
if r.hover != nil && x == r.hover.X && y == r.hover.Y {
|
if r.hover != nil && x == r.hover.X && y == r.hover.Y {
|
||||||
ctx.Textures.Texture("tile-hover").Copy(ctx.Renderer, rect)
|
ctx.Textures.Texture("tile-hover").CopyResize(ctx.Renderer, rect)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -171,7 +174,7 @@ func (r *terrainRenderer) Render(ctx *Context) {
|
|||||||
|
|
||||||
placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y)
|
placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y)
|
||||||
pos = r.project.mapToScreenF(float32(x)-.2+float32(.9*placeX-.45), float32(y)-.2+float32(.9*placeY-.45))
|
pos = r.project.mapToScreenF(float32(x)-.2+float32(.9*placeX-.45), float32(y)-.2+float32(.9*placeY-.45))
|
||||||
text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
|
text.CopyResize(ctx.Renderer, r.project.screenToTileRect(pos))
|
||||||
})
|
})
|
||||||
|
|
||||||
// gfx.RectangleColor(ctx.Renderer, r.project.windowRect.X, r.project.windowRect.Y, r.project.windowRect.X+r.project.windowRect.W, r.project.windowRect.Y+r.project.windowRect.H, sdl.Color{R: 255, A: 255})
|
// gfx.RectangleColor(ctx.Renderer, r.project.windowRect.X, r.project.windowRect.Y, r.project.windowRect.X+r.project.windowRect.W, r.project.windowRect.Y+r.project.windowRect.H, sdl.Color{R: 255, A: 255})
|
||||||
|
30
textures.go
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
type Texture struct {
|
type Texture struct {
|
||||||
texture *sdl.Texture
|
texture *sdl.Texture
|
||||||
rect *sdl.Rect
|
size Point
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Texture, error) {
|
func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Texture, error) {
|
||||||
@ -19,21 +19,33 @@ func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Textu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Texture{texture: texture, rect: &sdl.Rect{X: 0, Y: 0, W: surface.W, H: surface.H}}, nil
|
return &Texture{texture: texture, size: Pt(surface.W, surface.H)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Texture) Rect() *sdl.Rect { return t.rect }
|
func (t *Texture) Size() Point { return t.size }
|
||||||
|
|
||||||
func (t *Texture) RectOffset(offset Point) *sdl.Rect {
|
// func (t *Texture) Rect() Rectangle { return t.rect }
|
||||||
return &sdl.Rect{X: offset.X, Y: offset.Y, W: t.rect.W, H: t.rect.H}
|
|
||||||
|
// func (t *Texture) SDLRectPtr() *sdl.Rect { return t.rect.SDLPtr() }
|
||||||
|
|
||||||
|
func (t *Texture) Copy(renderer *sdl.Renderer, dst Point) {
|
||||||
|
t.CopyResize(renderer, RectSize(dst.X, dst.Y, t.size.X, t.size.Y))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Texture) Copy(renderer *sdl.Renderer, target *sdl.Rect) {
|
func (t *Texture) CopyPart(renderer *sdl.Renderer, src Rectangle, dst Point) {
|
||||||
renderer.Copy(t.texture, t.rect, target)
|
t.CopyPartResize(renderer, src, RectSize(dst.X, dst.Y, src.W, src.H))
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (t *Texture) CopyF(renderer *sdl.Renderer, target *sdl.FRect) {
|
func (t *Texture) CopyPartResize(renderer *sdl.Renderer, src Rectangle, dst Rectangle) {
|
||||||
// renderer.CopyF(t.texture, t.rect, target) // Depends on SDL >=2.0.10
|
renderer.Copy(t.texture, src.SDLPtr(), dst.SDLPtr())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Texture) CopyResize(renderer *sdl.Renderer, dst Rectangle) {
|
||||||
|
t.CopyPartResize(renderer, Rect(0, 0, t.size.X, t.size.Y), dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (t *Texture) CopyF(renderer *sdl.Renderer, dst *sdl.FRect) {
|
||||||
|
// renderer.CopyF(t.texture, t.rect, dst) // Depends on SDL >=2.0.10
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func (t *Texture) Destroy() { t.texture.Destroy() }
|
func (t *Texture) Destroy() { t.texture.Destroy() }
|
||||||
|