First compiling & running version.

This commit is contained in:
Sander Schobers 2020-05-17 10:56:56 +02:00
parent a139b87d58
commit 3474d7d973
39 changed files with 1127 additions and 2011 deletions

View File

@ -1,57 +0,0 @@
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()
a.lastUpdate = 0
}
func (a *Animation) SetInterval(interval time.Duration) {
a.interval = interval
}

View File

@ -1,59 +1,64 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
import (
"image/color"
"opslag.de/schobers/zntg/ui"
)
// import "github.com/veandco/go-sdl2/sdl"
type ButtonBar struct {
Container
ui.ContainerBase
Background sdl.Color
ButtonLength int32
Orientation Orientation
Buttons []Control
Background color.Color
ButtonLength float32
Orientation ui.Orientation
}
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) Init(ctx ui.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)
length := b.ButtonLength
switch b.Orientation {
case OrientationHorizontal:
if length == 0 {
length = bounds.H
}
offset := bounds.X
for i := range b.Buttons {
b.Buttons[i].Arrange(ctx, Rect(offset, bounds.Y, length, bounds.H))
offset += length
}
default:
if length == 0 {
length = bounds.W
}
offset := bounds.Y
for i := range b.Buttons {
b.Buttons[i].Arrange(ctx, Rect(bounds.X, offset, bounds.W, length))
offset += length
}
}
}
// func (b *ButtonBar) Arrange(ctx ui.Context, bounds Rectangle) {
// b.Container.Arrange(ctx, bounds)
// length := b.ButtonLength
// switch b.Orientation {
// case OrientationHorizontal:
// if length == 0 {
// length = bounds.H
// }
// offset := bounds.X
// for i := range b.Buttons {
// b.Buttons[i].Arrange(ctx, Rect(offset, bounds.Y, length, bounds.H))
// offset += length
// }
// default:
// if length == 0 {
// length = bounds.W
// }
// offset := bounds.Y
// for i := range b.Buttons {
// b.Buttons[i].Arrange(ctx, Rect(bounds.X, offset, bounds.W, length))
// offset += length
// }
// }
// }
func (b *ButtonBar) Render(ctx *Context) {
SetDrawColor(ctx.Renderer, b.Background)
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
b.Container.Render(ctx)
}
// func (b *ButtonBar) Render(ctx ui.Context) {
// SetDrawColor(ctx.Renderer, b.Background)
// ctx.Renderer.FillRect(b.Bounds.SDLPtr())
// b.Container.Render(ctx)
// }
type Orientation int
// type Orientation int
const (
OrientationVertical Orientation = iota
OrientationHorizontal
)
// const (
// OrientationVertical Orientation = iota
// OrientationHorizontal
// )

View File

