Added intro dialog.

Refactored event handling to be able to "handle" events so no other controls will handle the same event again.
This commit is contained in:
Sander Schobers 2020-05-11 11:44:50 +02:00
parent a72f416650
commit 9641719579
25 changed files with 467 additions and 47 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) {
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())
b.Container.Render(ctx)
}

View File

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

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-research: images/genericItem_color_111.png
control-settings: images/gear.png
control-save: images/save.png
control-load: images/import.png
control-new: images/return.png
control-information: images/information.png
control-quit: images/power.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-disabled: images/fastForward_disabled.png
control-cancel: images/cross.png
control-confirm: images/checkmark.png
tile-dirt: images/tile_dirt.png
tile-grass: images/tile_grass.png
tile-snow: images/tile_snow.png

View File

@ -28,7 +28,7 @@ func run() error {
}
defer sdl.Quit()
// logSDLVersion()
logSDLVersion()
if err := ttf.Init(); err != nil {
return err
@ -56,7 +56,7 @@ func run() error {
sdl.SetHint(sdl.HINT_RENDER_VSYNC, "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.Size.X, ctx.Settings.Window.Size.Y,
sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
@ -74,10 +74,11 @@ func run() error {
ctx.Init(renderer)
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: "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
@ -93,18 +94,25 @@ func run() error {
game := tins2020.NewGame()
app := tins2020.NewContainer()
overlays := tins2020.NewContainer()
gameControls := tins2020.NewGameControls(game)
overlays.AddChild(gameControls)
overlays.AddChild(&tins2020.FPS{})
content := tins2020.NewContainer()
dialogs := tins2020.NewDialogs()
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)
content.AddChild(tins2020.NewTerrainRenderer(game))
err = app.Init(ctx)
if err != nil {
return err
}
dialogs.ShowIntro()
w, h := window.GetSize()
app.Arrange(ctx, tins2020.Rect(0, 0, w, h))

View File

@ -6,4 +6,17 @@ import (
"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 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) {
c.ControlBase.Handle(ctx, event)
for _, child := range c.Children {
child.Handle(ctx, event)
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 {

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 {
Init(*Context) error
Arrange(*Context, Rectangle)
Handle(*Context, sdl.Event)
Handle(*Context, sdl.Event) bool
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) 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) {
case *sdl.MouseMotionEvent:
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 {
ControlBase
Show *bool
start time.Time
stamp time.Duration
slot int
@ -25,6 +26,10 @@ func (f *FPS) Init(*Context) error {
}
func (f *FPS) Render(ctx *Context) {
if f.Show == nil || !*f.Show {
return
}
elapsed := time.Since(f.start)
stamp := elapsed / (20 * time.Millisecond)
for f.stamp < stamp {

View File

@ -7,6 +7,8 @@ import (
)
type Game struct {
Debug bool
Balance int
Speed GameSpeed
SpeedBeforePause GameSpeed
@ -29,7 +31,7 @@ const (
)
const simulationInterval = 120 * time.Millisecond
const fastSimulationInterval = 40 * time.Millisecond
const fastSimulationInterval = 20 * time.Millisecond
func NewGame() *Game {
terrain := &Map{

View File

@ -7,7 +7,8 @@ import (
type GameControls struct {
Container
game *Game
game *Game
dialogs *Dialogs
menu ButtonBar
top ButtonBar
@ -22,8 +23,8 @@ type GameControls struct {
research *IconButton
}
func NewGameControls(game *Game) *GameControls {
return &GameControls{game: game}
func NewGameControls(game *Game, dialogs *Dialogs) *GameControls {
return &GameControls{game: game, dialogs: dialogs}
}
func (c *GameControls) createBuyFlowerButton(id string) *BuyFlowerButton {
@ -75,6 +76,8 @@ func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) {
func (c *GameControls) Init(ctx *Context) error {
c.game.SpeedChanged().RegisterItf(c.speedChanged)
c.game.ToolChanged().RegisterItf(c.toolChanged)
c.dialogs.DialogOpened().Register(func() { c.game.Pause() })
c.dialogs.DialogClosed().Register(func() { c.game.Resume() })
c.flowers.Background = MustHexColor("#356dad")
c.flowers.ButtonLength = 64
@ -104,7 +107,7 @@ func (c *GameControls) Init(ctx *Context) error {
c.menu.Background = MustHexColor("#356dad")
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-load", func(ctx *Context) {
c.game.Load()
@ -126,11 +129,14 @@ func (c *GameControls) Init(ctx *Context) error {
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) {
c.Container.Handle(ctx, event)
func (c *GameControls) Handle(ctx *Context, event sdl.Event) bool {
if c.Container.Handle(ctx, event) {
return true
}
switch e := event.(type) {
case *sdl.KeyboardEvent:
@ -148,18 +154,22 @@ func (c *GameControls) Handle(ctx *Context, event sdl.Event) {
c.game.SelectResearch()
case sdl.K_ESCAPE:
if c.game.Tool() == nil {
// TODO: display menu
c.dialogs.ShowIntro()
} else {
c.game.CancelTool()
}
return true
case sdl.K_F3:
c.game.Debug = !c.game.Debug
}
}
}
return false
}
func (c *GameControls) Render(ctx *Context) {
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.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
type HoverEffect int
const (
HoverEffectLigthen HoverEffect = iota
HoverEffectColor
)
type IconButton struct {
ControlBase
@ -9,17 +16,13 @@ type IconButton struct {
IconScale Scale
IconWidth int32
IconActive HoverEffect
IconHover HoverEffect
IsActive bool
IsDisabled bool
}
type Scale int
const (
ScaleCenter Scale = iota
ScaleStretch
)
func NewIconButton(icon string, onClick EventContextFn) *IconButton {
return &IconButton{
ControlBase: ControlBase{
@ -47,7 +50,11 @@ func (b *IconButton) activeTexture(ctx *Context) *Texture {
func (b *IconButton) Render(ctx *Context) {
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 {
size := iconTexture.Size()
@ -60,7 +67,16 @@ func (b *IconButton) Render(ctx *Context) {
} else {
iconTexture.CopyResize(ctx.Renderer, b.Bounds)
}
if (b.IsMouseOver && !b.IsDisabled) || b.IsActive {
mouseOverTexture.CopyResize(ctx.Renderer, b.Bounds)
if (hover && b.IconHover == HoverEffectLigthen) || (b.IsActive && b.IconActive == HoverEffectLigthen) {
SetDrawColor(ctx.Renderer, TransparentWhite)
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
}
iconTexture.SetColor(White)
}
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 {
return "", err
}
dir := filepath.Join(config, "tins2020_flowers_sim")
dir := filepath.Join(config, "tins2020_botanim")
err = os.MkdirAll(dir, 0777)
if err != nil {
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)
}

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
}
func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool {
switch e := event.(type) {
case *sdl.MouseButtonEvent:
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
@ -97,6 +97,7 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
r.project.update(ctx.Renderer)
}
}
return false
}
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)
}
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
// }