From 5193dc5166bef45f343eff53948306fecb810912 Mon Sep 17 00:00:00 2001 From: Frank Mayer Date: Sun, 7 Jan 2024 16:59:49 +0100 Subject: [PATCH] 2D GUI (#20) --- assets/fonts/GlassAntiqua-Regular.ttf | Bin 0 -> 61224 bytes assets/textures/charmap.bmp | Bin 32890 -> 1572918 bytes assets/ui/helloworld.xml | 9 ++ assets/ui/schema.xsd | 61 ++++++++ cmd/game/main.go | 3 +- engine/core/window.go | 48 ++++++ engine/firstperson/input.go | 18 +-- engine/firstperson/window.go | 2 - engine/font/register.go | 107 +++++++++++--- engine/font/rune.go | 44 ------ engine/level/level.go | 19 +-- engine/textures/register.go | 9 +- engine/topdown/input.go | 69 --------- engine/topdown/renderer.go | 75 ---------- engine/topdown/topdown.go | 29 ---- engine/ui/button.go | 105 +++++++++++++ engine/ui/document.go | 85 +++++++++++ engine/ui/image.go | 132 +++++++++++++++++ engine/ui/list.go | 75 ++++++++++ engine/ui/parser.go | 131 ++++++++++++++++ engine/ui/render.go | 205 ++++++++++++++++++++++++++ engine/ui/text.go | 82 +++++++++++ engine/ui/utils.go | 147 ++++++++++++++++++ engine/ui/utils_test.go | 87 +++++++++++ engine/utils/stack.go | 39 +++++ engine/utils/stack_test.go | 56 +++++++ examples/firstperson/main.go | 59 +++++++- examples/topdown/main.go | 33 ----- 28 files changed, 1416 insertions(+), 313 deletions(-) create mode 100644 assets/fonts/GlassAntiqua-Regular.ttf create mode 100644 assets/ui/helloworld.xml create mode 100644 assets/ui/schema.xsd delete mode 100644 engine/font/rune.go delete mode 100644 engine/topdown/input.go delete mode 100644 engine/topdown/renderer.go delete mode 100644 engine/topdown/topdown.go create mode 100644 engine/ui/button.go create mode 100644 engine/ui/document.go create mode 100644 engine/ui/image.go create mode 100644 engine/ui/list.go create mode 100644 engine/ui/parser.go create mode 100644 engine/ui/render.go create mode 100644 engine/ui/text.go create mode 100644 engine/ui/utils.go create mode 100644 engine/ui/utils_test.go create mode 100644 engine/utils/stack.go create mode 100644 engine/utils/stack_test.go delete mode 100644 examples/topdown/main.go diff --git a/assets/fonts/GlassAntiqua-Regular.ttf b/assets/fonts/GlassAntiqua-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8f9a067b3ae065d35d96b0b4b76fffe3cf777ce1 GIT binary patch literal 61224 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 r`^v&G$DB2A*7UmvXRR4< z%yVDZ2hTk@vdr~NaA{y_a@m(JIrqlQq&`aP=5!o;srF2`yw=E4(Xx_*r_Ws1JM&&S z_PXZY+}-sZ=Q+mS)#a>b&b{$8Cq14Gw#PVUrbZqgdeu7U#(1Aa;~uVg-@J3z!&R1( zGvTS|^sZ<*H~FdeEHGYrS8L#^<)Jr*qo(HGxcpsTTpi}&dt=}JT1(xUGli{X&U)rj z^BimDH{LI>U*P9_fz|Ivy@>Vch4OdBnbeMEc7FDHE*y2G;pysXU*~@WH|?B0L;Br2 zW1f3jhs)aK%9DKd%$fL3Pw!FR%_uH&_$KYD|7f1|#H(HDrC()Ib8lR;+#9FHUTS*b zT_?}PleKPK`cdD_=yLduH2c7L2l#1L`SUn4R0!?MRTTb zepc(L?S8+&eu4c0M=#L*uAI$G*@9q8@?=aj^&0Y3)8eG<+96CArd+KUdYT4J`j=OT7tNHTh$$h1FcY{Cj zPrl4`JI;D)2j^@CU**7$sMEZ>U+3$%D|fmjPloD!#c|AoO9Q)3ygeO0lS@zad6=Di z^=PYlYmS-S9=NPzs-l^@g0JSwpNDa{#Ab_(|L;Hbn{E=`1j;;r~ATn z*bk<{U#(Lfp6a=9)YOXSpmn{q4#QQgynExT8vd+jXWI8@%$;hk@zkg1>V2j>Jk@jI ksMflB!cBhblV{@D-RT{N|3|6y893dYOY8W}{oF6`4n+a diff --git a/assets/ui/helloworld.xml b/assets/ui/helloworld.xml new file mode 100644 index 0000000..9a1965a --- /dev/null +++ b/assets/ui/helloworld.xml @@ -0,0 +1,9 @@ + + + 0 + + diff --git a/assets/ui/schema.xsd b/assets/ui/schema.xsd new file mode 100644 index 0000000..67d40ce --- /dev/null +++ b/assets/ui/schema.xsd @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmd/game/main.go b/cmd/game/main.go index 0732851..ff6ff2c 100644 --- a/cmd/game/main.go +++ b/cmd/game/main.go @@ -13,7 +13,7 @@ func main() { l, err := level.Load("assets/levels/level1.bmlvl") if err != nil { - panic(err) + log.Fatal(err) } level.Set(l) @@ -25,7 +25,6 @@ func main() { // game loop for core.Running() { - firstperson.GetMouseInput() firstperson.MovePlayer() err = firstperson.RenderViewport() if err != nil { diff --git a/engine/core/window.go b/engine/core/window.go index 033d68b..eeb4f3f 100644 --- a/engine/core/window.go +++ b/engine/core/window.go @@ -17,6 +17,8 @@ package core import ( + "runtime" + "github.com/charmbracelet/log" "github.com/veandco/go-sdl2/sdl" ) @@ -143,6 +145,8 @@ func Start(t string) { log.Fatal("window already started") } + runtime.LockOSThread() + var err error title = t @@ -229,6 +233,7 @@ func Stop() { _ = window.Destroy() _ = renderer.Destroy() sdl.Quit() + log.Print("Engine stopped") } // Running returns whether the game loop should continue or not. @@ -242,7 +247,33 @@ func Running() bool { return running } +// Mouse states. +var ( + // MouseX is the current X position of the mouse. + MouseX int32 + // MouseY is the current Y position of the mouse. + MouseY int32 + // MouseDeltaX is the change in X position of the mouse since the last frame. + MouseDeltaX int32 + // MouseDeltaY is the change in Y position of the mouse since the last frame. + MouseDeltaY int32 + // MouseState is the current state of the mouse. + MouseState uint32 +) + func eventLoop() { + var mouseX, mouseY int32 + mouseX, mouseY, MouseState = sdl.GetMouseState() + if cursorLocked { + MouseDeltaX = mouseX - MouseX + MouseDeltaY = mouseY - MouseY + } else { + MouseDeltaX = 0 + MouseDeltaY = 0 + } + MouseX = mouseX + MouseY = mouseY + keyStates = sdl.GetKeyboardState() for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { switch event.GetType() { @@ -257,13 +288,30 @@ func eventLoop() { // Present draws the frame to the screen. // Call this at the very end of the game loop. func Present() { + if cursorHover { + sdl.SetCursor(sdl.CreateSystemCursor(sdl.SYSTEM_CURSOR_HAND)) + } else { + sdl.SetCursor(sdl.CreateSystemCursor(sdl.SYSTEM_CURSOR_ARROW)) + } renderer.Present() frameTime := uint32(sdl.GetTicks64() - frameStartTime) if frameTime < targetFrameTime { sdl.Delay(targetFrameTime - frameTime) } + + cursorHover = false } func FPS() float32 { return 1.0 / DeltaTime } + +func ScreenRect() *sdl.Rect { + return &sdl.Rect{W: width, H: height} +} + +var cursorHover = false + +func NotifyCursorHover() { + cursorHover = true +} diff --git a/engine/firstperson/input.go b/engine/firstperson/input.go index 4b439e0..1257a41 100644 --- a/engine/firstperson/input.go +++ b/engine/firstperson/input.go @@ -33,28 +33,12 @@ const ( turnSpeed float32 = 0.1 ) -var ( - MouseX int32 - MouseDeltaX int32 - MouseState uint32 -) - -func GetMouseInput() { - MouseX, _, MouseState = sdl.GetMouseState() - if core.IsCursorLocked() { - MouseDeltaX = MouseX - core.CenterX() - core.Window().WarpMouseInWindow(core.CenterX(), core.CenterY()) - } else { - MouseDeltaX = 0 - } -} - func KeyDown(key uint8) bool { return core.KeyStates()[key] != 0 } func MovePlayer() { - core.P.Angle += float32(MouseDeltaX) * turnSpeed * core.DeltaTime + core.P.Angle += float32(core.MouseDeltaX) * turnSpeed * core.DeltaTime if KeyDown(KeyForward) { core.P.Speed = 1 diff --git a/engine/firstperson/window.go b/engine/firstperson/window.go index e1eb6ff..3b74287 100644 --- a/engine/firstperson/window.go +++ b/engine/firstperson/window.go @@ -18,13 +18,11 @@ package firstperson import ( "github.com/bloodmagesoftware/bloodmage-engine/engine/core" - "github.com/bloodmagesoftware/bloodmage-engine/engine/level" "github.com/charmbracelet/log" "github.com/chewxy/math32" ) func Init() { - level.CollisionRound = 0.125 f := func() { log.Debug("firstperson window resize") screenDist = core.HalfWidthF() / math32.Tan(halfFov) diff --git a/engine/font/register.go b/engine/font/register.go index ba4f3cf..38a0ef7 100644 --- a/engine/font/register.go +++ b/engine/font/register.go @@ -14,28 +14,101 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +// Package font is for dealing with ttf fonts in the ui. +// +// Call font.Init() before using any other font function. +// Call font.Quit() at the end of your game. package font import ( - "github.com/bloodmagesoftware/bloodmage-engine/engine/textures" + "fmt" + "os" + + "github.com/veandco/go-sdl2/ttf" +) + +var ( + // fonts holds all registered fonts wether they are currently loaded or not. + fonts map[string]*font + // defaultFont is the font that is used when no other font is specified. + defaultFont *font ) type font struct { - startChar uint32 - endChar uint32 - texture *textures.Texture - charWidth int32 - charHeight int32 - collumnCount int32 -} - -func Load(texturePath string, startChar uint32, endChar uint32, charWidth int32, charHeight int32, collumnCount int32) *font { - return &font{ - startChar: startChar, - endChar: endChar, - texture: textures.Unregistered(texturePath), - charWidth: charWidth, - charHeight: charHeight, - collumnCount: collumnCount, + path string + ttf *ttf.Font +} + +// Font ensures the font is loaded and returns it. +func (f *font) Font() (*ttf.Font, error) { + if f.ttf != nil { + return f.ttf, nil + } + + font, err := ttf.OpenFont(f.path, 32) + if err != nil { + return nil, err + } + f.ttf = font + + return font, nil +} + +func Init() error { + return ttf.Init() +} + +// Quit closes all loaded fonts. +// After calling this function you can no longer use any font functions before calling Init() again. +func Quit() { + for _, font := range fonts { + if font.ttf != nil { + font.ttf.Close() + font.ttf = nil + } + } + ttf.Quit() +} + +func Register(fontPath string, name string) error { + if fonts == nil { + fonts = make(map[string]*font) + } + if _, ok := fonts[name]; ok { + return fmt.Errorf("font %s already registered", name) + } + // check if file exists with os.Stat() + if _, err := os.Stat(fontPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("font %s does not exist", fontPath) + } else { + return fmt.Errorf("error checking if font %s exists: %s", fontPath, err) + } + } + fonts[name] = &font{ + path: fontPath, + } + return nil +} + +func SetDefault(name string) error { + if _, ok := fonts[name]; !ok { + return fmt.Errorf("font %s not registered", name) + } + defaultFont = fonts[name] + return nil +} + +func Default() (*ttf.Font, error) { + if defaultFont != nil { + return defaultFont.Font() + } + return nil, fmt.Errorf("no default font set") +} + +func Get(name string) (*ttf.Font, error) { + if font, ok := fonts[name]; ok { + return font.Font() } + return nil, fmt.Errorf("font %s not registered", name) } diff --git a/engine/font/rune.go b/engine/font/rune.go deleted file mode 100644 index df504fd..0000000 --- a/engine/font/rune.go +++ /dev/null @@ -1,44 +0,0 @@ -package font - -import ( - "errors" - "fmt" - - "github.com/veandco/go-sdl2/sdl" -) - -func (f *font) Rune(r rune) (*sdl.Texture, *sdl.Rect, error) { - // get rune as number - rn := uint32(r) - - // check if rune is in range - if rn < f.startChar || rn > f.endChar { - return nil, nil, fmt.Errorf("rune %v (%v) is not in range %v-%v", rn, r, f.startChar, f.endChar) - } - - // get rune index - runeIndex := rn - f.startChar - - // get rune position in texture atlas raster (x, y) - runeX := runeIndex % uint32(f.collumnCount) - runeY := runeIndex / uint32(f.collumnCount) - - // get rune position in texture atlas (x, y, w, h) - runeRect := &sdl.Rect{ - X: int32(runeX) * f.charWidth, - Y: int32(runeY) * f.charHeight, - W: f.charWidth, - H: f.charHeight, - } - - // get rune texture - runeTexture, err := f.texture.Texture() - if err != nil { - return nil, nil, errors.Join( - errors.New("Failed to get font texture"), - err, - ) - } - - return runeTexture, runeRect, nil -} diff --git a/engine/level/level.go b/engine/level/level.go index ae17e3c..58336e3 100644 --- a/engine/level/level.go +++ b/engine/level/level.go @@ -21,6 +21,7 @@ import ( "path/filepath" "github.com/bloodmagesoftware/bloodmage-engine/engine/textures" + "github.com/charmbracelet/log" "github.com/veandco/go-sdl2/sdl" "google.golang.org/protobuf/proto" ) @@ -171,25 +172,13 @@ func Collision(x int, y int) bool { return currentLevel.SaveCollision(x, y) } -var CollisionRound = float32(0.4921875) - -func CollisionF(x float32, y float32) bool { - for y1 := int(y - CollisionRound); y1 <= int(y+CollisionRound); y1++ { - for x1 := int(x - CollisionRound); x1 <= int(x+CollisionRound); x1++ { - if Collision(x1, y1) { - return true - } - } - } - return false -} - func (self *Level) WallTexture(x int, y int) *textures.Texture { if !InBounds(x, y) { return textures.DefaultTexture() } return textures.Get(textures.Key(self.Walls[y*int(self.Width)+x])) } + func (self *Level) SetWall(x int, y int, wall byte) { if !self.InBounds(x, y) { return @@ -206,7 +195,7 @@ func (self *Level) FloorTexture() *sdl.Texture { if err != nil { t, err = textures.DefaultTexture().Texture() if err != nil { - panic(err) + log.Fatal(err) } } return t @@ -221,7 +210,7 @@ func (self *Level) CeilingTexture() *sdl.Texture { if err != nil { t, err = textures.DefaultTexture().Texture() if err != nil { - panic(err) + log.Fatal(err) } } return t diff --git a/engine/textures/register.go b/engine/textures/register.go index df8ffb7..a5fc398 100644 --- a/engine/textures/register.go +++ b/engine/textures/register.go @@ -20,6 +20,7 @@ import ( "image/color" "github.com/bloodmagesoftware/bloodmage-engine/engine/core" + "github.com/charmbracelet/log" "github.com/veandco/go-sdl2/sdl" ) @@ -33,7 +34,7 @@ var ( ) func Register(texturepath string, key Key) *Texture { - t := Unregistered(texturepath) + t := unregistered(texturepath) // add texture to registry registry[key] = t @@ -41,7 +42,7 @@ func Register(texturepath string, key Key) *Texture { return t } -func Unregistered(texturepath string) *Texture { +func unregistered(texturepath string) *Texture { t := &Texture{ path: texturepath, } @@ -65,13 +66,13 @@ func DefaultTexture() *Texture { // create pink texture for missing textures using sdl s, err := sdl.CreateRGBSurface(0, 1, 1, 32, 0, 0, 0, 0) if err != nil { - panic(err) + log.Fatal(err) } s.Set(0, 0, color.RGBA{R: 255, G: 0, B: 255, A: 255}) t, err := core.Renderer().CreateTextureFromSurface(s) if err != nil { - panic(err) + log.Fatal(err) } s.Free() diff --git a/engine/topdown/input.go b/engine/topdown/input.go deleted file mode 100644 index 7ef9830..0000000 --- a/engine/topdown/input.go +++ /dev/null @@ -1,69 +0,0 @@ -// Bloodmage Engine -// Copyright (C) 2024 Frank Mayer -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package topdown - -import ( - "github.com/bloodmagesoftware/bloodmage-engine/engine/core" - "github.com/bloodmagesoftware/bloodmage-engine/engine/level" - "github.com/chewxy/math32" - "github.com/veandco/go-sdl2/sdl" -) - -const ( - velocity = 2 -) - -func ProcessInput() { - core.P.Speed = 0.0 - core.P.Strafe = 0.0 - - if core.KeyStates()[sdl.SCANCODE_DOWN] == 1 { - core.P.Speed = 1 - } else if core.KeyStates()[sdl.SCANCODE_UP] == 1 { - core.P.Speed = -1 - } - - if core.KeyStates()[sdl.SCANCODE_LEFT] == 1 { - core.P.Strafe = -1 - } else if core.KeyStates()[sdl.SCANCODE_RIGHT] == 1 { - core.P.Strafe = 1 - } -} - -func MovePlayer() { - // calculate direction vector (normalized) - length := math32.Sqrt(core.P.Speed*core.P.Speed + core.P.Strafe*core.P.Strafe) - if length == 0 { - return - } - - dirX := core.P.Strafe / length * velocity - dirY := core.P.Speed / length * velocity - - // calculate new position - newX := core.P.X + dirX*core.DeltaTime - newY := core.P.Y + dirY*core.DeltaTime - - // check if new position is valid - if !level.CollisionF(newX, core.P.Y) { - core.P.X = newX - } - - if !level.CollisionF(core.P.X, newY) { - core.P.Y = newY - } -} diff --git a/engine/topdown/renderer.go b/engine/topdown/renderer.go deleted file mode 100644 index 7626578..0000000 --- a/engine/topdown/renderer.go +++ /dev/null @@ -1,75 +0,0 @@ -// Bloodmage Engine -// Copyright (C) 2024 Frank Mayer -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package topdown - -import ( - "github.com/bloodmagesoftware/bloodmage-engine/engine/core" - "github.com/bloodmagesoftware/bloodmage-engine/engine/level" - "github.com/veandco/go-sdl2/sdl" -) - -func minInt32(a, b int32) int32 { - if a < b { - return a - } - return b -} - -// RenderViewport renders a tow down view of the current level with the player in the center. -func RenderViewport() { - tileSize := minInt32(core.Width(), core.Height()) / 10 - - // draw floor - rect := sdl.Rect{X: 0, Y: 0, W: tileSize, H: tileSize} - for y := 0; y != level.Height(); y++ { - rect.Y = int32(y)*tileSize - int32(core.P.Y*float32(tileSize)) + int32(core.Height())/2 - - if rect.Y > int32(core.Height()) { - continue - } - - if rect.Y+rect.H < 0 { - continue - } - - for x := 0; x != level.Width(); x++ { - rect.X = int32(x)*tileSize - int32(core.P.X*float32(tileSize)) + int32(core.Width())/2 - - if rect.X > int32(core.Width()) { - continue - } - - if rect.X+rect.W < 0 { - continue - } - - if level.Collision(x, y) { - _ = core.Renderer().SetDrawColor(128, 128, 128, 255) - } else { - _ = core.Renderer().SetDrawColor(8, 8, 8, 255) - } - - _ = core.Renderer().FillRect(&rect) - } - } - - // draw player - rect.X = int32(core.Width())/2 - tileSize/2 - rect.Y = int32(core.Height())/2 - tileSize/2 - _ = core.Renderer().SetDrawColor(255, 255, 255, 255) - _ = core.Renderer().FillRect(&rect) -} diff --git a/engine/topdown/topdown.go b/engine/topdown/topdown.go deleted file mode 100644 index 4701cff..0000000 --- a/engine/topdown/topdown.go +++ /dev/null @@ -1,29 +0,0 @@ -// Bloodmage Engine -// Copyright (C) 2024 Frank Mayer -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package topdown - -import ( - "github.com/bloodmagesoftware/bloodmage-engine/engine/core" - "github.com/charmbracelet/log" -) - -func Init() { - f := func() { - log.Debug("topdown window resize") - } - core.OnResize(&f) -} diff --git a/engine/ui/button.go b/engine/ui/button.go new file mode 100644 index 0000000..9697a4a --- /dev/null +++ b/engine/ui/button.go @@ -0,0 +1,105 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui + +import ( + "errors" + + "github.com/bloodmagesoftware/bloodmage-engine/engine/core" + "github.com/veandco/go-sdl2/sdl" +) + +type Button struct { + doc *document + id string + mouseDown bool + content Element + rect sdl.Rect +} + +func newButton() *Button { + return &Button{ + doc: nil, + id: "", + mouseDown: false, + content: nil, + rect: sdl.Rect{}, + } +} + +func (b *Button) AppendChild(e Element) error { + if b.content != nil { + return errors.New("button already has content") + } + b.content = e + return nil +} + +func (b *Button) SetAttribute(key, value string) error { + switch key { + case "id": + b.id = value + default: + return errors.New("unknown attribute: " + key) + } + return nil +} + +func (b *Button) setDocument(doc *document) { + b.doc = doc +} + +func (b *Button) setTextContent(content string) error { + return errors.New("button cannot have text content") +} + +func (b *Button) setRect(rect *sdl.Rect) { + b.rect.X = rect.X + b.rect.Y = rect.Y + b.rect.W = rect.W + b.rect.H = rect.H +} + +func (b *Button) MouseOver() bool { + if b.rect.X <= core.MouseX && core.MouseX <= b.rect.X+b.rect.W && b.rect.Y <= core.MouseY && core.MouseY <= b.rect.Y+b.rect.H { + core.NotifyCursorHover() + return true + } + b.mouseDown = false + return false +} + +// Clicked returns true if these is a rising edge of the left mouse button +func (b *Button) Clicked() bool { + lMouseDown := core.MouseState&sdl.ButtonLMask() != 0 + if b.MouseOver() { + if lMouseDown { + b.mouseDown = true + return false + } else { + if b.mouseDown { + b.mouseDown = false + return true + } else { + return false + } + } + } else { + // b.mouseDown = false + return false + } +} diff --git a/engine/ui/document.go b/engine/ui/document.go new file mode 100644 index 0000000..554e1ad --- /dev/null +++ b/engine/ui/document.go @@ -0,0 +1,85 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui + +type document struct { + root Element + idMap map[string]Element +} + +func newDocument() document { + return document{ + idMap: make(map[string]Element), + } +} + +func (d *document) RootElement() Element { + return d.root +} + +func (d *document) GetElementById(id string) (Element, bool) { + e, ok := d.idMap[id] + return e, ok +} + +func (d *document) GetTextElementById(id string) (*Text, bool) { + e, ok := d.idMap[id] + if !ok { + return nil, false + } + t, ok := e.(*Text) + if !ok { + return nil, false + } + return t, true +} + +func (d *document) GetButtonElementById(id string) (*Button, bool) { + e, ok := d.idMap[id] + if !ok { + return nil, false + } + b, ok := e.(*Button) + if !ok { + return nil, false + } + return b, true +} + +func (d *document) GetImageElementById(id string) (*Image, bool) { + e, ok := d.idMap[id] + if !ok { + return nil, false + } + i, ok := e.(*Image) + if !ok { + return nil, false + } + return i, true +} + +func (d *document) GetListElementById(id string) (*List, bool) { + e, ok := d.idMap[id] + if !ok { + return nil, false + } + l, ok := e.(*List) + if !ok { + return nil, false + } + return l, true +} diff --git a/engine/ui/image.go b/engine/ui/image.go new file mode 100644 index 0000000..a9df7c3 --- /dev/null +++ b/engine/ui/image.go @@ -0,0 +1,132 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui + +import ( + "errors" + "strconv" + + "github.com/bloodmagesoftware/bloodmage-engine/engine/core" + "github.com/veandco/go-sdl2/sdl" +) + +type Image struct { + doc *document + id string + src string + width int32 + height int32 + texture *sdl.Texture +} + +func newImage() *Image { + return &Image{ + doc: nil, + id: "", + src: "", + width: -1, + height: -1, + texture: nil, + } +} + +func (i *Image) AppendChild(e Element) error { + return errors.New("Image cannot have children") +} + +func (i *Image) SetAttribute(key, value string) error { + switch key { + case "id": + i.id = value + case "src": + return i.SetSrc(value) + case "width": + if w, err := strconv.Atoi(value); err != nil { + return err + } else if w < 0 { + return errors.New("width must be greater than 0") + } else { + i.width = int32(w) + } + case "height": + if h, err := strconv.Atoi(value); err != nil { + return err + } else if h < 0 { + return errors.New("height must be greater than 0") + } else { + i.height = int32(h) + } + default: + return errors.New("unknown attribute: " + key) + } + return nil +} + +func (i *Image) setDocument(doc *document) { + i.doc = doc +} + +func (i *Image) Src() string { + return i.src +} + +func (i *Image) SetSrc(src string) error { + if i.src == src { + return nil + } + if i.texture != nil { + err := i.texture.Destroy() + if err != nil { + return err + } + } + surface, err := sdl.LoadBMP(src) + if err != nil { + return err + } + i.texture, err = core.Renderer().CreateTextureFromSurface(surface) + if err != nil { + return err + } + surface.Free() + i.src = src + return nil +} + +func (i *Image) Texture() (*sdl.Texture, error) { + if i.texture == nil { + return nil, errors.New("texture is nil") + } + return i.texture, nil +} + +func (i *Image) rect() (*sdl.Rect, error) { + if i.width <= 0 { + return nil, errors.New("image width must be greater than 0") + } + if i.height <= 0 { + return nil, errors.New("image height must be greater than 0") + } + return &sdl.Rect{ + X: 0, Y: 0, + W: i.width, H: i.height, + }, nil +} + +func (i *Image) setTextContent(content string) error { + return errors.New("Image cannot have text content") +} diff --git a/engine/ui/list.go b/engine/ui/list.go new file mode 100644 index 0000000..45fbd22 --- /dev/null +++ b/engine/ui/list.go @@ -0,0 +1,75 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui + +import "errors" + +type orientation uint8 + +const ( + orientation_unset orientation = iota + orientation_vertical + orientation_horizontal +) + +type List struct { + doc *document + orientation + items []Element +} + +func newList() *List { + return &List{ + doc: nil, + orientation: orientation_unset, + items: nil, + } +} + +func (l *List) AppendChild(e Element) error { + l.items = append(l.items, e) + return nil +} + +func (l *List) SetAttribute(key, value string) error { + switch key { + case "orientation": + switch value { + case "vertical": + l.orientation = orientation_vertical + case "horizontal": + l.orientation = orientation_horizontal + default: + return errors.New("unknown orientation: " + value) + } + default: + return errors.New("unknown attribute: " + key) + } + return nil +} + +func (l *List) setDocument(doc *document) { + l.doc = doc +} + +func (l *List) Items() []Element { + return l.items +} + +func (l *List) setTextContent(content string) error { + return errors.New("list cannot have text content") +} diff --git a/engine/ui/parser.go b/engine/ui/parser.go new file mode 100644 index 0000000..de35348 --- /dev/null +++ b/engine/ui/parser.go @@ -0,0 +1,131 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui + +import ( + "encoding/xml" + "errors" + "fmt" + "os" + "strings" + + "github.com/bloodmagesoftware/bloodmage-engine/engine/utils" + "github.com/veandco/go-sdl2/sdl" +) + +type Element interface { + AppendChild(Element) error + SetAttribute(string, string) error + setTextContent(string) error + setDocument(*document) + draw() (drawFn, *sdl.Rect, error) +} + +func CreateElement(name string) (Element, error) { + switch name { + case "List": + return newList(), nil + case "Button": + return newButton(), nil + case "Text": + return newText(), nil + case "Image": + return newImage(), nil + default: + return nil, fmt.Errorf("unknown element type: %s", name) + } +} + +func Parse(path string) (*document, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + decoder := xml.NewDecoder(file) + + var root Element + doc := newDocument() + elStack := utils.NewStack[Element]() + + for { + token, err := decoder.Token() + if err != nil { + break + } + + switch t := token.(type) { + case xml.StartElement: + // create element + el, err := CreateElement(t.Name.Local) + if err != nil { + return nil, err + } + // might be root element + if root == nil { + root = el + doc.root = el + } + el.setDocument(&doc) + // set attributes + for _, attr := range t.Attr { + if attr.Name.Space != "" { + continue + } + if attr.Name.Local == "id" { + doc.idMap[attr.Value] = el + } + if err := el.SetAttribute(attr.Name.Local, attr.Value); err != nil { + return nil, errors.Join( + fmt.Errorf("error setting attribute %s=%s on element %s", attr.Name.Local, attr.Value, t.Name.Local), + err, + ) + } + } + // append to parent if exists + if parent, hasParent := elStack.Peek(); hasParent { + if err := (*parent).AppendChild(el); err != nil { + return nil, err + } + } + // push to stack for future children + elStack.Push(el) + + case xml.CharData: + text := strings.TrimSpace(string(t)) + if text == "" { + continue + } + if el, hasElement := elStack.Peek(); hasElement { + if err := (*el).setTextContent(text); err != nil { + return nil, err + } + } + + case xml.EndElement: + // pop from stack because we're done with this element + elStack.Pop() + } + } + + if root == nil { + return nil, fmt.Errorf("no root element found") + } + + return &doc, nil +} diff --git a/engine/ui/render.go b/engine/ui/render.go new file mode 100644 index 0000000..423bfd4 --- /dev/null +++ b/engine/ui/render.go @@ -0,0 +1,205 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui + +import ( + "errors" + "fmt" + + "github.com/bloodmagesoftware/bloodmage-engine/engine/core" + "github.com/bloodmagesoftware/bloodmage-engine/engine/font" + "github.com/charmbracelet/log" + "github.com/veandco/go-sdl2/sdl" + "github.com/veandco/go-sdl2/ttf" +) + +type drawFn func(dest *sdl.Rect) error + +func (document *document) Draw() error { + fn, rect, err := document.root.draw() + if err != nil { + return err + } + if rect == nil { + rect = core.ScreenRect() + } + if err := fn(rect); err != nil { + return err + } + return nil +} + +// draw functions + +func (image *Image) draw() (drawFn, *sdl.Rect, error) { + t, err := image.Texture() + if err != nil { + return nil, defaultRect, err + } + rect, err := image.rect() + if err != nil { + return nil, defaultRect, err + } + + return func(dest *sdl.Rect) error { + return core.Renderer().Copy(t, nil, dest) + }, rect, nil +} + +func (button *Button) draw() (drawFn, *sdl.Rect, error) { + if button.content == nil { + return nil, defaultRect, errors.New("Button content is nil") + } + + fn, rect, err := button.content.draw() + return func(dest *sdl.Rect) error { + button.setRect(dest) + return fn(dest) + }, rect, err +} + +func (list *List) draw() (drawFn, *sdl.Rect, error) { + fnList := make([]drawFn, len(list.items)) + rectList := make([]*sdl.Rect, len(list.items)) + + expectedSize := sdl.Rect{ + X: 0, Y: 0, + W: 0, H: 0, + } + + for i, child := range list.items { + fn, srcRect, err := child.draw() + if err != nil { + return nil, defaultRect, errors.Join( + fmt.Errorf("Error retrieving draw function for list item %d", i), + err, + ) + } + if srcRect == nil { + return nil, srcRect, fmt.Errorf("List item %d returned nil rect", i) + } + if fn == nil { + return nil, srcRect, fmt.Errorf("List item %d returned nil draw function", i) + } + fnList[i] = fn + rectList[i] = srcRect + switch list.orientation { + case orientation_horizontal: + expectedSize.W += srcRect.W + if srcRect.H > expectedSize.H { + expectedSize.H = srcRect.H + } + case orientation_vertical: + expectedSize.H += srcRect.H + if srcRect.W > expectedSize.W { + expectedSize.W = srcRect.W + } + } + } + + switch list.orientation { + case orientation_horizontal: + return func(dest *sdl.Rect) error { + x := dest.X + for i, fn := range fnList { + itemRect := rectList[i] + width := itemRect.W + itemRect.X + itemDestRect := &sdl.Rect{ + X: x, Y: dest.Y, + W: width, H: itemRect.H, + } + if x+width > dest.W { + log.Warnf("list item %d is too wide %d > %d", i, x+width, dest.W) + itemDestRect.W = 0 + } + itemDestRect.X = x + itemDestRect.W = width + if err := fn(itemDestRect); err != nil { + return err + } + x += width + } + return nil + }, &expectedSize, nil + case orientation_vertical: + return func(dest *sdl.Rect) error { + y := dest.Y + for i, fn := range fnList { + itemRect := rectList[i] + height := itemRect.H + itemRect.Y + itemDestRect := &sdl.Rect{ + X: dest.X, Y: y, + W: itemRect.W, H: height, + } + if y+height > dest.H { + log.Warnf("list item %d is too tall %d > %d", i, y+height, dest.H) + itemDestRect.H = 0 + } + itemDestRect.Y = y + itemDestRect.H = height + if err := fn(itemDestRect); err != nil { + return err + } + y += height + } + return nil + }, &expectedSize, nil + default: + return nil, &expectedSize, errors.New("Invalid orientation") + } +} + +func (text *Text) draw() (drawFn, *sdl.Rect, error) { + var err error + var f *ttf.Font + if text.font == "" { + if f, err = font.Default(); err != nil { + return nil, defaultRect, errors.Join( + errors.New("No font specified for text element"), + err, + ) + } + } else { + if f, err = font.Get(text.font); err != nil { + return nil, defaultRect, errors.Join( + fmt.Errorf("Font %s not found", text.font), + err, + ) + } + } + + surface, err := f.RenderUTF8Solid(text.content, text.color) + if err != nil { + return nil, defaultRect, err + } + defer surface.Free() + + scrRect := sdl.Rect{ + X: 0, Y: 0, + W: surface.W, H: surface.H, + } + + texture, err := core.Renderer().CreateTextureFromSurface(surface) + if err != nil { + log.Fatal(err) + } + + return func(dest *sdl.Rect) error { + defer texture.Destroy() + return core.Renderer().Copy(texture, &scrRect, dest) + }, &scrRect, nil +} diff --git a/engine/ui/text.go b/engine/ui/text.go new file mode 100644 index 0000000..e30e007 --- /dev/null +++ b/engine/ui/text.go @@ -0,0 +1,82 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui + +import ( + "errors" + + "github.com/veandco/go-sdl2/sdl" +) + +type Text struct { + doc *document + id string + content string + font string + color sdl.Color +} + +func newText() *Text { + return &Text{ + doc: nil, + id: "", + content: "", + font: "", + color: sdl.Color{R: 255, G: 255, B: 255, A: 255}, + } +} + +func (t *Text) AppendChild(e Element) error { + return errors.New("Text cannot have children") +} + +func (t *Text) SetAttribute(key, value string) error { + switch key { + case "id": + t.id = value + case "content": + t.content = value + case "font": + t.font = value + case "color": + var err error + if t.color, err = ParseColor(value); err != nil { + return err + } + default: + return errors.New("unknown attribute: " + key) + } + return nil +} + +func (t *Text) setDocument(doc *document) { + t.doc = doc +} + +func (t *Text) Content() string { + return t.content +} + +func (t *Text) SetContent(content string) error { + t.content = content + return nil +} + +func (t *Text) setTextContent(content string) error { + t.content = content + return nil +} diff --git a/engine/ui/utils.go b/engine/ui/utils.go new file mode 100644 index 0000000..02b1269 --- /dev/null +++ b/engine/ui/utils.go @@ -0,0 +1,147 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui + +import ( + "fmt" + "strconv" + "strings" + + "github.com/veandco/go-sdl2/sdl" +) + +var ( + // default rect + defaultRect = &sdl.Rect{X: 0, Y: 0, W: 0, H: 0} +) + +// parseColor parses a color string and returns the color channel values. +// +// Possible formats: +// - #RRGGBB +// - #RRGGBBAA +// - rgb(R, G, B) +// - rgba(R, G, B, A) +func ParseColorChannels(color string) (r uint8, g uint8, b uint8, a uint8, err error) { + // Remove any whitespace + color = strings.ReplaceAll(color, " ", "") + + // Check for hex color format + if strings.HasPrefix(color, "#") { + color = strings.TrimPrefix(color, "#") + + switch len(color) { + case 6: + // #RRGGBB format + if r, err = parseHex(color[0:2]); err != nil { + return 0, 0, 0, 0, err + } + if g, err = parseHex(color[2:4]); err != nil { + return 0, 0, 0, 0, err + } + if b, err = parseHex(color[4:6]); err != nil { + return 0, 0, 0, 0, err + } + a = 255 + case 8: + // #RRGGBBAA format + if r, err = parseHex(color[0:2]); err != nil { + return 0, 0, 0, 0, err + } + if g, err = parseHex(color[2:4]); err != nil { + return 0, 0, 0, 0, err + } + if b, err = parseHex(color[4:6]); err != nil { + return 0, 0, 0, 0, err + } + if a, err = parseHex(color[6:8]); err != nil { + return 0, 0, 0, 0, err + } + default: + return 0, 0, 0, 0, fmt.Errorf("invalid hex color format: %s", color) + } + } else if strings.HasPrefix(color, "rgb(") && strings.HasSuffix(color, ")") { + // cut off the "rgb(" and ")" using indexes + // split the values by comma + rgbValues := strings.Split(color[4:len(color)-1], ",") + if len(rgbValues) != 3 { + return 0, 0, 0, 0, fmt.Errorf("invalid rgb color format: %s", color) + } + if r64, err := strconv.ParseUint(rgbValues[0], 10, 8); err == nil { + r = uint8(r64) + } else { + return 0, 0, 0, 0, err + } + if g64, err := strconv.ParseUint(rgbValues[1], 10, 8); err == nil { + g = uint8(g64) + } else { + return 0, 0, 0, 0, err + } + if b64, err := strconv.ParseUint(rgbValues[2], 10, 8); err == nil { + b = uint8(b64) + } else { + return 0, 0, 0, 0, err + } + a = 255 + } else if strings.HasPrefix(color, "rgba(") && strings.HasSuffix(color, ")") { + // cut off the "rgba(" and ")" using indexes + // split the values by comma + rgbaValues := strings.Split(color[5:len(color)-1], ",") + if len(rgbaValues) != 4 { + return 0, 0, 0, 0, fmt.Errorf("invalid rgba color format: %s", color) + } + if r64, err := strconv.ParseUint(rgbaValues[0], 10, 8); err == nil { + r = uint8(r64) + } else { + return 0, 0, 0, 0, err + } + if g64, err := strconv.ParseUint(rgbaValues[1], 10, 8); err == nil { + g = uint8(g64) + } else { + return 0, 0, 0, 0, err + } + if b64, err := strconv.ParseUint(rgbaValues[2], 10, 8); err == nil { + b = uint8(b64) + } else { + return 0, 0, 0, 0, err + } + if a64, err := strconv.ParseUint(rgbaValues[3], 10, 8); err == nil { + a = uint8(a64) + } else { + return 0, 0, 0, 0, err + } + } else { + return 0, 0, 0, 0, fmt.Errorf("unsupported color format: %s", color) + } + + return r, g, b, a, nil +} + +// Helper function to parse hexadecimal values +func parseHex(hex string) (uint8, error) { + value, err := strconv.ParseUint(hex, 16, 8) + if err != nil { + return 0, err + } + return uint8(value), nil +} + +// ParseColor parses a color string into an sdl.Color struct. +func ParseColor(color string) (sdl.Color, error) { + r, g, b, a, err := ParseColorChannels(color) + return sdl.Color{R: r, G: g, B: b, A: a}, err +} diff --git a/engine/ui/utils_test.go b/engine/ui/utils_test.go new file mode 100644 index 0000000..3f209a2 --- /dev/null +++ b/engine/ui/utils_test.go @@ -0,0 +1,87 @@ +// Bloodmage Engine +// Copyright (C) 2024 Frank Mayer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ui_test + +import ( + "fmt" + "testing" + + "github.com/bloodmagesoftware/bloodmage-engine/engine/ui" +) + +func TestParseColorChannels(t *testing.T) { + t.Parallel() + + tests := []struct { + color string + r uint8 + g uint8 + b uint8 + a uint8 + err bool + }{ + {"#000000", 0, 0, 0, 255, false}, + {"#00000000", 0, 0, 0, 0, false}, + {"#ffffff", 255, 255, 255, 255, false}, + {"#ffffffff", 255, 255, 255, 255, false}, + {"#ff0000", 255, 0, 0, 255, false}, + {"#ff000000", 255, 0, 0, 0, false}, + {"#00ff00", 0, 255, 0, 255, false}, + {"#00ff0000", 0, 255, 0, 0, false}, + {"#0000ff", 0, 0, 255, 255, false}, + {"#0000ff00", 0, 0, 255, 0, false}, + {"rgb(0, 0, 0)", 0, 0, 0, 255, false}, + {"rgb(11,22,23)", 11, 22, 23, 255, false}, + {"rgb(11, 22, 23)", 11, 22, 23, 255, false}, + {"rgb(255, 255, 255)", 255, 255, 255, 255, false}, + {"rgb(255, 0, 0)", 255, 0, 0, 255, false}, + {"rgb(0, 255, 0)", 0, 255, 0, 255, false}, + {"rgb(0, 0, 255)", 0, 0, 255, 255, false}, + {"rgba(0, 0, 0, 0)", 0, 0, 0, 0, false}, + {"rgba(255, 255, 255, 255)", 255, 255, 255, 255, false}, + {"rgba(255, 0, 0, 255)", 255, 0, 0, 255, false}, + {"rgba(0, 255, 0, 255)", 0, 255, 0, 255, false}, + {"rgba(0, 0, 255, 255)", 0, 0, 255, 255, false}, + {"#000", 0, 0, 0, 0, true}, + {"#0000000", 0, 0, 0, 0, true}, + {"#000000000", 0, 0, 0, 0, true}, + {"#0000000000", 0, 0, 0, 0, true}, + {"#00000000000", 0, 0, 0, 0, true}, + {"#üdußnz", 0, 0, 0, 0, true}, + {"#üdußnzßz", 0, 0, 0, 0, true}, + {"rgb(foo, bar, baz)", 0, 0, 0, 0, true}, + {"rgba(foo, bar, baz, qux)", 0, 0, 0, 0, true}, + {"#ff1493", 255, 20, 147, 255, false}, + {"#ff149380", 255, 20, 147, 128, false}, + {"rgb(255, 20, 147)", 255, 20, 147, 255, false}, + {"rgb(255, 20, 147, 128)", 0, 0, 0, 0, true}, + {"rgba(255, 20, 147, 128)", 255, 20, 147, 128, false}, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("test %d: %s", i, test.color), func(t *testing.T) { + r, g, b, a, err := ui.ParseColorChannels(test.color) + if err != nil && !test.err { + t.Errorf("unexpected error: %s", err) + } else if err == nil && test.err { + t.Errorf("expected error, got nil (%d, %d, %d, %d)", r, g, b, a) + } else if r != test.r || g != test.g || b != test.b || a != test.a { + t.Errorf("expected (%d, %d, %d, %d), got (%d, %d, %d, %d)", test.r, test.g, test.b, test.a, r, g, b, a) + } + }) + } +} diff --git a/engine/utils/stack.go b/engine/utils/stack.go new file mode 100644 index 0000000..22ea4f9 --- /dev/null +++ b/engine/utils/stack.go @@ -0,0 +1,39 @@ +package utils + +type Stack[T any] struct { + data []T +} + +func NewStack[T any]() *Stack[T] { + return &Stack[T]{ + data: make([]T, 0), + } +} + +func (s *Stack[T]) Push(data T) { + s.data = append(s.data, data) +} + +func (s *Stack[T]) Pop() (*T, bool) { + if len(s.data) == 0 { + return nil, false + } + data := s.data[len(s.data)-1] + s.data = s.data[:len(s.data)-1] + return &data, true +} + +func (s *Stack[T]) Empty() bool { + return len(s.data) == 0 +} + +func (s *Stack[T]) Len() int { + return len(s.data) +} + +func (s *Stack[T]) Peek() (*T, bool) { + if len(s.data) == 0 { + return nil, false + } + return &s.data[len(s.data)-1], true +} diff --git a/engine/utils/stack_test.go b/engine/utils/stack_test.go new file mode 100644 index 0000000..09ee803 --- /dev/null +++ b/engine/utils/stack_test.go @@ -0,0 +1,56 @@ +package utils_test + +import ( + "testing" + + "github.com/bloodmagesoftware/bloodmage-engine/engine/utils" +) + +func TestStack(t *testing.T) { + t.Parallel() + s := utils.NewStack[int]() + + if s.Len() != 0 { + t.Errorf("Stack should be empty") + } + + s.Push(1) + s.Push(2) + s.Push(3) + + if s.Len() != 3 { + t.Errorf("Stack should have 3 elements") + } + + if v, b := s.Pop(); *v != 3 || !b { + t.Errorf("Stack should return 3") + } + + if s.Len() != 2 { + t.Errorf("Stack should have 2 elements") + } + + if v, b := s.Pop(); *v != 2 || !b { + t.Errorf("Stack should return 2") + } + + if s.Len() != 1 { + t.Errorf("Stack should have 1 element") + } + + if v, b := s.Pop(); *v != 1 || !b { + t.Errorf("Stack should return 1") + } + + if s.Len() != 0 { + t.Errorf("Stack should be empty") + } + + if v, b := s.Pop(); v != nil || b { + t.Errorf("Stack should return nil and false") + } + + if s.Len() != 0 { + t.Errorf("Stack should be empty") + } +} diff --git a/examples/firstperson/main.go b/examples/firstperson/main.go index 90f8c99..60615e8 100644 --- a/examples/firstperson/main.go +++ b/examples/firstperson/main.go @@ -1,43 +1,90 @@ package main import ( + "fmt" + "github.com/bloodmagesoftware/bloodmage-engine/engine/core" "github.com/bloodmagesoftware/bloodmage-engine/engine/firstperson" + "github.com/bloodmagesoftware/bloodmage-engine/engine/font" "github.com/bloodmagesoftware/bloodmage-engine/engine/level" "github.com/bloodmagesoftware/bloodmage-engine/engine/textures" + "github.com/bloodmagesoftware/bloodmage-engine/engine/ui" "github.com/charmbracelet/log" "github.com/veandco/go-sdl2/sdl" + "github.com/veandco/go-sdl2/ttf" ) func main() { + var err error core.InitOptions() l := level.New() level.Set(l) + + // register textures textures.Register("assets/textures/2.bmp", 2) textures.Register("assets/textures/1.bmp", 1) + // register fonts + if err = font.Init(); err != nil { + log.Fatal(err) + } + if err = font.Register("./assets/fonts/GlassAntiqua-Regular.ttf", "Glass Antiqua"); err != nil { + log.Fatal(err) + } + if err = font.SetDefault("Glass Antiqua"); err != nil { + log.Fatal(err) + } + + // set player start position core.P.X = 1.5 core.P.Y = 1.5 + // inet game mode firstperson.Init() core.Start("First Person Example") defer core.Stop() - var err error + core.LockCursor(false) - core.LockCursor(true) + err = ttf.Init() + if err != nil { + log.Fatal(err) + } + + document, err := ui.Parse("./assets/ui/helloworld.xml") + if err != nil { + log.Fatal(err) + } + + btnEl, ok := document.GetButtonElementById("btn") + if !ok { + log.Fatal("Could not find element with id 'btn'") + } + i := 0 + + counterEl, ok := document.GetTextElementById("counter") + if !ok { + log.Fatal("Could not find element with id 'counter'") + } // game loop for core.Running() { if core.KeyStates()[sdl.SCANCODE_ESCAPE] != 0 { break } - firstperson.GetMouseInput() firstperson.MovePlayer() - err = firstperson.RenderViewport() - if err != nil { - log.Error(err) + if err = firstperson.RenderViewport(); err != nil { + log.Fatal(err) + } + + if err = document.Draw(); err != nil { + log.Fatal(err) + } + + if btnEl.Clicked() { + i++ + _ = counterEl.SetContent(fmt.Sprintf("Clicked %d times", i)) } // draw frame diff --git a/examples/topdown/main.go b/examples/topdown/main.go deleted file mode 100644 index f11cf20..0000000 --- a/examples/topdown/main.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "github.com/bloodmagesoftware/bloodmage-engine/engine/core" - "github.com/bloodmagesoftware/bloodmage-engine/engine/level" - "github.com/bloodmagesoftware/bloodmage-engine/engine/topdown" -) - -func main() { - core.InitOptions() - - l := level.New() - level.Set(l) - - core.P.X = 1.5 - core.P.Y = 1.5 - - topdown.Init() - core.Start("Top Down Example") - defer core.Stop() - - core.LockCursor(true) - - // game loop - for core.Running() { - topdown.ProcessInput() - topdown.MovePlayer() - topdown.RenderViewport() - - // draw frame - core.Present() - } -}