From b14f79a61af3d026cd94c5817bc113200b372cc2 Mon Sep 17 00:00:00 2001 From: Sander Schobers Date: Sun, 10 May 2020 17:16:18 +0200 Subject: [PATCH] Lots of UI work. Added more icons and placed buttons on the bars. Implemented pause/run/fast. --- animation.go | 56 +++++++ buttonbar.go | 57 +++++++ buyflowerbutton.go | 95 ++++++++++++ cmd/tins2020/res/images/basket.png | Bin 0 -> 15216 bytes cmd/tins2020/res/images/fastForward.png | Bin 0 -> 15165 bytes cmd/tins2020/res/images/forward.png | Bin 0 -> 15117 bytes cmd/tins2020/res/images/gear.png | Bin 0 -> 15475 bytes .../res/images/genericItem_color_022.png | Bin 0 -> 2049 bytes .../res/images/genericItem_color_111.png | Bin 0 -> 2193 bytes cmd/tins2020/res/images/pause.png | Bin 0 -> 15015 bytes cmd/tins2020/res/images/power.png | Bin 0 -> 15372 bytes cmd/tins2020/res/images/save.png | Bin 0 -> 15040 bytes cmd/tins2020/res/textures.txt | 13 +- cmd/tins2020/tins2020.go | 13 +- color.go | 8 + container.go | 26 +++- control.go | 24 +++ fonts.go | 20 +-- fpsrenderer.go | 2 +- game.go | 52 +++++-- gamecontrols.go | 139 +++++++----------- iconbutton.go | 62 ++++++++ math.go | 14 ++ money.go | 5 + projection.go | 27 ++-- rect.go | 16 +- terrainrenderer.go | 30 ++-- textures.go | 30 ++-- 28 files changed, 522 insertions(+), 167 deletions(-) create mode 100644 animation.go create mode 100644 buttonbar.go create mode 100644 buyflowerbutton.go create mode 100644 cmd/tins2020/res/images/basket.png create mode 100644 cmd/tins2020/res/images/fastForward.png create mode 100644 cmd/tins2020/res/images/forward.png create mode 100644 cmd/tins2020/res/images/gear.png create mode 100644 cmd/tins2020/res/images/genericItem_color_022.png create mode 100644 cmd/tins2020/res/images/genericItem_color_111.png create mode 100644 cmd/tins2020/res/images/pause.png create mode 100644 cmd/tins2020/res/images/power.png create mode 100644 cmd/tins2020/res/images/save.png create mode 100644 iconbutton.go create mode 100644 money.go diff --git a/animation.go b/animation.go new file mode 100644 index 0000000..4afd21b --- /dev/null +++ b/animation.go @@ -0,0 +1,56 @@ +package tins2020 + +import "time" + +type Animation struct { + active bool + start time.Time + interval time.Duration + lastUpdate time.Duration +} + +func NewAnimation(interval time.Duration) Animation { + return Animation{ + active: true, + start: time.Now(), + interval: interval, + } +} + +func NewAnimationPtr(interval time.Duration) *Animation { + ani := NewAnimation(interval) + return &ani +} + +func (a *Animation) Animate() bool { + since := time.Since(a.start) + if !a.active || since < a.lastUpdate+a.interval { + return false + } + a.lastUpdate += a.interval + return true +} + +func (a *Animation) AnimateFn(fn func()) { + since := time.Since(a.start) + for a.active && since > a.lastUpdate+a.interval { + fn() + a.lastUpdate += a.interval + } +} + +func (a *Animation) Pause() { + a.active = false +} + +func (a *Animation) Run() { + if a.active { + return + } + a.active = true + a.start = time.Now() +} + +func (a *Animation) SetInterval(interval time.Duration) { + a.interval = interval +} diff --git a/buttonbar.go b/buttonbar.go new file mode 100644 index 0000000..3fe82ee --- /dev/null +++ b/buttonbar.go @@ -0,0 +1,57 @@ +package tins2020 + +import "github.com/veandco/go-sdl2/sdl" + +type ButtonBar struct { + Container + + Background sdl.Color + Orientation Orientation + Buttons []Control +} + +const buttonBarWidth = 96 + +func (b *ButtonBar) Init(ctx *Context) error { + for i := range b.Buttons { + b.AddChild(b.Buttons[i]) + } + return b.Container.Init(ctx) +} + +func (b *ButtonBar) Arrange(ctx *Context, bounds Rectangle) { + b.Container.Arrange(ctx, bounds) + switch b.Orientation { + case OrientationHorizontal: + length := bounds.H + offset := bounds.X + for i := range b.Buttons { + b.Buttons[i].Arrange(ctx, RectSize(offset, bounds.Y, length, length)) + offset += length + } + default: + length := bounds.W + offset := bounds.Y + for i := range b.Buttons { + b.Buttons[i].Arrange(ctx, RectSize(bounds.X, offset, length, length)) + offset += length + } + } +} + +func (b *ButtonBar) Handle(ctx *Context, event sdl.Event) { + b.Container.Handle(ctx, event) +} + +func (b *ButtonBar) Render(ctx *Context) { + ctx.Renderer.SetDrawColor(b.Background.R, b.Background.G, b.Background.B, b.Background.A) + ctx.Renderer.FillRect(b.Bounds.SDLPtr()) + b.Container.Render(ctx) +} + +type Orientation int + +const ( + OrientationVertical Orientation = iota + OrientationHorizontal +) diff --git a/buyflowerbutton.go b/buyflowerbutton.go new file mode 100644 index 0000000..282e2b2 --- /dev/null +++ b/buyflowerbutton.go @@ -0,0 +1,95 @@ +package tins2020 + +import ( + "fmt" + "time" + + "github.com/veandco/go-sdl2/sdl" +) + +type BuyFlowerButton struct { + IconButton + + Name string + Price int + Description string + + hoverAnimation *Animation + hoverOffset int32 + hoverTexture *Texture + priceTexture *Texture +} + +func NewBuyFlowerButton(icon, iconDisabled, name string, price int, description string, isDisabled bool, onClick EventFn) *BuyFlowerButton { + return &BuyFlowerButton{ + IconButton: *NewIconButtonConfig(icon, onClick, func(b *IconButton) { + b.IconDisabled = iconDisabled + b.IsDisabled = isDisabled + }), + Name: name, + Price: price, + Description: description, + } +} + +func (b *BuyFlowerButton) animate() { + b.hoverOffset++ + if b.hoverOffset > b.hoverTexture.Size().X+b.Bounds.W { + b.hoverOffset = b.priceTexture.Size().X + } +} + +func (b *BuyFlowerButton) Init(ctx *Context) error { + text := fmt.Sprintf("%s - %s - %s", FmtMoney(b.Price), b.Name, b.Description) + font := ctx.Fonts.Font("small") + color := MustHexColor("#ffffff") + texture, err := font.Render(ctx.Renderer, text, color) + if err != nil { + return err + } + b.hoverTexture = texture + texture, err = font.Render(ctx.Renderer, FmtMoney(b.Price), color) + if err != nil { + return err + } + b.priceTexture = texture + return nil +} + +func (b *BuyFlowerButton) Handle(ctx *Context, event sdl.Event) { + b.IconButton.Handle(ctx, event) + if b.IsMouseOver && b.hoverAnimation == nil { + b.hoverAnimation = NewAnimationPtr(10 * time.Millisecond) + b.hoverOffset = b.priceTexture.Size().X + } else if !b.IsMouseOver { + b.hoverAnimation = nil + } +} + +func (b *BuyFlowerButton) Render(ctx *Context) { + iconTexture := b.activeTexture(ctx) + mouseOverTexture := ctx.Textures.Texture("control-hover") + + pos := Pt(b.Bounds.X, b.Bounds.Y) + iconTexture.CopyResize(ctx.Renderer, RectSize(pos.X, pos.Y-40, buttonBarWidth, 120)) + if b.IsMouseOver && !b.IsDisabled { + mouseOverTexture.Copy(ctx.Renderer, pos) + } + + if b.hoverAnimation != nil { + b.hoverAnimation.AnimateFn(b.animate) + } + + if b.IsMouseOver { + left := buttonBarWidth - 8 - b.hoverOffset + top := pos.Y + buttonBarWidth - 20 + if left < 0 { + part := Rect(-left, 0, b.hoverTexture.Size().X, b.hoverTexture.Size().Y) + b.hoverTexture.CopyPart(ctx.Renderer, part, Pt(pos.X, top)) + } else { + b.hoverTexture.Copy(ctx.Renderer, Pt(pos.X+left, top)) + } + } else { + b.priceTexture.Copy(ctx.Renderer, Pt(pos.X+buttonBarWidth-8-b.priceTexture.Size().X, pos.Y+buttonBarWidth-20)) + } +} diff --git a/cmd/tins2020/res/images/basket.png b/cmd/tins2020/res/images/basket.png new file mode 100644 index 0000000000000000000000000000000000000000..3750941da7c33d5ce69a5dbddd8e8891636d4f6a GIT binary patch literal 15216 zcmeI3eQXnD9LH}VkTIMYFNuR0ymS*qXs>r`xAo{cwszeX=$ze@ZHa+f@9B1|?UlRR z+AYe9s2Pe0K}`r*5TpD-qC^B=39ulVXhgHH_?9h-L7j+!kZ3X>!RPw=v}<{0!kj_xFA7`8>bp>HTwi+FF~6i&hj-6jj{P?DE2IPX8-h06$mOjU0zx3&YJ_Dn%_? zs{a*G2S-aO%J!Pn(W!NMHu63>Xc9!Z2biM4FjP~Ny)GISeEmS9dqA%gsx@3Yb=E*j zVy$75)noRAouE%@9#%m6aBGKexZh_J4Rwwpdz6O;L7)kAG#Ch}e6-dO^W|ZzH!}u0 zCeiw94fXnfbf>3{cFGE%ttP9{XXaSC+Gb*{78}d1r8zTO$*^XIvl&^Iw^Z|N6`gn) z97Qm)E25wGy4;E3;D5D-K1~br3=@e&Op!{Htn@Oh&1PfF9K&%&s4%KSAx(%HLuz^4 zBYs^SH(Hx8#2SG^7X(jqnKH&i+5Cbjx z7!UwKn4&@oY|goa`(#a4`{X3%w_A8i-1H()6BqTCAqxOKpdR%sUkz zXtL5F%K=BcT-%aYPdlCTqno6VC`Z)Qb|zb$u9>tEa0wc4z?!zeYBrj=4vyt3S>9Z| z#tc8rh)G8S<$v-s9gM$XVl|wxcWwr<0gQB1bb8;!sE(e^u zNs>gq3Wyd9YhjI5R-4Ue<6u++ceK&c!vP@r{Wf!De3F^zv#edRZ$Pi1gtZ8*IcE*) zuHhQ0IH#Ls8!a}AldX5N?uHt-)51FIW9*D(mnrY0r5$BAgrkzt5(o@YUX~^ht zMS>@~5I|VzEBJpf=deI70=mIrnk5x(ogrj=ect-H+77m5wkA6Q(i|5iwLI`N;{~67 zVmnj;F0P%4op8yv^hL!p^nv1n9BVqa@|~5Qe~d^U2xV;dtgRZiimCFOsIp&+2nwk0 zg*yUs2*nLEGx9t8`h-v~5FJbsTCyQK#m?xf7LYO*E#8{BmdW~?z4ms|$NOa^C}<8T zDD(m*9O|_*>BjV!_S7!`@00_w0uM9bsI)V=nvpF1?AJ>hQW9eIz}lHvZQ1%xed7lP zV)|;%3}5hW1HKZeXm@sM*WFXm?(Ecb=93{wb8v~#vYL@B{Rb)zqaEIq0Y{b9T4}X2 z+0EG*ZBi5jI$aVxOI3Y`SmC^4IoTaK6T9*yA{~jucqt&aPq9jgx5AqUY)o)r5upNt3#EaL2`(%mR6uZ{G_Wzjg++u42riTcHYT{R zh)@B+h0?&r1Q!+&Dj>K}8rYcN!XiQi1Q$vJ8xveuM5ut^LTO-Qf(wfX6%bq~4Qxzs zVG*GMf(xaAjR`I+B2++dp){~D!G%SH3J5Ng1~w+Ru!v9r!G+Sm#sn7@5h@_KP#V~n z;KCw81q2sL0~-@uSVX9R;6iC&V}c8d2o(@qC=F~(aA6Ul0)h*rfsF|+EFx4uaG^A? zF~NmJgbD~Qlm<4wE3TsSqn029pRbI-2Pv73>1Oy4ChcqXdMIl6VT#(ho1$j!!0$gP zYQRiUSGG_Te}JMMkoRx?+)YshPqnz}JEA{bJun_TXD)fMuI=Yni}{A-Z^@Pld}LQ?{9Snc z?c1jpL>Ij^zjf@^^E29>k?;Ckm+2iJl>HE3zFzmrKO3B*10UY{_KOPzmsgxG5jS?3 z`(HRPz4OAzh3|j)r0n=DYSP$K@q5MSxpj?CujzF*ZrSQww!5X^$H#*70{^BZmk&=b zY3B~{BZD{hp58ri@!FAPH;bQHwd$Ld(fPMupLqAs_Oit@<0e))d+Pl=-I4omJor`N zQOmz$8}^iL^KL%o9*=%>>^J$nraNtP`$c)_&8r8ues<*Ou%R~mPT{t_yP8io-JYDh za;g5)m+0~SBNN@$d1s8v%b$ICU;A2D>-h8^%O0}PQ=`B7o_J*c#m>K-FLz%tOm5t> m_^*=U^Xu<}?~10g^C> zq4e1G5B`OI(T_MhF=wQ*fz!;%Jyq8dA+;cocN+kbU);q{(1JE!1ReKNY$q^NmK z_FoP4+QG#XCG1msdad5r22s`%9!b&rfhV0vLN!JC*QAq@JPa(l9}KEm!1c$6AG>H( z3AncSVr(oK0z+za+yI-$H}=Tm!?K{b)&v{;X%QMEfF;rCL|ik)bikGI6=7^QGcGzK zv4#V#c6&g&H`YambOX>nkIyZ$JV&<)9?s_#IBpfqvs??qu?#P`IZpJpiCin4{kVb+ zF!CG9fY=?5WQT+Q1zbaxl@u8!l}dS1Egsz%WH><(7?x*v-VGIQb4;_Ov|BTq9h00- z7?`r5CM{LhXxmrn*GH^?%ViIg|77QtNahF9%q%-dkx5HQhV!sYm84kg#%YN}#Z1%c z28Eeb9MJ3;O9G}FnEHqz11N8|E^KGp2~tgU`OWvPYxf#!avTd6wJ5Y8D%EjZ^f6sB5I(h&V7Lxm^06h~OuEBJpf zSFk{C0=mIrqN)kE&KNSjJ#YJ5-3*>CZq0SX)mbh~etF<&CQ7n>Vh2qLF0P-+oN&3e z!c93D_CSuH!kW&me9O|SkC7SzTG4hd*{Y6JMpfO!)Ca7TWPtWTxFax!kYiY!QQcV{ zlC(jf1eqMPTtjJ!pRrdht`;wv(^|Zix%w+#d%q%!1G1D`hFC!30P zm!@vLaVpwfnp((wL{qIGpB=5F8OgGLpd1*R;X@e+w)%W6K0j01T$<6Pra`|skD|o=6tzg7 zeDc!>Mb$KShTD76-(EgxbrDd(JGKj0&;kyY`=%KRGSL=p*e5mQ601>018EOwaSTp8DY6OK-lg zZ`B*Fr|83{kJQamtVe_VZFf%Besb~H`IYMe$M5d{N4~?E-m3>br`#7#T|1!f_|AU8 OwX0&9XZB0aGXU?{h?#`?; z)9$t=TIG`BE!7xBF}`SmyckPjqSk}}f(nX6gAwEH!57iQM52ff;+egky>V*7`Ga>n8dsoor&kE2W1x%67rjn{5WCQk`uK;7Kn6cA2i8&Fl zcUuk8Bhf)Rr0Ib6I6V%Dd6siA9Lw-N2geESPJ#2%h07ka!N{-6 z31KK4DKrP~0`_s!ObZN?$z+@vms8Wn7|!SOF)YvUyaOs6#w;nSPQf7+5G}!|cM7OxT`O5C`J^ZHWDc|}F(3(2FvWlr zxVmFWk87r8jB5=5mZ}?KG?hdH7FJEeG>}yqq**ojwA`A?PqiN|WFe#j(bV*qrX_>< zBy@X8KptDAGg6LAUNvR)9610Z{Sxj|N@*I0)&Y0_Fl1#1~v^F5*7&fcwFFCU6< zT(8^b4sqQPF4D6#5^{5)?xHP{T5Ah*WeUgr5l%BIxv<=(zw{3v zgtVll!@~>&U4EusF_LAS{npZkl*HV6;QUNYS+#z|+W5)IoW7H1;RT;I;8jRPtE*E- zubqlkSErUU?^hKw$QN3xDn_!bKd3y69q^$H1ic=Q%j0LNi>osRl`Ke(gcW#}8rBZ+ zz;Wersw?Uyjy6g}Dhi4DrGPv>cO7fJ&g6I@tCsDR)?X<*}P;P|`zu%B?=KWJ$x_tCT@)o8ps2Ol3lE=+ zK<%aeaCa>G?Qa*3H6LeJFF*I=z(-B*tjcyoW>>ZEZEJs``S{+}n-Bf{_~J{BKZgz< ze*M9Zrxri=ZQs=NR5-Nm%lUn7`Q`bJ*%h-_0%y0+rxuQMEnENC*@vj_E;n7tEUZ6q z`uT(Zc%-`)Z#nV9yK?)*C4{Z)};1J@dhluh^^_^bnAl67k~7NmbKx1LVV}b&)Vib*?H!}wHw;jv|O57xb^n7 zHK$+s{+rI*+SVlJRv+8b*RpYbxM|OdpKU*Fc$WJ5=&3{d|CP?eUcR}hd366xaJ*Nq b-axfY^F7b~amRb`mg?`_7Jj>D*OUJNFyDEq literal 0 HcmV?d00001 diff --git a/cmd/tins2020/res/images/gear.png b/cmd/tins2020/res/images/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..9663f903d9b24b5e71f8ea3cc395879444c575f3 GIT binary patch literal 15475 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 literal 0 HcmV?d00001 diff --git a/cmd/tins2020/res/images/genericItem_color_022.png b/cmd/tins2020/res/images/genericItem_color_022.png new file mode 100644 index 0000000000000000000000000000000000000000..128d3ef2c42a7c55656e69ec6495e61af617b1f3 GIT binary patch literal 2049 zcmV+c2>$npP)^k@9%eSZ*Ti| zcXy+w>IgCpOM%!{;Pn9F!^1;lD)sf@#l^*jAZA(UL{$R??2-aBS@?czh9H>Ip?K8u z0%`!cD~LXV+O<9ME(Kn0G^ncPzCmp?z~EDEP#Z@PYLaidJpSruA|=0~tE;Q%^71lL zud}MgQ~E#2Fz9kAe3PEhs22G**3O~B$Y z%{uvg7NsI8WiBu%Of=xC0DP252gAfvNfj*1qyZQH4L&^sccsa#5P*&A`uf@gZa;K7 zcxGq0)qqQ-$+MuT0Yjk*z*60dyrU948xj>{?cN|&Ly~9Jzk>$WB+Za}1g&S$5upQX zQVwX6(A2?KbWnyt@Tq%4>e_ylcs2_)1fh4)wMq9?C1B&+H^@_pWY<=S4k*=7x?+GH zteQG772bPL5hUJ#{)Wm7V|*Km(!tdgLY2EVjdx20H>qo@+_hL2klE@Hfvw)lc zcThSd_}D8f3oZm=10*ClMOZBQ9;rImm(rS6{Q)Y(s(ERW;-sWE^)P;!Q3!w8;zf+1c6L zCna#5ENkEnT2)w}4By(8>i|>(w;C6rRoC`?Qvz2)7H@PVK0*arP=jviQ`>T$cm#b{ zEV;n9zO^l9;>}Z9`*IQZT6$D&x!yf9ybU+uY|M!{%k+{|oE@K#s_6#SXSoKlSXhS zlieh^aUC2Uu7FM|32KmmQ+*v@Rh|<5olNE?aP>MoI*Lv|eTtSAs|y})$!fLUPN(fG zz$R<1_*nrx_DKn~TWXN?UBG>JaIj1aqp?p)sKo+EPT)=^gS24;um#meuI;!0ivpbp zf>hV4?sEYa15nT-*52OUhHKY?rv*W(Yp2t$Py!#gW1)mK+1;!KHzx?vUE6a8S8a?S z=)vwj)3%CA1U&}3E0n;fZG{pT?JmM}L61q>;%5axIsqHHf(u6{2=drnp#;)65Jx!~ zT$%hwMI3fF4#j0L*xj^HT-MivtS^)hn%xyjumanWP&`1pE1|eW+q&9a3B?)hu22G_ z-4#mU(zX(c^VwYq#hJ9NgyNP}3d8!>014@nyCa*07SP!A)xaLbnUY7bWr56r@`GC2pup0+jvSS%BMmJCD6eWCG?w2 z2Lmo(Q7|42%?Vy~ERXc4h)Rg{ZR0o>bO1i2xH5KBFclwmwt7YA13wFjFd3AZ(E;6& zrfx$}p3HL{x_N}xX(}C9ttvVYok(@RHPBH{L!;>6OJ^y0mLucs&Uf15yb`T;K~A0l z1;Fw=zEB2GA6dG&u3bBF8Af?P25^n*TKzG0nf!5xLjm*1#dPP^u4_#l<}#4*Gb?G9 z0VwjcOXzwWfrus7a8nWg2>vb{14U z4ZUYwo5PeeFywKMCZ%h$5519eE%VUJnkC461;(M*i2Rn5k4;0b@ki>MLr=PvY3Mbr zYd-^n)xe%}vj6n}001p?MObuGZ)S9NVRB^vVtFoNY;SL5WO*)Qa(QrcZ!T$VVP|D7 fP)Z)9b1s0M%T00000NkvXXu0mjf5~R3n literal 0 HcmV?d00001 diff --git a/cmd/tins2020/res/images/genericItem_color_111.png b/cmd/tins2020/res/images/genericItem_color_111.png new file mode 100644 index 0000000000000000000000000000000000000000..20d5ed63b5f27cca7350380795e0a4cc94a45cd8 GIT binary patch literal 2193 zcmV;C2yXX@P)z1^@s6OOAhU000OdNkl9=QkdiM4-x8ZpD`1{?%yFF^|wA=CY>CcCMm&5UF zadC0CPd#FJ10=u#G-m$K&(t%HL1TsyNIm2jv<P#77{C@G;NXn0`N#_njUqx>@65H*!N}c^_%zaXwoHw_N4oho4Pp`WjZ`785bdYV)zQZSNn0EPS5^JqG8#=qGBE%0--rc`Jx z|8L%orunP}SZkdh+5*mNbhP-hT-2S;8#rEn=6sEoJb_E=&v@Wj8z$rhoTxu@w(hh| z;6#J)-jAQ917oWyR!^6DIWU3iagRq8FN{%m^{#fc>_ zmAl>CIVrutrruW)Je+`4;m@2~J@FcC<4az(ZtqQD(zsN2RsL)>Jx<-(w_|TNvapOQ zO0Rto{$AVX6jb^%=k87jPf19fGd4|j29eVfgQwD;Ay#v$l5Lb9!G@-GXudGQdTHGE z{OQ-5LskCFxeB&%&L8=X*j~{I4tXwd-xJf9vZ>Mb7>ySiGX%l+^WrG=&g9#SQCeb=-%Napq;3(;aSfG5-EL5L(lQw7hd7%dC5 zuvJ^Vg@n^O^IhsuB|PWux03Owz1t;^6vTNZfE#b~mi#T``& z&dlMUBTl787=#BtlXis3gy&3*hE9V2K!?`a=W}dI!bB2qY&@cr=ALpVVl+%cNvr!1 z7`&%JvBdQC$;p9@&l;4^Pk`q{jD{6;nK}A^X8U=vUGSWU(b7Uof(M`IivNy+=R}N_ zHBY44I*(M2F10W`XJRxN=h#?AmCJ|R0nZt^FY9Ct1PfK-(b#03tl8dmrlzn--B$5^ zCH1I_;IRle#D7cZ^NXhN+v&Kl|cr8*oq@&WD?2T~w`) zlbF0}<6A7V@&j@;*Kp^?0n1EYm9+>g^~Kas)yN27(G`=ZYIZipa5fYYNCfP^TnsSt zzljPRACNI!+8TiK`Z=2|yuHN$vsi8-v%-OG@Jv-W(s3?g4?oB(IcO6+6Mjxx8sa*Y zV4m&|^mi8dv<05ceok7_FK?Mb-pV~Tz{`W5<5KTXHBr+GrbTSwq&=c*)3nJ{8*}o3 z(-(9T#{C;l%bG0Y!gfzkH`<3dSi|F8NkAG{PIK9(g2!=Ht(t`YKYQSh=pIFgERxvfrMtIL<< zOpCiJrQ4cI8tMc&Z!JbJ#1Gz5z?A*qI3CCizdp-n+_loBz1i00fj@qpe0!RX+ z*Z}b$SnespT4Zhjt_8@VDA2YSl%1?3p<@%00LqTdH0)^+K$)ZLv~ozJ%rSs@6d)`~ zHPS*^$x(*GEi9=u%DiUR?9PG8#bKPE+tqoyIxd_I9A`#z`At@us5E-zO1=Fm`qt^b z6vC?0GbX`hJxW|;BX9(cz!5ky0!QEo9DyTn1dhNFI08rD2ppMh;Qj-Pm#C%BwUt`{ z001p?MObuGZ)S9NVRB^vVtFoNY;SL5WO*)Qa(QrcZ!T$VVP|D7P)Z)9b1 Ts0M%T00000NkvXXu0mjfK8GXd literal 0 HcmV?d00001 diff --git a/cmd/tins2020/res/images/pause.png b/cmd/tins2020/res/images/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..2fc9a9b2647a8acd3812aba8dfe49534e8871a55 GIT binary patch literal 15015 zcmeI3&ubiI7{}itN=yk@Y^ivVajIaeyYpjzBs%)&m28>g?v5kR8D#b?R_%% z>_H!2dJ3W32j{S&gFjjrS6d1(XkcICT8+BpDXlyo`YI$2ngyNX+#Zhnm*>}f-%$ji*=#18sif_$3Q{hY6GT~%H@bdI;Lk1zg7slHw5x z($Kj$YqoEDYxW3$-R_YXeX}-#MbI-c4Y$k@X+ckv7S>d>)G8@trRZYKx7{V%t{0+m zo$uT|S1fWboG>lJZhD7RA+GM#OwS0HG#?j8O{YmUCq#KkmXwsFh}joK@>c{Vkuv5{ zwGFejIp85H;*=uF&;uyr9%K(1ny>B31#0_!s#Rsq@_fzG@m#4u=AJZ7LrG2QnpVqB zCZ@7ABcZ3#IFYMO=?O{0>GX;=HDyTIaFYG$1J)&5-w0}`ZEX;1d1f|~k+S8iTr5k{ zY&w@NO4DVjJTp};rlsO^#}=dx+G5u{jg7KvH^S}hXs(BYU6bcK+opRSv}+*TcZxYU z)%N`qN8istYVUrCxf*pFBWGHoIfK-ma~DHzsz$#e5|ATpm1-J>~ zMuw@F9@#pZVEka-!MVDK-{^1cbkxmVE=+fM$Z4i%dT?SFJdG@_DugFor>%EWQARLO zBp9-$yDQ&;^wDE9*Ra*M-D6udvIIfNp^{*2Mi`kH2~Vxu5* zpmiGJDXI{xTHWklw5YXzEj#r$xb~`{D>d6~Xnw(LXscLotW{O$HTK3-yT1TR(XQJr zIm~b&r3%BEK~`|~2c=CYX<5O0oW%$qG-UoM&CEcJpNk|Fa7bK)IC z6Hksx1Rd?fs1(5CQ&=fcD|t90&xJzw!LYxGy4&(02fZ0RWMgQ@27xrFF~dbg1O*Hi zq(O}tE-E4@V7MR+YRqs^5kUdN1!+)YhKq^_3K%X(gBmkjR76m~a6uZ>nBk%#f&zvM z(xAo+7Zni{FkFxZHDIYL5&$MDk3OgxF8K`%y3Z=K>@=B zX;5Q^i;4&e7%oVI8Z%r}L{PwRK^oMU;i4jf0)`9HpvDXr6%iCLT#yDeX1J(`pn&0m zG^jDdMMVS!3>Tz9jTtT~A}Cx$^pj17BXLoE_W88;$Kf z`#-z?)~|om?p{5AWA9&k{{HsQiAy)`rMK^-xQqXMy6@SUKvz2VM1Sz(%}0NF=e7G^ q-@0dgw{s`bY|p%Z5TTu&N04;j$o(Tbl1^?6&CMPweLi#Q-TweTdrffw literal 0 HcmV?d00001 diff --git a/cmd/tins2020/res/images/power.png b/cmd/tins2020/res/images/power.png new file mode 100644 index 0000000000000000000000000000000000000000..60f2d5fc6f829e1604e29927ce3c048e44e9cb31 GIT binary patch literal 15372 zcmeI3e{dA#8OL8TZ3F8Abpzt8vG_wzpQd;8BmzP|Ck`DF{s008qF>H|&an>RmmOVQsuS3dSK z`kEK7Z_xp`soea`0f(Ns9RSadm2k7s99k<#YP4FE)izk2jK+}~0Po6VT$H+?!L-2+ zC01*_eDai)QRG_dW>?4_iu+-wQs1k=jlGRwskck=$kvsyq3$hfLqHp$L&%nDC6=W=>DZaKr-IS0$xS>9vg zIKjC>;M`2+viizU3 z-V-y#q%Ec|N}FVT0#KJUC2lBcj4^%1HnrQRwOY-Aa#vOMCP5@$G}|rc#+vPDN*P2qV6?xWClS+{^vnQRF zs;Tr+dr=_^ehrF-s)bcG;!Bt7`t02^em`^1W+f)834NKDEmY@gCT|1-q5*xVrk$vo zZFWA)bAp2t?CyK*=&v0!Nh=FHLaMB^_ssC%1$&KP=W!2AS?GcGpe!2V4Y}aj={}*5 z&=AuNF($!=fDg^RT2W-7#_4p+o*LQ4uV@!-PLETx)xer|8?50a=xS@1+U#6vlGD>? zSO-+8+pM9CwT!HJzl#g3cDUWVKge-wkhh^DmrQ4&-2fR36O zZH4+~bK^(4Q~DLW9bM>Y16`R^yt^>9<@%|3cVTKib5l$)e0*lKf@Uns{DDft*oYp= zpwI1cIb2@0u(>c}y^@5H=756EQr+AkE;O%HPGQHai7mwvv5rh)x)iX-r&Oh+ThYTA zdM;#h4~El=D7P)Y;2=MvFW4Bjql4fyh%v=QM1%_{E}RB2rnrcRZ~?`I(;&ta7ZDLI zptx`v#F*kDBEkg}7fyp1Q(QzuxPaoqX%J(Ii--spP+T|-VoY%n5#a)g3#UPhDJ~)+ zTtIQ*G>9?9MMQ)PC@!1^F{ZePh;RYLh0`F$6c-T@E}*z@8pN35A|k>C6c#miyv4dC8-v9q-=s z(~$-BB@N8H#g8l=c>m_NfaQs|N9GRiS-v6s`esF0VmSf9L*RR(J0>qU^sOVS|1-RH z?wgORC*M@ZYWBZ2`i;cZ!=r=4Q@e~OM#7Io>-f7yIw$tczi_1f*gT-zvEz7O`tCb;ma!oOH_%w66N@Q%gT8*}LJ(pY?8TT@|jY zy4y0q9{k(dsj-KCU9#%(b5FOPc%XD!(`Wm^?l%tqA>4Y(e(#el9}Zm@xM!+=mEpUx zc;U73GvjYOyubD7lAZVe>%7)_`ASJi^2(W`#@Sbu;ITdbJURD8`Qm~8@-xp)ymi^8xnuHH275C!6SDr`!Lk>-b5fV zn5^hOxARxee==tI-H`YEroBVg-g^BvJyScBvqx?nkL^pB+4ZU*DzIKL0@5_}JTrCXW95tmWH({rkwWg&pAX@JEBEe_=3N zhko*v(JfPh3l9Er{CL%-%9o|9D~~_7WN$^)_{FVL&o4b}DK}T=_meL0@NZxF;OZfV R#e844VNGM;*y?S&{s+hz{qq0- literal 0 HcmV?d00001 diff --git a/cmd/tins2020/res/images/save.png b/cmd/tins2020/res/images/save.png new file mode 100644 index 0000000000000000000000000000000000000000..d9e1d6e1c9978ff2dfe986b20996d7b8fb7d7b4f GIT binary patch literal 15040 zcmeI3TWl0n7{?Eah((m>14`74TSzdtJD1&QyOY^1Ww%Q=VN282P~rnjXU?`0c4yX^ zDZ6cmqP_?rF(8SQsPTy?7$b>BqYuP*;|(DOf-%uYyd=Ju7!yoAv$L0PZ=9O&cqZBI z*K_{g|IGQF@0^{NIW{rAdt=|0K7`Q5>{xn|d`tdk{W|jZso~cylCK9!WBXl%HazNo z)}XU*J%&*Hv^hQF&E)ndx?PNDhCPcTm12pg5mJXMB~3qsJ#H4ynN}k7=NFekoM|LN z`{bOEE2Z$fIkx2BsipC0ed&-MH$ub7KDD9{gCh1cu2L*mu2M;as=f+|{boMIRVCh` zL}wA^W}0mQjSJ!XO0)+@i;F?yd;H*BJ3_&o>mE4Zhv4>_eo<{ zcg&J!+7{>gYP0r&mk5RYff`qBUd2*lAj_?>BNX|HR^r8o!1qYXBSP}C$VcUI65Zs{%*J6dZUNQlxm1pVKjOeTV7WyLvQ$$7MwyAw4w93fQux> zB^1Q2b1BW+p6$-tJpeYkdt&s=d=D0WPtP=5F?*!>JwaNvrh=tbNg*pK2Wy_~Oxt!L z8ISoO}jc{7?aFWz?j8t=2kftS3iHeFautOk!1z-{= z+dOi%VdfV*JS0UJQiLe<0Lpd`vIh;#(^lmIwXHt6oRYO%PqTEKO()6RBc^F6LQFO! z92LV-G_Qw;W@B1d8=95ELqoA>T+Yi_9vZApvNgTKI&JFQ|ug49l1?3jnKk#X!|b$gdI$Ho4xNrdX#rg0wBYM{FB z6ti=xwfl+nzMqcN=KTD445Un8xyu(@fEH|HMwZ8d+SGuby!Aw&qO* z8U8?lpv#)Bu6#Svdyi3`$5zXBZ`-PYRaMp7#I^HYS#$8n9N7_+Lntt8&FJl{&ui8k zHj;cDTD_q?MdkffE10c|7PPjmWxf77*IqSrC2u=L%}bg^Z4UD#Yfj~xjm|YBTdW%k+{`ZM3##kmdhC1u&+_Lm5sE$Z}Lx`S#}aj0v-X3o~hxoTaY6Lu4|q zYEFAc*Tnt35^6b@nG0mM2&5EmxJbv?y@noV}n2%)R^I- zB7y>j3(}y*3>Os<6fj(n1~q25sED9|;es@%F~dbg1O*Hiq(O}tE-E4@V7MR+YRqs^ z5kUdN1!+)YhKq^_3K%X(gBmkjR76m~a6uZ>nBk%#f&zvM(xAo+7Zni{FkFxZHDIYL5&$MDk3OgxF8K`%y3Z=K>@=BX;5Q^i;4&e7%oVI8Z%r} zL{PwRK^oMU;i4jf0)`9HpvDXr6%iCLT#yDeX1J(`pn&0mG^p_%arHG{wZs;Azp_kT zq|EkTh>({sIelz0htSeCgpR(7&_65W`&Wb(1cZJ&h>&syq0RQG1K(tb_FOhSGF|!c zj~mZFbd-BAef9cBdp|w5xGdg0XazfUZ_ed5xQr_WEl zu5P=gzI67D#OJ$@{P6YG$(yO4UVQn3>;1{kA{Rdpn;I-dS;*TGl*1AE|7)Bpeg literal 0 HcmV?d00001 diff --git a/cmd/tins2020/res/textures.txt b/cmd/tins2020/res/textures.txt index 75fa049..a3fb192 100644 --- a/cmd/tins2020/res/textures.txt +++ b/cmd/tins2020/res/textures.txt @@ -1,4 +1,15 @@ -game-control-hover: images/game_control_hover.png +control-hover: images/game_control_hover.png +control-shovel: images/genericItem_color_022.png +control-research: images/genericItem_color_111.png + +control-settings: images/gear.png +control-save: images/save.png +control-load: images/basket.png +control-quit: images/power.png + +control-pause: images/pause.png +control-run: images/forward.png +control-run-fast: images/fastForward.png tile-dirt: images/tile_dirt.png tile-grass: images/tile_grass.png diff --git a/cmd/tins2020/tins2020.go b/cmd/tins2020/tins2020.go index e50d65e..a1279b8 100644 --- a/cmd/tins2020/tins2020.go +++ b/cmd/tins2020/tins2020.go @@ -67,9 +67,10 @@ func run() error { ctx.Init(renderer) err = ctx.Fonts.LoadDesc( - tins2020.FontDescriptor{Name: "debug", Path: "fonts/OpenSans-Regular.ttf", Size: 12}, - tins2020.FontDescriptor{Name: "default", Path: "fonts/FiraMono-Regular.ttf", Size: 16}, - tins2020.FontDescriptor{Name: "small", Path: "fonts/FiraMono-Regular.ttf", Size: 12}, + 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}, ) if err != nil { return err @@ -86,7 +87,7 @@ func run() error { app := tins2020.NewContainer() overlays := tins2020.NewContainer() - gameControls := tins2020.NewGameControls() + gameControls := tins2020.NewGameControls(game) overlays.AddChild(gameControls) overlays.AddChild(&tins2020.FPS{}) content := tins2020.NewContainer() @@ -98,6 +99,9 @@ func run() error { return err } + w, h := window.GetSize() + app.Arrange(ctx, tins2020.Rect(0, 0, w, h)) + for { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { switch e := event.(type) { @@ -111,6 +115,7 @@ func run() error { ctx.Settings.Window.Location = tins2020.PtPtr(x, y) case sdl.WINDOWEVENT_SIZE_CHANGED: w, h := window.GetSize() + app.Arrange(ctx, tins2020.Rect(0, 0, w, h)) ctx.Settings.Window.Size = tins2020.PtPtr(w, h) } case *sdl.KeyboardEvent: diff --git a/color.go b/color.go index 4ef740b..2c65621 100644 --- a/color.go +++ b/color.go @@ -25,6 +25,14 @@ func HexColor(s string) (sdl.Color, error) { return sdl.Color{R: uint8(values[0]), G: uint8(values[1]), B: uint8(values[2]), A: uint8(a)}, nil } +func MustHexColor(s string) sdl.Color { + color, err := HexColor(s) + if err != nil { + panic(err) + } + return color +} + func HexToInt(s string) (int, error) { var i int for _, c := range s { diff --git a/container.go b/container.go index d4fbc90..4ca9673 100644 --- a/container.go +++ b/container.go @@ -5,6 +5,8 @@ import ( ) type Container struct { + ControlBase + Children []Control } @@ -16,19 +18,23 @@ 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) { + c.ControlBase.Handle(ctx, event) for _, child := range c.Children { child.Handle(ctx, event) } } -func (c *Container) Render(ctx *Context) { - for _, child := range c.Children { - child.Render(ctx) - } -} - func (c *Container) Init(ctx *Context) error { + c.ControlBase.Init(ctx) + for _, child := range c.Children { err := child.Init(ctx) if err != nil { @@ -37,3 +43,11 @@ func (c *Container) Init(ctx *Context) error { } return nil } + +func (c *Container) Render(ctx *Context) { + c.ControlBase.Render(ctx) + + for _, child := range c.Children { + child.Render(ctx) + } +} diff --git a/control.go b/control.go index 9a9e66b..e7540e4 100644 --- a/control.go +++ b/control.go @@ -4,23 +4,47 @@ import "github.com/veandco/go-sdl2/sdl" type Control interface { Init(*Context) error + Arrange(*Context, Rectangle) Handle(*Context, sdl.Event) Render(*Context) } +type EventFn func(*Context) + +type EmptyEventFn func() + +func EmptyEvent(fn EmptyEventFn) EventFn { + return func(*Context) { fn() } +} + type ControlBase struct { Bounds Rectangle IsMouseOver bool + + OnLeftMouseButtonClick EventFn } +func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds } + func (b *ControlBase) Init(*Context) error { return nil } func (b *ControlBase) Handle(ctx *Context, event sdl.Event) { switch e := event.(type) { case *sdl.MouseMotionEvent: b.IsMouseOver = b.Bounds.IsPointInside(e.X, e.Y) + case *sdl.MouseButtonEvent: + if b.IsMouseOver && e.Button == sdl.BUTTON_LEFT && e.Type == sdl.MOUSEBUTTONDOWN { + b.Invoke(ctx, b.OnLeftMouseButtonClick) + } } } +func (b *ControlBase) Invoke(ctx *Context, fn EventFn) { + if fn == nil { + return + } + fn(ctx) +} + func (b *ControlBase) Render(*Context) {} diff --git a/fonts.go b/fonts.go index 7a0e65c..5751b48 100644 --- a/fonts.go +++ b/fonts.go @@ -20,7 +20,7 @@ type Font struct { *ttf.Font } -func (f *Font) Render(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) (*Texture, error) { +func (f *Font) Render(renderer *sdl.Renderer, text string, color sdl.Color) (*Texture, error) { surface, err := f.RenderUTF8Blended(text, color) if err != nil { return nil, err @@ -34,31 +34,25 @@ func (f *Font) Render(renderer *sdl.Renderer, text string, pos Point, color sdl. } func (f *Font) RenderCopyAlign(renderer *sdl.Renderer, text string, pos Point, color sdl.Color, align TextAlignment) error { - texture, err := f.Render(renderer, text, pos, color) + texture, err := f.Render(renderer, text, color) if err != nil { return err } defer texture.Destroy() - rect := texture.Rect() + size := texture.Size() switch align { case TextAlignmentLeft: - texture.Copy(renderer, RectSize(pos.X, pos.Y, rect.W, rect.H).SDLPtr()) + texture.Copy(renderer, Pt(pos.X, pos.Y-size.Y)) case TextAlignmentCenter: - texture.Copy(renderer, RectSize(pos.X-(rect.W/2), pos.Y, rect.W, rect.H).SDLPtr()) + texture.Copy(renderer, Pt(pos.X-(size.X/2), pos.Y-size.Y)) case TextAlignmentRight: - texture.Copy(renderer, RectSize(pos.X-rect.W, pos.Y, rect.W, rect.H).SDLPtr()) + 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 { - texture, err := f.Render(renderer, text, pos, color) - if err != nil { - return err - } - defer texture.Destroy() - texture.Copy(renderer, texture.RectOffset(pos)) - return nil + return f.RenderCopyAlign(renderer, text, pos, color, TextAlignmentLeft) } type Fonts struct { diff --git a/fpsrenderer.go b/fpsrenderer.go index 539af83..ddacc9a 100644 --- a/fpsrenderer.go +++ b/fpsrenderer.go @@ -37,5 +37,5 @@ func (f *FPS) Render(ctx *Context) { f.ticks[f.slot]++ font := ctx.Fonts.Font("debug") - font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 5), sdl.Color{R: 255, G: 255, B: 255, A: 255}) + font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 17), sdl.Color{R: 255, G: 255, B: 255, A: 255}) } diff --git a/game.go b/game.go index 5802e52..1bd918d 100644 --- a/game.go +++ b/game.go @@ -6,13 +6,21 @@ import ( ) type Game struct { - Money int + Balance int + Speed GameSpeed Terrain *Map - start time.Time - lastUpdate time.Duration + simulation Animation } +type GameSpeed string + +const ( + GameSpeedNormal GameSpeed = "normal" + GameSpeedFast = "fast" + GameSpeedPaused = "paused" +) + type Map struct { Temp NoiseMap Humid NoiseMap @@ -36,6 +44,9 @@ func (m *Map) NewFlower(pos Point, traits FlowerTraits) Flower { return flower } +const simulationInterval = 120 * time.Millisecond +const fastSimulationInterval = 40 * time.Millisecond + func NewGame() *Game { terrain := &Map{ Temp: NewNoiseMap(rand.Int63()), @@ -47,18 +58,10 @@ func NewGame() *Game { } terrain.AddFlower(Pt(0, 0), NewPoppyTraits()) return &Game{ - Money: 100, + Balance: 100, Terrain: terrain, - start: time.Now(), - } -} - -func (g *Game) Update() { - update := time.Since(g.start) - for g.lastUpdate < update { - g.tick() - g.lastUpdate += time.Millisecond * 10 + simulation: NewAnimation(time.Millisecond * 10), } } @@ -92,3 +95,26 @@ func (g *Game) tick() { } g.Terrain.Flowers = flowers } + +func (g *Game) Pause() { + g.Speed = GameSpeedPaused + g.simulation.Pause() +} + +func (g *Game) Run() { + g.Speed = GameSpeedNormal + g.simulation.SetInterval(simulationInterval) + g.simulation.Run() +} + +func (g *Game) RunFast() { + g.Speed = GameSpeedFast + g.simulation.SetInterval(fastSimulationInterval) + g.simulation.Run() +} + +func (g *Game) Update() { + for g.simulation.Animate() { + g.tick() + } +} diff --git a/gamecontrols.go b/gamecontrols.go index a27eabf..f240005 100644 --- a/gamecontrols.go +++ b/gamecontrols.go @@ -1,114 +1,77 @@ package tins2020 -import ( - "log" - - "github.com/veandco/go-sdl2/sdl" -) - type GameControls struct { - ControlBase + Container + + game *Game menu ButtonBar + top ButtonBar flowers ButtonBar + + pause *IconButton + run *IconButton + runFast *IconButton } -type ButtonBar struct { - Top int32 - Left int32 - Bottom int32 - Hover int - Buttons []Button +func NewGameControls(game *Game) *GameControls { + return &GameControls{game: game} } -type Button struct { - Icon string - Disabled string - - IsDisabled bool +func (c *GameControls) updateSpeedControls() { } -const buttonBarWidth = 96 - -func (b *ButtonBar) Handle(ctx *Context, event sdl.Event) { - switch e := event.(type) { - case *sdl.MouseMotionEvent: - if e.X > b.Left && e.X < b.Left+buttonBarWidth { - button := int(e.Y-b.Top) / buttonBarWidth - if button < 0 || button >= len(b.Buttons) || b.Buttons[button].IsDisabled { - button = -1 - } - b.Hover = button - } else { - b.Hover = -1 - } - } +func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) { + c.Bounds = bounds + c.menu.Arrange(ctx, RectSize(bounds.X, bounds.Y, buttonBarWidth, bounds.H)) + c.top.Arrange(ctx, Rect(bounds.X+bounds.W/2+8, bounds.Y, bounds.Right(), bounds.Y+64)) + c.flowers.Arrange(ctx, RectSize(bounds.Right()-buttonBarWidth, bounds.Y, buttonBarWidth, bounds.H)) } -func (b *ButtonBar) Render(ctx *Context) { - ctx.Renderer.FillRect(Rect(b.Left, b.Top, b.Left+buttonBarWidth, b.Bottom).SDLPtr()) - texture := func(b Button) *Texture { - if b.IsDisabled { - texture := ctx.Textures.Texture(b.Disabled) - if texture != nil { - return texture - } - } - return ctx.Textures.Texture(b.Icon) - } - hoverTexture := ctx.Textures.Texture("game-control-hover") - for i, button := range b.Buttons { - pos := Pt(b.Left, b.Top+int32(i)*buttonBarWidth) - texture := texture(button) - texture.Copy(ctx.Renderer, &sdl.Rect{X: pos.X, Y: pos.Y - 40, W: buttonBarWidth, H: 120}) - if b.Hover == i { - hoverTexture.Copy(ctx.Renderer, hoverTexture.RectOffset(pos)) - } - } -} - -func NewGameControls() *GameControls { - return &GameControls{} +func (c *GameControls) buyPoppy(ctx *Context) { + c.game.Balance -= 10 } func (c *GameControls) Init(ctx *Context) error { - c.flowers.Hover = -1 - c.flowers.Buttons = []Button{ - Button{Icon: "flower-poppy-1", Disabled: "flower-poppy-disabled"}, - Button{Icon: "flower-red-c-1", Disabled: "flower-red-c-disabled", IsDisabled: true}, + c.flowers.Background = MustHexColor("#356dad") // brown alternative? #4ac69a + c.flowers.Buttons = []Control{ + NewBuyFlowerButton("flower-poppy-1", "flower-poppy-disabled", "Poppy", 10, "A very generic flower that thrives in a moderate climate.", false, c.buyPoppy), + NewBuyFlowerButton("flower-red-c-1", "flower-poppy-disabled", "Unknown", 100, "Traits are not known yet.", true, nil), } - return c.updateBarPositions(ctx) -} -func (c *GameControls) Handle(ctx *Context, event sdl.Event) { - c.menu.Handle(ctx, event) - c.flowers.Handle(ctx, event) + c.top.Orientation = OrientationHorizontal + c.pause = NewIconButton("control-pause", EmptyEvent(func() { + c.game.Pause() + c.updateSpeedControls() + })) + c.run = NewIconButton("control-run", EmptyEvent(func() { + c.game.Run() + c.updateSpeedControls() + })) + c.runFast = NewIconButton("control-run-fast", EmptyEvent(func() { + c.game.RunFast() + c.updateSpeedControls() + })) + c.top.Buttons = []Control{c.pause, c.run, c.runFast} - switch e := event.(type) { - case *sdl.WindowEvent: - switch e.Event { - case sdl.WINDOWEVENT_RESIZED: - err := c.updateBarPositions(ctx) - if err != nil { - log.Fatal(err) - } - } + c.menu.Background = MustHexColor("#356dad") + c.menu.Buttons = []Control{ + NewIconButton("control-settings", EmptyEvent(func() {})), + NewIconButton("control-save", EmptyEvent(func() {})), + NewIconButton("control-load", EmptyEvent(func() {})), } + + c.Container.AddChild(&c.menu) + c.Container.AddChild(&c.top) + c.Container.AddChild(&c.flowers) + return c.Container.Init(ctx) } func (c *GameControls) Render(ctx *Context) { - // ctx.Renderer.SetDrawColor(74, 198, 154, 255) // #4ac69a - ctx.Renderer.SetDrawColor(53, 109, 173, 255) - c.menu.Render(ctx) - c.flowers.Render(ctx) -} + topBar := MustHexColor("#0000007f") + ctx.Renderer.SetDrawColor(topBar.R, topBar.G, topBar.B, topBar.A) + 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("#79A6D9"), TextAlignmentRight) -func (c *GameControls) updateBarPositions(ctx *Context) error { - w, h, err := ctx.Renderer.GetOutputSize() - if err != nil { - return err - } - c.menu.Top, c.menu.Left, c.menu.Bottom = 0, 0, h - c.flowers.Top, c.flowers.Left, c.flowers.Bottom = 0, w-buttonBarWidth, h - return nil + c.Container.Render(ctx) } diff --git a/iconbutton.go b/iconbutton.go new file mode 100644 index 0000000..2cd449d --- /dev/null +++ b/iconbutton.go @@ -0,0 +1,62 @@ +package tins2020 + +type IconButton struct { + ControlBase + + Icon string + IconDisabled string + IconScale Scale + IconWidth int32 + + IsDisabled bool +} + +type Scale int + +const ( + ScaleCenter Scale = iota + ScaleStretch +) + +func NewIconButton(icon string, onClick EventFn) *IconButton { + return &IconButton{ + ControlBase: ControlBase{ + OnLeftMouseButtonClick: onClick, + }, + Icon: icon, + } +} + +func NewIconButtonConfig(icon string, onClick EventFn, configure func(*IconButton)) *IconButton { + button := NewIconButton(icon, onClick) + configure(button) + return button +} + +func (b *IconButton) activeTexture(ctx *Context) *Texture { + if b.IsDisabled { + texture := ctx.Textures.Texture(b.IconDisabled) + if texture != nil { + return texture + } + } + return ctx.Textures.Texture(b.Icon) +} + +func (b *IconButton) Render(ctx *Context) { + iconTexture := b.activeTexture(ctx) + mouseOverTexture := ctx.Textures.Texture("control-hover") + + if b.IconScale == ScaleCenter { + size := iconTexture.Size() + if b.IconWidth != 0 { + size = Pt(b.IconWidth, b.IconWidth*size.Y/size.X) + } + iconTexture.CopyResize(ctx.Renderer, RectSize(b.Bounds.X+(b.Bounds.W-size.X)/2, b.Bounds.Y+(b.Bounds.H-size.Y)/2, size.X, size.Y)) + } else { + iconTexture.CopyResize(ctx.Renderer, b.Bounds) + } + if b.IsMouseOver && !b.IsDisabled { + mouseOverTexture.CopyResize(ctx.Renderer, b.Bounds) + } +} diff --git a/math.go b/math.go index b599c3c..fb059ee 100644 --- a/math.go +++ b/math.go @@ -20,6 +20,13 @@ func Ceil32(x float32) float32 { return float32(math.Ceil(float64(x))) } func Floor32(x float32) float32 { return float32(math.Floor(float64(x))) } +func Max(a, b int32) int32 { + if a > b { + return a + } + return b +} + func Max32(a, b float32) float32 { if a > b { return a @@ -27,6 +34,13 @@ func Max32(a, b float32) float32 { return b } +func Min(a, b int32) int32 { + if a < b { + return a + } + return b +} + func Min32(a, b float32) float32 { if a < b { return a diff --git a/money.go b/money.go new file mode 100644 index 0000000..992653d --- /dev/null +++ b/money.go @@ -0,0 +1,5 @@ +package tins2020 + +import "fmt" + +func FmtMoney(amount int) string { return fmt.Sprintf("$ %d", amount) } diff --git a/projection.go b/projection.go index e1aaa23..9c00816 100644 --- a/projection.go +++ b/projection.go @@ -11,7 +11,8 @@ type projection struct { zoom float32 zoomInv float32 - windowRect Rectangle + windowInteractRect Rectangle + windowVisibleRect Rectangle tileScreenDelta PointF tileScreenDeltaInv PointF tileScreenOffset Point @@ -44,12 +45,12 @@ func (p *projection) screenToMapRel(x, y int32) PointF { return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY)) } -func (p *projection) screenToTileFitRect(pos Point) *sdl.Rect { - return &sdl.Rect{X: pos.X - p.tileFitScreenSize.X, Y: pos.Y - p.tileFitScreenSize.Y, W: 2 * p.tileFitScreenSize.X, H: 2 * p.tileFitScreenSize.Y} +func (p *projection) screenToTileFitRect(pos Point) Rectangle { + return RectSize(pos.X-p.tileFitScreenSize.X, pos.Y-p.tileFitScreenSize.Y, 2*p.tileFitScreenSize.X, 2*p.tileFitScreenSize.Y) } -func (p *projection) screenToTileRect(pos Point) *sdl.Rect { - return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y} +func (p *projection) screenToTileRect(pos Point) Rectangle { + return RectSize(pos.X-p.tileScreenOffset.X, pos.Y-p.tileScreenOffset.Y, p.tileScreenSize.X, p.tileScreenSize.Y) } func (p *projection) update(renderer *sdl.Renderer) { @@ -64,24 +65,26 @@ func (p *projection) update(renderer *sdl.Renderer) { log.Fatal(err) } p.windowCenter = Pt(windowW/2, windowH/2) - p.windowRect = RectSize(buttonBarWidth, 0, windowW-2*buttonBarWidth, windowH) + p.windowInteractRect = Rect(buttonBarWidth, 64, windowW-buttonBarWidth, windowH) + p.windowVisibleRect = Rect(buttonBarWidth, 0, windowW-buttonBarWidth, windowH+p.tileScreenSize.Y) // Adding a tile height to the bottom for trees that stick out from the cells below. } func (p *projection) visibleTiles(action func(int32, int32, Point)) { - topLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y) - topRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y) - bottomLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y+p.windowRect.H) - bottomRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y+p.windowRect.H) + visible := p.windowVisibleRect + topLeft := p.screenToMap(visible.X, visible.Y) + topRight := p.screenToMap(visible.X+visible.W, visible.Y) + bottomLeft := p.screenToMap(visible.X, visible.Y+visible.H) + bottomRight := p.screenToMap(visible.X+visible.W, visible.Y+visible.H) minY, maxY := int32(Floor32(topRight.Y)), int32(Ceil32(bottomLeft.Y)) minX, maxX := int32(Floor32(topLeft.X)), int32(Ceil32(bottomRight.X)) for y := minY; y <= maxY; y++ { for x := minX; x <= maxX; x++ { pos := p.mapToScreen(x, y) rectFit := p.screenToTileFitRect(pos) - if rectFit.X+rectFit.W < p.windowRect.X || rectFit.Y+rectFit.H < p.windowRect.Y { + if rectFit.X+rectFit.W < visible.X || rectFit.Y+rectFit.H < visible.Y { continue } - if rectFit.X > p.windowRect.X+p.windowRect.W || rectFit.Y > p.windowRect.Y+p.windowRect.H { + if rectFit.X > visible.X+visible.W || rectFit.Y > visible.Y+visible.H { break } action(x, y, pos) diff --git a/rect.go b/rect.go index 5eba166..f345692 100644 --- a/rect.go +++ b/rect.go @@ -6,12 +6,6 @@ type Rectangle struct { sdl.Rect } -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 Rect(x1, y1, x2, y2 int32) Rectangle { if x1 > x2 { x1, x2 = x2, x1 @@ -24,5 +18,15 @@ func Rect(x1, y1, x2, y2 int32) Rectangle { func RectSize(x, y, w, h int32) Rectangle { return Rectangle{sdl.Rect{X: x, Y: y, W: w, H: h}} } +func (r Rectangle) Bottom() int32 { return r.Y + r.H } + +func (r Rectangle) IsPointInside(x, y int32) bool { + return x >= r.X && x < r.X+r.W && y >= r.Y && y < r.Y+r.H +} + +func (r Rectangle) IsPointInsidePt(p Point) bool { return r.IsPointInside(p.X, p.Y) } + +func (r Rectangle) Right() int32 { return r.X + r.W } + func (r Rectangle) SDL() sdl.Rect { return r.Rect } func (r Rectangle) SDLPtr() *sdl.Rect { return &r.Rect } diff --git a/terrainrenderer.go b/terrainrenderer.go index 58e65fd..15701c5 100644 --- a/terrainrenderer.go +++ b/terrainrenderer.go @@ -24,6 +24,10 @@ func NewTerrainRenderer(terrain *Map) Control { return &terrainRenderer{terrain: terrain, project: newProjection()} } +func (r *terrainRenderer) Arrange(ctx *Context, _ Rectangle) { + r.project.update(ctx.Renderer) +} + func (r *terrainRenderer) Init(ctx *Context) error { r.project.update(ctx.Renderer) return nil @@ -32,16 +36,18 @@ func (r *terrainRenderer) Init(ctx *Context) error { func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) { switch e := event.(type) { case *sdl.MouseButtonEvent: - if e.Button == sdl.BUTTON_LEFT { - r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN - if r.interact.mouseLeftDown && r.interact.mouseDrag == nil { - r.interact.mouseDrag = PtPtr(e.X, e.Y) - } else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil { - r.interact.mouseDrag = nil + if r.project.windowInteractRect.IsPointInside(e.X, e.Y) { + if e.Button == sdl.BUTTON_LEFT { + r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN + if r.interact.mouseLeftDown && r.interact.mouseDrag == nil { + r.interact.mouseDrag = PtPtr(e.X, e.Y) + } else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil { + r.interact.mouseDrag = nil + } } } case *sdl.MouseMotionEvent: - if r.project.windowRect.IsPointInside(e.X, e.Y) { + if r.project.windowInteractRect.IsPointInside(e.X, e.Y) { hover := r.project.screenToMap(e.X, e.Y) r.hover = PtPtr(int32(Round32(hover.X)), int32(Round32(hover.Y))) } else { @@ -67,10 +73,6 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) { r.project.update(ctx.Renderer) } } - case *sdl.WindowEvent: - if e.Event == sdl.WINDOWEVENT_RESIZED { - r.project.update(ctx.Renderer) - } } } @@ -157,10 +159,10 @@ func (r *terrainRenderer) Render(ctx *Context) { r.project.visibleTiles(func(x, y int32, pos Point) { text := toTileTexture(x, y) rect := r.project.screenToTileRect(pos) - text.Copy(ctx.Renderer, rect) + text.CopyResize(ctx.Renderer, rect) if r.hover != nil && x == r.hover.X && y == r.hover.Y { - ctx.Textures.Texture("tile-hover").Copy(ctx.Renderer, rect) + ctx.Textures.Texture("tile-hover").CopyResize(ctx.Renderer, rect) } }) @@ -172,7 +174,7 @@ func (r *terrainRenderer) Render(ctx *Context) { placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y) pos = r.project.mapToScreenF(float32(x)-.2+float32(.9*placeX-.45), float32(y)-.2+float32(.9*placeY-.45)) - text.Copy(ctx.Renderer, r.project.screenToTileRect(pos)) + text.CopyResize(ctx.Renderer, r.project.screenToTileRect(pos)) }) // gfx.RectangleColor(ctx.Renderer, r.project.windowRect.X, r.project.windowRect.Y, r.project.windowRect.X+r.project.windowRect.W, r.project.windowRect.Y+r.project.windowRect.H, sdl.Color{R: 255, A: 255}) diff --git a/textures.go b/textures.go index d7bbdfb..9128eb1 100644 --- a/textures.go +++ b/textures.go @@ -11,7 +11,7 @@ import ( type Texture struct { texture *sdl.Texture - rect *sdl.Rect + size Point } func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Texture, error) { @@ -19,21 +19,33 @@ func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Textu if err != nil { return nil, err } - return &Texture{texture: texture, rect: &sdl.Rect{X: 0, Y: 0, W: surface.W, H: surface.H}}, nil + return &Texture{texture: texture, size: Pt(surface.W, surface.H)}, nil } -func (t *Texture) Rect() *sdl.Rect { return t.rect } +func (t *Texture) Size() Point { return t.size } -func (t *Texture) RectOffset(offset Point) *sdl.Rect { - return &sdl.Rect{X: offset.X, Y: offset.Y, W: t.rect.W, H: t.rect.H} +// 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, RectSize(dst.X, dst.Y, t.size.X, t.size.Y)) } -func (t *Texture) Copy(renderer *sdl.Renderer, target *sdl.Rect) { - renderer.Copy(t.texture, t.rect, target) +func (t *Texture) CopyPart(renderer *sdl.Renderer, src Rectangle, dst Point) { + t.CopyPartResize(renderer, src, RectSize(dst.X, dst.Y, src.W, src.H)) } -// func (t *Texture) CopyF(renderer *sdl.Renderer, target *sdl.FRect) { -// renderer.CopyF(t.texture, t.rect, target) // Depends on SDL >=2.0.10 +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, Rect(0, 0, t.size.X, t.size.Y), dst) +} + +// func (t *Texture) CopyF(renderer *sdl.Renderer, dst *sdl.FRect) { +// renderer.CopyF(t.texture, t.rect, dst) // Depends on SDL >=2.0.10 // } func (t *Texture) Destroy() { t.texture.Destroy() }