Compare commits

...

3 Commits

Author SHA1 Message Date
5ebd582498 Added keyboard support for zooming.
Added information but to show intro.
2020-05-11 12:05:54 +02:00
a6004a8ab6 Added "new game" functionality. 2020-05-11 11:53:15 +02:00
9641719579 Added intro dialog.
Refactored event handling to be able to "handle" events so no other controls will handle the same event again.
2020-05-11 11:44:50 +02:00
26 changed files with 546 additions and 80 deletions

View File

@ -45,12 +45,8 @@ func (b *ButtonBar) Arrange(ctx *Context, bounds Rectangle) {
} }
} }
func (b *ButtonBar) Handle(ctx *Context, event sdl.Event) {
b.Container.Handle(ctx, event)
}
func (b *ButtonBar) Render(ctx *Context) { func (b *ButtonBar) Render(ctx *Context) {
ctx.Renderer.SetDrawColor(b.Background.R, b.Background.G, b.Background.B, b.Background.A) 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)
} }

View File

@ -71,24 +71,27 @@ func (b *BuyFlowerButton) Init(ctx *Context) error {
return b.updateTexts(ctx) return b.updateTexts(ctx)
} }
func (b *BuyFlowerButton) Handle(ctx *Context, event sdl.Event) { func (b *BuyFlowerButton) Handle(ctx *Context, event sdl.Event) bool {
b.IconButton.Handle(ctx, event) if b.IconButton.Handle(ctx, event) {
return true
}
if b.IsMouseOver && b.hoverAnimation == nil { if b.IsMouseOver && b.hoverAnimation == nil {
b.hoverAnimation = NewAnimationPtr(10 * time.Millisecond) b.hoverAnimation = NewAnimationPtr(10 * time.Millisecond)
b.hoverOffset = b.priceTexture.Size().X b.hoverOffset = b.priceTexture.Size().X
} else if !b.IsMouseOver { } else if !b.IsMouseOver {
b.hoverAnimation = nil b.hoverAnimation = nil
} }
return false
} }
func (b *BuyFlowerButton) Render(ctx *Context) { func (b *BuyFlowerButton) Render(ctx *Context) {
iconTexture := b.activeTexture(ctx) iconTexture := b.activeTexture(ctx)
mouseOverTexture := ctx.Textures.Texture("control-hover")
pos := Pt(b.Bounds.X, b.Bounds.Y) pos := Pt(b.Bounds.X, b.Bounds.Y)
iconTexture.CopyResize(ctx.Renderer, RectSize(pos.X, pos.Y-60, b.Bounds.W, 120)) iconTexture.CopyResize(ctx.Renderer, RectSize(pos.X, pos.Y-60, b.Bounds.W, 120))
if (b.IsMouseOver && !b.IsDisabled) || b.IsActive { if (b.IsMouseOver && !b.IsDisabled) || b.IsActive {
mouseOverTexture.CopyResize(ctx.Renderer, b.Bounds) SetDrawColor(ctx.Renderer, TransparentWhite)
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
} }
if b.hoverAnimation != nil { if b.hoverAnimation != nil {

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,10 +1,11 @@
control-hover: images/game_control_hover.png
control-shovel: images/genericItem_color_022.png control-shovel: images/genericItem_color_022.png
control-research: images/genericItem_color_111.png control-research: images/genericItem_color_111.png
control-settings: images/gear.png control-settings: images/gear.png
control-save: images/save.png control-save: images/save.png
control-load: images/import.png control-load: images/import.png
control-new: images/return.png
control-information: images/information.png
control-quit: images/power.png control-quit: images/power.png
control-pause: images/pause.png control-pause: images/pause.png
@ -14,6 +15,9 @@ control-run-disabled: images/forward_disabled.png
control-run-fast: images/fastForward.png control-run-fast: images/fastForward.png
control-run-fast-disabled: images/fastForward_disabled.png control-run-fast-disabled: images/fastForward_disabled.png
control-cancel: images/cross.png
control-confirm: images/checkmark.png
tile-dirt: images/tile_dirt.png tile-dirt: images/tile_dirt.png
tile-grass: images/tile_grass.png tile-grass: images/tile_grass.png
tile-snow: images/tile_snow.png tile-snow: images/tile_snow.png

View File

@ -28,7 +28,7 @@ func run() error {
} }
defer sdl.Quit() defer sdl.Quit()
// logSDLVersion() logSDLVersion()
if err := ttf.Init(); err != nil { if err := ttf.Init(); err != nil {
return err return err
@ -56,7 +56,7 @@ func run() error {
sdl.SetHint(sdl.HINT_RENDER_VSYNC, "1") sdl.SetHint(sdl.HINT_RENDER_VSYNC, "1")
} }
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1") sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
window, err := sdl.CreateWindow("TINS 2020", window, err := sdl.CreateWindow("Botanim - TINS 2020",
ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y, ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y,
ctx.Settings.Window.Size.X, ctx.Settings.Window.Size.Y, ctx.Settings.Window.Size.X, ctx.Settings.Window.Size.Y,
sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE) sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
@ -74,10 +74,11 @@ func run() error {
ctx.Init(renderer) ctx.Init(renderer)
err = ctx.Fonts.LoadDesc( err = ctx.Fonts.LoadDesc(
tins2020.FontDescriptor{Name: "debug", Path: "fonts/FiraMono-Regular.ttf", Size: 12},
tins2020.FontDescriptor{Name: "default", Path: "fonts/OpenSans-Regular.ttf", Size: 16},
tins2020.FontDescriptor{Name: "small", Path: "fonts/OpenSans-Regular.ttf", Size: 12},
tins2020.FontDescriptor{Name: "balance", Path: "fonts/OpenSans-Bold.ttf", Size: 40}, 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 { if err != nil {
return err return err
@ -93,18 +94,25 @@ func run() error {
game := tins2020.NewGame() game := tins2020.NewGame()
app := tins2020.NewContainer() app := tins2020.NewContainer()
overlays := tins2020.NewContainer() overlays := tins2020.NewContainer()
gameControls := tins2020.NewGameControls(game) dialogs := tins2020.NewDialogs()
overlays.AddChild(gameControls)
overlays.AddChild(&tins2020.FPS{}) overlays.AddChild(dialogs)
content := tins2020.NewContainer() 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(content)
app.AddChild(overlays) app.AddChild(overlays)
content.AddChild(tins2020.NewTerrainRenderer(game))
err = app.Init(ctx) err = app.Init(ctx)
if err != nil { if err != nil {
return err return err
} }
dialogs.ShowIntro()
w, h := window.GetSize() w, h := window.GetSize()
app.Arrange(ctx, tins2020.Rect(0, 0, w, h)) app.Arrange(ctx, tins2020.Rect(0, 0, w, h))

View File

@ -6,4 +6,17 @@ import (
"github.com/veandco/go-sdl2/sdl" "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 MustHexColor(s string) sdl.Color { return sdl.Color(img.MustHexColor(s)) } func MustHexColor(s string) sdl.Color { return sdl.Color(img.MustHexColor(s)) }
func SetDrawColor(renderer *sdl.Renderer, color sdl.Color) {
renderer.SetDrawColor(color.R, color.G, color.B, color.A)
}
func SetDrawColorHex(renderer *sdl.Renderer, s string) {
SetDrawColor(renderer, MustHexColor(s))
}

View File

@ -25,11 +25,16 @@ func (c *Container) Arrange(ctx *Context, bounds Rectangle) {
} }
} }
func (c *Container) Handle(ctx *Context, event sdl.Event) { func (c *Container) Handle(ctx *Context, event sdl.Event) bool {
c.ControlBase.Handle(ctx, event) if c.ControlBase.Handle(ctx, event) {
for _, child := range c.Children { return true
child.Handle(ctx, event)
} }
for _, child := range c.Children {
if child.Handle(ctx, event) {
return true
}
}
return false
} }
func (c *Container) Init(ctx *Context) error { func (c *Container) Init(ctx *Context) error {

28
content.go Normal file
View File

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

View File

@ -5,7 +5,7 @@ import "github.com/veandco/go-sdl2/sdl"
type Control interface { type Control interface {
Init(*Context) error Init(*Context) error
Arrange(*Context, Rectangle) Arrange(*Context, Rectangle)
Handle(*Context, sdl.Event) Handle(*Context, sdl.Event) bool
Render(*Context) Render(*Context)
} }
@ -31,7 +31,12 @@ func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bound
func (b *ControlBase) Init(*Context) error { return nil } func (b *ControlBase) Init(*Context) error { return nil }
func (b *ControlBase) Handle(ctx *Context, event sdl.Event) { func (b *ControlBase) Handle(ctx *Context, event sdl.Event) bool {
b.HandleNoFeedback(ctx, event)
return false
}
func (b *ControlBase) HandleNoFeedback(ctx *Context, event sdl.Event) {
switch e := event.(type) { switch e := event.(type) {
case *sdl.MouseMotionEvent: case *sdl.MouseMotionEvent:
b.IsMouseOver = b.Bounds.IsPointInside(e.X, e.Y) b.IsMouseOver = b.Bounds.IsPointInside(e.X, e.Y)

53
dialogs.go Normal file
View File

@ -0,0 +1,53 @@
package tins2020
type Dialogs struct {
Proxy
intro Control
settings Control
dialogClosed *Events
dialogOpened *Events
}
func NewDialogs() *Dialogs {
return &Dialogs{
dialogClosed: NewEvents(),
dialogOpened: NewEvents(),
}
}
func (d *Dialogs) DialogClosed() EventHandler { return d.dialogClosed }
func (d *Dialogs) DialogOpened() EventHandler { return d.dialogOpened }
func (d *Dialogs) Init(ctx *Context) error {
d.intro = &Intro{}
d.settings = &DialogBase{}
err := d.intro.Init(ctx)
if err != nil {
return err
}
err = d.settings.Init(ctx)
if err != nil {
return err
}
return nil
}
func (d *Dialogs) Close() {
d.Proxied = nil
d.dialogClosed.Notify(nil)
}
func (d *Dialogs) ShowIntro() {
d.Proxied = d.intro
d.intro.(Dialog).ShowDialog(d.Close)
d.dialogOpened.Notify(nil)
}
func (d *Dialogs) ShowSettings() {
d.Proxied = d.settings
d.settings.(Dialog).ShowDialog(d.Close)
d.dialogOpened.Notify(nil)
}

View File

@ -10,6 +10,7 @@ import (
type FPS struct { type FPS struct {
ControlBase ControlBase
Show *bool
start time.Time start time.Time
stamp time.Duration stamp time.Duration
slot int slot int
@ -25,6 +26,10 @@ func (f *FPS) Init(*Context) error {
} }
func (f *FPS) Render(ctx *Context) { func (f *FPS) Render(ctx *Context) {
if f.Show == nil || !*f.Show {
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 {

42
game.go
View File

@ -7,6 +7,8 @@ import (
) )
type Game struct { type Game struct {
Debug bool
Balance int Balance int
Speed GameSpeed Speed GameSpeed
SpeedBeforePause GameSpeed SpeedBeforePause GameSpeed
@ -29,29 +31,17 @@ const (
) )
const simulationInterval = 120 * time.Millisecond const simulationInterval = 120 * time.Millisecond
const fastSimulationInterval = 40 * time.Millisecond const fastSimulationInterval = 20 * time.Millisecond
func NewGame() *Game { func NewGame() *Game {
terrain := &Map{ game := &Game{
Temp: NewNoiseMap(rand.Int63()),
Humid: NewNoiseMap(rand.Int63()),
Variant: NewRandomNoiseMap(rand.Int63()),
PlaceX: NewRandomNoiseMap(rand.Int63()),
PlaceY: NewRandomNoiseMap(rand.Int63()),
Flowers: map[Point]Flower{},
}
herbarium := NewHerbarium()
return &Game{
Speed: GameSpeedNormal,
Balance: 100,
Terrain: terrain,
Herbarium: herbarium,
centerChanged: NewEvents(), centerChanged: NewEvents(),
speedChanged: NewEvents(), speedChanged: NewEvents(),
toolChanged: NewEvents(), toolChanged: NewEvents(),
simulation: NewAnimation(time.Millisecond * 10), simulation: NewAnimation(time.Millisecond * 10),
} }
game.Reset()
return game
} }
func (g *Game) selectTool(t Tool) { func (g *Game) selectTool(t Tool) {
@ -127,6 +117,11 @@ func (g *Game) Dig(tile Point) {
g.Balance += desc.SellPrice g.Balance += desc.SellPrice
} }
func (g *Game) New() {
g.Pause()
g.Reset()
}
func (g *Game) Load() { func (g *Game) Load() {
g.CancelTool() g.CancelTool()
g.Pause() g.Pause()
@ -181,6 +176,21 @@ 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() {
g.Balance = 100
g.Herbarium = NewHerbarium()
g.Terrain = &Map{
Temp: NewNoiseMap(rand.Int63()),
Humid: NewNoiseMap(rand.Int63()),
Variant: NewRandomNoiseMap(rand.Int63()),
PlaceX: NewRandomNoiseMap(rand.Int63()),
PlaceY: NewRandomNoiseMap(rand.Int63()),
Flowers: map[Point]Flower{},
}
g.CancelTool()
g.setSpeed(GameSpeedNormal)
}
func (g *Game) Resume() { g.setSpeed(g.SpeedBeforePause) } func (g *Game) Resume() { g.setSpeed(g.SpeedBeforePause) }
func (g *Game) Run() { g.setSpeed(GameSpeedNormal) } func (g *Game) Run() { g.setSpeed(GameSpeedNormal) }

View File

@ -7,7 +7,8 @@ import (
type GameControls struct { type GameControls struct {
Container Container
game *Game game *Game
dialogs *Dialogs
menu ButtonBar menu ButtonBar
top ButtonBar top ButtonBar
@ -22,8 +23,8 @@ type GameControls struct {
research *IconButton research *IconButton
} }
func NewGameControls(game *Game) *GameControls { func NewGameControls(game *Game, dialogs *Dialogs) *GameControls {
return &GameControls{game: game} return &GameControls{game: game, dialogs: dialogs}
} }
func (c *GameControls) createBuyFlowerButton(id string) *BuyFlowerButton { func (c *GameControls) createBuyFlowerButton(id string) *BuyFlowerButton {
@ -64,6 +65,16 @@ func (c *GameControls) toolChanged(state interface{}) {
c.shovel.IsActive = shovel c.shovel.IsActive = shovel
} }
func (c *GameControls) updateFlowerControls(ctx *Context) {
for _, b := range c.flowers.Buttons {
button := b.(*BuyFlowerButton)
flower, ok := c.game.Herbarium.Find(button.FlowerID)
if ok {
button.Update(ctx, flower)
}
}
}
func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) { func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) {
c.Bounds = bounds c.Bounds = bounds
c.menu.Arrange(ctx, RectSize(bounds.X, bounds.Y, buttonBarWidth, bounds.H)) c.menu.Arrange(ctx, RectSize(bounds.X, bounds.Y, buttonBarWidth, bounds.H))
@ -75,6 +86,8 @@ func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) {
func (c *GameControls) Init(ctx *Context) error { func (c *GameControls) Init(ctx *Context) error {
c.game.SpeedChanged().RegisterItf(c.speedChanged) c.game.SpeedChanged().RegisterItf(c.speedChanged)
c.game.ToolChanged().RegisterItf(c.toolChanged) c.game.ToolChanged().RegisterItf(c.toolChanged)
c.dialogs.DialogOpened().Register(func() { c.game.Pause() })
c.dialogs.DialogClosed().Register(func() { c.game.Resume() })
c.flowers.Background = MustHexColor("#356dad") c.flowers.Background = MustHexColor("#356dad")
c.flowers.ButtonLength = 64 c.flowers.ButtonLength = 64
@ -104,18 +117,17 @@ func (c *GameControls) Init(ctx *Context) error {
c.menu.Background = MustHexColor("#356dad") c.menu.Background = MustHexColor("#356dad")
c.menu.Buttons = []Control{ c.menu.Buttons = []Control{
NewIconButton("control-settings", func(*Context) {}), NewIconButton("control-settings", func(*Context) { c.dialogs.ShowSettings() }),
NewIconButton("control-save", func(*Context) { c.game.Save() }), NewIconButton("control-save", func(*Context) { c.game.Save() }),
NewIconButton("control-load", func(ctx *Context) { NewIconButton("control-load", func(ctx *Context) {
c.game.Load() c.game.Load()
for _, b := range c.flowers.Buttons { c.updateFlowerControls(ctx)
button := b.(*BuyFlowerButton)
flower, ok := c.game.Herbarium.Find(button.FlowerID)
if ok {
button.Update(ctx, flower)
}
}
}), }),
NewIconButton("control-new", func(ctx *Context) {
c.game.New()
c.updateFlowerControls(ctx)
}),
NewIconButton("control-information", func(*Context) { c.dialogs.ShowIntro() }),
} }
c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 32 }) c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 32 })
@ -126,11 +138,14 @@ func (c *GameControls) Init(ctx *Context) error {
c.Container.AddChild(&c.top) c.Container.AddChild(&c.top)
c.Container.AddChild(&c.flowers) c.Container.AddChild(&c.flowers)
c.Container.AddChild(&c.otherTools) c.Container.AddChild(&c.otherTools)
return c.Container.Init(ctx) return c.Container.Init(ctx)
} }
func (c *GameControls) Handle(ctx *Context, event sdl.Event) { func (c *GameControls) Handle(ctx *Context, event sdl.Event) bool {
c.Container.Handle(ctx, event) if c.Container.Handle(ctx, event) {
return true
}
switch e := event.(type) { switch e := event.(type) {
case *sdl.KeyboardEvent: case *sdl.KeyboardEvent:
@ -148,18 +163,22 @@ func (c *GameControls) Handle(ctx *Context, event sdl.Event) {
c.game.SelectResearch() c.game.SelectResearch()
case sdl.K_ESCAPE: case sdl.K_ESCAPE:
if c.game.Tool() == nil { if c.game.Tool() == nil {
// TODO: display menu c.dialogs.ShowIntro()
} else { } else {
c.game.CancelTool() c.game.CancelTool()
} }
return true
case sdl.K_F3:
c.game.Debug = !c.game.Debug
} }
} }
} }
return false
} }
func (c *GameControls) Render(ctx *Context) { func (c *GameControls) Render(ctx *Context) {
topBar := MustHexColor("#0000007f") topBar := MustHexColor("#0000007f")
ctx.Renderer.SetDrawColor(topBar.R, topBar.G, topBar.B, topBar.A) SetDrawColor(ctx.Renderer, topBar)
ctx.Renderer.FillRect(Rect(c.menu.Bounds.Right(), 0, c.flowers.Bounds.X, 64).SDLPtr()) ctx.Renderer.FillRect(Rect(c.menu.Bounds.Right(), 0, c.flowers.Bounds.X, 64).SDLPtr())
ctx.Fonts.Font("balance").RenderCopyAlign(ctx.Renderer, FmtMoney(c.game.Balance), Pt(c.top.Bounds.X-8, 58), MustHexColor("#4AC69A"), TextAlignmentRight) ctx.Fonts.Font("balance").RenderCopyAlign(ctx.Renderer, FmtMoney(c.game.Balance), Pt(c.top.Bounds.X-8, 58), MustHexColor("#4AC69A"), TextAlignmentRight)

View File

@ -1,5 +1,12 @@
package tins2020 package tins2020
type HoverEffect int
const (
HoverEffectLigthen HoverEffect = iota
HoverEffectColor
)
type IconButton struct { type IconButton struct {
ControlBase ControlBase
@ -9,17 +16,13 @@ type IconButton struct {
IconScale Scale IconScale Scale
IconWidth int32 IconWidth int32
IconActive HoverEffect
IconHover HoverEffect
IsActive bool IsActive bool
IsDisabled bool IsDisabled bool
} }
type Scale int
const (
ScaleCenter Scale = iota
ScaleStretch
)
func NewIconButton(icon string, onClick EventContextFn) *IconButton { func NewIconButton(icon string, onClick EventContextFn) *IconButton {
return &IconButton{ return &IconButton{
ControlBase: ControlBase{ ControlBase: ControlBase{
@ -47,7 +50,11 @@ func (b *IconButton) activeTexture(ctx *Context) *Texture {
func (b *IconButton) Render(ctx *Context) { func (b *IconButton) Render(ctx *Context) {
iconTexture := b.activeTexture(ctx) iconTexture := b.activeTexture(ctx)
mouseOverTexture := ctx.Textures.Texture("control-hover")
hover := b.IsMouseOver && !b.IsDisabled
if (hover && b.IconHover == HoverEffectColor) || (b.IsActive && b.IconActive == HoverEffectColor) {
iconTexture.SetColor(MustHexColor("#15569F"))
}
if b.IconScale == ScaleCenter { if b.IconScale == ScaleCenter {
size := iconTexture.Size() size := iconTexture.Size()
@ -60,7 +67,16 @@ func (b *IconButton) Render(ctx *Context) {
} else { } else {
iconTexture.CopyResize(ctx.Renderer, b.Bounds) iconTexture.CopyResize(ctx.Renderer, b.Bounds)
} }
if (b.IsMouseOver && !b.IsDisabled) || b.IsActive { if (hover && b.IconHover == HoverEffectLigthen) || (b.IsActive && b.IconActive == HoverEffectLigthen) {
mouseOverTexture.CopyResize(ctx.Renderer, b.Bounds) SetDrawColor(ctx.Renderer, TransparentWhite)
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
} }
iconTexture.SetColor(White)
} }
type Scale int
const (
ScaleCenter Scale = iota
ScaleStretch
)

27
intro.go Normal file
View File

@ -0,0 +1,27 @@
package tins2020
type Intro struct {
LargeDialog
welcome Paragraph
}
func (i *Intro) Init(ctx *Context) error {
i.welcome.Text =
"Welcome to Botanim!\n\n" +
"In Botanim you play the role of botanist and your goal is to cultivate flowers in an open landscape.\n\n" +
"Flowers can only grow (well) in certain climates based on two properties: humidity and temperature. Watch out for existing vegetation to get an idea how humid the land is and check the appearance of the tile to see how hot it is. When well placed your planted flower will spread soon but an odd choice might kill your flower almost instantly. So choose carefully. When the flower spread significantly you can dig up flowers again to collect more money.\n\n" +
"Controls:\n" +
" - D: Selects shovel\n" +
" - R: Selects research\n" +
" - Spacebar: pauses game\n" +
" - 1: runs game at normal speed\n" +
" - 2: runs game extra fast\n" +
" - Mouse wheel or plus/minus: zooms landscape\n" +
" - CTRL + left mouse button or middle mouse button: pans landscape\n" +
"\n" +
"Have fun playing!"
i.SetContent(&i.welcome)
return i.LargeDialog.Init(ctx)
}

2
io.go
View File

@ -33,7 +33,7 @@ func UserDir() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
dir := filepath.Join(config, "tins2020_flowers_sim") dir := filepath.Join(config, "tins2020_botanim")
err = os.MkdirAll(dir, 0777) err = os.MkdirAll(dir, 0777)
if err != nil { if err != nil {
return "", err return "", err

108
label.go Normal file
View File

@ -0,0 +1,108 @@
package tins2020
import (
"strings"
"github.com/veandco/go-sdl2/sdl"
)
type Label struct {
ControlBase
FontColor sdl.Color
FontName string
Text string
Alignment TextAlignment
}
func (l *Label) fontColor() sdl.Color {
var none sdl.Color
if l.FontColor == none {
return MustHexColor("#ffffff")
}
return l.FontColor
}
func (l *Label) fontName() string {
if len(l.FontName) == 0 {
return "default"
}
return l.FontName
}
func (l *Label) Render(ctx *Context) {
font := ctx.Fonts.Font(l.fontName())
color := l.fontColor()
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) Arrange(ctx *Context, bounds Rectangle) {
// p.Label.Arrange(ctx, bounds)
// }
func (p *Paragraph) Render(ctx *Context) {
font := ctx.Fonts.Font(p.fontName())
color := p.fontColor()
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)
}
}
}

95
largedialog.go Normal file
View File

@ -0,0 +1,95 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type DialogBase struct {
Container
content Proxy
close EventFn
}
type Dialog interface {
CloseDialog()
ShowDialog(EventFn)
}
func (d *DialogBase) CloseDialog() {
close := d.close
if close != nil {
close()
}
}
func (d *DialogBase) SetContent(control Control) {
d.content.Proxied = control
}
func (d *DialogBase) ShowDialog(close EventFn) {
d.close = close
}
func (d *DialogBase) Init(ctx *Context) error {
d.AddChild(&d.content)
return d.Container.Init(ctx)
}
type LargeDialog struct {
DialogBase
title Label
close IconButton
}
func (d *LargeDialog) Arrange(ctx *Context, bounds Rectangle) {
const titleHeight = 64
d.ControlBase.Arrange(ctx, bounds)
d.title.Arrange(ctx, RectSize(bounds.X, bounds.Y, bounds.W, titleHeight))
d.close.Arrange(ctx, RectSize(bounds.W-64, 0, 64, 64))
d.content.Arrange(ctx, RectSize(bounds.X+titleHeight, 96, bounds.W-2*titleHeight, bounds.H-titleHeight))
}
func (d *LargeDialog) Init(ctx *Context) error {
d.title = Label{
Text: "Botanim",
FontName: "title",
Alignment: TextAlignmentCenter,
}
d.close = IconButton{
Icon: "control-cancel",
IconHover: HoverEffectColor,
IconWidth: 32,
}
d.close.OnLeftMouseButtonClick = EmptyEvent(d.CloseDialog)
d.AddChild(&d.title)
d.AddChild(&d.close)
return d.DialogBase.Init(ctx)
}
func (d *LargeDialog) Handle(ctx *Context, event sdl.Event) bool {
if d.DialogBase.Handle(ctx, event) {
return true
}
switch e := event.(type) {
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
switch e.Keysym.Sym {
case sdl.K_ESCAPE:
d.CloseDialog()
return true
case sdl.K_RETURN:
d.CloseDialog()
return true
}
}
}
return false
}
func (d *LargeDialog) Render(ctx *Context) {
SetDrawColor(ctx.Renderer, MustHexColor("#356DAD"))
ctx.Renderer.FillRect(d.Bounds.SDLPtr())
d.DialogBase.Render(ctx)
}

View File

@ -100,3 +100,26 @@ func (p *projection) visibleTiles(action func(int32, int32, Point)) {
} }
} }
} }
func (p *projection) ZoomOut(ctx *Context, center PointF) {
if p.zoom <= .25 {
return
}
p.SetZoom(ctx, center, .5*p.zoom)
}
func (p *projection) ZoomIn(ctx *Context, center PointF) {
if p.zoom >= 2 {
return
}
p.SetZoom(ctx, center, 2*p.zoom)
}
func (p *projection) SetZoom(ctx *Context, center PointF, zoom float32) {
if p.zoom == zoom {
return
}
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
p.zoom = zoom
p.update(ctx.Renderer)
}

37
proxy.go Normal file
View File

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

View File

@ -37,7 +37,7 @@ func isControlKeyDown() bool {
return state[sdl.SCANCODE_LCTRL] == 1 || state[sdl.SCANCODE_RCTRL] == 1 return state[sdl.SCANCODE_LCTRL] == 1 || state[sdl.SCANCODE_RCTRL] == 1
} }
func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) { func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool {
switch e := event.(type) { switch e := event.(type) {
case *sdl.MouseButtonEvent: case *sdl.MouseButtonEvent:
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) { if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
@ -78,17 +78,10 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
} }
case *sdl.MouseWheelEvent: case *sdl.MouseWheelEvent:
if r.hover != nil { if r.hover != nil {
zoom := r.project.zoom if e.Y < 0 {
if e.Y < 0 && r.project.zoom > .25 { r.project.ZoomOut(ctx, r.hover.ToPtF())
zoom *= .5 } else {
} else if e.Y > 0 && r.project.zoom < 2 { r.project.ZoomIn(ctx, r.hover.ToPtF())
zoom *= 2
}
if zoom != r.project.zoom {
hover := r.hover.ToPtF()
r.project.center = hover.Sub(hover.Sub(r.project.center).Mul(r.project.zoom / zoom))
r.project.zoom = zoom
r.project.update(ctx.Renderer)
} }
} }
case *sdl.WindowEvent: case *sdl.WindowEvent:
@ -96,7 +89,21 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
r.hover = nil r.hover = nil
r.project.update(ctx.Renderer) r.project.update(ctx.Renderer)
} }
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
switch e.Keysym.Sym {
case sdl.K_PLUS:
r.project.ZoomIn(ctx, r.project.center)
case sdl.K_KP_PLUS:
r.project.ZoomIn(ctx, r.project.center)
case sdl.K_MINUS:
r.project.ZoomOut(ctx, r.project.center)
case sdl.K_KP_MINUS:
r.project.ZoomOut(ctx, r.project.center)
}
}
} }
return false
} }
func (r *terrainRenderer) Render(ctx *Context) { func (r *terrainRenderer) Render(ctx *Context) {

View File

@ -44,6 +44,10 @@ func (t *Texture) CopyResize(renderer *sdl.Renderer, dst Rectangle) {
t.CopyPartResize(renderer, Rect(0, 0, t.size.X, t.size.Y), dst) t.CopyPartResize(renderer, Rect(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) { // func (t *Texture) CopyF(renderer *sdl.Renderer, dst *sdl.FRect) {
// renderer.CopyF(t.texture, t.rect, dst) // Depends on SDL >=2.0.10 // renderer.CopyF(t.texture, t.rect, dst) // Depends on SDL >=2.0.10
// } // }