@ -1,10 +1,8 @@
package tins2020
import (
"fmt"
"time"
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/zntg"
"opslag.de/schobers/zntg/ui"
)
type BuyFlowerButton struct {
@ -13,106 +11,116 @@ type BuyFlowerButton struct {
FlowerID string
Flower FlowerDescriptor
hoverAnimation *Animation
upToDate bool
hoverAnimation *zntg.Animation
hoverOffset int32
hoverTexture *Texture
priceTexture *Texture
hoverTexture ui.Texture
priceTexture ui.Texture
}
func NewBuyFlowerButton(icon, iconDisabled, flowerID string, flower FlowerDescriptor, onClick EventContextFn) *BuyFlowerButton {
func NewBuyFlowerButton(icon, iconDisabled, flowerID string, flower FlowerDescriptor, click ui.EventEmptyFn) *BuyFlowerButton {
return &BuyFlowerButton{
IconButton: *NewIconButtonConfigure(icon, onClick, func(b *IconButton) {
b.IconDisabled = iconDisabled
b.IsDisabled = !flower.Unlocked
IconButton: *NewIconButtonConfigure(icon, click, func(b *IconButton) {
// b.IconDisabled = iconDisabled
// b.Disabled = !flower.Unlocked
}),
FlowerID: flowerID,
Flower: flower,
}
}
func (b *BuyFlowerButton) animate() {
b.hoverOffset++
if b.hoverOffset > b.hoverTexture.Size().X+b.Bounds.W {
b.hoverOffset = 0
}
}
// func (b *BuyFlowerButton) animate() {
// b.hoverOffset++
// if b.hoverOffset > b.hoverTexture.Size().X+b.Bounds.W {
// b.hoverOffset = 0
// }
// }
func (b *BuyFlowerButton) fmtTooltipText() string {
if !b.Flower.Unlocked {
return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, "Traits are not known yet.")
}
return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, b.Flower.Description)
}
// func (b *BuyFlowerButton) fmtTooltipText() string {
// if !b.Flower.Unlocked {
// return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, "Traits are not known yet.")
// }
// return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, b.Flower.Description)
// }
func (b *BuyFlowerButton) updateTexts(ctx *Context) error {
text := b.fmtTooltipText()
font := ctx.Fonts.Font("small")
color := MustHexColor("#ffffff")
texture, err := font.Render(ctx.Renderer, text, color)
if err != nil {
return err
func (b *BuyFlowerButton) updateTexts(ctx ui.Context) error {
if b.upToDate {
return nil
}
if b.hoverTexture != nil {
b.hoverTexture.Destroy()
}
b.hoverTexture = texture
texture, err = font.Render(ctx.Renderer, FmtMoney(b.Flower.BuyPrice), color)
if err != nil {
return err
}
if b.priceTexture != nil {
b.priceTexture.Destroy()
}
b.priceTexture = texture
// text := b.fmtTooltipText()
// font := ctx.Fonts.Font("small")
// color := MustHexColor("#ffffff")
// texture, err := font.Render(ctx.Renderer, text, color)
// if err != nil {
// return err
// }
// if b.hoverTexture != nil {
// b.hoverTexture.Destroy()
// }
// b.hoverTexture = texture
// texture, err = font.Render(ctx.Renderer, FmtMoney(b.Flower.BuyPrice), color)
// if err != nil {
// return err
// }
// if b.priceTexture != nil {
// b.priceTexture.Destroy()
// }
// b.priceTexture = texture
b.upToDate = true
return nil
}
func (b *BuyFlowerButton) Init(ctx *Context) error {
return b.updateTexts(ctx)
}
// func (b *BuyFlowerButton) Init(ctx ui.Context) error {
// return b.updateTexts(ctx)
// }
func (b *BuyFlowerButton) Handle(ctx *Context, event sdl.Event) bool {
if b.IconButton.Handle(ctx, event) {
return true
}
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) Handle(ctx ui.Context, event ui.Event) bool {
b.updateTexts(ctx)
// if b.IconButton.Handle(ctx, event) {
// return true
// }
// 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
// }
return false
}
func (b *BuyFlowerButton) Render(ctx *Context) {
iconTexture := b.activeTexture(ctx)
pos := Pt(b.Bounds.X, b.Bounds.Y)
iconTexture.CopyResize(ctx.Renderer, Rect(pos.X, pos.Y-60, b.Bounds.W, 120))
if (b.IsMouseOver && !b.IsDisabled) || b.IsActive {
SetDrawColor(ctx.Renderer, TransparentWhite)
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
}
if b.hoverAnimation != nil {
b.hoverAnimation.AnimateFn(b.animate)
}
if b.IsMouseOver {
left := b.Bounds.W - 8 - b.hoverOffset
top := pos.Y + b.Bounds.H - 20
if left < 0 {
part := RectAbs(-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+b.Bounds.W-8-b.priceTexture.Size().X, pos.Y+b.Bounds.H-20))
}
}
func (b *BuyFlowerButton) Update(ctx *Context, desc FlowerDescriptor) {
b.Flower = desc
func (b *BuyFlowerButton) Render(ctx ui.Context) {
if !b.upToDate {
b.updateTexts(ctx)
}
// iconTexture := b.activeTexture(ctx)
// pos := Pt(b.Bounds.X, b.Bounds.Y)
// iconTexture.CopyResize(ctx.Renderer, Rect(pos.X, pos.Y-60, b.Bounds.W, 120))
// if (b.IsMouseOver && !b.Disabled) || b.IsActive {
// SetDrawColor(ctx.Renderer, TransparentWhite)
// ctx.Renderer.FillRect(b.Bounds.SDLPtr())
// }
// if b.hoverAnimation != nil {
// b.hoverAnimation.AnimateFn(b.animate)
// }
// if b.IsMouseOver {
// left := b.Bounds.W - 8 - b.hoverOffset
// top := pos.Y + b.Bounds.H - 20
// if left < 0 {
// part := RectAbs(-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+b.Bounds.W-8-b.priceTexture.Size().X, pos.Y+b.Bounds.H-20))
// }
}
func (b *BuyFlowerButton) Update(desc FlowerDescriptor) {
b.Flower = desc
b.upToDate = false
}

View File

@ -2,11 +2,17 @@ package main
import (
"flag"
"image/color"
"log"
"opslag.de/schobers/fs/ricefs"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/addons/res"
_ "opslag.de/schobers/zntg/sdlui" // rendering backend
"opslag.de/schobers/zntg/ui"
rice "github.com/GeertJohan/go.rice"
"github.com/veandco/go-sdl2/sdl"
"github.com/veandco/go-sdl2/ttf"
"opslag.de/schobers/tins2020"
)
@ -20,10 +26,89 @@ func main() {
}
}
func logSDLVersion() {
var version sdl.Version
sdl.GetVersion(&version)
log.Printf("SDL version: %d.%d.%d", version.Major, version.Minor, version.Patch)
func openResources(box *rice.Box) ui.Resources {
fs := ricefs.NewFs(box)
resources, _ := res.NewAferoFallbackResources(`res`, fs, `botanim`)
return resources
}
type app struct {
ui.Proxy
settings *tins2020.Settings
game *tins2020.Game
dialogs *tins2020.Dialogs
}
type fontDescriptor struct {
Name string
Path string
Size int
}
func (a *app) loadFonts(ctx ui.Context, descriptors ...fontDescriptor) error {
fonts := ctx.Fonts()
for _, desc := range descriptors {
_, err := fonts.CreateFontPath(desc.Name, desc.Path, desc.Size)
if err != nil {
return err
}
}
return nil
}
func (a *app) Init(ctx ui.Context) error {
if err := a.loadFonts(ctx,
fontDescriptor{"balance", "fonts/OpenSans-Bold.ttf", 40},
fontDescriptor{"debug", "fonts/FiraMono-Regular.ttf", 12},
fontDescriptor{"default", "fonts/OpenSans-Regular.ttf", 14},
fontDescriptor{"small", "fonts/OpenSans-Regular.ttf", 12},
fontDescriptor{"title", "fonts/OpenSans-Bold.ttf", 40},
); err != nil {
return err
}
textureLoader := tins2020.NewResourceLoader()
textures := ctx.Textures()
if err := textureLoader.LoadFromFile(ctx.Resources(), "textures.txt", func(name, content string) error {
_, err := textures.CreateTexturePath(name, content, false)
return err
}); err != nil {
return err
}
// ctx.Overlays().AddOnTop("fps", &tins2020.FPS{Show: &a.game.Debug})
content := tins2020.NewContent(a.dialogs)
content.AddChild(tins2020.NewTerrainRenderer(a.game))
content.AddChild(tins2020.NewGameControls(a.game, a.dialogs))
a.Content = content
a.dialogs.Init(ctx)
a.game.Reset(ctx)
a.dialogs.ShowIntro(ctx)
return nil
}
func (a *app) Handle(ctx ui.Context, event ui.Event) bool {
// switch e := event.(type) {
// case *ui.WindowMoved:
// // x, y := window.GetPosition()
// // settings.Window.Location = ptPtr(x, y)
// case *ui.WindowSizeChanged:
// w, h := window.GetSize()
// app.Arrange(ctx, geom.RectRelF32(0, 0, w, h))
// settings.Window.Size = ptPtr(w, h)
// }
return a.Proxy.Handle(ctx, event)
}
func (a *app) Render(ctx ui.Context) {
a.game.Update()
ctx.Renderer().Clear(color.White)
a.Proxy.Render(ctx)
}
func run() error {
@ -31,135 +116,48 @@ func run() error {
flag.BoolVar(&extract, "extract-resources", false, "extracts all resources to the current working directory")
flag.Parse()
ctx, err := tins2020.NewContext(rice.MustFindBox("res"))
if err != nil {
return err
}
defer ctx.Destroy()
box := rice.MustFindBox(`res`)
if extract {
return copyBoxToDisk(ctx.Resources.Box())
return copyBoxToDisk(box)
}
res := openResources(box)
ptPtr := func(x, y int) *geom.Point {
p := geom.Pt(x, y)
return &p
}
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
settings := &tins2020.Settings{}
err := settings.Init()
if err != nil {
return err
}
defer sdl.Quit()
logSDLVersion()
if err := ttf.Init(); err != nil {
return err
if settings.Window.Location == nil {
settings.Window.Location = ptPtr(sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED)
}
defer ttf.Quit()
if ctx.Settings.Window.Location == nil {
ctx.Settings.Window.Location = tins2020.PtPtr(sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED)
if settings.Window.Size == nil {
settings.Window.Size = ptPtr(800, 600)
}
if ctx.Settings.Window.Size == nil {
ctx.Settings.Window.Size = tins2020.PtPtr(800, 600)
}
if ctx.Settings.Window.VSync == nil {
if settings.Window.VSync == nil {
vsync := true
ctx.Settings.Window.VSync = &vsync
settings.Window.VSync = &vsync
}
if *ctx.Settings.Window.VSync {
sdl.SetHint(sdl.HINT_RENDER_VSYNC, "1")
}
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
window, err := sdl.CreateWindow("Botanim - TINS 2020",
ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y,
ctx.Settings.Window.Size.X, ctx.Settings.Window.Size.Y,
sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
if err != nil {
return err
}
defer window.Destroy()
renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
if err != nil {
return err
}
defer renderer.Destroy()
ctx.Init(renderer)
err = ctx.Fonts.LoadDesc(
tins2020.FontDescriptor{Name: "balance", Path: "fonts/OpenSans-Bold.ttf", Size: 40},
tins2020.FontDescriptor{Name: "debug", Path: "fonts/FiraMono-Regular.ttf", Size: 12},
tins2020.FontDescriptor{Name: "default", Path: "fonts/OpenSans-Regular.ttf", Size: 14},
tins2020.FontDescriptor{Name: "small", Path: "fonts/OpenSans-Regular.ttf", Size: 12},
tins2020.FontDescriptor{Name: "title", Path: "fonts/OpenSans-Bold.ttf", Size: 40},
)
if err != nil {
return err
}
textureLoader := tins2020.NewResourceLoader()
err = textureLoader.LoadFromFile(&ctx.Resources, "textures.txt", func(name, content string) error {
return ctx.Textures.Load(name, content)
renderer, err := ui.NewRenderer("Botanim - TINS 2020", settings.Window.Size.X, settings.Window.Size.Y, ui.NewRendererOptions{
Resizable: true,
VSync: *settings.Window.VSync,
})
if err != nil {
return err
}
defer renderer.Destroy()
renderer.SetResourceProvider(func() ui.Resources { return res })
game := tins2020.NewGame()
app := tins2020.NewContainer()
overlays := tins2020.NewContainer()
dialogs := tins2020.NewDialogs(game)
overlays.AddChild(dialogs)
overlays.AddChild(&tins2020.FPS{Show: &game.Debug})
content := tins2020.NewContent(dialogs)
content.AddChild(tins2020.NewTerrainRenderer(game))
content.AddChild(tins2020.NewGameControls(game, dialogs))
app.AddChild(content)
app.AddChild(overlays)
err = app.Init(ctx)
if err != nil {
return err
app := &app{
game: game,
dialogs: tins2020.NewDialogs(game),
settings: settings,
}
dialogs.ShowIntro(ctx)
w, h := window.GetSize()
app.Arrange(ctx, tins2020.RectAbs(0, 0, w, h))
for {
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch e := event.(type) {
case *sdl.QuitEvent:
ctx.Quit()
break
case *sdl.WindowEvent:
switch e.Event {
case sdl.WINDOWEVENT_MOVED:
x, y := window.GetPosition()
ctx.Settings.Window.Location = tins2020.PtPtr(x, y)
case sdl.WINDOWEVENT_SIZE_CHANGED:
w, h := window.GetSize()
app.Arrange(ctx, tins2020.RectAbs(0, 0, w, h))
ctx.Settings.Window.Size = tins2020.PtPtr(w, h)
}
case *sdl.MouseMotionEvent:
ctx.MousePosition = tins2020.Pt(e.X, e.Y)
}
app.Handle(ctx, event)
}
game.Update()
if ctx.ShouldQuit {
break
}
renderer.SetDrawColor(0, 0, 0, 255)
renderer.Clear()
app.Render(ctx)
renderer.Present()
}
return nil
return ui.Run(renderer, nil, app)
}

View File

@ -1,30 +0,0 @@
package tins2020
import (
"opslag.de/schobers/tins2020/img"
"github.com/veandco/go-sdl2/sdl"
)
var Black = sdl.Color{R: 0, G: 0, B: 0, A: 255}
var Transparent = sdl.Color{R: 0, G: 0, B: 0, A: 0}
var TransparentWhite = sdl.Color{R: 255, G: 255, B: 255, A: 31}
var White = sdl.Color{R: 255, G: 255, B: 255, A: 255}
func HexColor(s string) (sdl.Color, error) {
c, err := img.HexColor(s)
if err != nil {
return sdl.Color{}, err
}
return sdl.Color(c), nil
}
func MustHexColor(s string) sdl.Color { return sdl.Color(img.MustHexColor(s)) }
func SetDrawColor(renderer *sdl.Renderer, color sdl.Color) {
renderer.SetDrawColor(color.R, color.G, color.B, color.A)
}
func SetDrawColorHex(renderer *sdl.Renderer, s string) {
SetDrawColor(renderer, MustHexColor(s))
}

View File

@ -1,33 +0,0 @@
package tins2020
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/veandco/go-sdl2/sdl"
)
func TestHexColor(t *testing.T) {
type test struct {
Hex string
Success bool
Expected sdl.Color
}
tests := []test{
test{Hex: "#AA3939", Success: true, Expected: sdl.Color{R: 170, G: 57, B: 57, A: 255}},
test{Hex: "AA3939", Success: true, Expected: sdl.Color{R: 170, G: 57, B: 57, A: 255}},
test{Hex: "#AA3939BB", Success: true, Expected: sdl.Color{R: 170, G: 57, B: 57, A: 187}},
test{Hex: "AG3939", Success: false, Expected: sdl.Color{}},
test{Hex: "AA3939A", Success: false, Expected: sdl.Color{}},
test{Hex: "AA39A", Success: false, Expected: sdl.Color{}},
}
for _, test := range tests {
color, err := HexColor(test.Hex)
if test.Success {
assert.Nil(t, err)
assert.Equal(t, color, test.Expected)
} else {
assert.NotNil(t, err)
}
}
}

View File

@ -1,58 +0,0 @@
package tins2020
import (
"github.com/veandco/go-sdl2/sdl"
)
type Container struct {
ControlBase
Children []Control
}
func NewContainer() *Container {
return &Container{}
}
func (c *Container) AddChild(child Control) {
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) bool {
if c.ControlBase.Handle(ctx, event) {
return true
}
for _, child := range c.Children {
if child.Handle(ctx, event) {
return true
}
}
return false
}
func (c *Container) Init(ctx *Context) error {
c.ControlBase.Init(ctx)
for _, child := range c.Children {
err := child.Init(ctx)
if err != nil {
return err
}
}
return nil
}
func (c *Container) Render(ctx *Context) {
c.ControlBase.Render(ctx)
for _, child := range c.Children {
child.Render(ctx)
}
}

View File

@ -1,28 +1,36 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
import (
"opslag.de/schobers/zntg/ui"
)
// import "github.com/veandco/go-sdl2/sdl"
// Content shortcuts events when a dialog is opened.
type Content struct {
Container
ui.Proxy
dialogOverlayed bool
content ui.ContainerBase
shortcut bool
}
func NewContent(dialogs *Dialogs) *Content {
content := &Content{}
dialogs.DialogOpened().Register(func() {
content.dialogOverlayed = true
content.Proxy.Content = &content.content
dialogs.DialogOpened().AddHandlerEmpty(func(ui.Context) {
content.shortcut = true
})
dialogs.DialogClosed().Register(func() {
content.dialogOverlayed = false
dialogs.DialogClosed().AddHandlerEmpty(func(ui.Context) {
content.shortcut = false
})
return content
}
func (c *Content) Handle(ctx *Context, event sdl.Event) bool {
if c.dialogOverlayed {
func (c *Content) AddChild(child ui.Control) { c.content.AddChild(child) }
func (c *Content) Handle(ctx ui.Context, event ui.Event) bool {
if c.shortcut {
return false
}
return c.Container.Handle(ctx, event)
return c.Proxy.Handle(ctx, event)
}

View File

@ -1,46 +1,46 @@
package tins2020
import (
rice "github.com/GeertJohan/go.rice"
"github.com/veandco/go-sdl2/sdl"
)
// import (
// rice "github.com/GeertJohan/go.rice"
// "github.com/veandco/go-sdl2/sdl"
// )
type Context struct {
Renderer *sdl.Renderer
Fonts Fonts
Resources Resources
Textures Textures
Settings Settings
MousePosition Point
ShouldQuit bool
}
// type Context struct {
// Renderer *sdl.Renderer
// Fonts Fonts
// Resources Resources
// Textures Textures
// Settings Settings
// MousePosition geom.Point
// ShouldQuit bool
// }
func NewContext(res *rice.Box) (*Context, error) {
ctx := &Context{}
err := ctx.Settings.Init()
if err != nil {
return nil, err
}
err = ctx.Resources.Open(res)
if err != nil {
return nil, err
}
return ctx, nil
}
// func NewContext(res *rice.Box) (ui.Context, error) {
// ctx := &Context{}
// err := ctx.Settings.Init()
// if err != nil {
// return nil, err
// }
// err = ctx.Resources.Open(res)
// if err != nil {
// return nil, err
// }
// return ctx, nil
// }
func (c *Context) Destroy() {
c.Fonts.Destroy()
c.Resources.Destroy()
c.Textures.Destroy()
c.Settings.Store()
}
// func (c ui.Context) Destroy() {
// c.Fonts.Destroy()
// c.Resources.Destroy()
// c.Textures.Destroy()
// c.Settings.Store()
// }
func (c *Context) Init(renderer *sdl.Renderer) {
c.Renderer = renderer
c.Fonts.Init(c.Resources.Copy())
c.Textures.Init(renderer, c.Resources.Copy())
// func (c ui.Context) Init(renderer *sdl.Renderer) {
// c.Renderer = renderer
// c.Fonts.Init(c.Resources.Copy())
// c.Textures.Init(renderer, c.Resources.Copy())
c.Renderer.SetDrawBlendMode(sdl.BLENDMODE_BLEND)
}
// c.Renderer.SetDrawBlendMode(sdl.BLENDMODE_BLEND)
// }
func (c *Context) Quit() { c.ShouldQuit = true }
// func (c ui.Context) Quit() { c.ShouldQuit = true }

View File

@ -1,82 +0,0 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Control interface {
Init(*Context) error
Arrange(*Context, Rectangle)
Handle(*Context, sdl.Event) bool
Render(*Context)
}
type EventContextFn func(*Context)
type EventFn func()
type EventInterfaceFn func(interface{})
func EmptyEvent(fn EventFn) EventContextFn {
return func(*Context) { fn() }
}
type ControlBase struct {
Bounds Rectangle
FontName string
Foreground sdl.Color
IsDisabled bool
IsMouseOver bool
OnLeftMouseButtonClick EventContextFn
}
func (c *ControlBase) ActualForeground() sdl.Color {
var none sdl.Color
if c.Foreground == none {
return MustHexColor("#ffffff")
}
return c.Foreground
}
func (c *ControlBase) ActualFont(ctx *Context) *Font {
name := c.ActualFontName()
return ctx.Fonts.Font(name)
}
func (c *ControlBase) ActualFontName() string {
if c.FontName == "" {
return "default"
}
return c.FontName
}
func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds }
func (b *ControlBase) Init(*Context) error { return nil }
func (b *ControlBase) Handle(ctx *Context, event sdl.Event) bool {
switch e := event.(type) {
case *sdl.MouseMotionEvent:
b.IsMouseOver = b.Bounds.IsPointInside(e.X, e.Y)
case *sdl.MouseButtonEvent:
if !b.IsDisabled && b.IsMouseOver && e.Button == sdl.BUTTON_LEFT && e.Type == sdl.MOUSEBUTTONDOWN {
return b.Invoke(ctx, b.OnLeftMouseButtonClick)
}
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_LEAVE {
b.IsMouseOver = false
}
}
return false
}
func (b *ControlBase) Invoke(ctx *Context, fn EventContextFn) bool {
if fn == nil {
return false
}
fn(ctx)
return true
}
func (b *ControlBase) Render(*Context) {}

View File

@ -1,42 +0,0 @@
package tins2020_test
import (
"math/rand"
"testing"
)
type native struct{ a, b, c, d int }
type similar struct{ a, b, c, d int }
func (s similar) toNative() native { return native{s.a, s.b, s.c, s.d} }
type wrapper struct{ native }
func (w wrapper) toNative() native { return w.native }
func nativeFunction(n native) int { return n.a + n.b + n.c + n.d }
func BenchmarkNative(b *testing.B) {
var sum int
for i := 0; i < b.N; i++ {
n := native{rand.Int(), rand.Int(), rand.Int(), rand.Int()}
sum += nativeFunction(n)
}
}
func BenchmarkSimilar(b *testing.B) {
var sum int
for i := 0; i < b.N; i++ {
s := similar{rand.Int(), rand.Int(), rand.Int(), rand.Int()}
sum += nativeFunction(s.toNative())
}
}
func BenchmarkWrapper(b *testing.B) {
var sum int
for i := 0; i < b.N; i++ {
w := wrapper{native{rand.Int(), rand.Int(), rand.Int(), rand.Int()}}
sum += nativeFunction(w.toNative())
}
}

View File

@ -1,66 +1,70 @@
package tins2020
import (
"opslag.de/schobers/zntg/ui"
)
type Dialogs struct {
Proxy
intro ui.Overlay
settings ui.Overlay
research ui.Overlay
intro Control
settings Control
research Control
open string
dialogClosed *Events
dialogOpened *Events
closed ui.Events
opened ui.Events
}
const introDialogName = "dialog-intro"
const settingsDialogName = "dialog-settings"
const researchDialogName = "dialog-research"
func NewDialogs(game *Game) *Dialogs {
return &Dialogs{
intro: &Intro{},
intro: NewIntro(),
settings: &LargeDialog{},
research: NewResearch(game),
dialogClosed: NewEvents(),
dialogOpened: NewEvents(),
}
}
func (d *Dialogs) showDialog(ctx *Context, control Control) {
d.SetContent(ctx, control)
control.(Dialog).ShowDialog(ctx, d.Close)
d.dialogOpened.Notify(nil)
func (d *Dialogs) Init(ctx ui.Context) {
overlays := ctx.Overlays()
overlays.AddOnTop(introDialogName, d.intro, false)
overlays.AddOnTop(settingsDialogName, d.settings, false)
overlays.AddOnTop(researchDialogName, d.research, false)
}
func (d *Dialogs) Arrange(ctx *Context, bounds Rectangle) {
d.Proxy.Arrange(ctx, bounds)
func (d *Dialogs) showDialog(ctx ui.Context, name string) {
d.Close(ctx)
ctx.Overlays().Show(name)
d.open = name
d.opened.Notify(ctx, name)
}
func (d *Dialogs) DialogClosed() EventHandler { return d.dialogClosed }
func (d *Dialogs) DialogOpened() EventHandler { return d.dialogOpened }
func (d *Dialogs) Init(ctx *Context) error {
err := d.intro.Init(ctx)
if err != nil {
return err
func (d *Dialogs) Close(ctx ui.Context) {
name := d.open
if name == "" {
return
}
err = d.settings.Init(ctx)
if err != nil {
return err
}
err = d.research.Init(ctx)
return nil
ctx.Overlays().Hide(name)
d.open = ""
d.closed.Notify(ctx, name)
}
func (d *Dialogs) Close() {
d.SetContent(nil, nil)
d.dialogClosed.Notify(nil)
func (d *Dialogs) DialogClosed() ui.EventHandler { return &d.closed }
func (d *Dialogs) DialogOpened() ui.EventHandler { return &d.opened }
func (d *Dialogs) ShowIntro(ctx ui.Context) {
d.showDialog(ctx, introDialogName)
}
func (d *Dialogs) ShowIntro(ctx *Context) {
d.showDialog(ctx, d.intro)
func (d *Dialogs) ShowResearch(ctx ui.Context) {
d.showDialog(ctx, researchDialogName)
}
func (d *Dialogs) ShowResearch(ctx *Context) {
d.showDialog(ctx, d.research)
}
func (d *Dialogs) ShowSettings(ctx *Context) {
d.showDialog(ctx, d.settings)
func (d *Dialogs) ShowSettings(ctx ui.Context) {
d.showDialog(ctx, settingsDialogName)
}

View File

@ -1,24 +0,0 @@
package tins2020
type Drageable struct {
start *Point
dragged bool
}
func (d *Drageable) Cancel() { d.start = nil }
func (d *Drageable) IsDragging() bool { return d.start != nil }
func (d *Drageable) HasDragged() bool { return d.dragged }
func (d *Drageable) Move(p Point) Point {
delta := p.Sub(*d.start)
d.start = &p
d.dragged = true
return delta
}
func (d *Drageable) Start(p Point) {
d.start = &p
d.dragged = false
}

View File

@ -1,33 +0,0 @@
package tins2020
func NewEvents() *Events {
return &Events{events: map[int]EventInterfaceFn{}}
}
type Events struct {
nextID int
events map[int]EventInterfaceFn
}
type EventHandler interface {
Register(EventFn) int
RegisterItf(EventInterfaceFn) int
Unregister(int)
}
func (h *Events) Notify(state interface{}) {
for _, event := range h.events {
event(state)
}
}
func (h *Events) Register(fn EventFn) int { return h.RegisterItf(func(interface{}) { fn() }) }
func (h *Events) RegisterItf(fn EventInterfaceFn) int {
id := h.nextID
h.nextID++
h.events[id] = fn
return id
}
func (h *Events) Unregister(id int) { delete(h.events, id) }

109
fonts.go
View File

@ -1,109 +0,0 @@
package tins2020
import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
"github.com/veandco/go-sdl2/ttf"
"opslag.de/schobers/fs/vfs"
)
type TextAlignment int
const (
TextAlignmentLeft TextAlignment = iota
TextAlignmentCenter
TextAlignmentRight
)
type Font struct {
*ttf.Font
}
func (f *Font) Render(renderer *sdl.Renderer, text string, color sdl.Color) (*Texture, error) {
surface, err := f.RenderUTF8Blended(text, color)
if err != nil {
return nil, err
}
defer surface.Free()
texture, err := NewTextureFromSurface(renderer, surface)
if err != nil {
return nil, err
}
return texture, nil
}
func (f *Font) RenderCopyAlign(renderer *sdl.Renderer, text string, pos Point, color sdl.Color, align TextAlignment) error {
texture, err := f.Render(renderer, text, color)
if err != nil {
return err
}
defer texture.Destroy()
size := texture.Size()
switch align {
case TextAlignmentLeft:
texture.Copy(renderer, Pt(pos.X, pos.Y-size.Y))
case TextAlignmentCenter:
texture.Copy(renderer, Pt(pos.X-(size.X/2), pos.Y-size.Y))
case TextAlignmentRight:
texture.Copy(renderer, Pt(pos.X-size.X, pos.Y-size.Y))
}
return nil
}
func (f *Font) RenderCopy(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) error {
return f.RenderCopyAlign(renderer, text, pos, color, TextAlignmentLeft)
}
type Fonts struct {
dir vfs.CopyDir
fonts map[string]*Font
}
func (f *Fonts) Init(dir vfs.CopyDir) {
f.dir = dir
f.fonts = map[string]*Font{}
}
func (f *Fonts) Font(name string) *Font { return f.fonts[name] }
type FontDescriptor struct {
Name string
Path string
Size int
}
func (f *Fonts) LoadDesc(fonts ...FontDescriptor) error {
for _, desc := range fonts {
err := f.Load(desc.Name, desc.Path, desc.Size)
if err != nil {
return fmt.Errorf("error loading '%s'; error: %v", desc.Name, err)
}
}
return nil
}
func (f *Fonts) Load(name, path string, size int) error {
fontPath, err := f.dir.Retrieve(path)
if err != nil {
return err
}
font, err := ttf.OpenFont(fontPath, size)
if err != nil {
return err
}
if font, ok := f.fonts[name]; ok {
font.Close()
}
f.fonts[name] = &Font{font}
return nil
}
func (f *Fonts) Destroy() {
if f.fonts == nil {
return
}
for _, f := range f.fonts {
f.Close()
}
}

View File

@ -1,46 +1,46 @@
package tins2020
import (
"fmt"
"time"
// import (
// "fmt"
// "time"
"github.com/veandco/go-sdl2/sdl"
)
// "github.com/veandco/go-sdl2/sdl"
// )
type FPS struct {
ControlBase
// type FPS struct {
// ControlBase
Show *bool
start time.Time
stamp time.Duration
slot int
ticks []int
total int
}
// Show *bool
// start time.Time
// stamp time.Duration
// slot int
// ticks []int
// total int
// }
func (f *FPS) Init(*Context) error {
f.start = time.Now()
f.stamp = 0
f.ticks = make([]int, 51)
return nil
}
// func (f *FPS) Init(ui.Context) error {
// f.start = time.Now()
// f.stamp = 0
// f.ticks = make([]int, 51)
// return nil
// }
func (f *FPS) Render(ctx *Context) {
if f.Show == nil || !*f.Show {
return
}
// func (f *FPS) Render(ctx ui.Context) {
// if f.Show == nil || !*f.Show {
// return
// }
elapsed := time.Since(f.start)
stamp := elapsed / (20 * time.Millisecond)
for f.stamp < stamp {
f.total += f.ticks[f.slot]
f.slot = (f.slot + 1) % len(f.ticks)
f.total -= f.ticks[f.slot]
f.ticks[f.slot] = 0
f.stamp++
}
f.ticks[f.slot]++
// elapsed := time.Since(f.start)
// stamp := elapsed / (20 * time.Millisecond)
// for f.stamp < stamp {
// f.total += f.ticks[f.slot]
// f.slot = (f.slot + 1) % len(f.ticks)
// f.total -= f.ticks[f.slot]
// f.ticks[f.slot] = 0
// f.stamp++
// }
// f.ticks[f.slot]++
font := ctx.Fonts.Font("debug")
font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 17), sdl.Color{R: 255, G: 255, B: 255, A: 255})
}
// font := ctx.Fonts.Font("debug")
// font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 17), sdl.Color{R: 255, G: 255, B: 255, A: 255})
// }

115
game.go
View File

@ -4,6 +4,10 @@ import (
"log"
"math/rand"
"time"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
"opslag.de/schobers/zntg/ui"
)
type Game struct {
@ -16,10 +20,10 @@ type Game struct {
Terrain *Map
tool Tool
centerChanged *Events
toolChanged *Events
speedChanged *Events
simulation Animation
centerChanged ui.Events
toolChanged ui.Events
speedChanged ui.Events
simulation zntg.Animation
}
type GameSpeed string
@ -35,21 +39,17 @@ const fastSimulationInterval = 20 * time.Millisecond
func NewGame() *Game {
game := &Game{
centerChanged: NewEvents(),
speedChanged: NewEvents(),
toolChanged: NewEvents(),
simulation: NewAnimation(time.Millisecond * 10),
simulation: zntg.Animation{Interval: time.Millisecond * 10},
}
game.Reset()
return game
}
func (g *Game) selectTool(t Tool) {
func (g *Game) selectTool(ctx ui.Context, t Tool) {
g.tool = t
g.toolChanged.Notify(t)
g.toolChanged.Notify(ctx, t)
}
func (g *Game) setSpeed(speed GameSpeed) {
func (g *Game) setSpeed(ctx ui.Context, speed GameSpeed) {
if speed == g.Speed {
return
}
@ -57,35 +57,35 @@ func (g *Game) setSpeed(speed GameSpeed) {
g.SpeedBeforePause = g.Speed
}
g.Speed = speed
g.speedChanged.Notify(speed)
g.speedChanged.Notify(ctx, speed)
switch speed {
case GameSpeedPaused:
g.simulation.Pause()
case GameSpeedNormal:
g.simulation.SetInterval(simulationInterval)
g.simulation.Run()
g.simulation.Interval = simulationInterval
g.simulation.Start()
case GameSpeedFast:
g.simulation.SetInterval(fastSimulationInterval)
g.simulation.Run()
g.simulation.Interval = fastSimulationInterval
g.simulation.Start()
}
}
func (g *Game) tick() {
randomNeighbor := func(pos Point) Point {
randomNeighbor := func(pos geom.Point) geom.Point {
switch rand.Intn(4) {
case 0:
return Pt(pos.X-1, pos.Y)
return geom.Pt(pos.X-1, pos.Y)
case 1:
return Pt(pos.X, pos.Y-1)
return geom.Pt(pos.X, pos.Y-1)
case 2:
return Pt(pos.X+1, pos.Y)
return geom.Pt(pos.X+1, pos.Y)
case 3:
return Pt(pos.X, pos.Y+1)
return geom.Pt(pos.X, pos.Y+1)
}
return pos
}
flowers := map[Point]Flower{}
flowers := map[geom.Point]Flower{}
for pos, flower := range g.Terrain.Flowers {
if rand.Float32() < flower.Traits.Spread {
dst := randomNeighbor(pos)
@ -102,13 +102,13 @@ func (g *Game) tick() {
g.Terrain.Flowers = flowers
}
func (g *Game) CancelTool() {
g.selectTool(nil)
func (g *Game) CancelTool(ctx ui.Context) {
g.selectTool(ctx, nil)
}
func (g *Game) CenterChanged() EventHandler { return g.centerChanged }
func (g *Game) CenterChanged() ui.EventHandler { return &g.centerChanged }
func (g *Game) Dig(tile Point) {
func (g *Game) Dig(tile geom.Point) {
id := g.Terrain.DigFlower(tile)
desc, ok := g.Herbarium.Find(id)
if !ok {
@ -125,14 +125,14 @@ func (g *Game) Dig(tile Point) {
}
}
func (g *Game) New() {
g.Pause()
g.Reset()
func (g *Game) New(ctx ui.Context) {
g.Pause(ctx)
g.Reset(ctx)
}
func (g *Game) Load() {
g.CancelTool()
g.Pause()
func (g *Game) Load(ctx ui.Context) {
g.CancelTool(ctx)
g.Pause(ctx)
var state GameState
err := state.Deserialize(SaveGameName())
@ -155,22 +155,19 @@ func (g *Game) Load() {
Variant: NewRandomNoiseMap(state.Terrain.Variant),
PlaceX: NewRandomNoiseMap(state.Terrain.PlaceX),
PlaceY: NewRandomNoiseMap(state.Terrain.PlaceY),
Flowers: map[Point]Flower{},
Flowers: map[geom.Point]Flower{},
}
for _, flower := range state.Terrain.Flowers {
desc, _ := g.Herbarium.Find(flower.ID)
g.Terrain.AddFlower(flower.Location, flower.ID, desc.Traits)
}
g.Terrain.Center = state.View.Center
g.centerChanged.Notify(g.Terrain.Center)
g.CancelTool()
g.setSpeed(state.Speed)
g.centerChanged.Notify(ctx, g.Terrain.Center)
}
func (g *Game) Pause() { g.setSpeed(GameSpeedPaused) }
func (g *Game) Pause(ctx ui.Context) { g.setSpeed(ctx, GameSpeedPaused) }
func (g *Game) PlantFlower(id string, tile Point) {
func (g *Game) PlantFlower(id string, tile geom.Point) {
if g.Terrain.HasFlower(tile) {
// TODO: notify user it tried to plant on tile with flower?
return
@ -189,7 +186,7 @@ func (g *Game) PlantFlower(id string, tile Point) {
g.Terrain.AddFlower(tile, id, flower.Traits)
}
func (g *Game) Reset() {
func (g *Game) Reset(ctx ui.Context) {
g.Balance = 100
g.Herbarium = NewHerbarium()
g.Terrain = &Map{
@ -198,17 +195,17 @@ func (g *Game) Reset() {
Variant: NewRandomNoiseMap(rand.Int63()),
PlaceX: NewRandomNoiseMap(rand.Int63()),
PlaceY: NewRandomNoiseMap(rand.Int63()),
Flowers: map[Point]Flower{},
Flowers: map[geom.Point]Flower{},
}
g.CancelTool()
g.setSpeed(GameSpeedNormal)
g.CancelTool(ctx)
g.setSpeed(ctx, GameSpeedNormal)
}
func (g *Game) Resume() { g.setSpeed(g.SpeedBeforePause) }
func (g *Game) Resume(ctx ui.Context) { g.setSpeed(ctx, g.SpeedBeforePause) }
func (g *Game) Run() { g.setSpeed(GameSpeedNormal) }
func (g *Game) Run(ctx ui.Context) { g.setSpeed(ctx, GameSpeedNormal) }
func (g *Game) RunFast() { g.setSpeed(GameSpeedFast) }
func (g *Game) RunFast(ctx ui.Context) { g.setSpeed(ctx, GameSpeedFast) }
func (g *Game) Save() {
state := g.State()
@ -218,15 +215,15 @@ func (g *Game) Save() {
}
}
func (g *Game) SelectPlantFlowerTool(id string) {
g.selectTool(&PlantFlowerTool{FlowerID: id})
func (g *Game) SelectPlantFlowerTool(ctx ui.Context, id string) {
g.selectTool(ctx, &PlantFlowerTool{FlowerID: id})
}
func (g *Game) SelectShovel() {
g.selectTool(&ShovelTool{})
func (g *Game) SelectShovel(ctx ui.Context) {
g.selectTool(ctx, &ShovelTool{})
}
func (g *Game) SpeedChanged() EventHandler { return g.speedChanged }
func (g *Game) SpeedChanged() ui.EventHandler { return &g.speedChanged }
func (g *Game) State() GameState {
var state GameState
@ -255,22 +252,22 @@ func (g *Game) State() GameState {
return state
}
func (g *Game) TogglePause() {
func (g *Game) TogglePause(ctx ui.Context) {
if g.Speed == GameSpeedPaused {
g.Resume()
g.Resume(ctx)
} else {
g.Pause()
g.Pause(ctx)
}
}
func (g *Game) Tool() Tool { return g.tool }
func (g *Game) ToolChanged() EventHandler { return g.toolChanged }
func (g *Game) ToolChanged() ui.EventHandler { return &g.toolChanged }
func (g *Game) UnlockNextFlower() {
func (g *Game) UnlockNextFlower(ctx ui.Context) {
price := g.Herbarium.UnlockNext()
g.Balance -= price
g.selectTool(nil)
g.selectTool(ctx, nil)
}
func (g *Game) Update() {
@ -279,7 +276,7 @@ func (g *Game) Update() {
}
}
func (g *Game) UserClickedTile(pos Point) {
func (g *Game) UserClickedTile(pos geom.Point) {
if g.tool == nil {
return
}

View File

@ -1,11 +1,13 @@
package tins2020
import (
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
"opslag.de/schobers/zntg/ui"
)
type GameControls struct {
Container
ui.ContainerBase
game *Game
dialogs *Dialogs
@ -24,7 +26,87 @@ type GameControls struct {
}
func NewGameControls(game *Game, dialogs *Dialogs) *GameControls {
return &GameControls{game: game, dialogs: dialogs}
c := &GameControls{game: game, dialogs: dialogs}
c.game.SpeedChanged().AddHandler(c.speedChanged)
c.game.ToolChanged().AddHandler(c.toolChanged)
c.dialogs.DialogOpened().AddHandlerEmpty(func(ctx ui.Context) { c.game.Pause(ctx) })
c.dialogs.DialogClosed().AddHandlerEmpty(func(ctx ui.Context) {
c.updateFlowerControls()
c.game.Resume(ctx)
})
c.flowers.Background = zntg.MustHexColor("#356dad")
c.flowers.ButtonLength = 64
for _, id := range c.game.Herbarium.Flowers() {
c.flowers.Children = append(c.flowers.Children, c.createBuyFlowerButton(id))
}
c.top.Orientation = ui.OrientationHorizontal
c.pause = NewIconButtonConfigure("control-pause", func(ctx ui.Context) {
c.game.Pause(ctx)
}, func(b *IconButton) {
b.IconDisabled = "control-pause-disabled"
b.Tooltip = "Pause game"
})
c.run = NewIconButtonConfigure("control-run", func(ctx ui.Context) {
c.game.Run(ctx)
}, func(b *IconButton) {
b.IconDisabled = "control-run-disabled"
b.Tooltip = "Run game at normal speed"
})
c.runFast = NewIconButtonConfigure("control-run-fast", func(ctx ui.Context) {
c.game.RunFast(ctx)
}, func(b *IconButton) {
b.IconDisabled = "control-run-fast-disabled"
b.Tooltip = "Run game at fast speed"
})
c.speedChanged(nil, c.game.Speed)
c.top.Children = []ui.Control{c.pause, c.run, c.runFast}
c.menu.Background = zntg.MustHexColor("#356dad")
c.menu.Children = []ui.Control{
NewIconButtonConfigure("control-settings", c.dialogs.ShowSettings, func(b *IconButton) {
b.Disabled = true
b.IconDisabled = "#afafaf"
}),
NewIconButtonConfigure("control-save", func(ui.Context) { c.game.Save() }, func(b *IconButton) {
b.Tooltip = "Save game (overwrites previous save; no confirmation)"
}),
NewIconButtonConfigure("control-load", func(ctx ui.Context) {
c.game.Load(ctx)
c.updateFlowerControls()
}, func(b *IconButton) {
b.Tooltip = "Load last saved game (no confirmation)"
}),
NewIconButtonConfigure("control-new", func(ctx ui.Context) {
c.game.New(ctx)
c.updateFlowerControls()
}, func(b *IconButton) {
b.Tooltip = "Start new game (no confirmation)"
}),
NewIconButtonConfigure("control-information", c.dialogs.ShowIntro, func(b *IconButton) {
b.Tooltip = "Show information/intro"
}),
}
c.shovel = NewIconButtonConfigure("control-shovel", func(ctx ui.Context) { c.game.SelectShovel(ctx) }, func(b *IconButton) {
b.IconHeight = 32
b.Tooltip = "Select harvest tool (key: H)"
})
c.research = NewIconButtonConfigure("control-research", c.dialogs.ShowResearch, func(b *IconButton) {
b.IconHeight = 32
b.Tooltip = "Conduct research (key: R)"
})
c.otherTools.Children = []ui.Control{c.shovel, c.research}
c.AddChild(&c.menu)
c.AddChild(&c.top)
c.AddChild(&c.flowers)
c.AddChild(&c.otherTools)
return c
}
func (c *GameControls) createBuyFlowerButton(id string) *BuyFlowerButton {
@ -34,176 +116,92 @@ func (c *GameControls) createBuyFlowerButton(id string) *BuyFlowerButton {
flower.IconTemplate.Disabled(),
id,
flower,
EmptyEvent(func() {
c.game.SelectPlantFlowerTool(id)
}),
func(ctx ui.Context) {
c.game.SelectPlantFlowerTool(ctx, id)
},
)
}
func (c *GameControls) speedChanged(state interface{}) {
func (c *GameControls) speedChanged(_ ui.Context, state interface{}) {
speed := state.(GameSpeed)
disable := func(b *IconButton, expected GameSpeed) {
b.IsDisabled = speed == expected
b.Disabled = speed == expected
}
disable(c.pause, GameSpeedPaused)
disable(c.run, GameSpeedNormal)
disable(c.runFast, GameSpeedFast)
}
func (c *GameControls) toolChanged(state interface{}) {
func (c *GameControls) toolChanged(_ ui.Context, state interface{}) {
tool, _ := state.(Tool)
var flowerID string
if tool, ok := tool.(*PlantFlowerTool); ok {
flowerID = tool.FlowerID
}
for _, control := range c.flowers.Buttons {
for _, control := range c.flowers.Children {
button := control.(*BuyFlowerButton)
button.IsActive = button.FlowerID == flowerID
button.IsDisabled = !c.game.Herbarium.IsUnlocked(button.FlowerID)
button.Disabled = !c.game.Herbarium.IsUnlocked(button.FlowerID)
}
_, shovel := tool.(*ShovelTool)
c.shovel.IsActive = shovel
}
func (c *GameControls) updateFlowerControls(ctx *Context) {
for _, b := range c.flowers.Buttons {
func (c *GameControls) updateFlowerControls() {
for _, b := range c.flowers.Children {
button := b.(*BuyFlowerButton)
flower, ok := c.game.Herbarium.Find(button.FlowerID)
if ok {
button.Update(ctx, flower)
button.Update(flower)
}
}
}
func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) {
c.Bounds = bounds
c.menu.Arrange(ctx, Rect(bounds.X, bounds.Y, buttonBarWidth, bounds.H))
c.top.Arrange(ctx, RectAbs(bounds.X+bounds.W/2+8, bounds.Y, bounds.Right(), bounds.Y+64))
c.flowers.Arrange(ctx, Rect(bounds.Right()-buttonBarWidth, bounds.Y, buttonBarWidth, bounds.H))
c.otherTools.Arrange(ctx, Rect(bounds.Right()-buttonBarWidth, bounds.Bottom()-2*buttonBarWidth, buttonBarWidth, 2*buttonBarWidth))
func (c *GameControls) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.PointF32, parent ui.Control) {
c.ContainerBase.Arrange(ctx, bounds, offset, parent)
c.menu.Arrange(ctx, geom.RectRelF32(bounds.Min.X, bounds.Min.Y, buttonBarWidth, bounds.Dy()), offset, c)
c.top.Arrange(ctx, geom.RectF32(bounds.Min.X+bounds.Dx()/2+8, bounds.Min.Y, bounds.Max.X, bounds.Min.Y+64), offset, c)
c.flowers.Arrange(ctx, geom.RectRelF32(bounds.Max.X-buttonBarWidth, bounds.Min.Y, buttonBarWidth, bounds.Dy()), offset, c)
c.otherTools.Arrange(ctx, geom.RectRelF32(bounds.Max.X-buttonBarWidth, bounds.Max.Y-2*buttonBarWidth, buttonBarWidth, 2*buttonBarWidth), offset, c)
}
func (c *GameControls) Init(ctx *Context) error {
c.game.SpeedChanged().RegisterItf(c.speedChanged)
c.game.ToolChanged().RegisterItf(c.toolChanged)
c.dialogs.DialogOpened().Register(func() { c.game.Pause() })
c.dialogs.DialogClosed().Register(func() {
c.updateFlowerControls(ctx)
c.game.Resume()
})
c.flowers.Background = MustHexColor("#356dad")
c.flowers.ButtonLength = 64
for _, id := range c.game.Herbarium.Flowers() {
c.flowers.Buttons = append(c.flowers.Buttons, c.createBuyFlowerButton(id))
}
c.top.Orientation = OrientationHorizontal
c.pause = NewIconButtonConfigure("control-pause", EmptyEvent(func() {
c.game.Pause()
}), func(b *IconButton) {
b.IconDisabled = "control-pause-disabled"
b.Tooltip.Text = "Pause game"
})
c.run = NewIconButtonConfigure("control-run", EmptyEvent(func() {
c.game.Run()
}), func(b *IconButton) {
b.IconDisabled = "control-run-disabled"
b.Tooltip.Text = "Run game at normal speed"
})
c.runFast = NewIconButtonConfigure("control-run-fast", EmptyEvent(func() {
c.game.RunFast()
}), func(b *IconButton) {
b.IconDisabled = "control-run-fast-disabled"
b.Tooltip.Text = "Run game at fast speed"
})
c.speedChanged(c.game.Speed)
c.top.Buttons = []Control{c.pause, c.run, c.runFast}
c.menu.Background = MustHexColor("#356dad")
c.menu.Buttons = []Control{
NewIconButtonConfigure("control-settings", c.dialogs.ShowSettings, func(b *IconButton) {
b.IsDisabled = true
b.IconDisabled = "#afafaf"
}),
NewIconButtonConfigure("control-save", func(*Context) { c.game.Save() }, func(b *IconButton) {
b.Tooltip.Text = "Save game (overwrites previous save; no confirmation)"
}),
NewIconButtonConfigure("control-load", func(ctx *Context) {
c.game.Load()
c.updateFlowerControls(ctx)
}, func(b *IconButton) {
b.Tooltip.Text = "Load last saved game (no confirmation)"
}),
NewIconButtonConfigure("control-new", func(ctx *Context) {
c.game.New()
c.updateFlowerControls(ctx)
}, func(b *IconButton) {
b.Tooltip.Text = "Start new game (no confirmation)"
}),
NewIconButtonConfigure("control-information", c.dialogs.ShowIntro, func(b *IconButton) {
b.Tooltip.Text = "Show information/intro"
}),
}
c.shovel = NewIconButtonConfigure("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) {
b.IconHeight = 32
b.Tooltip.Text = "Select harvest tool (key: H)"
})
c.research = NewIconButtonConfigure("control-research", c.dialogs.ShowResearch, func(b *IconButton) {
b.IconHeight = 32
b.Tooltip.Text = "Conduct research (key: R)"
})
c.otherTools.Buttons = []Control{c.shovel, c.research}
c.Container.AddChild(&c.menu)
c.Container.AddChild(&c.top)
c.Container.AddChild(&c.flowers)
c.Container.AddChild(&c.otherTools)
return c.Container.Init(ctx)
}
func (c *GameControls) Handle(ctx *Context, event sdl.Event) bool {
if c.Container.Handle(ctx, event) {
func (c *GameControls) Handle(ctx ui.Context, event ui.Event) bool {
if c.ContainerBase.Handle(ctx, event) {
return true
}
switch e := event.(type) {
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
switch e.Keysym.Sym {
case sdl.K_SPACE:
c.game.TogglePause()
case sdl.K_1:
c.game.Run()
case sdl.K_2:
c.game.RunFast()
case sdl.K_h:
c.game.SelectShovel()
case sdl.K_r:
case *ui.KeyDownEvent:
switch e.Key {
case ui.KeySpace:
c.game.TogglePause(ctx)
case ui.Key1:
c.game.Run(ctx)
case ui.Key2:
c.game.RunFast(ctx)
case ui.KeyH:
c.game.SelectShovel(ctx)
case ui.KeyR:
c.dialogs.ShowResearch(ctx)
case sdl.K_ESCAPE:
case ui.KeyEscape:
if c.game.Tool() == nil {
c.dialogs.ShowIntro(ctx)
} else {
c.game.CancelTool()
c.game.CancelTool(ctx)
}
return true
case sdl.K_F3:
case ui.KeyF3:
c.game.Debug = !c.game.Debug
}
}
}
return false
}
func (c *GameControls) Render(ctx *Context) {
topBar := MustHexColor("#0000007f")
SetDrawColor(ctx.Renderer, topBar)
ctx.Renderer.FillRect(RectAbs(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) Render(ctx ui.Context) {
topBar := zntg.MustHexColor("#0000007f")
ctx.Renderer().FillRectangle(geom.RectF32(c.menu.Bounds().Max.X, 0, c.flowers.Bounds().Min.X, 64), topBar)
ctx.Fonts().TextAlign("balance", geom.PtF32(c.top.Bounds().Min.X-8, 58), zntg.MustHexColor("#4AC69A"), FmtMoney(c.game.Balance), ui.AlignRight)
c.Container.Render(ctx)
c.ContainerBase.Render(ctx)
}

View File

@ -1,8 +1,13 @@
package tins2020
import (
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
)
type FlowerState struct {
ID string
Location Point
Location geom.Point
}
type GameState struct {
@ -32,7 +37,7 @@ type TerrainState struct {
}
type ViewState struct {
Center Point
Center geom.Point
}
func (s *GameState) Serialize(name string) error {
@ -40,7 +45,7 @@ func (s *GameState) Serialize(name string) error {
if err != nil {
return err
}
return EncodeJSON(path, &s)
return zntg.EncodeJSON(path, &s)
}
func (s *GameState) Deserialize(name string) error {
@ -48,7 +53,7 @@ func (s *GameState) Deserialize(name string) error {
if err != nil {
return err
}
return DecodeJSON(path, &s)
return zntg.DecodeJSON(path, &s)
}
func SaveGameName() string { return "savegame.json" }

View File

@ -1,125 +1,129 @@
package tins2020
import (
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/zntg/ui"
)
type HoverEffect int
// import (
// "github.com/veandco/go-sdl2/sdl"
// )
const (
HoverEffectLigthen HoverEffect = iota
HoverEffectColor
)
// type HoverEffect int
// const (
// HoverEffectLigthen HoverEffect = iota
// HoverEffectColor
// )
type IconButton struct {
ControlBase
ui.Button
Icon string
IconDisabled string
IconHeight int32
IconScale Scale
IconWidth int32
// IconScale Scale
// IconWidth int32
IconActive HoverEffect
IconHover HoverEffect
// IconActive HoverEffect
// IconHover HoverEffect
Tooltip Tooltip
// Tooltip Tooltip
IsActive bool
}
func NewIconButton(icon string, onClick EventContextFn) *IconButton {
return &IconButton{
ControlBase: ControlBase{
OnLeftMouseButtonClick: onClick,
},
func NewIconButton(icon string, click ui.EventEmptyFn) *IconButton {
b := &IconButton{
Button: ui.Button{
Icon: icon,
},
}
b.ControlClicked().AddHandler(func(ctx ui.Context, _ ui.ControlClickedArgs) { click(ctx) })
return b
}
func NewIconButtonConfigure(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton {
button := NewIconButton(icon, onClick)
func NewIconButtonConfigure(icon string, click ui.EventEmptyFn, configure func(*IconButton)) *IconButton {
button := NewIconButton(icon, click)
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
}
// func (b *IconButton) activeTexture(ctx ui.Context) *Texture {
// if b.Disabled {
// texture := ctx.Textures.Texture(b.IconDisabled)
// if texture != nil {
// return texture
// }
texture = ctx.Textures.Texture(b.Icon)
if len(b.IconDisabled) == 0 {
return texture
}
color, err := HexColor(b.IconDisabled)
if err == nil {
texture.SetColor(color)
}
return texture
}
return ctx.Textures.Texture(b.Icon)
}
// texture = ctx.Textures.Texture(b.Icon)
// if len(b.IconDisabled) == 0 {
// return texture
// }
// color, err := HexColor(b.IconDisabled)
// if err == nil {
// texture.SetColor(color)
// }
// return texture
// }
// return ctx.Textures.Texture(b.Icon)
// }
func (b *IconButton) Arrange(ctx *Context, bounds Rectangle) {
b.ControlBase.Arrange(ctx, bounds)
b.Tooltip.Arrange(ctx, bounds)
}
// func (b *IconButton) Arrange(ctx ui.Context, bounds Rectangle) {
// b.ControlBase.Arrange(ctx, bounds)
// b.Tooltip.Arrange(ctx, bounds)
// }
func (b *IconButton) Handle(ctx *Context, event sdl.Event) bool {
if b.ControlBase.Handle(ctx, event) {
return true
}
if b.Tooltip.Handle(ctx, event) {
return true
}
return false
}
// func (b *IconButton) Handle(ctx ui.Context, event sdl.Event) bool {
// if b.ControlBase.Handle(ctx, event) {
// return true
// }
// if b.Tooltip.Handle(ctx, event) {
// return true
// }
// return false
// }
func (b *IconButton) Init(ctx *Context) error {
if err := b.ControlBase.Init(ctx); err != nil {
return err
}
if err := b.Tooltip.Init(ctx); err != nil {
return err
}
return nil
}
// func (b *IconButton) Init(ctx ui.Context) error {
// if err := b.ControlBase.Init(ctx); err != nil {
// return err
// }
// if err := b.Tooltip.Init(ctx); err != nil {
// return err
// }
// return nil
// }
func (b *IconButton) Render(ctx *Context) {
iconTexture := b.activeTexture(ctx)
// func (b *IconButton) Render(ctx ui.Context) {
// iconTexture := b.activeTexture(ctx)
hover := b.IsMouseOver && !b.IsDisabled
if (hover && b.IconHover == HoverEffectColor) || (b.IsActive && b.IconActive == HoverEffectColor) {
iconTexture.SetColor(MustHexColor("#15569F"))
}
// hover := b.IsMouseOver && !b.Disabled
// if (hover && b.IconHover == HoverEffectColor) || (b.IsActive && b.IconActive == HoverEffectColor) {
// iconTexture.SetColor(MustHexColor("#15569F"))
// }
if b.IconScale == ScaleCenter {
size := iconTexture.Size()
if b.IconWidth != 0 {
size = Pt(b.IconWidth, b.IconWidth*size.Y/size.X)
} else if b.IconHeight != 0 {
size = Pt(b.IconHeight*size.X/size.Y, b.IconHeight)
}
iconTexture.CopyResize(ctx.Renderer, Rect(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 (hover && b.IconHover == HoverEffectLigthen) || (b.IsActive && b.IconActive == HoverEffectLigthen) {
SetDrawColor(ctx.Renderer, TransparentWhite)
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
}
iconTexture.SetColor(White)
// if b.IconScale == ScaleCenter {
// size := iconTexture.Size()
// if b.IconWidth != 0 {
// size = Pt(b.IconWidth, b.IconWidth*size.Y/size.X)
// } else if b.IconHeight != 0 {
// size = Pt(b.IconHeight*size.X/size.Y, b.IconHeight)
// }
// iconTexture.CopyResize(ctx.Renderer, Rect(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 (hover && b.IconHover == HoverEffectLigthen) || (b.IsActive && b.IconActive == HoverEffectLigthen) {
// SetDrawColor(ctx.Renderer, TransparentWhite)
// ctx.Renderer.FillRect(b.Bounds.SDLPtr())
// }
// iconTexture.SetColor(White)
if len(b.Tooltip.Text) > 0 && b.IsMouseOver {
b.Tooltip.Render(ctx)
}
}
// if len(b.Tooltip.Text) > 0 && b.IsMouseOver {
// b.Tooltip.Render(ctx)
// }
// }
type Scale int
// type Scale int
const (
ScaleCenter Scale = iota
ScaleStretch
)
// const (
// ScaleCenter Scale = iota
// ScaleStretch
// )

View File

@ -1,62 +0,0 @@
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
}

View File

@ -1,29 +0,0 @@
package img
import (
"image"
"image/png"
"os"
)
func EncodePNG(path string, im image.Image) error {
dst, err := os.Create(path)
if err != nil {
return err
}
defer dst.Close()
return png.Encode(dst, im)
}
func DecodeImage(path string) (image.Image, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
im, _, err := image.Decode(f)
if err != nil {
return nil, err
}
return im, nil
}

View File

@ -1,13 +1,14 @@
package tins2020
type Intro struct {
LargeDialog
import "opslag.de/schobers/zntg/ui"
welcome Paragraph
type Intro struct {
ui.Paragraph
}
func (i *Intro) Init(ctx *Context) error {
i.welcome.Text =
func NewIntro() ui.Overlay {
i := &Intro{}
i.Text =
"Welcome to Botanim!\n\n" +
"In Botanim you play the role of botanist and your goal is to cultivate flowers in an open landscape.\n\n" +
"Flowers can only grow (well) in certain climates based on two properties: humidity and temperature. Watch out for existing vegetation to get an idea how humid the land is and check the appearance of the tile to see how hot it is. When well placed your planted flower will spread soon but an odd choice might kill your flower almost instantly. So choose carefully. When the flower spread significantly you can harvest flowers again to collect more money.\n\n" +
@ -21,7 +22,5 @@ func (i *Intro) Init(ctx *Context) error {
" - W, A, S, D keys or CTRL + left mouse button or middle mouse button: pans landscape\n" +
"\n" +
"Have fun playing!"
i.SetContent(&i.welcome)
return i.LargeDialog.Init(ctx)
return NewLargeDialog("Botanim", i)
}

47
io.go
View File

@ -1,50 +1,11 @@
package tins2020
import (
"encoding/json"
"os"
"path/filepath"
"opslag.de/schobers/zntg"
)
func DecodeJSON(path string, v interface{}) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
err = json.NewDecoder(f).Decode(v)
if err != nil {
return err
}
return nil
}
const appName = "tins2020_botanim"
func EncodeJSON(path string, v interface{}) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return json.NewEncoder(f).Encode(v)
}
func UserDir() (string, error) { return zntg.UserDir(appName) }
func UserDir() (string, error) {
config, err := os.UserConfigDir()
if err != nil {
return "", err
}
dir := filepath.Join(config, "tins2020_botanim")
err = os.MkdirAll(dir, 0777)
if err != nil {
return "", err
}
return dir, nil
}
func UserFile(name string) (string, error) {
dir, err := UserDir()
if err != nil {
return "", err
}
return filepath.Join(dir, name), nil
}
func UserFile(name string) (string, error) { return zntg.UserFile(appName, name) }

View File

@ -1,85 +0,0 @@
package tins2020
import (
"strings"
)
type Label struct {
ControlBase
Text string
Alignment TextAlignment
}
func (l *Label) Render(ctx *Context) {
font := ctx.Fonts.Font(l.ActualFontName())
color := l.ActualForeground()
bottom := l.Bounds.Y + l.Bounds.H
switch l.Alignment {
case TextAlignmentCenter:
font.RenderCopyAlign(ctx.Renderer, l.Text, Pt(l.Bounds.X+l.Bounds.W/2, bottom), color, TextAlignmentCenter)
case TextAlignmentLeft:
font.RenderCopyAlign(ctx.Renderer, l.Text, Pt(l.Bounds.X, bottom), color, TextAlignmentLeft)
case TextAlignmentRight:
font.RenderCopyAlign(ctx.Renderer, l.Text, Pt(l.Bounds.X+l.Bounds.W, bottom), color, TextAlignmentRight)
}
}
type Paragraph struct {
Label
}
func (p *Paragraph) Render(ctx *Context) {
font := ctx.Fonts.Font(p.ActualFontName())
color := p.ActualForeground()
fontHeight := int32(font.Height())
lines := strings.Split(p.Text, "\n")
measure := func(s string) int32 {
w, _, _ := font.SizeUTF8(s)
return int32(w)
}
spaces := func(s string) []int {
var spaces []int
offset := 0
for {
space := strings.Index(s[offset:], " ")
if space == -1 {
return spaces
}
offset += space
spaces = append(spaces, offset)
offset++
}
}
fit := func(s string) string {
if measure(s) < p.Bounds.W {
return s
}
spaces := spaces(s)
for split := len(spaces) - 1; split >= 0; split-- {
clipped := s[:spaces[split]]
if measure(clipped) < p.Bounds.W {
return clipped
}
}
return s
}
offset := p.Bounds.Y
for _, line := range lines {
if len(line) == 0 {
offset += fontHeight
continue
}
for len(line) > 0 {
offset += fontHeight
clipped := fit(line)
line = strings.TrimLeft(line[len(clipped):], " ")
font.RenderCopy(ctx.Renderer, clipped, Pt(p.Bounds.X, offset), color)
}
}
}

View File

@ -1,108 +1,141 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
import (
"opslag.de/schobers/zntg"
"opslag.de/schobers/zntg/ui"
)
type DialogBase struct {
Container
// type DialogBase struct {
// }
content Proxy
onShow *Events
close EventFn
}
// type Dialog interface {
// CloseDialog()
// OnShow() zntg.EventHandler
// ShowDialog(ui.Context, zntg.EventFn)
// }
type Dialog interface {
CloseDialog()
OnShow() EventHandler
ShowDialog(*Context, EventFn)
}
// func (d *DialogBase) CloseDialog() {
// close := d.close
// if close != nil {
// close()
// }
// }
func (d *DialogBase) CloseDialog() {
close := d.close
if close != nil {
close()
}
}
// func (d *DialogBase) Init(ctx ui.Context) error {
// d.AddChild(&d.content)
// return d.Container.Init(ctx)
// }
func (d *DialogBase) Init(ctx *Context) error {
d.AddChild(&d.content)
return d.Container.Init(ctx)
}
// func (d *DialogBase) OnShow() zntg.EventHandler {
// if d.onShow == nil {
// d.onShow = NewEvents()
// }
// return d.onShow
// }
func (d *DialogBase) OnShow() EventHandler {
if d.onShow == nil {
d.onShow = NewEvents()
}
return d.onShow
}
// func (d *DialogBase) SetContent(control ui.Control) {
// d.content.Proxied = control
// }
func (d *DialogBase) SetContent(control Control) {
d.content.Proxied = control
}
func (d *DialogBase) ShowDialog(ctx *Context, close EventFn) {
d.close = close
if d.onShow != nil {
d.onShow.Notify(ctx)
}
}
// func (d *DialogBase) ShowDialog(ctx ui.Context, close zntg.EventFn) {
// d.close = close
// if d.onShow != nil {
// d.onShow.Notify(ctx)
// }
// }
type LargeDialog struct {
DialogBase
ui.StackPanel
title Label
titleBar *LargeDialogTitleBar
content ui.OverlayProxy
closeRequested ui.Events
onShow zntg.Events
close zntg.EventFn
}
func NewLargeDialog(title string, content ui.Control) *LargeDialog {
dialog := &LargeDialog{}
dialog.titleBar = NewLargeDialogTitleBar(title, func(ctx ui.Context, state interface{}) {
dialog.closeRequested.Notify(ctx, state)
})
dialog.content.Content = content
dialog.Children = []ui.Control{dialog.titleBar, &dialog.content}
return dialog
}
func (d *LargeDialog) Hidden() { d.content.Hidden() }
func (d *LargeDialog) Shown() { d.content.Shown() }
type LargeDialogTitleBar struct {
ui.ContainerBase
title ui.Label
close IconButton
}
func (d *LargeDialog) Arrange(ctx *Context, bounds Rectangle) {
const titleHeight = 64
d.ControlBase.Arrange(ctx, bounds)
d.title.Arrange(ctx, Rect(bounds.X, bounds.Y, bounds.W, titleHeight))
d.close.Arrange(ctx, Rect(bounds.W-64, 0, 64, 64))
d.content.Arrange(ctx, Rect(bounds.X+titleHeight, 96, bounds.W-2*titleHeight, bounds.H-titleHeight))
func NewLargeDialogTitleBar(title string, closeRequested ui.EventFn) *LargeDialogTitleBar {
titleBar := &LargeDialogTitleBar{}
titleBar.Children = []ui.Control{&titleBar.title, &titleBar.close}
titleBar.close.ButtonClicked().AddHandler(func(ctx ui.Context, args ui.ControlClickedArgs) {
closeRequested(ctx, args)
})
return titleBar
}
func (d *LargeDialog) Init(ctx *Context) error {
d.title.Text = "Botanim"
d.title.FontName = "title"
d.title.Alignment = TextAlignmentCenter
// func (d *LargeDialog) Arrange(ctx ui.Context, bounds Rectangle) {
// const titleHeight = 64
// d.ControlBase.Arrange(ctx, bounds)
// d.title.Arrange(ctx, Rect(bounds.X, bounds.Y, bounds.W, titleHeight))
// d.close.Arrange(ctx, Rect(bounds.W-64, 0, 64, 64))
// d.content.Arrange(ctx, Rect(bounds.X+titleHeight, 96, bounds.W-2*titleHeight, bounds.H-titleHeight))
// }
d.close = IconButton{
Icon: "control-cancel",
IconHover: HoverEffectColor,
IconWidth: 32,
}
d.close.OnLeftMouseButtonClick = EmptyEvent(d.CloseDialog)
d.AddChild(&d.title)
d.AddChild(&d.close)
return d.DialogBase.Init(ctx)
}
// func (d *LargeDialog) Init(ctx ui.Context) error {
// d.title.Text = "Botanim"
// d.title.FontName = "title"
// d.title.Alignment = TextAlignmentCenter
func (d *LargeDialog) Handle(ctx *Context, event sdl.Event) bool {
if d.DialogBase.Handle(ctx, event) {
return true
}
// d.close = IconButton{
// Icon: "control-cancel",
// IconHover: HoverEffectColor,
// IconWidth: 32,
// }
// d.close.OnLeftMouseButtonClick = EmptyEvent(d.CloseDialog)
// d.AddChild(&d.title)
// d.AddChild(&d.close)
// return d.DialogBase.Init(ctx)
// }
switch e := event.(type) {
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
switch e.Keysym.Sym {
case sdl.K_ESCAPE:
d.CloseDialog()
return true
case sdl.K_RETURN:
d.CloseDialog()
return true
}
}
}
return false
}
// func (d *LargeDialog) Handle(ctx ui.Context, event sdl.Event) bool {
// if d.DialogBase.Handle(ctx, event) {
// return true
// }
func (d *LargeDialog) Render(ctx *Context) {
SetDrawColor(ctx.Renderer, MustHexColor("#356DAD"))
ctx.Renderer.FillRect(d.Bounds.SDLPtr())
// switch e := event.(type) {
// case *sdl.KeyboardEvent:
// if e.Type == sdl.KEYDOWN {
// switch e.Keysym.Sym {
// case sdl.K_ESCAPE:
// d.CloseDialog()
// return true
// case sdl.K_RETURN:
// d.CloseDialog()
// return true
// }
// }
// }
// return false
// }
d.DialogBase.Render(ctx)
}
// func (d *LargeDialog) Render(ctx ui.Context) {
// SetDrawColor(ctx.Renderer, MustHexColor("#356DAD"))
// ctx.Renderer.FillRect(d.Bounds.SDLPtr())
func (d *LargeDialog) SetCaption(s string) { d.title.Text = s }
// d.DialogBase.Render(ctx)
// }

24
map.go
View File

@ -1,5 +1,7 @@
package tins2020
import "opslag.de/schobers/geom"
type Map struct {
Temp NoiseMap
Humid NoiseMap
@ -7,32 +9,32 @@ type Map struct {
PlaceX NoiseMap // displacement map of props
PlaceY NoiseMap
Center Point
Flowers map[Point]Flower
Center geom.Point
Flowers map[geom.Point]Flower
}
func (m *Map) AddFlower(pos Point, id string, traits FlowerTraits) {
func (m *Map) AddFlower(pos geom.Point, id string, traits FlowerTraits) {
m.Flowers[pos] = m.NewFlower(pos, id, traits)
}
func (m *Map) FlowersOnAdjacentTiles(pos Point) int {
func (m *Map) FlowersOnAdjacentTiles(pos geom.Point) int {
var count int
if _, ok := m.Flowers[Pt(pos.X+1, pos.Y)]; ok {
if _, ok := m.Flowers[geom.Pt(pos.X+1, pos.Y)]; ok {
count++
}
if _, ok := m.Flowers[Pt(pos.X-1, pos.Y)]; ok {
if _, ok := m.Flowers[geom.Pt(pos.X-1, pos.Y)]; ok {
count++
}
if _, ok := m.Flowers[Pt(pos.X, pos.Y+1)]; ok {
if _, ok := m.Flowers[geom.Pt(pos.X, pos.Y+1)]; ok {
count++
}
if _, ok := m.Flowers[Pt(pos.X, pos.Y-1)]; ok {
if _, ok := m.Flowers[geom.Pt(pos.X, pos.Y-1)]; ok {
count++
}
return count
}
func (m *Map) DigFlower(pos Point) string {
func (m *Map) DigFlower(pos geom.Point) string {
flower, ok := m.Flowers[pos]
if !ok {
return ""
@ -41,12 +43,12 @@ func (m *Map) DigFlower(pos Point) string {
return flower.ID
}
func (m *Map) HasFlower(pos Point) bool {
func (m *Map) HasFlower(pos geom.Point) bool {
_, ok := m.Flowers[pos]
return ok
}
func (m *Map) NewFlower(pos Point, id string, traits FlowerTraits) Flower {
func (m *Map) NewFlower(pos geom.Point, id string, traits FlowerTraits) Flower {
flower := Flower{
ID: id,
Traits: traits,

View File

@ -14,7 +14,7 @@ func clipNormalized(x float64) float64 {
type NoiseMap interface {
Seed() int64
Value(x, y int32) float64
Value(x, y int) float64
}
func NewNoiseMap(seed int64) NoiseMap {
@ -33,7 +33,7 @@ type noiseMap struct {
}
// Value generates the noise value for an x/y pair.
func (m noiseMap) Value(x, y int32) float64 {
func (m noiseMap) Value(x, y int) float64 {
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
return clipNormalized(value)
}
@ -49,7 +49,7 @@ type randomNoiseMap struct {
}
// Value generates the noise value for an x/y pair.
func (m randomNoiseMap) Value(x, y int32) float64 {
func (m randomNoiseMap) Value(x, y int) float64 {
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
return clipNormalized(value)
}

View File

@ -1,42 +0,0 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Point struct {
sdl.Point
}
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) Sub(q Point) Point { return Pt(p.X-q.X, p.Y-q.Y) }
func (p Point) ToPtF() PointF { return PtF(float32(p.X), float32(p.Y)) }
type PointF struct {
sdl.FPoint
}
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 Pt(x, y int32) Point { return Point{sdl.Point{X: x, Y: 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
}

View File

@ -1,99 +1,96 @@
package tins2020
import (
"log"
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui"
)
func mapToTile(q PointF) Point {
return Pt(int32(Round32(q.X)), int32(Round32(q.Y)))
func mapToTile(q geom.PointF32) geom.Point {
return geom.Pt(int(geom.Round32(q.X)), int(geom.Round32(q.Y)))
}
type projection struct {
center PointF
center geom.PointF32
zoom float32
zoomInv float32
windowInteractRect Rectangle
windowVisibleRect Rectangle
tileScreenDelta PointF
tileScreenDeltaInv PointF
tileScreenOffset Point
tileScreenSize Point
tileFitScreenSize Point
windowCenter Point
windowInteractRect geom.Rectangle
windowVisibleRect geom.Rectangle
tileScreenDelta geom.PointF32
tileScreenDeltaInv geom.PointF32
tileScreenOffset geom.Point
tileScreenSize geom.Point
tileFitScreenSize geom.Point
windowCenter geom.Point
}
func newProjection() projection {
return projection{zoom: 1, tileScreenDelta: PtF(64, 32), tileScreenDeltaInv: PtF(1./64, 1./32)}
return projection{zoom: 1, tileScreenDelta: geom.PtF32(64, 32), tileScreenDeltaInv: geom.PtF32(1./64, 1./32)}
}
func (p *projection) mapToScreen(x, y int32) Point {
func (p *projection) mapToScreen(x, y int) geom.Point {
return p.mapToScreenF(float32(x), float32(y))
}
func (p *projection) mapToScreenF(x, y float32) Point {
translated := PtF(x-p.center.X, y-p.center.Y)
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) mapToScreenF(x, y float32) geom.Point {
translated := geom.PtF32(x-p.center.X, y-p.center.Y)
return geom.Pt(p.windowCenter.X+int((translated.X-translated.Y)*64*p.zoom), p.windowCenter.Y+int((translated.X+translated.Y)*32*p.zoom))
}
func (p *projection) screenToMap(x, y int32) PointF {
func (p *projection) screenToMap(x, y int) geom.PointF32 {
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
return p.center.Add(pos)
}
func (p *projection) screenToMapInt(x, y int32) Point {
func (p *projection) screenToMapInt(x, y int) geom.Point {
pos := p.screenToMap(x, y)
return mapToTile(pos)
}
func (p *projection) screenToMapRel(x, y int32) PointF {
func (p *projection) screenToMapRel(x, y int) geom.PointF32 {
normX := p.zoomInv * float32(x)
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 geom.PtF32(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
}
func (p *projection) screenToTileFitRect(pos Point) Rectangle {
return Rect(pos.X-p.tileFitScreenSize.X, pos.Y-p.tileFitScreenSize.Y, 2*p.tileFitScreenSize.X, 2*p.tileFitScreenSize.Y)
func (p *projection) screenToTileFitRect(pos geom.Point) geom.Rectangle {
return geom.RectRel(pos.X-p.tileFitScreenSize.X, pos.Y-p.tileFitScreenSize.Y, 2*p.tileFitScreenSize.X, 2*p.tileFitScreenSize.Y)
}
func (p *projection) screenToTileRect(pos Point) Rectangle {
return Rect(pos.X-p.tileScreenOffset.X, pos.Y-p.tileScreenOffset.Y, p.tileScreenSize.X, p.tileScreenSize.Y)
func (p *projection) screenToTileRect(pos geom.Point) geom.Rectangle {
return geom.RectRel(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 ui.Renderer) {
p.zoomInv = 1 / p.zoom
p.tileScreenOffset = Pt(int32(p.zoom*64), int32(p.zoom*112))
p.tileScreenSize = Pt(int32(p.zoom*128), int32(p.zoom*160))
p.tileFitScreenSize = Pt(int32(p.zoom*64), int32(p.zoom*32))
p.tileScreenOffset = geom.Pt(int(p.zoom*64), int(p.zoom*112))
p.tileScreenSize = geom.Pt(int(p.zoom*128), int(p.zoom*160))
p.tileFitScreenSize = geom.Pt(int(p.zoom*64), int(p.zoom*32))
windowW, windowH, err := renderer.GetOutputSize()
if err != nil {
log.Fatal(err)
}
p.windowCenter = Pt(windowW/2, windowH/2)
p.windowInteractRect = RectAbs(buttonBarWidth, 64, windowW-buttonBarWidth, windowH)
p.windowVisibleRect = RectAbs(buttonBarWidth, 0, windowW-buttonBarWidth, windowH+p.tileScreenSize.Y) // Adding a tile height to the bottom for trees that stick out from the cells below.
windowF32 := renderer.Size()
window := geom.Pt(int(windowF32.X), int(windowF32.Y))
p.windowCenter = geom.Pt(window.X/2, window.Y/2)
p.windowInteractRect = geom.Rect(buttonBarWidth, 64, window.X-buttonBarWidth, window.Y)
p.windowVisibleRect = geom.Rect(buttonBarWidth, 0, window.X-buttonBarWidth, window.Y+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(int, int, geom.Point)) {
visible := p.windowVisibleRect
topLeft := p.screenToMap(visible.X, visible.Y)
topRight := p.screenToMap(visible.X+visible.W, visible.Y)
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))
minX, maxX := int32(Floor32(topLeft.X)), int32(Ceil32(bottomRight.X))
topLeft := p.screenToMap(visible.Min.X, visible.Min.Y)
topRight := p.screenToMap(visible.Max.X, visible.Min.Y)
bottomLeft := p.screenToMap(visible.Min.X, visible.Max.Y)
bottomRight := p.screenToMap(visible.Max.Y, visible.Max.Y)
minY, maxY := int(Floor32(topRight.Y)), int(Ceil32(bottomLeft.Y))
minX, maxX := int(Floor32(topLeft.X)), int(Ceil32(bottomRight.X))
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
pos := p.mapToScreen(x, y)
rectFit := p.screenToTileFitRect(pos)
if rectFit.X+rectFit.W < visible.X || rectFit.Y+rectFit.H < visible.Y {
if rectFit.Max.X < visible.Min.X || rectFit.Max.Y < visible.Min.Y {
continue
}
if rectFit.X > visible.X+visible.W || rectFit.Y > visible.Y+visible.H {
if rectFit.Min.X > visible.Max.X || rectFit.Min.Y > visible.Max.Y {
break
}
action(x, y, pos)
@ -101,28 +98,28 @@ func (p *projection) visibleTiles(action func(int32, int32, Point)) {
}
}
func (p *projection) Pan(ctx *Context, delta PointF) {
func (p *projection) Pan(ctx ui.Context, delta geom.PointF32) {
p.center = p.center.Add(delta.Mul(p.zoomInv))
p.update(ctx.Renderer)
p.update(ctx.Renderer())
}
func (p *projection) SetZoom(ctx *Context, center PointF, zoom float32) {
func (p *projection) SetZoom(ctx ui.Context, center geom.PointF32, zoom float32) {
if p.zoom == zoom {
return
}
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
p.zoom = zoom
p.update(ctx.Renderer)
p.update(ctx.Renderer())
}
func (p *projection) ZoomOut(ctx *Context, center PointF) {
func (p *projection) ZoomOut(ctx ui.Context, center geom.PointF32) {
if p.zoom <= .25 {
return
}
p.SetZoom(ctx, center, .5*p.zoom)
}
func (p *projection) ZoomIn(ctx *Context, center PointF) {
func (p *projection) ZoomIn(ctx ui.Context, center geom.PointF32) {
if p.zoom >= 2 {
return
}

View File

@ -1,48 +1,48 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
// import "github.com/veandco/go-sdl2/sdl"
var _ Control = &Proxy{}
// var _ Control = &Proxy{}
type Proxy struct {
Proxied Control
// type Proxy struct {
// Proxied Control
bounds Rectangle
}
// bounds Rectangle
// }
func (p *Proxy) Arrange(ctx *Context, bounds Rectangle) {
p.bounds = bounds
if p.Proxied == nil {
return
}
p.Proxied.Arrange(ctx, bounds)
}
// func (p *Proxy) Arrange(ctx ui.Context, bounds Rectangle) {
// p.bounds = bounds
// if p.Proxied == nil {
// return
// }
// p.Proxied.Arrange(ctx, bounds)
// }
func (p *Proxy) Handle(ctx *Context, event sdl.Event) bool {
if p.Proxied == nil {
return false
}
return p.Proxied.Handle(ctx, event)
}
// func (p *Proxy) Handle(ctx ui.Context, event sdl.Event) bool {
// if p.Proxied == nil {
// return false
// }
// return p.Proxied.Handle(ctx, event)
// }
func (p *Proxy) Init(ctx *Context) error {
if p.Proxied == nil {
return nil
}
return p.Proxied.Init(ctx)
}
// func (p *Proxy) Init(ctx ui.Context) error {
// if p.Proxied == nil {
// return nil
// }
// return p.Proxied.Init(ctx)
// }
func (p *Proxy) Render(ctx *Context) {
if p.Proxied == nil {
return
}
p.Proxied.Render(ctx)
}
// func (p *Proxy) Render(ctx ui.Context) {
// if p.Proxied == nil {
// return
// }
// p.Proxied.Render(ctx)
// }
func (p *Proxy) SetContent(ctx *Context, content Control) {
p.Proxied = content
if content == nil {
return
}
content.Arrange(ctx, p.bounds)
}
// func (p *Proxy) SetContent(ctx ui.Context, content Control) {
// p.Proxied = content
// if content == nil {
// return
// }
// content.Arrange(ctx, p.bounds)
// }

32
rect.go
View File

@ -1,32 +0,0 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Rectangle struct {
sdl.Rect
}
func RectAbs(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 Rect(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 }

View File

@ -8,11 +8,14 @@ import (
"strings"
"time"
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
"opslag.de/schobers/zntg/ui"
)
type Research struct {
Container
ui.ContainerBase
game *Game
botanist Specialist
@ -22,30 +25,39 @@ type Research struct {
digitCount int
close func()
description Paragraph
specialists Paragraph
input Label
description ui.Paragraph
specialists ui.Paragraph
input ui.Label
digits []Digit
animate Animation
animate zntg.Animation
}
func NewResearch(game *Game) Control {
research := &Research{
game: game,
animate: NewAnimation(20 * time.Millisecond),
}
dialog := &LargeDialog{}
dialog.SetCaption("Research")
dialog.SetContent(research)
dialog.OnShow().RegisterItf(func(state interface{}) {
research.onShow(state.(*Context))
func NewResearch(game *Game) ui.Overlay {
research := &Research{game: game}
research.animate.Interval = 20 * time.Millisecond
research.Children = []ui.Control{&research.description, &research.specialists, &research.input}
research.description.Text = "Call a specialist to conduct research with."
research.digits = make([]Digit, 10)
for i := range research.digits {
j := i
research.digits[i].Value = strconv.Itoa(i)
research.digits[i].ControlClicked().AddHandler(func(ctx ui.Context, _ ui.ControlClickedArgs) {
research.userTyped(ctx, j)
})
research.close = func() { dialog.CloseDialog() }
research.AddChild(&research.digits[i])
}
dialog := NewLargeDialog("Research", research)
// dialog.OnShow().RegisterItf(func(state interface{}) {
// research.onShow(state.(ui.Context))
// })
// research.close = func() { dialog.CloseDialog() }
return dialog
}
type Digit struct {
ControlBase
ui.ControlBase
Value string
@ -56,13 +68,13 @@ func (d *Digit) Blink() {
d.highlight = 4
}
func (d *Digit) Render(ctx *Context) {
font := ctx.Fonts.Font("title")
color := White
func (d *Digit) Render(ctx ui.Context) {
color := zntg.MustHexColor(`#FFFFFF`)
if d.highlight > 0 {
color = MustHexColor("#15569F")
color = zntg.MustHexColor(`#15569F`)
}
font.RenderCopyAlign(ctx.Renderer, d.Value, Pt(d.Bounds.X+d.Bounds.W/2, d.Bounds.Y+int32(font.Height())), color, TextAlignmentCenter)
bounds := d.Bounds()
ctx.Fonts().TextAlign("title", geom.PtF32(bounds.Center().X, bounds.Min.Y), color, d.Value, ui.AlignCenter)
}
func (d *Digit) Tick() {
@ -76,43 +88,26 @@ type Specialist struct {
Number string
}
func (r *Research) Init(ctx *Context) error {
r.AddChild(&r.description)
r.AddChild(&r.specialists)
r.AddChild(&r.input)
func (r *Research) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.PointF32, parent ui.Control) {
r.ContainerBase.Arrange(ctx, bounds, offset, parent)
r.description.Text = "Call a specialist to conduct research with."
r.digits = make([]Digit, 10)
size := bounds.Size()
r.specialists.Arrange(ctx, geom.RectRelF32(bounds.Min.X, bounds.Min.Y+40, size.X, size.Y-40), offset, r)
r.input.Arrange(ctx, geom.RectRelF32(bounds.Min.X, bounds.Min.X+size.Y-48, size.X, 24), offset, r)
r.input.TextAlignment = ui.AlignCenter
center := bounds.Center()
distance := size.Y * .3
for i := range r.digits {
j := i
r.digits[i].Value = strconv.Itoa(i)
r.digits[i].OnLeftMouseButtonClick = func(*Context) {
r.userTyped(j)
}
r.AddChild(&r.digits[i])
}
return nil
}
func (r *Research) Arrange(ctx *Context, bounds Rectangle) {
r.Container.Arrange(ctx, bounds)
r.specialists.Arrange(ctx, Rect(r.Bounds.X, r.Bounds.Y+40, r.Bounds.W, r.Bounds.H-40))
r.input.Arrange(ctx, Rect(r.Bounds.X, r.Bounds.X+r.Bounds.H-48, r.Bounds.W, 24))
r.input.Alignment = TextAlignmentCenter
center := Pt(r.Bounds.X+r.Bounds.W/2, r.Bounds.Y+r.Bounds.H/2)
distance := float64(bounds.H) * .3
for i := range r.digits {
angle := (float64((10-i)%10)*0.16 + .2) * math.Pi
pos := Pt(int32(distance*math.Cos(angle)), int32(.8*distance*math.Sin(angle)))
angle := (float32((10-i)%10)*0.16 + .2) * math.Pi
pos := geom.PtF32(distance*geom.Cos32(angle), .8*distance*geom.Sin32(angle))
digitCenter := center.Add(pos)
r.digits[i].Arrange(ctx, Rect(digitCenter.X-24, digitCenter.Y-24, 48, 48))
r.digits[i].Arrange(ctx, geom.RectRelF32(digitCenter.X-24, digitCenter.Y-24, 48, 48), offset, r)
}
}
func (r *Research) userTyped(i int) {
func (r *Research) userTyped(ctx ui.Context, i int) {
r.digits[i].Blink()
digit := strconv.Itoa(i)
if len(r.typing) == 0 || digit != r.typing {
@ -132,75 +127,73 @@ func (r *Research) userTyped(i int) {
r.digitCount = 0
if r.input.Text == r.botanist.Number {
r.game.UnlockNextFlower()
r.game.UnlockNextFlower(ctx)
r.close()
r.input.Text = ""
}
}
}
func (r *Research) Handle(ctx *Context, event sdl.Event) bool {
if r.Container.Handle(ctx, event) {
func (r *Research) Handle(ctx ui.Context, event ui.Event) bool {
if r.ContainerBase.Handle(ctx, event) {
return true
}
switch e := event.(type) {
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
switch e.Keysym.Sym {
case sdl.K_0:
r.userTyped(0)
case sdl.K_KP_0:
r.userTyped(0)
case sdl.K_1:
r.userTyped(1)
case sdl.K_KP_1:
r.userTyped(1)
case sdl.K_2:
r.userTyped(2)
case sdl.K_KP_2:
r.userTyped(2)
case sdl.K_3:
r.userTyped(3)
case sdl.K_KP_3:
r.userTyped(3)
case sdl.K_4:
r.userTyped(4)
case sdl.K_KP_4:
r.userTyped(4)
case sdl.K_5:
r.userTyped(5)
case sdl.K_KP_5:
r.userTyped(5)
case sdl.K_6:
r.userTyped(6)
case sdl.K_KP_6:
r.userTyped(6)
case sdl.K_7:
r.userTyped(7)
case sdl.K_KP_7:
r.userTyped(7)
case sdl.K_8:
r.userTyped(8)
case sdl.K_KP_8:
r.userTyped(8)
case sdl.K_9:
r.userTyped(9)
case sdl.K_KP_9:
r.userTyped(9)
}
case *ui.KeyDownEvent:
switch e.Key {
case ui.Key0:
r.userTyped(ctx, 0)
case ui.KeyPad0:
r.userTyped(ctx, 0)
case ui.Key1:
r.userTyped(ctx, 1)
case ui.KeyPad1:
r.userTyped(ctx, 1)
case ui.Key2:
r.userTyped(ctx, 2)
case ui.KeyPad2:
r.userTyped(ctx, 2)
case ui.Key3:
r.userTyped(ctx, 3)
case ui.KeyPad3:
r.userTyped(ctx, 3)
case ui.Key4:
r.userTyped(ctx, 4)
case ui.KeyPad4:
r.userTyped(ctx, 4)
case ui.Key5:
r.userTyped(ctx, 5)
case ui.KeyPad5:
r.userTyped(ctx, 5)
case ui.Key6:
r.userTyped(ctx, 6)
case ui.KeyPad6:
r.userTyped(ctx, 6)
case ui.Key7:
r.userTyped(ctx, 7)
case ui.KeyPad7:
r.userTyped(ctx, 7)
case ui.Key8:
r.userTyped(ctx, 8)
case ui.KeyPad8:
r.userTyped(ctx, 8)
case ui.Key9:
r.userTyped(ctx, 9)
case ui.KeyPad9:
r.userTyped(ctx, 9)
}
}
return false
}
func (r *Research) Render(ctx *Context) {
func (r *Research) Render(ctx ui.Context) {
for i := range r.digits {
r.digits[i].Tick()
}
r.Container.Render(ctx)
r.ContainerBase.Render(ctx)
}
func (r *Research) onShow(ctx *Context) {
func (r *Research) onShow(ctx ui.Context) {
generateNumber := func() string {
var number string
for i := 0; i < 3; i++ {

View File

@ -4,6 +4,8 @@ import (
"bufio"
"fmt"
"strings"
"opslag.de/schobers/zntg/ui"
)
type ResourceLoader struct {
@ -14,8 +16,8 @@ func NewResourceLoader() *ResourceLoader {
return &ResourceLoader{}
}
func (l *ResourceLoader) parseResourcesFile(res *Resources, name string) error {
f, err := res.Fs().Open(name)
func (l *ResourceLoader) parseResourcesFile(res ui.Resources, name string) error {
f, err := res.OpenResource(name)
if err != nil {
return err
}
@ -36,7 +38,7 @@ func (l *ResourceLoader) parseResourcesFile(res *Resources, name string) error {
return nil
}
func (l *ResourceLoader) LoadFromFile(res *Resources, name string, action func(string, string) error) error {
func (l *ResourceLoader) LoadFromFile(res ui.Resources, name string, action func(string, string) error) error {
err := l.parseResourcesFile(res, name)
if err != nil {
return err

View File

@ -1,6 +1,11 @@
package tins2020
import "os"
import (
"os"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
)
type Settings struct {
Window WindowSettings
@ -18,7 +23,7 @@ func (s *Settings) Init() error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil
}
return DecodeJSON(path, s)
return zntg.DecodeJSON(path, s)
}
func (s *Settings) Store() error {
@ -26,11 +31,11 @@ func (s *Settings) Store() error {
if err != nil {
return err
}
return EncodeJSON(path, s)
return zntg.EncodeJSON(path, s)
}
type WindowSettings struct {
Location *Point
Size *Point
Location *geom.Point
Size *geom.Point
VSync *bool
}

View File

@ -3,129 +3,129 @@ package tins2020
import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui"
)
type terrainRenderer struct {
ui.ControlBase
game *Game
hover *Point
hover *geom.Point
project projection
drag Drageable
drag ui.Dragable
}
func NewTerrainRenderer(game *Game) Control {
return &terrainRenderer{game: game, project: newProjection()}
}
func NewTerrainRenderer(game *Game) ui.Control {
renderer := &terrainRenderer{game: game, project: newProjection()}
func (r *terrainRenderer) Arrange(ctx *Context, _ Rectangle) {
r.project.update(ctx.Renderer)
}
func (r *terrainRenderer) Init(ctx *Context) error {
r.game.CenterChanged().RegisterItf(func(state interface{}) {
center := state.(Point)
r.project.center = center.ToPtF()
r.project.update(ctx.Renderer)
renderer.game.CenterChanged().AddHandler(func(ctx ui.Context, state interface{}) {
center := state.(geom.Point)
renderer.project.center = center.ToF32()
renderer.project.update(ctx.Renderer())
})
r.project.update(ctx.Renderer)
// renderer.project.update(ctx.Renderer)
return renderer
}
func (r *terrainRenderer) Arrange(ctx ui.Context, _ geom.RectangleF32, _ geom.PointF32, _ ui.Control) {
r.project.update(ctx.Renderer())
}
func (r *terrainRenderer) Init(ctx ui.Context) error {
return nil
}
func isControlKeyDown() bool {
state := sdl.GetKeyboardState()
return state[sdl.SCANCODE_LCTRL] == 1 || state[sdl.SCANCODE_RCTRL] == 1 || state[sdl.SCANCODE_LGUI] == 1 || state[sdl.SCANCODE_RGUI] == 1
func isControlKeyDown(ctx ui.Context) bool {
return false
// ctx.MousePosition()
// state := ui.GetKeyboardState()
// return state[ui.SCANCODE_LCTRL] == 1 || state[ui.SCANCODE_RCTRL] == 1 || state[ui.SCANCODE_LGUI] == 1 || state[ui.SCANCODE_RGUI] == 1
}
func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool {
func (r *terrainRenderer) Handle(ctx ui.Context, event ui.Event) bool {
switch e := event.(type) {
case *sdl.MouseButtonEvent:
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
if e.Type == sdl.MOUSEBUTTONDOWN {
controlKeyDown := isControlKeyDown()
if e.Button == sdl.BUTTON_MIDDLE || (e.Button == sdl.BUTTON_LEFT && controlKeyDown) {
if !r.drag.IsDragging() {
r.drag.Start(Pt(e.X, e.Y))
case *ui.MouseButtonDownEvent:
pos := e.Pos()
if pos.ToInt().In(r.project.windowInteractRect) {
controlKeyDown := isControlKeyDown(ctx)
if e.Button == ui.MouseButtonMiddle || (e.Button == ui.MouseButtonLeft && controlKeyDown) {
if _, ok := r.drag.IsDragging(); !ok {
r.drag.Start(pos)
}
}
if e.Button == sdl.BUTTON_LEFT && !controlKeyDown {
pos := r.project.screenToMapInt(e.X, e.Y)
if e.Button == ui.MouseButtonLeft && !controlKeyDown {
pos := r.project.screenToMapInt(int(e.X), int(e.Y))
r.game.UserClickedTile(pos)
}
if e.Button == sdl.BUTTON_RIGHT {
if e.Type == sdl.MOUSEBUTTONDOWN {
r.game.CancelTool()
if e.Button == ui.MouseButtonRight {
r.game.CancelTool(ctx)
}
}
}
if e.Type == sdl.MOUSEBUTTONUP {
if r.drag.IsDragging() {
case *ui.MouseButtonUpEvent:
pos := e.Pos().ToInt()
if pos.In(r.project.windowInteractRect) {
if _, ok := r.drag.IsDragging(); ok {
r.game.Terrain.Center = mapToTile(r.project.center)
r.drag.Cancel()
}
}
}
case *sdl.MouseMotionEvent:
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
hover := r.project.screenToMapInt(e.X, e.Y)
case *ui.MouseMoveEvent:
pos := e.Pos()
if pos.ToInt().In(r.project.windowInteractRect) {
hover := r.project.screenToMapInt(int(e.X), int(e.Y))
r.hover = &hover
} else {
r.hover = nil
}
if r.drag.IsDragging() {
delta := r.drag.Move(Pt(e.X, e.Y))
r.project.center = r.project.center.Sub(r.project.screenToMapRel(delta.X, delta.Y))
r.project.update(ctx.Renderer)
if _, ok := r.drag.IsDragging(); ok {
delta, _ := r.drag.Move(pos)
r.project.center = r.project.center.Sub(r.project.screenToMapRel(int(delta.X), int(delta.Y)))
r.project.update(ctx.Renderer())
}
case *sdl.MouseWheelEvent:
if r.hover != nil {
if e.Y < 0 {
r.project.ZoomOut(ctx, r.hover.ToPtF())
r.project.ZoomOut(ctx, r.hover.ToF32())
} else {
r.project.ZoomIn(ctx, r.hover.ToPtF())
r.project.ZoomIn(ctx, r.hover.ToF32())
}
}
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_LEAVE {
case *ui.MouseLeaveEvent:
r.hover = nil
r.project.update(ctx.Renderer)
}
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
switch e.Keysym.Sym {
case sdl.K_PLUS:
r.project.update(ctx.Renderer())
case *ui.KeyDownEvent:
switch e.Key {
case ui.KeyPadPlus:
r.project.ZoomIn(ctx, r.project.center)
case sdl.K_KP_PLUS:
r.project.ZoomIn(ctx, r.project.center)
case sdl.K_MINUS:
case ui.KeyMinus:
r.project.ZoomOut(ctx, r.project.center)
case sdl.K_KP_MINUS:
case ui.KeyPadMinus:
r.project.ZoomOut(ctx, r.project.center)
case sdl.K_w:
r.project.Pan(ctx, PtF(-1, -1))
case sdl.K_a:
r.project.Pan(ctx, PtF(-1, 1))
case sdl.K_s:
r.project.Pan(ctx, PtF(1, 1))
case sdl.K_d:
r.project.Pan(ctx, PtF(1, -1))
}
case ui.KeyW:
r.project.Pan(ctx, geom.PtF32(-1, -1))
case ui.KeyA:
r.project.Pan(ctx, geom.PtF32(-1, 1))
case ui.KeyS:
r.project.Pan(ctx, geom.PtF32(1, 1))
case ui.KeyD:
r.project.Pan(ctx, geom.PtF32(1, -1))
}
}
return false
}
func (r *terrainRenderer) Render(ctx *Context) {
func (r *terrainRenderer) Render(ctx ui.Context) {
terrain := r.game.Terrain
toTileTexture := func(x, y int32) *Texture {
toTileTexture := func(x, y int) ui.Texture {
temp := terrain.Temp.Value(x, y)
if temp < .35 {
return ctx.Textures.Texture("tile-snow")
return ctx.Textures().Texture("tile-snow")
}
if temp > .65 {
return ctx.Textures.Texture("tile-dirt")
return ctx.Textures().Texture("tile-dirt")
}
return ctx.Textures.Texture("tile-grass")
return ctx.Textures().Texture("tile-grass")
}
variantToInt := func(variant float64) int {
@ -144,14 +144,14 @@ func (r *terrainRenderer) Render(ctx *Context) {
return -1
}
variantToTexture := func(format string, variant float64) *Texture {
variantToTexture := func(format string, variant float64) ui.Texture {
textName := fmt.Sprintf(format, variantToInt(variant))
return ctx.Textures.Texture(textName)
return ctx.Textures().Texture(textName)
}
stretch := func(x, from, to float64) float64 { return (x - from) * 1 / (to - from) }
toPropTexture := func(temp, humid, variant float64) *Texture {
toPropTexture := func(temp, humid, variant float64) ui.Texture {
if temp < .35 {
if humid < .2 {
return nil
@ -181,12 +181,12 @@ func (r *terrainRenderer) Render(ctx *Context) {
return variantToTexture("bush-large-%d", stretch(variant, .8, 1)*multiplier)
}
toItemTexture := func(x, y int32) *Texture {
toItemTexture := func(x, y int) ui.Texture {
variant := terrain.Variant.Value(x, y)
flower, ok := terrain.Flowers[Pt(x, y)]
flower, ok := terrain.Flowers[geom.Pt(x, y)]
if ok {
desc, _ := r.game.Herbarium.Find(flower.ID)
return ctx.Textures.Texture(desc.IconTemplate.Variant(variantToInt(variant)))
return ctx.Textures().Texture(desc.IconTemplate.Variant(variantToInt(variant)))
}
temp := terrain.Temp.Value(x, y)
humid := terrain.Humid.Value(x, y)
@ -197,17 +197,17 @@ func (r *terrainRenderer) Render(ctx *Context) {
// vertical (tile): [96,160) = 64
// vertical (total): [0,160) = 160
r.project.visibleTiles(func(x, y int32, pos Point) {
r.project.visibleTiles(func(x, y int, pos geom.Point) {
text := toTileTexture(x, y)
rect := r.project.screenToTileRect(pos)
text.CopyResize(ctx.Renderer, rect)
ctx.Renderer().DrawTexture(text, rect.ToF32())
if r.hover != nil && x == r.hover.X && y == r.hover.Y {
ctx.Textures.Texture("tile-hover").CopyResize(ctx.Renderer, rect)
ctx.Renderer().DrawTexture(ctx.Textures().Texture("tile-hover"), rect.ToF32())
}
})
r.project.visibleTiles(func(x, y int32, pos Point) {
r.project.visibleTiles(func(x, y int, pos geom.Point) {
text := toItemTexture(x, y)
if text == nil {
return
@ -215,6 +215,7 @@ func (r *terrainRenderer) Render(ctx *Context) {
placeX, placeY := terrain.PlaceX.Value(x, y), terrain.PlaceY.Value(x, y)
pos = r.project.mapToScreenF(float32(x)-.2+float32(.9*placeX-.45), float32(y)-.2+float32(.9*placeY-.45))
text.CopyResize(ctx.Renderer, r.project.screenToTileRect(pos))
rect := r.project.screenToTileRect(pos)
ctx.Renderer().DrawTexture(text, rect.ToF32())
})
}

View File

@ -1,116 +0,0 @@
package tins2020
import (
"errors"
"fmt"
"github.com/veandco/go-sdl2/img"
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/fs/vfs"
)
type Texture struct {
texture *sdl.Texture
size Point
}
func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Texture, error) {
texture, err := renderer.CreateTextureFromSurface(surface)
if err != nil {
return nil, err
}
return &Texture{texture: texture, size: Pt(surface.W, surface.H)}, nil
}
func (t *Texture) Size() Point { return t.size }
// func (t *Texture) Rect() Rectangle { return t.rect }
// func (t *Texture) SDLRectPtr() *sdl.Rect { return t.rect.SDLPtr() }
func (t *Texture) Copy(renderer *sdl.Renderer, dst Point) {
t.CopyResize(renderer, Rect(dst.X, dst.Y, t.size.X, t.size.Y))
}
func (t *Texture) CopyPart(renderer *sdl.Renderer, src Rectangle, dst Point) {
t.CopyPartResize(renderer, src, Rect(dst.X, dst.Y, src.W, src.H))
}
func (t *Texture) CopyPartResize(renderer *sdl.Renderer, src Rectangle, dst Rectangle) {
renderer.Copy(t.texture, src.SDLPtr(), dst.SDLPtr())
}
func (t *Texture) CopyResize(renderer *sdl.Renderer, dst Rectangle) {
t.CopyPartResize(renderer, RectAbs(0, 0, t.size.X, t.size.Y), dst)
}
func (t *Texture) SetColor(color sdl.Color) {
t.texture.SetColorMod(color.R, color.G, color.B)
}
// 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() }
type Textures struct {
dir vfs.CopyDir
renderer *sdl.Renderer
textures map[string]*Texture
}
func (t *Textures) Init(renderer *sdl.Renderer, dir vfs.CopyDir) {
t.dir = dir
t.renderer = renderer
t.textures = map[string]*Texture{}
}
func (t *Textures) Load(name, path string, other ...string) error {
err := t.load(name, path)
if err != nil {
return err
}
if len(other) == 0 {
return nil
}
if len(other)%2 != 0 {
return errors.New("expected name/path pairs")
}
for i := 0; i < len(other); i += 2 {
err = t.load(other[i], other[i+1])
if err != nil {
return fmt.Errorf("error loading '%s'; error: %v", other[i], err)
}
}
return nil
}
func (t *Textures) load(name, path string) error {
texturePath, err := t.dir.Retrieve(path)
if err != nil {
return err
}
surface, err := img.Load(texturePath)
if err != nil {
return err
}
defer surface.Free()
texture, err := NewTextureFromSurface(t.renderer, surface)
if err != nil {
return err
}
t.textures[name] = texture
return nil
}
func (t *Textures) Texture(name string) *Texture { return t.textures[name] }
func (t *Textures) Destroy() {
if t.textures == nil {
return
}
for _, t := range t.textures {
t.Destroy()
}
}

View File

@ -1,8 +1,10 @@
package tins2020
import "opslag.de/schobers/geom"
type Tool interface {
Type() string
ClickedTile(*Game, Point)
ClickedTile(*Game, geom.Point)
}
type PlantFlowerTool struct {
@ -11,7 +13,7 @@ type PlantFlowerTool struct {
func (t *PlantFlowerTool) Type() string { return "plant-flower" }
func (t *PlantFlowerTool) ClickedTile(game *Game, tile Point) {
func (t *PlantFlowerTool) ClickedTile(game *Game, tile geom.Point) {
game.PlantFlower(t.FlowerID, tile)
}
@ -19,6 +21,6 @@ type ShovelTool struct{}
func (t *ShovelTool) Type() string { return "shovel" }
func (t *ShovelTool) ClickedTile(game *Game, tile Point) {
func (t *ShovelTool) ClickedTile(game *Game, tile geom.Point) {
game.Dig(tile)
}

View File

@ -1,72 +0,0 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Tooltip struct {
ControlBase
Text string
}
const tooltipBorderThickness = 1
const tooltipHorizontalPadding = 6
const tooltipVerticalPadding = 2
const tooltipMouseDistance = 12
func (t *Tooltip) ActualFontName() string {
if t.FontName == "" {
return "small"
}
return t.FontName
}
func (t *Tooltip) Handle(ctx *Context, event sdl.Event) bool {
if len(t.Text) == 0 {
return false
}
font := ctx.Fonts.Font(t.ActualFontName())
if font == nil {
font = ctx.Fonts.Font("default")
}
windowW, windowH, err := ctx.Renderer.GetOutputSize()
if err != nil {
return false
}
labelW, labelH, err := font.SizeUTF8(t.Text)
if err != nil {
return false
}
mouse := ctx.MousePosition
width := int32(labelW) + 2*tooltipBorderThickness + 2*tooltipHorizontalPadding
height := int32(labelH) + 2*tooltipBorderThickness + 2*tooltipVerticalPadding
left := mouse.X + tooltipMouseDistance
top := mouse.Y + tooltipMouseDistance
if left+width > windowW {
left = mouse.X - tooltipMouseDistance - width
}
if top+height > windowH {
top = mouse.Y - tooltipMouseDistance - height
}
t.Bounds = Rect(left, top, width, height)
return false
}
func (t *Tooltip) Render(ctx *Context) {
almostBlack := MustHexColor("#0000007f")
SetDrawColor(ctx.Renderer, almostBlack)
ctx.Renderer.FillRect(t.Bounds.SDLPtr())
almostWhite := MustHexColor("ffffff7f")
SetDrawColor(ctx.Renderer, almostWhite)
ctx.Renderer.DrawRect(t.Bounds.SDLPtr())
font := ctx.Fonts.Font(t.ActualFontName())
bottomLeft := Pt(t.Bounds.X+tooltipBorderThickness+tooltipHorizontalPadding, t.Bounds.Y+t.Bounds.H-tooltipBorderThickness-tooltipVerticalPadding)
font.RenderCopy(ctx.Renderer, t.Text, bottomLeft, White)
}