First compiling & running version.
This commit is contained in:
parent
a139b87d58
commit
3474d7d973
57
animation.go
57
animation.go
@ -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
|
|
||||||
}
|
|
97
buttonbar.go
97
buttonbar.go
@ -1,59 +1,64 @@
|
|||||||
package tins2020
|
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 {
|
type ButtonBar struct {
|
||||||
Container
|
ui.ContainerBase
|
||||||
|
|
||||||
Background sdl.Color
|
Background color.Color
|
||||||
ButtonLength int32
|
ButtonLength float32
|
||||||
Orientation Orientation
|
Orientation ui.Orientation
|
||||||
Buttons []Control
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonBarWidth = 96
|
const buttonBarWidth = 96
|
||||||
|
|
||||||
func (b *ButtonBar) Init(ctx *Context) error {
|
// func (b *ButtonBar) Init(ctx ui.Context) error {
|
||||||
for i := range b.Buttons {
|
// for i := range b.Buttons {
|
||||||
b.AddChild(b.Buttons[i])
|
// b.AddChild(b.Buttons[i])
|
||||||
}
|
// }
|
||||||
return b.Container.Init(ctx)
|
// return b.Container.Init(ctx)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *ButtonBar) Arrange(ctx *Context, bounds Rectangle) {
|
// func (b *ButtonBar) Arrange(ctx ui.Context, bounds Rectangle) {
|
||||||
b.Container.Arrange(ctx, bounds)
|
// b.Container.Arrange(ctx, bounds)
|
||||||
length := b.ButtonLength
|
// length := b.ButtonLength
|
||||||
switch b.Orientation {
|
// switch b.Orientation {
|
||||||
case OrientationHorizontal:
|
// case OrientationHorizontal:
|
||||||
if length == 0 {
|
// if length == 0 {
|
||||||
length = bounds.H
|
// length = bounds.H
|
||||||
}
|
// }
|
||||||
offset := bounds.X
|
// offset := bounds.X
|
||||||
for i := range b.Buttons {
|
// for i := range b.Buttons {
|
||||||
b.Buttons[i].Arrange(ctx, Rect(offset, bounds.Y, length, bounds.H))
|
// b.Buttons[i].Arrange(ctx, Rect(offset, bounds.Y, length, bounds.H))
|
||||||
offset += length
|
// offset += length
|
||||||
}
|
// }
|
||||||
default:
|
// default:
|
||||||
if length == 0 {
|
// if length == 0 {
|
||||||
length = bounds.W
|
// length = bounds.W
|
||||||
}
|
// }
|
||||||
offset := bounds.Y
|
// offset := bounds.Y
|
||||||
for i := range b.Buttons {
|
// for i := range b.Buttons {
|
||||||
b.Buttons[i].Arrange(ctx, Rect(bounds.X, offset, bounds.W, length))
|
// b.Buttons[i].Arrange(ctx, Rect(bounds.X, offset, bounds.W, length))
|
||||||
offset += length
|
// offset += length
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *ButtonBar) Render(ctx *Context) {
|
// func (b *ButtonBar) Render(ctx ui.Context) {
|
||||||
SetDrawColor(ctx.Renderer, b.Background)
|
// SetDrawColor(ctx.Renderer, b.Background)
|
||||||
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
|
// ctx.Renderer.FillRect(b.Bounds.SDLPtr())
|
||||||
b.Container.Render(ctx)
|
// b.Container.Render(ctx)
|
||||||
}
|
// }
|
||||||
|
|
||||||
type Orientation int
|
// type Orientation int
|
||||||
|
|
||||||
const (
|
// const (
|
||||||
OrientationVertical Orientation = iota
|
// OrientationVertical Orientation = iota
|
||||||
OrientationHorizontal
|
// OrientationHorizontal
|
||||||
)
|
// )
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"opslag.de/schobers/zntg"
|
||||||
"time"
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuyFlowerButton struct {
|
type BuyFlowerButton struct {
|
||||||
@ -13,106 +11,116 @@ type BuyFlowerButton struct {
|
|||||||
FlowerID string
|
FlowerID string
|
||||||
Flower FlowerDescriptor
|
Flower FlowerDescriptor
|
||||||
|
|
||||||
hoverAnimation *Animation
|
upToDate bool
|
||||||
|
hoverAnimation *zntg.Animation
|
||||||
hoverOffset int32
|
hoverOffset int32
|
||||||
hoverTexture *Texture
|
hoverTexture ui.Texture
|
||||||
priceTexture *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{
|
return &BuyFlowerButton{
|
||||||
IconButton: *NewIconButtonConfigure(icon, onClick, func(b *IconButton) {
|
IconButton: *NewIconButtonConfigure(icon, click, func(b *IconButton) {
|
||||||
b.IconDisabled = iconDisabled
|
// b.IconDisabled = iconDisabled
|
||||||
b.IsDisabled = !flower.Unlocked
|
// b.Disabled = !flower.Unlocked
|
||||||
}),
|
}),
|
||||||
FlowerID: flowerID,
|
FlowerID: flowerID,
|
||||||
Flower: flower,
|
Flower: flower,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BuyFlowerButton) animate() {
|
// func (b *BuyFlowerButton) animate() {
|
||||||
b.hoverOffset++
|
// b.hoverOffset++
|
||||||
if b.hoverOffset > b.hoverTexture.Size().X+b.Bounds.W {
|
// if b.hoverOffset > b.hoverTexture.Size().X+b.Bounds.W {
|
||||||
b.hoverOffset = 0
|
// b.hoverOffset = 0
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *BuyFlowerButton) fmtTooltipText() string {
|
// func (b *BuyFlowerButton) fmtTooltipText() string {
|
||||||
if !b.Flower.Unlocked {
|
// 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, "Traits are not known yet.")
|
||||||
}
|
// }
|
||||||
return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, b.Flower.Description)
|
// return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, b.Flower.Description)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *BuyFlowerButton) updateTexts(ctx *Context) error {
|
func (b *BuyFlowerButton) updateTexts(ctx ui.Context) error {
|
||||||
text := b.fmtTooltipText()
|
if b.upToDate {
|
||||||
font := ctx.Fonts.Font("small")
|
return nil
|
||||||
color := MustHexColor("#ffffff")
|
|
||||||
texture, err := font.Render(ctx.Renderer, text, color)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if b.hoverTexture != nil {
|
|
||||||
b.hoverTexture.Destroy()
|
// text := b.fmtTooltipText()
|
||||||
}
|
// font := ctx.Fonts.Font("small")
|
||||||
b.hoverTexture = texture
|
// color := MustHexColor("#ffffff")
|
||||||
texture, err = font.Render(ctx.Renderer, FmtMoney(b.Flower.BuyPrice), color)
|
// texture, err := font.Render(ctx.Renderer, text, color)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if b.priceTexture != nil {
|
// if b.hoverTexture != nil {
|
||||||
b.priceTexture.Destroy()
|
// b.hoverTexture.Destroy()
|
||||||
}
|
// }
|
||||||
b.priceTexture = texture
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BuyFlowerButton) Init(ctx *Context) error {
|
// func (b *BuyFlowerButton) Init(ctx ui.Context) error {
|
||||||
return b.updateTexts(ctx)
|
// return b.updateTexts(ctx)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *BuyFlowerButton) Handle(ctx *Context, event sdl.Event) bool {
|
func (b *BuyFlowerButton) Handle(ctx ui.Context, event ui.Event) bool {
|
||||||
if b.IconButton.Handle(ctx, event) {
|
b.updateTexts(ctx)
|
||||||
return true
|
// if b.IconButton.Handle(ctx, event) {
|
||||||
}
|
// return true
|
||||||
if b.IsMouseOver && b.hoverAnimation == nil {
|
// }
|
||||||
b.hoverAnimation = NewAnimationPtr(10 * time.Millisecond)
|
// if b.IsMouseOver && b.hoverAnimation == nil {
|
||||||
b.hoverOffset = b.priceTexture.Size().X
|
// b.hoverAnimation = NewAnimationPtr(10 * time.Millisecond)
|
||||||
} else if !b.IsMouseOver {
|
// b.hoverOffset = b.priceTexture.Size().X
|
||||||
b.hoverAnimation = nil
|
// } else if !b.IsMouseOver {
|
||||||
}
|
// b.hoverAnimation = nil
|
||||||
|
// }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BuyFlowerButton) Render(ctx *Context) {
|
func (b *BuyFlowerButton) Render(ctx ui.Context) {
|
||||||
iconTexture := b.activeTexture(ctx)
|
if !b.upToDate {
|
||||||
|
b.updateTexts(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())
|
|
||||||
}
|
}
|
||||||
|
// iconTexture := b.activeTexture(ctx)
|
||||||
|
|
||||||
if b.hoverAnimation != nil {
|
// pos := Pt(b.Bounds.X, b.Bounds.Y)
|
||||||
b.hoverAnimation.AnimateFn(b.animate)
|
// 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.IsMouseOver {
|
// if b.hoverAnimation != nil {
|
||||||
left := b.Bounds.W - 8 - b.hoverOffset
|
// b.hoverAnimation.AnimateFn(b.animate)
|
||||||
top := pos.Y + b.Bounds.H - 20
|
// }
|
||||||
if left < 0 {
|
|
||||||
part := RectAbs(-left, 0, b.hoverTexture.Size().X, b.hoverTexture.Size().Y)
|
// if b.IsMouseOver {
|
||||||
b.hoverTexture.CopyPart(ctx.Renderer, part, Pt(pos.X, top))
|
// left := b.Bounds.W - 8 - b.hoverOffset
|
||||||
} else {
|
// top := pos.Y + b.Bounds.H - 20
|
||||||
b.hoverTexture.Copy(ctx.Renderer, Pt(pos.X+left, top))
|
// if left < 0 {
|
||||||
}
|
// part := RectAbs(-left, 0, b.hoverTexture.Size().X, b.hoverTexture.Size().Y)
|
||||||
} else {
|
// b.hoverTexture.CopyPart(ctx.Renderer, part, Pt(pos.X, top))
|
||||||
b.priceTexture.Copy(ctx.Renderer, Pt(pos.X+b.Bounds.W-8-b.priceTexture.Size().X, pos.Y+b.Bounds.H-20))
|
// } 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) {
|
func (b *BuyFlowerButton) Update(desc FlowerDescriptor) {
|
||||||
b.Flower = desc
|
b.Flower = desc
|
||||||
b.updateTexts(ctx)
|
b.upToDate = false
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,17 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"image/color"
|
||||||
"log"
|
"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"
|
rice "github.com/GeertJohan/go.rice"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
"github.com/veandco/go-sdl2/ttf"
|
|
||||||
"opslag.de/schobers/tins2020"
|
"opslag.de/schobers/tins2020"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,10 +26,89 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logSDLVersion() {
|
func openResources(box *rice.Box) ui.Resources {
|
||||||
var version sdl.Version
|
fs := ricefs.NewFs(box)
|
||||||
sdl.GetVersion(&version)
|
resources, _ := res.NewAferoFallbackResources(`res`, fs, `botanim`)
|
||||||
log.Printf("SDL version: %d.%d.%d", version.Major, version.Minor, version.Patch)
|
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 {
|
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.BoolVar(&extract, "extract-resources", false, "extracts all resources to the current working directory")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
ctx, err := tins2020.NewContext(rice.MustFindBox("res"))
|
box := rice.MustFindBox(`res`)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ctx.Destroy()
|
|
||||||
|
|
||||||
if extract {
|
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
|
return err
|
||||||
}
|
}
|
||||||
defer sdl.Quit()
|
|
||||||
|
|
||||||
logSDLVersion()
|
if settings.Window.Location == nil {
|
||||||
|
settings.Window.Location = ptPtr(sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED)
|
||||||
if err := ttf.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
defer ttf.Quit()
|
if settings.Window.Size == nil {
|
||||||
|
settings.Window.Size = ptPtr(800, 600)
|
||||||
if ctx.Settings.Window.Location == nil {
|
|
||||||
ctx.Settings.Window.Location = tins2020.PtPtr(sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED)
|
|
||||||
}
|
}
|
||||||
if ctx.Settings.Window.Size == nil {
|
if settings.Window.VSync == nil {
|
||||||
ctx.Settings.Window.Size = tins2020.PtPtr(800, 600)
|
|
||||||
}
|
|
||||||
if ctx.Settings.Window.VSync == nil {
|
|
||||||
vsync := true
|
vsync := true
|
||||||
ctx.Settings.Window.VSync = &vsync
|
settings.Window.VSync = &vsync
|
||||||
}
|
}
|
||||||
|
renderer, err := ui.NewRenderer("Botanim - TINS 2020", settings.Window.Size.X, settings.Window.Size.Y, ui.NewRendererOptions{
|
||||||
if *ctx.Settings.Window.VSync {
|
Resizable: true,
|
||||||
sdl.SetHint(sdl.HINT_RENDER_VSYNC, "1")
|
VSync: *settings.Window.VSync,
|
||||||
}
|
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer renderer.Destroy()
|
||||||
|
renderer.SetResourceProvider(func() ui.Resources { return res })
|
||||||
|
|
||||||
game := tins2020.NewGame()
|
game := tins2020.NewGame()
|
||||||
|
app := &app{
|
||||||
app := tins2020.NewContainer()
|
game: game,
|
||||||
|
dialogs: tins2020.NewDialogs(game),
|
||||||
overlays := tins2020.NewContainer()
|
settings: settings,
|
||||||
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
|
|
||||||
}
|
}
|
||||||
dialogs.ShowIntro(ctx)
|
return ui.Run(renderer, nil, app)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
30
color.go
30
color.go
@ -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))
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
58
container.go
58
container.go
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
28
content.go
28
content.go
@ -1,28 +1,36 @@
|
|||||||
package tins2020
|
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.
|
// Content shortcuts events when a dialog is opened.
|
||||||
type Content struct {
|
type Content struct {
|
||||||
Container
|
ui.Proxy
|
||||||
|
|
||||||
dialogOverlayed bool
|
content ui.ContainerBase
|
||||||
|
shortcut bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContent(dialogs *Dialogs) *Content {
|
func NewContent(dialogs *Dialogs) *Content {
|
||||||
content := &Content{}
|
content := &Content{}
|
||||||
dialogs.DialogOpened().Register(func() {
|
content.Proxy.Content = &content.content
|
||||||
content.dialogOverlayed = true
|
dialogs.DialogOpened().AddHandlerEmpty(func(ui.Context) {
|
||||||
|
content.shortcut = true
|
||||||
})
|
})
|
||||||
dialogs.DialogClosed().Register(func() {
|
dialogs.DialogClosed().AddHandlerEmpty(func(ui.Context) {
|
||||||
content.dialogOverlayed = false
|
content.shortcut = false
|
||||||
})
|
})
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Content) Handle(ctx *Context, event sdl.Event) bool {
|
func (c *Content) AddChild(child ui.Control) { c.content.AddChild(child) }
|
||||||
if c.dialogOverlayed {
|
|
||||||
|
func (c *Content) Handle(ctx ui.Context, event ui.Event) bool {
|
||||||
|
if c.shortcut {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return c.Container.Handle(ctx, event)
|
return c.Proxy.Handle(ctx, event)
|
||||||
}
|
}
|
||||||
|
76
context.go
76
context.go
@ -1,46 +1,46 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
rice "github.com/GeertJohan/go.rice"
|
// rice "github.com/GeertJohan/go.rice"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
// "github.com/veandco/go-sdl2/sdl"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type Context struct {
|
// type Context struct {
|
||||||
Renderer *sdl.Renderer
|
// Renderer *sdl.Renderer
|
||||||
Fonts Fonts
|
// Fonts Fonts
|
||||||
Resources Resources
|
// Resources Resources
|
||||||
Textures Textures
|
// Textures Textures
|
||||||
Settings Settings
|
// Settings Settings
|
||||||
MousePosition Point
|
// MousePosition geom.Point
|
||||||
ShouldQuit bool
|
// ShouldQuit bool
|
||||||
}
|
// }
|
||||||
|
|
||||||
func NewContext(res *rice.Box) (*Context, error) {
|
// func NewContext(res *rice.Box) (ui.Context, error) {
|
||||||
ctx := &Context{}
|
// ctx := &Context{}
|
||||||
err := ctx.Settings.Init()
|
// err := ctx.Settings.Init()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
err = ctx.Resources.Open(res)
|
// err = ctx.Resources.Open(res)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
return ctx, nil
|
// return ctx, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (c *Context) Destroy() {
|
// func (c ui.Context) Destroy() {
|
||||||
c.Fonts.Destroy()
|
// c.Fonts.Destroy()
|
||||||
c.Resources.Destroy()
|
// c.Resources.Destroy()
|
||||||
c.Textures.Destroy()
|
// c.Textures.Destroy()
|
||||||
c.Settings.Store()
|
// c.Settings.Store()
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (c *Context) Init(renderer *sdl.Renderer) {
|
// func (c ui.Context) Init(renderer *sdl.Renderer) {
|
||||||
c.Renderer = renderer
|
// c.Renderer = renderer
|
||||||
c.Fonts.Init(c.Resources.Copy())
|
// c.Fonts.Init(c.Resources.Copy())
|
||||||
c.Textures.Init(renderer, 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 }
|
||||||
|
82
control.go
82
control.go
@ -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) {}
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
84
dialogs.go
84
dialogs.go
@ -1,66 +1,70 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
type Dialogs struct {
|
type Dialogs struct {
|
||||||
Proxy
|
intro ui.Overlay
|
||||||
|
settings ui.Overlay
|
||||||
|
research ui.Overlay
|
||||||
|
|
||||||
intro Control
|
open string
|
||||||
settings Control
|
|
||||||
research Control
|
|
||||||
|
|
||||||
dialogClosed *Events
|
closed ui.Events
|
||||||
dialogOpened *Events
|
opened ui.Events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const introDialogName = "dialog-intro"
|
||||||
|
const settingsDialogName = "dialog-settings"
|
||||||
|
const researchDialogName = "dialog-research"
|
||||||
|
|
||||||
func NewDialogs(game *Game) *Dialogs {
|
func NewDialogs(game *Game) *Dialogs {
|
||||||
return &Dialogs{
|
return &Dialogs{
|
||||||
intro: &Intro{},
|
intro: NewIntro(),
|
||||||
settings: &LargeDialog{},
|
settings: &LargeDialog{},
|
||||||
research: NewResearch(game),
|
research: NewResearch(game),
|
||||||
|
|
||||||
dialogClosed: NewEvents(),
|
|
||||||
dialogOpened: NewEvents(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialogs) showDialog(ctx *Context, control Control) {
|
func (d *Dialogs) Init(ctx ui.Context) {
|
||||||
d.SetContent(ctx, control)
|
overlays := ctx.Overlays()
|
||||||
control.(Dialog).ShowDialog(ctx, d.Close)
|
|
||||||
d.dialogOpened.Notify(nil)
|
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) {
|
func (d *Dialogs) showDialog(ctx ui.Context, name string) {
|
||||||
d.Proxy.Arrange(ctx, bounds)
|
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) Close(ctx ui.Context) {
|
||||||
func (d *Dialogs) DialogOpened() EventHandler { return d.dialogOpened }
|
name := d.open
|
||||||
|
if name == "" {
|
||||||
func (d *Dialogs) Init(ctx *Context) error {
|
return
|
||||||
err := d.intro.Init(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
err = d.settings.Init(ctx)
|
ctx.Overlays().Hide(name)
|
||||||
if err != nil {
|
d.open = ""
|
||||||
return err
|
d.closed.Notify(ctx, name)
|
||||||
}
|
|
||||||
err = d.research.Init(ctx)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialogs) Close() {
|
func (d *Dialogs) DialogClosed() ui.EventHandler { return &d.closed }
|
||||||
d.SetContent(nil, nil)
|
func (d *Dialogs) DialogOpened() ui.EventHandler { return &d.opened }
|
||||||
d.dialogClosed.Notify(nil)
|
|
||||||
|
func (d *Dialogs) ShowIntro(ctx ui.Context) {
|
||||||
|
d.showDialog(ctx, introDialogName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialogs) ShowIntro(ctx *Context) {
|
func (d *Dialogs) ShowResearch(ctx ui.Context) {
|
||||||
d.showDialog(ctx, d.intro)
|
d.showDialog(ctx, researchDialogName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialogs) ShowResearch(ctx *Context) {
|
func (d *Dialogs) ShowSettings(ctx ui.Context) {
|
||||||
d.showDialog(ctx, d.research)
|
d.showDialog(ctx, settingsDialogName)
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dialogs) ShowSettings(ctx *Context) {
|
|
||||||
d.showDialog(ctx, d.settings)
|
|
||||||
}
|
}
|
||||||
|
24
drageable.go
24
drageable.go
@ -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
|
|
||||||
}
|
|
@ -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
109
fonts.go
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +1,46 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"time"
|
// "time"
|
||||||
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
// "github.com/veandco/go-sdl2/sdl"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type FPS struct {
|
// type FPS struct {
|
||||||
ControlBase
|
// ControlBase
|
||||||
|
|
||||||
Show *bool
|
// Show *bool
|
||||||
start time.Time
|
// start time.Time
|
||||||
stamp time.Duration
|
// stamp time.Duration
|
||||||
slot int
|
// slot int
|
||||||
ticks []int
|
// ticks []int
|
||||||
total int
|
// total int
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (f *FPS) Init(*Context) error {
|
// func (f *FPS) Init(ui.Context) error {
|
||||||
f.start = time.Now()
|
// f.start = time.Now()
|
||||||
f.stamp = 0
|
// f.stamp = 0
|
||||||
f.ticks = make([]int, 51)
|
// f.ticks = make([]int, 51)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (f *FPS) Render(ctx *Context) {
|
// func (f *FPS) Render(ctx ui.Context) {
|
||||||
if f.Show == nil || !*f.Show {
|
// if f.Show == nil || !*f.Show {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
elapsed := time.Since(f.start)
|
// elapsed := time.Since(f.start)
|
||||||
stamp := elapsed / (20 * time.Millisecond)
|
// stamp := elapsed / (20 * time.Millisecond)
|
||||||
for f.stamp < stamp {
|
// for f.stamp < stamp {
|
||||||
f.total += f.ticks[f.slot]
|
// f.total += f.ticks[f.slot]
|
||||||
f.slot = (f.slot + 1) % len(f.ticks)
|
// f.slot = (f.slot + 1) % len(f.ticks)
|
||||||
f.total -= f.ticks[f.slot]
|
// f.total -= f.ticks[f.slot]
|
||||||
f.ticks[f.slot] = 0
|
// f.ticks[f.slot] = 0
|
||||||
f.stamp++
|
// f.stamp++
|
||||||
}
|
// }
|
||||||
f.ticks[f.slot]++
|
// f.ticks[f.slot]++
|
||||||
|
|
||||||
font := ctx.Fonts.Font("debug")
|
// font := ctx.Fonts.Font("debug")
|
||||||
font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 17), sdl.Color{R: 255, G: 255, B: 255, A: 255})
|
// font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 17), sdl.Color{R: 255, G: 255, B: 255, A: 255})
|
||||||
}
|
// }
|
||||||
|
115
game.go
115
game.go
@ -4,6 +4,10 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
@ -16,10 +20,10 @@ type Game struct {
|
|||||||
Terrain *Map
|
Terrain *Map
|
||||||
|
|
||||||
tool Tool
|
tool Tool
|
||||||
centerChanged *Events
|
centerChanged ui.Events
|
||||||
toolChanged *Events
|
toolChanged ui.Events
|
||||||
speedChanged *Events
|
speedChanged ui.Events
|
||||||
simulation Animation
|
simulation zntg.Animation
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameSpeed string
|
type GameSpeed string
|
||||||
@ -35,21 +39,17 @@ const fastSimulationInterval = 20 * time.Millisecond
|
|||||||
|
|
||||||
func NewGame() *Game {
|
func NewGame() *Game {
|
||||||
game := &Game{
|
game := &Game{
|
||||||
centerChanged: NewEvents(),
|
simulation: zntg.Animation{Interval: time.Millisecond * 10},
|
||||||
speedChanged: NewEvents(),
|
|
||||||
toolChanged: NewEvents(),
|
|
||||||
simulation: NewAnimation(time.Millisecond * 10),
|
|
||||||
}
|
}
|
||||||
game.Reset()
|
|
||||||
return game
|
return game
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) selectTool(t Tool) {
|
func (g *Game) selectTool(ctx ui.Context, t Tool) {
|
||||||
g.tool = t
|
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 {
|
if speed == g.Speed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -57,35 +57,35 @@ func (g *Game) setSpeed(speed GameSpeed) {
|
|||||||
g.SpeedBeforePause = g.Speed
|
g.SpeedBeforePause = g.Speed
|
||||||
}
|
}
|
||||||
g.Speed = speed
|
g.Speed = speed
|
||||||
g.speedChanged.Notify(speed)
|
g.speedChanged.Notify(ctx, speed)
|
||||||
|
|
||||||
switch speed {
|
switch speed {
|
||||||
case GameSpeedPaused:
|
case GameSpeedPaused:
|
||||||
g.simulation.Pause()
|
g.simulation.Pause()
|
||||||
case GameSpeedNormal:
|
case GameSpeedNormal:
|
||||||
g.simulation.SetInterval(simulationInterval)
|
g.simulation.Interval = simulationInterval
|
||||||
g.simulation.Run()
|
g.simulation.Start()
|
||||||
case GameSpeedFast:
|
case GameSpeedFast:
|
||||||
g.simulation.SetInterval(fastSimulationInterval)
|
g.simulation.Interval = fastSimulationInterval
|
||||||
g.simulation.Run()
|
g.simulation.Start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) tick() {
|
func (g *Game) tick() {
|
||||||
randomNeighbor := func(pos Point) Point {
|
randomNeighbor := func(pos geom.Point) geom.Point {
|
||||||
switch rand.Intn(4) {
|
switch rand.Intn(4) {
|
||||||
case 0:
|
case 0:
|
||||||
return Pt(pos.X-1, pos.Y)
|
return geom.Pt(pos.X-1, pos.Y)
|
||||||
case 1:
|
case 1:
|
||||||
return Pt(pos.X, pos.Y-1)
|
return geom.Pt(pos.X, pos.Y-1)
|
||||||
case 2:
|
case 2:
|
||||||
return Pt(pos.X+1, pos.Y)
|
return geom.Pt(pos.X+1, pos.Y)
|
||||||
case 3:
|
case 3:
|
||||||
return Pt(pos.X, pos.Y+1)
|
return geom.Pt(pos.X, pos.Y+1)
|
||||||
}
|
}
|
||||||
return pos
|
return pos
|
||||||
}
|
}
|
||||||
flowers := map[Point]Flower{}
|
flowers := map[geom.Point]Flower{}
|
||||||
for pos, flower := range g.Terrain.Flowers {
|
for pos, flower := range g.Terrain.Flowers {
|
||||||
if rand.Float32() < flower.Traits.Spread {
|
if rand.Float32() < flower.Traits.Spread {
|
||||||
dst := randomNeighbor(pos)
|
dst := randomNeighbor(pos)
|
||||||
@ -102,13 +102,13 @@ func (g *Game) tick() {
|
|||||||
g.Terrain.Flowers = flowers
|
g.Terrain.Flowers = flowers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) CancelTool() {
|
func (g *Game) CancelTool(ctx ui.Context) {
|
||||||
g.selectTool(nil)
|
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)
|
id := g.Terrain.DigFlower(tile)
|
||||||
desc, ok := g.Herbarium.Find(id)
|
desc, ok := g.Herbarium.Find(id)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -125,14 +125,14 @@ func (g *Game) Dig(tile Point) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) New() {
|
func (g *Game) New(ctx ui.Context) {
|
||||||
g.Pause()
|
g.Pause(ctx)
|
||||||
g.Reset()
|
g.Reset(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Load() {
|
func (g *Game) Load(ctx ui.Context) {
|
||||||
g.CancelTool()
|
g.CancelTool(ctx)
|
||||||
g.Pause()
|
g.Pause(ctx)
|
||||||
|
|
||||||
var state GameState
|
var state GameState
|
||||||
err := state.Deserialize(SaveGameName())
|
err := state.Deserialize(SaveGameName())
|
||||||
@ -155,22 +155,19 @@ func (g *Game) Load() {
|
|||||||
Variant: NewRandomNoiseMap(state.Terrain.Variant),
|
Variant: NewRandomNoiseMap(state.Terrain.Variant),
|
||||||
PlaceX: NewRandomNoiseMap(state.Terrain.PlaceX),
|
PlaceX: NewRandomNoiseMap(state.Terrain.PlaceX),
|
||||||
PlaceY: NewRandomNoiseMap(state.Terrain.PlaceY),
|
PlaceY: NewRandomNoiseMap(state.Terrain.PlaceY),
|
||||||
Flowers: map[Point]Flower{},
|
Flowers: map[geom.Point]Flower{},
|
||||||
}
|
}
|
||||||
for _, flower := range state.Terrain.Flowers {
|
for _, flower := range state.Terrain.Flowers {
|
||||||
desc, _ := g.Herbarium.Find(flower.ID)
|
desc, _ := g.Herbarium.Find(flower.ID)
|
||||||
g.Terrain.AddFlower(flower.Location, flower.ID, desc.Traits)
|
g.Terrain.AddFlower(flower.Location, flower.ID, desc.Traits)
|
||||||
}
|
}
|
||||||
g.Terrain.Center = state.View.Center
|
g.Terrain.Center = state.View.Center
|
||||||
g.centerChanged.Notify(g.Terrain.Center)
|
g.centerChanged.Notify(ctx, g.Terrain.Center)
|
||||||
|
|
||||||
g.CancelTool()
|
|
||||||
g.setSpeed(state.Speed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if g.Terrain.HasFlower(tile) {
|
||||||
// TODO: notify user it tried to plant on tile with flower?
|
// TODO: notify user it tried to plant on tile with flower?
|
||||||
return
|
return
|
||||||
@ -189,7 +186,7 @@ func (g *Game) PlantFlower(id string, tile Point) {
|
|||||||
g.Terrain.AddFlower(tile, id, flower.Traits)
|
g.Terrain.AddFlower(tile, id, flower.Traits)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Reset() {
|
func (g *Game) Reset(ctx ui.Context) {
|
||||||
g.Balance = 100
|
g.Balance = 100
|
||||||
g.Herbarium = NewHerbarium()
|
g.Herbarium = NewHerbarium()
|
||||||
g.Terrain = &Map{
|
g.Terrain = &Map{
|
||||||
@ -198,17 +195,17 @@ func (g *Game) Reset() {
|
|||||||
Variant: NewRandomNoiseMap(rand.Int63()),
|
Variant: NewRandomNoiseMap(rand.Int63()),
|
||||||
PlaceX: NewRandomNoiseMap(rand.Int63()),
|
PlaceX: NewRandomNoiseMap(rand.Int63()),
|
||||||
PlaceY: NewRandomNoiseMap(rand.Int63()),
|
PlaceY: NewRandomNoiseMap(rand.Int63()),
|
||||||
Flowers: map[Point]Flower{},
|
Flowers: map[geom.Point]Flower{},
|
||||||
}
|
}
|
||||||
g.CancelTool()
|
g.CancelTool(ctx)
|
||||||
g.setSpeed(GameSpeedNormal)
|
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() {
|
func (g *Game) Save() {
|
||||||
state := g.State()
|
state := g.State()
|
||||||
@ -218,15 +215,15 @@ func (g *Game) Save() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) SelectPlantFlowerTool(id string) {
|
func (g *Game) SelectPlantFlowerTool(ctx ui.Context, id string) {
|
||||||
g.selectTool(&PlantFlowerTool{FlowerID: id})
|
g.selectTool(ctx, &PlantFlowerTool{FlowerID: id})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) SelectShovel() {
|
func (g *Game) SelectShovel(ctx ui.Context) {
|
||||||
g.selectTool(&ShovelTool{})
|
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 {
|
func (g *Game) State() GameState {
|
||||||
var state GameState
|
var state GameState
|
||||||
@ -255,22 +252,22 @@ func (g *Game) State() GameState {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) TogglePause() {
|
func (g *Game) TogglePause(ctx ui.Context) {
|
||||||
if g.Speed == GameSpeedPaused {
|
if g.Speed == GameSpeedPaused {
|
||||||
g.Resume()
|
g.Resume(ctx)
|
||||||
} else {
|
} else {
|
||||||
g.Pause()
|
g.Pause(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Tool() Tool { return g.tool }
|
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()
|
price := g.Herbarium.UnlockNext()
|
||||||
g.Balance -= price
|
g.Balance -= price
|
||||||
g.selectTool(nil)
|
g.selectTool(ctx, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Update() {
|
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 {
|
if g.tool == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
262
gamecontrols.go
262
gamecontrols.go
@ -1,11 +1,13 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GameControls struct {
|
type GameControls struct {
|
||||||
Container
|
ui.ContainerBase
|
||||||
|
|
||||||
game *Game
|
game *Game
|
||||||
dialogs *Dialogs
|
dialogs *Dialogs
|
||||||
@ -24,7 +26,87 @@ type GameControls struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewGameControls(game *Game, dialogs *Dialogs) *GameControls {
|
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 {
|
func (c *GameControls) createBuyFlowerButton(id string) *BuyFlowerButton {
|
||||||
@ -34,176 +116,92 @@ func (c *GameControls) createBuyFlowerButton(id string) *BuyFlowerButton {
|
|||||||
flower.IconTemplate.Disabled(),
|
flower.IconTemplate.Disabled(),
|
||||||
id,
|
id,
|
||||||
flower,
|
flower,
|
||||||
EmptyEvent(func() {
|
func(ctx ui.Context) {
|
||||||
c.game.SelectPlantFlowerTool(id)
|
c.game.SelectPlantFlowerTool(ctx, id)
|
||||||
}),
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GameControls) speedChanged(state interface{}) {
|
func (c *GameControls) speedChanged(_ ui.Context, state interface{}) {
|
||||||
speed := state.(GameSpeed)
|
speed := state.(GameSpeed)
|
||||||
disable := func(b *IconButton, expected GameSpeed) {
|
disable := func(b *IconButton, expected GameSpeed) {
|
||||||
b.IsDisabled = speed == expected
|
b.Disabled = speed == expected
|
||||||
}
|
}
|
||||||
disable(c.pause, GameSpeedPaused)
|
disable(c.pause, GameSpeedPaused)
|
||||||
disable(c.run, GameSpeedNormal)
|
disable(c.run, GameSpeedNormal)
|
||||||
disable(c.runFast, GameSpeedFast)
|
disable(c.runFast, GameSpeedFast)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GameControls) toolChanged(state interface{}) {
|
func (c *GameControls) toolChanged(_ ui.Context, state interface{}) {
|
||||||
tool, _ := state.(Tool)
|
tool, _ := state.(Tool)
|
||||||
var flowerID string
|
var flowerID string
|
||||||
if tool, ok := tool.(*PlantFlowerTool); ok {
|
if tool, ok := tool.(*PlantFlowerTool); ok {
|
||||||
flowerID = tool.FlowerID
|
flowerID = tool.FlowerID
|
||||||
}
|
}
|
||||||
for _, control := range c.flowers.Buttons {
|
for _, control := range c.flowers.Children {
|
||||||
button := control.(*BuyFlowerButton)
|
button := control.(*BuyFlowerButton)
|
||||||
button.IsActive = button.FlowerID == flowerID
|
button.IsActive = button.FlowerID == flowerID
|
||||||
button.IsDisabled = !c.game.Herbarium.IsUnlocked(button.FlowerID)
|
button.Disabled = !c.game.Herbarium.IsUnlocked(button.FlowerID)
|
||||||
}
|
}
|
||||||
_, shovel := tool.(*ShovelTool)
|
_, shovel := tool.(*ShovelTool)
|
||||||
c.shovel.IsActive = shovel
|
c.shovel.IsActive = shovel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GameControls) updateFlowerControls(ctx *Context) {
|
func (c *GameControls) updateFlowerControls() {
|
||||||
for _, b := range c.flowers.Buttons {
|
for _, b := range c.flowers.Children {
|
||||||
button := b.(*BuyFlowerButton)
|
button := b.(*BuyFlowerButton)
|
||||||
flower, ok := c.game.Herbarium.Find(button.FlowerID)
|
flower, ok := c.game.Herbarium.Find(button.FlowerID)
|
||||||
if ok {
|
if ok {
|
||||||
button.Update(ctx, flower)
|
button.Update(flower)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) {
|
func (c *GameControls) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.PointF32, parent ui.Control) {
|
||||||
c.Bounds = bounds
|
c.ContainerBase.Arrange(ctx, bounds, offset, parent)
|
||||||
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.menu.Arrange(ctx, geom.RectRelF32(bounds.Min.X, bounds.Min.Y, buttonBarWidth, bounds.Dy()), offset, c)
|
||||||
c.flowers.Arrange(ctx, Rect(bounds.Right()-buttonBarWidth, bounds.Y, buttonBarWidth, bounds.H))
|
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.otherTools.Arrange(ctx, Rect(bounds.Right()-buttonBarWidth, bounds.Bottom()-2*buttonBarWidth, buttonBarWidth, 2*buttonBarWidth))
|
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 {
|
func (c *GameControls) Handle(ctx ui.Context, event ui.Event) bool {
|
||||||
c.game.SpeedChanged().RegisterItf(c.speedChanged)
|
if c.ContainerBase.Handle(ctx, event) {
|
||||||
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) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *sdl.KeyboardEvent:
|
case *ui.KeyDownEvent:
|
||||||
if e.Type == sdl.KEYDOWN {
|
switch e.Key {
|
||||||
switch e.Keysym.Sym {
|
case ui.KeySpace:
|
||||||
case sdl.K_SPACE:
|
c.game.TogglePause(ctx)
|
||||||
c.game.TogglePause()
|
case ui.Key1:
|
||||||
case sdl.K_1:
|
c.game.Run(ctx)
|
||||||
c.game.Run()
|
case ui.Key2:
|
||||||
case sdl.K_2:
|
c.game.RunFast(ctx)
|
||||||
c.game.RunFast()
|
case ui.KeyH:
|
||||||
case sdl.K_h:
|
c.game.SelectShovel(ctx)
|
||||||
c.game.SelectShovel()
|
case ui.KeyR:
|
||||||
case sdl.K_r:
|
c.dialogs.ShowResearch(ctx)
|
||||||
c.dialogs.ShowResearch(ctx)
|
case ui.KeyEscape:
|
||||||
case sdl.K_ESCAPE:
|
if c.game.Tool() == nil {
|
||||||
if c.game.Tool() == nil {
|
c.dialogs.ShowIntro(ctx)
|
||||||
c.dialogs.ShowIntro(ctx)
|
} else {
|
||||||
} else {
|
c.game.CancelTool(ctx)
|
||||||
c.game.CancelTool()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case sdl.K_F3:
|
|
||||||
c.game.Debug = !c.game.Debug
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
case ui.KeyF3:
|
||||||
|
c.game.Debug = !c.game.Debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GameControls) Render(ctx *Context) {
|
func (c *GameControls) Render(ctx ui.Context) {
|
||||||
topBar := MustHexColor("#0000007f")
|
topBar := zntg.MustHexColor("#0000007f")
|
||||||
SetDrawColor(ctx.Renderer, topBar)
|
ctx.Renderer().FillRectangle(geom.RectF32(c.menu.Bounds().Max.X, 0, c.flowers.Bounds().Min.X, 64), topBar)
|
||||||
ctx.Renderer.FillRect(RectAbs(c.menu.Bounds.Right(), 0, c.flowers.Bounds.X, 64).SDLPtr())
|
ctx.Fonts().TextAlign("balance", geom.PtF32(c.top.Bounds().Min.X-8, 58), zntg.MustHexColor("#4AC69A"), FmtMoney(c.game.Balance), ui.AlignRight)
|
||||||
ctx.Fonts.Font("balance").RenderCopyAlign(ctx.Renderer, FmtMoney(c.game.Balance), Pt(c.top.Bounds.X-8, 58), MustHexColor("#4AC69A"), TextAlignmentRight)
|
|
||||||
|
|
||||||
c.Container.Render(ctx)
|
c.ContainerBase.Render(ctx)
|
||||||
}
|
}
|
||||||
|
13
gamestate.go
13
gamestate.go
@ -1,8 +1,13 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
)
|
||||||
|
|
||||||
type FlowerState struct {
|
type FlowerState struct {
|
||||||
ID string
|
ID string
|
||||||
Location Point
|
Location geom.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameState struct {
|
type GameState struct {
|
||||||
@ -32,7 +37,7 @@ type TerrainState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ViewState struct {
|
type ViewState struct {
|
||||||
Center Point
|
Center geom.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GameState) Serialize(name string) error {
|
func (s *GameState) Serialize(name string) error {
|
||||||
@ -40,7 +45,7 @@ func (s *GameState) Serialize(name string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return EncodeJSON(path, &s)
|
return zntg.EncodeJSON(path, &s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GameState) Deserialize(name string) error {
|
func (s *GameState) Deserialize(name string) error {
|
||||||
@ -48,7 +53,7 @@ func (s *GameState) Deserialize(name string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return DecodeJSON(path, &s)
|
return zntg.DecodeJSON(path, &s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveGameName() string { return "savegame.json" }
|
func SaveGameName() string { return "savegame.json" }
|
||||||
|
186
iconbutton.go
186
iconbutton.go
@ -1,125 +1,129 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HoverEffect int
|
// import (
|
||||||
|
// "github.com/veandco/go-sdl2/sdl"
|
||||||
|
// )
|
||||||
|
|
||||||
const (
|
// type HoverEffect int
|
||||||
HoverEffectLigthen HoverEffect = iota
|
|
||||||
HoverEffectColor
|
// const (
|
||||||
)
|
// HoverEffectLigthen HoverEffect = iota
|
||||||
|
// HoverEffectColor
|
||||||
|
// )
|
||||||
|
|
||||||
type IconButton struct {
|
type IconButton struct {
|
||||||
ControlBase
|
ui.Button
|
||||||
|
|
||||||
Icon string
|
|
||||||
IconDisabled string
|
IconDisabled string
|
||||||
IconHeight int32
|
IconHeight int32
|
||||||
IconScale Scale
|
// IconScale Scale
|
||||||
IconWidth int32
|
// IconWidth int32
|
||||||
|
|
||||||
IconActive HoverEffect
|
// IconActive HoverEffect
|
||||||
IconHover HoverEffect
|
// IconHover HoverEffect
|
||||||
|
|
||||||
Tooltip Tooltip
|
// Tooltip Tooltip
|
||||||
IsActive bool
|
IsActive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIconButton(icon string, onClick EventContextFn) *IconButton {
|
func NewIconButton(icon string, click ui.EventEmptyFn) *IconButton {
|
||||||
return &IconButton{
|
b := &IconButton{
|
||||||
ControlBase: ControlBase{
|
Button: ui.Button{
|
||||||
OnLeftMouseButtonClick: onClick,
|
Icon: icon,
|
||||||
},
|
},
|
||||||
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 {
|
func NewIconButtonConfigure(icon string, click ui.EventEmptyFn, configure func(*IconButton)) *IconButton {
|
||||||
button := NewIconButton(icon, onClick)
|
button := NewIconButton(icon, click)
|
||||||
configure(button)
|
configure(button)
|
||||||
return button
|
return button
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *IconButton) activeTexture(ctx *Context) *Texture {
|
// func (b *IconButton) activeTexture(ctx ui.Context) *Texture {
|
||||||
if b.IsDisabled {
|
// if b.Disabled {
|
||||||
texture := ctx.Textures.Texture(b.IconDisabled)
|
// texture := ctx.Textures.Texture(b.IconDisabled)
|
||||||
if texture != nil {
|
// if texture != nil {
|
||||||
return texture
|
// return texture
|
||||||
}
|
// }
|
||||||
|
|
||||||
texture = ctx.Textures.Texture(b.Icon)
|
// texture = ctx.Textures.Texture(b.Icon)
|
||||||
if len(b.IconDisabled) == 0 {
|
// if len(b.IconDisabled) == 0 {
|
||||||
return texture
|
// return texture
|
||||||
}
|
// }
|
||||||
color, err := HexColor(b.IconDisabled)
|
// color, err := HexColor(b.IconDisabled)
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
texture.SetColor(color)
|
// texture.SetColor(color)
|
||||||
}
|
// }
|
||||||
return texture
|
// return texture
|
||||||
}
|
// }
|
||||||
return ctx.Textures.Texture(b.Icon)
|
// return ctx.Textures.Texture(b.Icon)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *IconButton) Arrange(ctx *Context, bounds Rectangle) {
|
// func (b *IconButton) Arrange(ctx ui.Context, bounds Rectangle) {
|
||||||
b.ControlBase.Arrange(ctx, bounds)
|
// b.ControlBase.Arrange(ctx, bounds)
|
||||||
b.Tooltip.Arrange(ctx, bounds)
|
// b.Tooltip.Arrange(ctx, bounds)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *IconButton) Handle(ctx *Context, event sdl.Event) bool {
|
// func (b *IconButton) Handle(ctx ui.Context, event sdl.Event) bool {
|
||||||
if b.ControlBase.Handle(ctx, event) {
|
// if b.ControlBase.Handle(ctx, event) {
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
if b.Tooltip.Handle(ctx, event) {
|
// if b.Tooltip.Handle(ctx, event) {
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *IconButton) Init(ctx *Context) error {
|
// func (b *IconButton) Init(ctx ui.Context) error {
|
||||||
if err := b.ControlBase.Init(ctx); err != nil {
|
// if err := b.ControlBase.Init(ctx); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if err := b.Tooltip.Init(ctx); err != nil {
|
// if err := b.Tooltip.Init(ctx); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (b *IconButton) Render(ctx *Context) {
|
// func (b *IconButton) Render(ctx ui.Context) {
|
||||||
iconTexture := b.activeTexture(ctx)
|
// iconTexture := b.activeTexture(ctx)
|
||||||
|
|
||||||
hover := b.IsMouseOver && !b.IsDisabled
|
// hover := b.IsMouseOver && !b.Disabled
|
||||||
if (hover && b.IconHover == HoverEffectColor) || (b.IsActive && b.IconActive == HoverEffectColor) {
|
// if (hover && b.IconHover == HoverEffectColor) || (b.IsActive && b.IconActive == HoverEffectColor) {
|
||||||
iconTexture.SetColor(MustHexColor("#15569F"))
|
// iconTexture.SetColor(MustHexColor("#15569F"))
|
||||||
}
|
// }
|
||||||
|
|
||||||
if b.IconScale == ScaleCenter {
|
// if b.IconScale == ScaleCenter {
|
||||||
size := iconTexture.Size()
|
// size := iconTexture.Size()
|
||||||
if b.IconWidth != 0 {
|
// if b.IconWidth != 0 {
|
||||||
size = Pt(b.IconWidth, b.IconWidth*size.Y/size.X)
|
// size = Pt(b.IconWidth, b.IconWidth*size.Y/size.X)
|
||||||
} else if b.IconHeight != 0 {
|
// } else if b.IconHeight != 0 {
|
||||||
size = Pt(b.IconHeight*size.X/size.Y, b.IconHeight)
|
// 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))
|
// 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 {
|
// } else {
|
||||||
iconTexture.CopyResize(ctx.Renderer, b.Bounds)
|
// iconTexture.CopyResize(ctx.Renderer, b.Bounds)
|
||||||
}
|
// }
|
||||||
if (hover && b.IconHover == HoverEffectLigthen) || (b.IsActive && b.IconActive == HoverEffectLigthen) {
|
// if (hover && b.IconHover == HoverEffectLigthen) || (b.IsActive && b.IconActive == HoverEffectLigthen) {
|
||||||
SetDrawColor(ctx.Renderer, TransparentWhite)
|
// SetDrawColor(ctx.Renderer, TransparentWhite)
|
||||||
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
|
// ctx.Renderer.FillRect(b.Bounds.SDLPtr())
|
||||||
}
|
// }
|
||||||
iconTexture.SetColor(White)
|
// iconTexture.SetColor(White)
|
||||||
|
|
||||||
if len(b.Tooltip.Text) > 0 && b.IsMouseOver {
|
// if len(b.Tooltip.Text) > 0 && b.IsMouseOver {
|
||||||
b.Tooltip.Render(ctx)
|
// b.Tooltip.Render(ctx)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
type Scale int
|
// type Scale int
|
||||||
|
|
||||||
const (
|
// const (
|
||||||
ScaleCenter Scale = iota
|
// ScaleCenter Scale = iota
|
||||||
ScaleStretch
|
// ScaleStretch
|
||||||
)
|
// )
|
||||||
|
62
img/color.go
62
img/color.go
@ -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
|
|
||||||
}
|
|
29
img/io.go
29
img/io.go
@ -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
|
|
||||||
}
|
|
15
intro.go
15
intro.go
@ -1,13 +1,14 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
type Intro struct {
|
import "opslag.de/schobers/zntg/ui"
|
||||||
LargeDialog
|
|
||||||
|
|
||||||
welcome Paragraph
|
type Intro struct {
|
||||||
|
ui.Paragraph
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Intro) Init(ctx *Context) error {
|
func NewIntro() ui.Overlay {
|
||||||
i.welcome.Text =
|
i := &Intro{}
|
||||||
|
i.Text =
|
||||||
"Welcome to Botanim!\n\n" +
|
"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" +
|
"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" +
|
"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" +
|
" - W, A, S, D keys or CTRL + left mouse button or middle mouse button: pans landscape\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"Have fun playing!"
|
"Have fun playing!"
|
||||||
i.SetContent(&i.welcome)
|
return NewLargeDialog("Botanim", i)
|
||||||
|
|
||||||
return i.LargeDialog.Init(ctx)
|
|
||||||
}
|
}
|
||||||
|
47
io.go
47
io.go
@ -1,50 +1,11 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"opslag.de/schobers/zntg"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func DecodeJSON(path string, v interface{}) error {
|
const appName = "tins2020_botanim"
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func EncodeJSON(path string, v interface{}) error {
|
func UserDir() (string, error) { return zntg.UserDir(appName) }
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
return json.NewEncoder(f).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UserDir() (string, error) {
|
func UserFile(name string) (string, error) { return zntg.UserFile(appName, name) }
|
||||||
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
|
|
||||||
}
|
|
||||||
|
85
label.go
85
label.go
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
203
largedialog.go
203
largedialog.go
@ -1,108 +1,141 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import "github.com/veandco/go-sdl2/sdl"
|
import (
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
type DialogBase struct {
|
// type DialogBase struct {
|
||||||
Container
|
// }
|
||||||
|
|
||||||
content Proxy
|
// type Dialog interface {
|
||||||
onShow *Events
|
// CloseDialog()
|
||||||
close EventFn
|
// OnShow() zntg.EventHandler
|
||||||
}
|
// ShowDialog(ui.Context, zntg.EventFn)
|
||||||
|
// }
|
||||||
|
|
||||||
type Dialog interface {
|
// func (d *DialogBase) CloseDialog() {
|
||||||
CloseDialog()
|
// close := d.close
|
||||||
OnShow() EventHandler
|
// if close != nil {
|
||||||
ShowDialog(*Context, EventFn)
|
// close()
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
func (d *DialogBase) CloseDialog() {
|
// func (d *DialogBase) Init(ctx ui.Context) error {
|
||||||
close := d.close
|
// d.AddChild(&d.content)
|
||||||
if close != nil {
|
// return d.Container.Init(ctx)
|
||||||
close()
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DialogBase) Init(ctx *Context) error {
|
// func (d *DialogBase) OnShow() zntg.EventHandler {
|
||||||
d.AddChild(&d.content)
|
// if d.onShow == nil {
|
||||||
return d.Container.Init(ctx)
|
// d.onShow = NewEvents()
|
||||||
}
|
// }
|
||||||
|
// return d.onShow
|
||||||
|
// }
|
||||||
|
|
||||||
func (d *DialogBase) OnShow() EventHandler {
|
// func (d *DialogBase) SetContent(control ui.Control) {
|
||||||
if d.onShow == nil {
|
// d.content.Proxied = control
|
||||||
d.onShow = NewEvents()
|
// }
|
||||||
}
|
|
||||||
return d.onShow
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DialogBase) SetContent(control Control) {
|
// func (d *DialogBase) ShowDialog(ctx ui.Context, close zntg.EventFn) {
|
||||||
d.content.Proxied = control
|
// d.close = close
|
||||||
}
|
// if d.onShow != nil {
|
||||||
|
// d.onShow.Notify(ctx)
|
||||||
func (d *DialogBase) ShowDialog(ctx *Context, close EventFn) {
|
// }
|
||||||
d.close = close
|
// }
|
||||||
if d.onShow != nil {
|
|
||||||
d.onShow.Notify(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type LargeDialog struct {
|
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
|
close IconButton
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LargeDialog) Arrange(ctx *Context, bounds Rectangle) {
|
func NewLargeDialogTitleBar(title string, closeRequested ui.EventFn) *LargeDialogTitleBar {
|
||||||
const titleHeight = 64
|
titleBar := &LargeDialogTitleBar{}
|
||||||
d.ControlBase.Arrange(ctx, bounds)
|
titleBar.Children = []ui.Control{&titleBar.title, &titleBar.close}
|
||||||
d.title.Arrange(ctx, Rect(bounds.X, bounds.Y, bounds.W, titleHeight))
|
titleBar.close.ButtonClicked().AddHandler(func(ctx ui.Context, args ui.ControlClickedArgs) {
|
||||||
d.close.Arrange(ctx, Rect(bounds.W-64, 0, 64, 64))
|
closeRequested(ctx, args)
|
||||||
d.content.Arrange(ctx, Rect(bounds.X+titleHeight, 96, bounds.W-2*titleHeight, bounds.H-titleHeight))
|
})
|
||||||
|
return titleBar
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *LargeDialog) Init(ctx *Context) error {
|
// func (d *LargeDialog) Arrange(ctx ui.Context, bounds Rectangle) {
|
||||||
d.title.Text = "Botanim"
|
// const titleHeight = 64
|
||||||
d.title.FontName = "title"
|
// d.ControlBase.Arrange(ctx, bounds)
|
||||||
d.title.Alignment = TextAlignmentCenter
|
// 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{
|
// func (d *LargeDialog) Init(ctx ui.Context) error {
|
||||||
Icon: "control-cancel",
|
// d.title.Text = "Botanim"
|
||||||
IconHover: HoverEffectColor,
|
// d.title.FontName = "title"
|
||||||
IconWidth: 32,
|
// d.title.Alignment = TextAlignmentCenter
|
||||||
}
|
|
||||||
d.close.OnLeftMouseButtonClick = EmptyEvent(d.CloseDialog)
|
|
||||||
d.AddChild(&d.title)
|
|
||||||
d.AddChild(&d.close)
|
|
||||||
return d.DialogBase.Init(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *LargeDialog) Handle(ctx *Context, event sdl.Event) bool {
|
// d.close = IconButton{
|
||||||
if d.DialogBase.Handle(ctx, event) {
|
// Icon: "control-cancel",
|
||||||
return true
|
// 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) {
|
// func (d *LargeDialog) Handle(ctx ui.Context, event sdl.Event) bool {
|
||||||
case *sdl.KeyboardEvent:
|
// if d.DialogBase.Handle(ctx, event) {
|
||||||
if e.Type == sdl.KEYDOWN {
|
// return true
|
||||||
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) Render(ctx *Context) {
|
// switch e := event.(type) {
|
||||||
SetDrawColor(ctx.Renderer, MustHexColor("#356DAD"))
|
// case *sdl.KeyboardEvent:
|
||||||
ctx.Renderer.FillRect(d.Bounds.SDLPtr())
|
// 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
24
map.go
@ -1,5 +1,7 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
type Map struct {
|
type Map struct {
|
||||||
Temp NoiseMap
|
Temp NoiseMap
|
||||||
Humid NoiseMap
|
Humid NoiseMap
|
||||||
@ -7,32 +9,32 @@ type Map struct {
|
|||||||
PlaceX NoiseMap // displacement map of props
|
PlaceX NoiseMap // displacement map of props
|
||||||
PlaceY NoiseMap
|
PlaceY NoiseMap
|
||||||
|
|
||||||
Center Point
|
Center geom.Point
|
||||||
Flowers map[Point]Flower
|
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)
|
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
|
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++
|
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++
|
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++
|
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++
|
count++
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Map) DigFlower(pos Point) string {
|
func (m *Map) DigFlower(pos geom.Point) string {
|
||||||
flower, ok := m.Flowers[pos]
|
flower, ok := m.Flowers[pos]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
@ -41,12 +43,12 @@ func (m *Map) DigFlower(pos Point) string {
|
|||||||
return flower.ID
|
return flower.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Map) HasFlower(pos Point) bool {
|
func (m *Map) HasFlower(pos geom.Point) bool {
|
||||||
_, ok := m.Flowers[pos]
|
_, ok := m.Flowers[pos]
|
||||||
return ok
|
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{
|
flower := Flower{
|
||||||
ID: id,
|
ID: id,
|
||||||
Traits: traits,
|
Traits: traits,
|
||||||
|
@ -14,7 +14,7 @@ func clipNormalized(x float64) float64 {
|
|||||||
|
|
||||||
type NoiseMap interface {
|
type NoiseMap interface {
|
||||||
Seed() int64
|
Seed() int64
|
||||||
Value(x, y int32) float64
|
Value(x, y int) float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNoiseMap(seed int64) NoiseMap {
|
func NewNoiseMap(seed int64) NoiseMap {
|
||||||
@ -33,7 +33,7 @@ type noiseMap struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Value generates the noise value for an x/y pair.
|
// 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
|
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
|
||||||
return clipNormalized(value)
|
return clipNormalized(value)
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ type randomNoiseMap struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Value generates the noise value for an x/y pair.
|
// 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
|
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
|
||||||
return clipNormalized(value)
|
return clipNormalized(value)
|
||||||
}
|
}
|
||||||
|
42
point.go
42
point.go
@ -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
|
|
||||||
}
|
|
103
projection.go
103
projection.go
@ -1,99 +1,96 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func mapToTile(q PointF) Point {
|
func mapToTile(q geom.PointF32) geom.Point {
|
||||||
return Pt(int32(Round32(q.X)), int32(Round32(q.Y)))
|
return geom.Pt(int(geom.Round32(q.X)), int(geom.Round32(q.Y)))
|
||||||
}
|
}
|
||||||
|
|
||||||
type projection struct {
|
type projection struct {
|
||||||
center PointF
|
center geom.PointF32
|
||||||
zoom float32
|
zoom float32
|
||||||
zoomInv float32
|
zoomInv float32
|
||||||
|
|
||||||
windowInteractRect Rectangle
|
windowInteractRect geom.Rectangle
|
||||||
windowVisibleRect Rectangle
|
windowVisibleRect geom.Rectangle
|
||||||
tileScreenDelta PointF
|
tileScreenDelta geom.PointF32
|
||||||
tileScreenDeltaInv PointF
|
tileScreenDeltaInv geom.PointF32
|
||||||
tileScreenOffset Point
|
tileScreenOffset geom.Point
|
||||||
tileScreenSize Point
|
tileScreenSize geom.Point
|
||||||
tileFitScreenSize Point
|
tileFitScreenSize geom.Point
|
||||||
windowCenter Point
|
windowCenter geom.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProjection() projection {
|
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))
|
return p.mapToScreenF(float32(x), float32(y))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projection) mapToScreenF(x, y float32) Point {
|
func (p *projection) mapToScreenF(x, y float32) geom.Point {
|
||||||
translated := PtF(x-p.center.X, y-p.center.Y)
|
translated := geom.PtF32(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))
|
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)
|
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
|
||||||
return p.center.Add(pos)
|
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)
|
pos := p.screenToMap(x, y)
|
||||||
return mapToTile(pos)
|
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)
|
normX := p.zoomInv * float32(x)
|
||||||
normY := p.zoomInv * float32(y)
|
normY := p.zoomInv * float32(y)
|
||||||
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
|
return 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 {
|
func (p *projection) screenToTileFitRect(pos geom.Point) geom.Rectangle {
|
||||||
return Rect(pos.X-p.tileFitScreenSize.X, pos.Y-p.tileFitScreenSize.Y, 2*p.tileFitScreenSize.X, 2*p.tileFitScreenSize.Y)
|
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 {
|
func (p *projection) screenToTileRect(pos geom.Point) geom.Rectangle {
|
||||||
return Rect(pos.X-p.tileScreenOffset.X, pos.Y-p.tileScreenOffset.Y, p.tileScreenSize.X, p.tileScreenSize.Y)
|
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.zoomInv = 1 / p.zoom
|
||||||
|
|
||||||
p.tileScreenOffset = Pt(int32(p.zoom*64), int32(p.zoom*112))
|
p.tileScreenOffset = geom.Pt(int(p.zoom*64), int(p.zoom*112))
|
||||||
p.tileScreenSize = Pt(int32(p.zoom*128), int32(p.zoom*160))
|
p.tileScreenSize = geom.Pt(int(p.zoom*128), int(p.zoom*160))
|
||||||
p.tileFitScreenSize = Pt(int32(p.zoom*64), int32(p.zoom*32))
|
p.tileFitScreenSize = geom.Pt(int(p.zoom*64), int(p.zoom*32))
|
||||||
|
|
||||||
windowW, windowH, err := renderer.GetOutputSize()
|
windowF32 := renderer.Size()
|
||||||
if err != nil {
|
window := geom.Pt(int(windowF32.X), int(windowF32.Y))
|
||||||
log.Fatal(err)
|
p.windowCenter = geom.Pt(window.X/2, window.Y/2)
|
||||||
}
|
p.windowInteractRect = geom.Rect(buttonBarWidth, 64, window.X-buttonBarWidth, window.Y)
|
||||||
p.windowCenter = Pt(windowW/2, windowH/2)
|
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.
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projection) visibleTiles(action func(int32, int32, Point)) {
|
func (p *projection) visibleTiles(action func(int, int, geom.Point)) {
|
||||||
visible := p.windowVisibleRect
|
visible := p.windowVisibleRect
|
||||||
topLeft := p.screenToMap(visible.X, visible.Y)
|
topLeft := p.screenToMap(visible.Min.X, visible.Min.Y)
|
||||||
topRight := p.screenToMap(visible.X+visible.W, visible.Y)
|
topRight := p.screenToMap(visible.Max.X, visible.Min.Y)
|
||||||
bottomLeft := p.screenToMap(visible.X, visible.Y+visible.H)
|
bottomLeft := p.screenToMap(visible.Min.X, visible.Max.Y)
|
||||||
bottomRight := p.screenToMap(visible.X+visible.W, visible.Y+visible.H)
|
bottomRight := p.screenToMap(visible.Max.Y, visible.Max.Y)
|
||||||
minY, maxY := int32(Floor32(topRight.Y)), int32(Ceil32(bottomLeft.Y))
|
minY, maxY := int(Floor32(topRight.Y)), int(Ceil32(bottomLeft.Y))
|
||||||
minX, maxX := int32(Floor32(topLeft.X)), int32(Ceil32(bottomRight.X))
|
minX, maxX := int(Floor32(topLeft.X)), int(Ceil32(bottomRight.X))
|
||||||
for y := minY; y <= maxY; y++ {
|
for y := minY; y <= maxY; y++ {
|
||||||
for x := minX; x <= maxX; x++ {
|
for x := minX; x <= maxX; x++ {
|
||||||
pos := p.mapToScreen(x, y)
|
pos := p.mapToScreen(x, y)
|
||||||
rectFit := p.screenToTileFitRect(pos)
|
rectFit := p.screenToTileFitRect(pos)
|
||||||
if rectFit.X+rectFit.W < visible.X || rectFit.Y+rectFit.H < visible.Y {
|
if rectFit.Max.X < visible.Min.X || rectFit.Max.Y < visible.Min.Y {
|
||||||
continue
|
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
|
break
|
||||||
}
|
}
|
||||||
action(x, y, pos)
|
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.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 {
|
if p.zoom == zoom {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
|
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
|
||||||
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 {
|
if p.zoom <= .25 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.SetZoom(ctx, center, .5*p.zoom)
|
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 {
|
if p.zoom >= 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
76
proxy.go
76
proxy.go
@ -1,48 +1,48 @@
|
|||||||
package tins2020
|
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 {
|
// type Proxy struct {
|
||||||
Proxied Control
|
// Proxied Control
|
||||||
|
|
||||||
bounds Rectangle
|
// bounds Rectangle
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *Proxy) Arrange(ctx *Context, bounds Rectangle) {
|
// func (p *Proxy) Arrange(ctx ui.Context, bounds Rectangle) {
|
||||||
p.bounds = bounds
|
// p.bounds = bounds
|
||||||
if p.Proxied == nil {
|
// if p.Proxied == nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
p.Proxied.Arrange(ctx, bounds)
|
// p.Proxied.Arrange(ctx, bounds)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *Proxy) Handle(ctx *Context, event sdl.Event) bool {
|
// func (p *Proxy) Handle(ctx ui.Context, event sdl.Event) bool {
|
||||||
if p.Proxied == nil {
|
// if p.Proxied == nil {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
return p.Proxied.Handle(ctx, event)
|
// return p.Proxied.Handle(ctx, event)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *Proxy) Init(ctx *Context) error {
|
// func (p *Proxy) Init(ctx ui.Context) error {
|
||||||
if p.Proxied == nil {
|
// if p.Proxied == nil {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return p.Proxied.Init(ctx)
|
// return p.Proxied.Init(ctx)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *Proxy) Render(ctx *Context) {
|
// func (p *Proxy) Render(ctx ui.Context) {
|
||||||
if p.Proxied == nil {
|
// if p.Proxied == nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
p.Proxied.Render(ctx)
|
// p.Proxied.Render(ctx)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *Proxy) SetContent(ctx *Context, content Control) {
|
// func (p *Proxy) SetContent(ctx ui.Context, content Control) {
|
||||||
p.Proxied = content
|
// p.Proxied = content
|
||||||
if content == nil {
|
// if content == nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
content.Arrange(ctx, p.bounds)
|
// content.Arrange(ctx, p.bounds)
|
||||||
}
|
// }
|
||||||
|
32
rect.go
32
rect.go
@ -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 }
|
|
201
research.go
201
research.go
@ -8,11 +8,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Research struct {
|
type Research struct {
|
||||||
Container
|
ui.ContainerBase
|
||||||
|
|
||||||
game *Game
|
game *Game
|
||||||
botanist Specialist
|
botanist Specialist
|
||||||
@ -22,30 +25,39 @@ type Research struct {
|
|||||||
digitCount int
|
digitCount int
|
||||||
|
|
||||||
close func()
|
close func()
|
||||||
description Paragraph
|
description ui.Paragraph
|
||||||
specialists Paragraph
|
specialists ui.Paragraph
|
||||||
input Label
|
input ui.Label
|
||||||
digits []Digit
|
digits []Digit
|
||||||
animate Animation
|
animate zntg.Animation
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResearch(game *Game) Control {
|
func NewResearch(game *Game) ui.Overlay {
|
||||||
research := &Research{
|
research := &Research{game: game}
|
||||||
game: game,
|
research.animate.Interval = 20 * time.Millisecond
|
||||||
animate: NewAnimation(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.AddChild(&research.digits[i])
|
||||||
}
|
}
|
||||||
dialog := &LargeDialog{}
|
dialog := NewLargeDialog("Research", research)
|
||||||
dialog.SetCaption("Research")
|
// dialog.OnShow().RegisterItf(func(state interface{}) {
|
||||||
dialog.SetContent(research)
|
// research.onShow(state.(ui.Context))
|
||||||
dialog.OnShow().RegisterItf(func(state interface{}) {
|
// })
|
||||||
research.onShow(state.(*Context))
|
// research.close = func() { dialog.CloseDialog() }
|
||||||
})
|
|
||||||
research.close = func() { dialog.CloseDialog() }
|
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
type Digit struct {
|
type Digit struct {
|
||||||
ControlBase
|
ui.ControlBase
|
||||||
|
|
||||||
Value string
|
Value string
|
||||||
|
|
||||||
@ -56,13 +68,13 @@ func (d *Digit) Blink() {
|
|||||||
d.highlight = 4
|
d.highlight = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Digit) Render(ctx *Context) {
|
func (d *Digit) Render(ctx ui.Context) {
|
||||||
font := ctx.Fonts.Font("title")
|
color := zntg.MustHexColor(`#FFFFFF`)
|
||||||
color := White
|
|
||||||
if d.highlight > 0 {
|
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() {
|
func (d *Digit) Tick() {
|
||||||
@ -76,43 +88,26 @@ type Specialist struct {
|
|||||||
Number string
|
Number string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Research) Init(ctx *Context) error {
|
func (r *Research) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.PointF32, parent ui.Control) {
|
||||||
r.AddChild(&r.description)
|
r.ContainerBase.Arrange(ctx, bounds, offset, parent)
|
||||||
r.AddChild(&r.specialists)
|
|
||||||
r.AddChild(&r.input)
|
|
||||||
|
|
||||||
r.description.Text = "Call a specialist to conduct research with."
|
size := bounds.Size()
|
||||||
r.digits = make([]Digit, 10)
|
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 {
|
for i := range r.digits {
|
||||||
j := i
|
angle := (float32((10-i)%10)*0.16 + .2) * math.Pi
|
||||||
r.digits[i].Value = strconv.Itoa(i)
|
pos := geom.PtF32(distance*geom.Cos32(angle), .8*distance*geom.Sin32(angle))
|
||||||
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)))
|
|
||||||
digitCenter := center.Add(pos)
|
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()
|
r.digits[i].Blink()
|
||||||
digit := strconv.Itoa(i)
|
digit := strconv.Itoa(i)
|
||||||
if len(r.typing) == 0 || digit != r.typing {
|
if len(r.typing) == 0 || digit != r.typing {
|
||||||
@ -132,75 +127,73 @@ func (r *Research) userTyped(i int) {
|
|||||||
r.digitCount = 0
|
r.digitCount = 0
|
||||||
|
|
||||||
if r.input.Text == r.botanist.Number {
|
if r.input.Text == r.botanist.Number {
|
||||||
r.game.UnlockNextFlower()
|
r.game.UnlockNextFlower(ctx)
|
||||||
r.close()
|
r.close()
|
||||||
r.input.Text = ""
|
r.input.Text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Research) Handle(ctx *Context, event sdl.Event) bool {
|
func (r *Research) Handle(ctx ui.Context, event ui.Event) bool {
|
||||||
if r.Container.Handle(ctx, event) {
|
if r.ContainerBase.Handle(ctx, event) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *sdl.KeyboardEvent:
|
case *ui.KeyDownEvent:
|
||||||
if e.Type == sdl.KEYDOWN {
|
switch e.Key {
|
||||||
switch e.Keysym.Sym {
|
case ui.Key0:
|
||||||
case sdl.K_0:
|
r.userTyped(ctx, 0)
|
||||||
r.userTyped(0)
|
case ui.KeyPad0:
|
||||||
case sdl.K_KP_0:
|
r.userTyped(ctx, 0)
|
||||||
r.userTyped(0)
|
case ui.Key1:
|
||||||
case sdl.K_1:
|
r.userTyped(ctx, 1)
|
||||||
r.userTyped(1)
|
case ui.KeyPad1:
|
||||||
case sdl.K_KP_1:
|
r.userTyped(ctx, 1)
|
||||||
r.userTyped(1)
|
case ui.Key2:
|
||||||
case sdl.K_2:
|
r.userTyped(ctx, 2)
|
||||||
r.userTyped(2)
|
case ui.KeyPad2:
|
||||||
case sdl.K_KP_2:
|
r.userTyped(ctx, 2)
|
||||||
r.userTyped(2)
|
case ui.Key3:
|
||||||
case sdl.K_3:
|
r.userTyped(ctx, 3)
|
||||||
r.userTyped(3)
|
case ui.KeyPad3:
|
||||||
case sdl.K_KP_3:
|
r.userTyped(ctx, 3)
|
||||||
r.userTyped(3)
|
case ui.Key4:
|
||||||
case sdl.K_4:
|
r.userTyped(ctx, 4)
|
||||||
r.userTyped(4)
|
case ui.KeyPad4:
|
||||||
case sdl.K_KP_4:
|
r.userTyped(ctx, 4)
|
||||||
r.userTyped(4)
|
case ui.Key5:
|
||||||
case sdl.K_5:
|
r.userTyped(ctx, 5)
|
||||||
r.userTyped(5)
|
case ui.KeyPad5:
|
||||||
case sdl.K_KP_5:
|
r.userTyped(ctx, 5)
|
||||||
r.userTyped(5)
|
case ui.Key6:
|
||||||
case sdl.K_6:
|
r.userTyped(ctx, 6)
|
||||||
r.userTyped(6)
|
case ui.KeyPad6:
|
||||||
case sdl.K_KP_6:
|
r.userTyped(ctx, 6)
|
||||||
r.userTyped(6)
|
case ui.Key7:
|
||||||
case sdl.K_7:
|
r.userTyped(ctx, 7)
|
||||||
r.userTyped(7)
|
case ui.KeyPad7:
|
||||||
case sdl.K_KP_7:
|
r.userTyped(ctx, 7)
|
||||||
r.userTyped(7)
|
case ui.Key8:
|
||||||
case sdl.K_8:
|
r.userTyped(ctx, 8)
|
||||||
r.userTyped(8)
|
case ui.KeyPad8:
|
||||||
case sdl.K_KP_8:
|
r.userTyped(ctx, 8)
|
||||||
r.userTyped(8)
|
case ui.Key9:
|
||||||
case sdl.K_9:
|
r.userTyped(ctx, 9)
|
||||||
r.userTyped(9)
|
case ui.KeyPad9:
|
||||||
case sdl.K_KP_9:
|
r.userTyped(ctx, 9)
|
||||||
r.userTyped(9)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Research) Render(ctx *Context) {
|
func (r *Research) Render(ctx ui.Context) {
|
||||||
for i := range r.digits {
|
for i := range r.digits {
|
||||||
r.digits[i].Tick()
|
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 {
|
generateNumber := func() string {
|
||||||
var number string
|
var number string
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResourceLoader struct {
|
type ResourceLoader struct {
|
||||||
@ -14,8 +16,8 @@ func NewResourceLoader() *ResourceLoader {
|
|||||||
return &ResourceLoader{}
|
return &ResourceLoader{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *ResourceLoader) parseResourcesFile(res *Resources, name string) error {
|
func (l *ResourceLoader) parseResourcesFile(res ui.Resources, name string) error {
|
||||||
f, err := res.Fs().Open(name)
|
f, err := res.OpenResource(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -36,7 +38,7 @@ func (l *ResourceLoader) parseResourcesFile(res *Resources, name string) error {
|
|||||||
return nil
|
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)
|
err := l.parseResourcesFile(res, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
15
settings.go
15
settings.go
@ -1,6 +1,11 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Window WindowSettings
|
Window WindowSettings
|
||||||
@ -18,7 +23,7 @@ func (s *Settings) Init() error {
|
|||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return DecodeJSON(path, s)
|
return zntg.DecodeJSON(path, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) Store() error {
|
func (s *Settings) Store() error {
|
||||||
@ -26,11 +31,11 @@ func (s *Settings) Store() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return EncodeJSON(path, s)
|
return zntg.EncodeJSON(path, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindowSettings struct {
|
type WindowSettings struct {
|
||||||
Location *Point
|
Location *geom.Point
|
||||||
Size *Point
|
Size *geom.Point
|
||||||
VSync *bool
|
VSync *bool
|
||||||
}
|
}
|
||||||
|
@ -3,129 +3,129 @@ package tins2020
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type terrainRenderer struct {
|
type terrainRenderer struct {
|
||||||
|
ui.ControlBase
|
||||||
|
|
||||||
game *Game
|
game *Game
|
||||||
hover *Point
|
hover *geom.Point
|
||||||
project projection
|
project projection
|
||||||
|
|
||||||
drag Drageable
|
drag ui.Dragable
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTerrainRenderer(game *Game) Control {
|
func NewTerrainRenderer(game *Game) ui.Control {
|
||||||
return &terrainRenderer{game: game, project: newProjection()}
|
renderer := &terrainRenderer{game: game, project: newProjection()}
|
||||||
}
|
|
||||||
|
|
||||||
func (r *terrainRenderer) Arrange(ctx *Context, _ Rectangle) {
|
renderer.game.CenterChanged().AddHandler(func(ctx ui.Context, state interface{}) {
|
||||||
r.project.update(ctx.Renderer)
|
center := state.(geom.Point)
|
||||||
}
|
renderer.project.center = center.ToF32()
|
||||||
|
renderer.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)
|
|
||||||
})
|
})
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isControlKeyDown() bool {
|
func isControlKeyDown(ctx ui.Context) bool {
|
||||||
state := sdl.GetKeyboardState()
|
return false
|
||||||
return state[sdl.SCANCODE_LCTRL] == 1 || state[sdl.SCANCODE_RCTRL] == 1 || state[sdl.SCANCODE_LGUI] == 1 || state[sdl.SCANCODE_RGUI] == 1
|
// 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) {
|
switch e := event.(type) {
|
||||||
case *sdl.MouseButtonEvent:
|
case *ui.MouseButtonDownEvent:
|
||||||
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
|
pos := e.Pos()
|
||||||
if e.Type == sdl.MOUSEBUTTONDOWN {
|
if pos.ToInt().In(r.project.windowInteractRect) {
|
||||||
controlKeyDown := isControlKeyDown()
|
controlKeyDown := isControlKeyDown(ctx)
|
||||||
if e.Button == sdl.BUTTON_MIDDLE || (e.Button == sdl.BUTTON_LEFT && controlKeyDown) {
|
if e.Button == ui.MouseButtonMiddle || (e.Button == ui.MouseButtonLeft && controlKeyDown) {
|
||||||
if !r.drag.IsDragging() {
|
if _, ok := r.drag.IsDragging(); !ok {
|
||||||
r.drag.Start(Pt(e.X, e.Y))
|
r.drag.Start(pos)
|
||||||
}
|
|
||||||
}
|
|
||||||
if e.Button == sdl.BUTTON_LEFT && !controlKeyDown {
|
|
||||||
pos := r.project.screenToMapInt(e.X, e.Y)
|
|
||||||
r.game.UserClickedTile(pos)
|
|
||||||
}
|
|
||||||
if e.Button == sdl.BUTTON_RIGHT {
|
|
||||||
if e.Type == sdl.MOUSEBUTTONDOWN {
|
|
||||||
r.game.CancelTool()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e.Type == sdl.MOUSEBUTTONUP {
|
if e.Button == ui.MouseButtonLeft && !controlKeyDown {
|
||||||
if r.drag.IsDragging() {
|
pos := r.project.screenToMapInt(int(e.X), int(e.Y))
|
||||||
r.game.Terrain.Center = mapToTile(r.project.center)
|
r.game.UserClickedTile(pos)
|
||||||
r.drag.Cancel()
|
}
|
||||||
}
|
if e.Button == ui.MouseButtonRight {
|
||||||
|
r.game.CancelTool(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *sdl.MouseMotionEvent:
|
case *ui.MouseButtonUpEvent:
|
||||||
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
|
pos := e.Pos().ToInt()
|
||||||
hover := r.project.screenToMapInt(e.X, e.Y)
|
if pos.In(r.project.windowInteractRect) {
|
||||||
|
if _, ok := r.drag.IsDragging(); ok {
|
||||||
|
r.game.Terrain.Center = mapToTile(r.project.center)
|
||||||
|
r.drag.Cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
r.hover = &hover
|
||||||
} else {
|
} else {
|
||||||
r.hover = nil
|
r.hover = nil
|
||||||
}
|
}
|
||||||
if r.drag.IsDragging() {
|
if _, ok := r.drag.IsDragging(); ok {
|
||||||
delta := r.drag.Move(Pt(e.X, e.Y))
|
delta, _ := r.drag.Move(pos)
|
||||||
r.project.center = r.project.center.Sub(r.project.screenToMapRel(delta.X, delta.Y))
|
r.project.center = r.project.center.Sub(r.project.screenToMapRel(int(delta.X), int(delta.Y)))
|
||||||
r.project.update(ctx.Renderer)
|
r.project.update(ctx.Renderer())
|
||||||
}
|
}
|
||||||
case *sdl.MouseWheelEvent:
|
|
||||||
if r.hover != nil {
|
if r.hover != nil {
|
||||||
if e.Y < 0 {
|
if e.Y < 0 {
|
||||||
r.project.ZoomOut(ctx, r.hover.ToPtF())
|
r.project.ZoomOut(ctx, r.hover.ToF32())
|
||||||
} else {
|
} else {
|
||||||
r.project.ZoomIn(ctx, r.hover.ToPtF())
|
r.project.ZoomIn(ctx, r.hover.ToF32())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *sdl.WindowEvent:
|
case *ui.MouseLeaveEvent:
|
||||||
if e.Event == sdl.WINDOWEVENT_LEAVE {
|
r.hover = nil
|
||||||
r.hover = nil
|
r.project.update(ctx.Renderer())
|
||||||
r.project.update(ctx.Renderer)
|
case *ui.KeyDownEvent:
|
||||||
}
|
switch e.Key {
|
||||||
case *sdl.KeyboardEvent:
|
case ui.KeyPadPlus:
|
||||||
if e.Type == sdl.KEYDOWN {
|
r.project.ZoomIn(ctx, r.project.center)
|
||||||
switch e.Keysym.Sym {
|
case ui.KeyMinus:
|
||||||
case sdl.K_PLUS:
|
r.project.ZoomOut(ctx, r.project.center)
|
||||||
r.project.ZoomIn(ctx, r.project.center)
|
case ui.KeyPadMinus:
|
||||||
case sdl.K_KP_PLUS:
|
r.project.ZoomOut(ctx, r.project.center)
|
||||||
r.project.ZoomIn(ctx, r.project.center)
|
case ui.KeyW:
|
||||||
case sdl.K_MINUS:
|
r.project.Pan(ctx, geom.PtF32(-1, -1))
|
||||||
r.project.ZoomOut(ctx, r.project.center)
|
case ui.KeyA:
|
||||||
case sdl.K_KP_MINUS:
|
r.project.Pan(ctx, geom.PtF32(-1, 1))
|
||||||
r.project.ZoomOut(ctx, r.project.center)
|
case ui.KeyS:
|
||||||
case sdl.K_w:
|
r.project.Pan(ctx, geom.PtF32(1, 1))
|
||||||
r.project.Pan(ctx, PtF(-1, -1))
|
case ui.KeyD:
|
||||||
case sdl.K_a:
|
r.project.Pan(ctx, geom.PtF32(1, -1))
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *terrainRenderer) Render(ctx *Context) {
|
func (r *terrainRenderer) Render(ctx ui.Context) {
|
||||||
terrain := r.game.Terrain
|
terrain := r.game.Terrain
|
||||||
toTileTexture := func(x, y int32) *Texture {
|
toTileTexture := func(x, y int) ui.Texture {
|
||||||
temp := terrain.Temp.Value(x, y)
|
temp := terrain.Temp.Value(x, y)
|
||||||
if temp < .35 {
|
if temp < .35 {
|
||||||
return ctx.Textures.Texture("tile-snow")
|
return ctx.Textures().Texture("tile-snow")
|
||||||
}
|
}
|
||||||
if temp > .65 {
|
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 {
|
variantToInt := func(variant float64) int {
|
||||||
@ -144,14 +144,14 @@ func (r *terrainRenderer) Render(ctx *Context) {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
variantToTexture := func(format string, variant float64) *Texture {
|
variantToTexture := func(format string, variant float64) ui.Texture {
|
||||||
textName := fmt.Sprintf(format, variantToInt(variant))
|
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) }
|
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 temp < .35 {
|
||||||
if humid < .2 {
|
if humid < .2 {
|
||||||
return nil
|
return nil
|
||||||
@ -181,12 +181,12 @@ func (r *terrainRenderer) Render(ctx *Context) {
|
|||||||
return variantToTexture("bush-large-%d", stretch(variant, .8, 1)*multiplier)
|
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)
|
variant := terrain.Variant.Value(x, y)
|
||||||
flower, ok := terrain.Flowers[Pt(x, y)]
|
flower, ok := terrain.Flowers[geom.Pt(x, y)]
|
||||||
if ok {
|
if ok {
|
||||||
desc, _ := r.game.Herbarium.Find(flower.ID)
|
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)
|
temp := terrain.Temp.Value(x, y)
|
||||||
humid := terrain.Humid.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 (tile): [96,160) = 64
|
||||||
// vertical (total): [0,160) = 160
|
// 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)
|
text := toTileTexture(x, y)
|
||||||
rect := r.project.screenToTileRect(pos)
|
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 {
|
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)
|
text := toItemTexture(x, y)
|
||||||
if text == nil {
|
if text == nil {
|
||||||
return
|
return
|
||||||
@ -215,6 +215,7 @@ func (r *terrainRenderer) Render(ctx *Context) {
|
|||||||
|
|
||||||
placeX, placeY := terrain.PlaceX.Value(x, y), terrain.PlaceY.Value(x, y)
|
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))
|
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())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
116
textures.go
116
textures.go
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
8
tools.go
8
tools.go
@ -1,8 +1,10 @@
|
|||||||
package tins2020
|
package tins2020
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
type Tool interface {
|
type Tool interface {
|
||||||
Type() string
|
Type() string
|
||||||
ClickedTile(*Game, Point)
|
ClickedTile(*Game, geom.Point)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlantFlowerTool struct {
|
type PlantFlowerTool struct {
|
||||||
@ -11,7 +13,7 @@ type PlantFlowerTool struct {
|
|||||||
|
|
||||||
func (t *PlantFlowerTool) Type() string { return "plant-flower" }
|
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)
|
game.PlantFlower(t.FlowerID, tile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +21,6 @@ type ShovelTool struct{}
|
|||||||
|
|
||||||
func (t *ShovelTool) Type() string { return "shovel" }
|
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)
|
game.Dig(tile)
|
||||||
}
|
}
|
||||||
|
72
tooltip.go
72
tooltip.go
@ -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)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user