diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..acc15af --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2020 Noah Petherbridge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fde0e4b..7dc29b7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,144 @@ # Render: Go Graphics Library -Render is a graphics library written in Go which targets desktop applications on -Windows, MacOS and Linux as well as WebAssembly to run in the browser. +Render is a graphics rendering library for Go. -For desktop systems it uses SDL2 under the hood, and in WebAssembly it interacts -with an HTML Canvas element for drawing. +It supports SDL2 and HTML Canvas back-ends enabling its use for both desktop +applications (Linux, Mac and Windows) and WebAssembly modules for running in +the web browser. + +![Screenshot](examples/hello-world/screenshot.png) + +## Example + +```go +package main + +import ( + "git.kirsle.net/go/render" + "git.kirsle.net/go/render/sdl" +) + +func main() { + mw := sdl.New("Hello World", 320, 240) + + if err := mw.Setup(); err != nil { + panic(err) + } + + // Text that we're gonna draw in the window. + text := render.Text{ + Text: "Hello, world!", + Size: 24, + Color: render.SkyBlue, + Shadow: render.Blue, + FontFilename: "DejaVuSans.ttf", + } + + // Compute the rendered size of the text. + rect, _ := mw.ComputeTextRect(text) + + for { + // Blank the window. + mw.Clear(render.White) + + // Poll for events (mouse clicks, keyboard keys, etc.) + ev, err := mw.Poll() + if err != nil { + panic(err) + } + + // Escape key closes the window. + if ev.Escape { + mw.Teardown() + break + } + + // Get the window size. + w, h := mw.WindowSize() + + // Draw the text centered in the window. + mw.DrawText(text, render.NewPoint( + (int32(w)/2)-(rect.W/2), + (int32(h)/2)-(rect.H/2), + )) + + mw.Present() + } +} +``` + +See the `examples/` directory for examples. More will come eventually, +including some WebAssembly examples. + +## Project Status: Alpha + +This module was written as part of my drawing-based maze game, code named +[Project: Doodle](https://www.kirsle.net/doodle). It is currently in +**alpha status** and its API may change and be cleaned up in the future. + +There are a few API cleanup tasks on my to-do list for this project, but they +will come later when I have a chance to update the Project: Doodle game +accordingly: + +* I want to replace all `int32` types with normal `int` -- int32 was used + initially due to SDL2 but for the Go API I want to abstract this away. + +## Drawing Methods (Engine) + +This package provides some _basic_ primitive drawing methods which are +implemented for SDL2 (desktops) and HTML Canvas (WebAssembly). See the +render.Engine interface. The drawing methods supported are: + +* Clear(Color): blank the window and fill it with this color. +* DrawPoint(Color, Point): draw a single pixel at a coordinate. +* DrawLine(Color, A Point, B Point): draw a line between two points. +* DrawRect(Color, Rect): draw a rectangle outline between two points. +* DrawBox(Color, Rect): draw a filled rectangle between two points. +* DrawText(Text, Point): draw text at a location. +* StoreTexture(name string, image.Image): load a Go image.Image object into + the engine as a "texture" that can be re-used and pasted on the canvas. +* LoadTexture(filename string): load an image from disk into a texture. +* Copy(Texturer, src Rect, dst Rect): copy a texture onto the canvas. + +## Drawing Types + +This package defines a handful of types useful for drawing operations. +See the godoc for full details. + +**Note:** all int32 values are to become normal ints at some point in the +future, pending refactor in my Project: Doodle game. + +* Color: an RGBA color holding uint8 values for each channel. + * NewRGBA(red, green, blue, alpha uint8) to construct a new color. +* Point: holds an X,Y pair of coordinates (int32, to become int at some point) +* Rect: holds an X,Y and a W,H value (int32). +* Text: holds text and configuration for rendering (color, stroke, shadow, + size, etc.) + +## Shape Generator Functions + +The render package includes a few convenience functions for drawing +complex shapes. + +The generator functions return a channel that yields all of the Points +that should be drawn to complete the shape. Example: + +```go +var ( + A Point = render.NewPoint(10, 10) + B Point = render.NewPoint(15, 20) +) + +for pt := range render.IterLine(A, B) { + engine.DrawPoint(render.Red, pt) +} +``` + +* IterLine(A Point, B Point): draw a line from A to B. +* IterRect(A Point, B Point): iterate all the points to draw a rectangle. +* IterEllipse(A Point, B Point): draw an elipse fitting inside the + rectangle bounded by points A and B. + +## License + +MIT. diff --git a/examples/hello-world/DejaVuSans.ttf b/examples/hello-world/DejaVuSans.ttf new file mode 100644 index 0000000..a5f96b7 Binary files /dev/null and b/examples/hello-world/DejaVuSans.ttf differ diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md new file mode 100644 index 0000000..a2fcff7 --- /dev/null +++ b/examples/hello-world/README.md @@ -0,0 +1,20 @@ +# Hello World Example + +![Screenshot](screenshot.png) + +This example program draws a yellow window, with a red "ridged" border +and a blue Gopher that bounces around inside the bordered area. Some blue +text with drop-shadow is also present. + +The window can be resized and the gopher will find his way into the visible +area if he ends up outside of it. + +To run this example: `go run main.go` + +## Credits + +Gopher image was created by Takuya Ueda (https://twitter.com/tenntenn) +from https://github.com/golang-samples/gopher-vector + +This example comes with the DejaVu Sans font. License information +at https://dejavu-fonts.github.io/License.html diff --git a/examples/hello-world/gopher.png b/examples/hello-world/gopher.png new file mode 100644 index 0000000..a4c4f68 Binary files /dev/null and b/examples/hello-world/gopher.png differ diff --git a/examples/hello-world/main.go b/examples/hello-world/main.go new file mode 100644 index 0000000..aaa1e5b --- /dev/null +++ b/examples/hello-world/main.go @@ -0,0 +1,164 @@ +package main + +import ( + "image/png" + "log" + "os" + "time" + + "git.kirsle.net/go/render" + "git.kirsle.net/go/render/sdl" +) + +var ( + // Cap animation speed to 60 FPS + targetFPS = 1000 / 60 + + // Gopher sprite variables + gopher render.Texturer + texSize render.Rect + speed int32 = 4 + position = render.NewPoint(0, 0) + velocity = render.NewPoint(speed, speed) + + // Decorative border variables + borderColor = render.Red + borderSize int32 = 12 + + // Background color of the window. + bgColor = render.RGBA(255, 255, 128, 255) +) + +func main() { + mw := sdl.New("Hello World", 800, 600) + setup(mw) + + for { + start := time.Now() + + ev, err := mw.Poll() + if err != nil { + panic(err) + } + + if ev.Escape { + mw.Teardown() + break + } + + update(mw) + draw(mw) + mw.Present() + + // Delay to maintain constant 60 FPS. + var delay uint32 + elapsed := time.Now().Sub(start) + tmp := elapsed / time.Millisecond + if targetFPS-int(tmp) > 0 { + delay = uint32(targetFPS - int(tmp)) + } + mw.Delay(delay) + } +} + +func setup(e render.Engine) { + if err := e.Setup(); err != nil { + panic(err) + } + + // Load gopher sprite. + fh, err := os.Open("gopher.png") + if err != nil { + log.Fatalf("read gopher.png: %s", err) + } + img, _ := png.Decode(fh) + + gopher, _ = e.StoreTexture("gopher.png", img) + texSize = gopher.Size() +} + +func update(e render.Engine) { + position.X += velocity.X + position.Y += velocity.Y + + // Bounce off the walls. + w, h := e.WindowSize() + + if velocity.X > 0 && position.X+texSize.W >= int32(w)-borderSize { + velocity.X *= -1 + } else if velocity.X < 0 && position.X <= borderSize { + velocity.X *= -1 + } + + if velocity.Y > 0 && position.Y+texSize.H >= int32(h)-borderSize { + velocity.Y *= -1 + } else if velocity.Y < 0 && position.Y <= borderSize { + velocity.Y *= -1 + } +} + +func draw(e render.Engine) { + w, h := e.WindowSize() + + drawBorder(e, w, h) + + // Draw some text centered along the top of the canvas. + text := render.Text{ + Text: "Hello, world!", + Size: 24, + Color: render.SkyBlue, + Shadow: render.Blue, + FontFilename: "DejaVuSans.ttf", + } + rect, _ := e.ComputeTextRect(text) + e.DrawText(text, render.NewPoint( + (int32(w)/2)-(rect.W/2), + 25, + )) + + e.Copy(gopher, texSize, render.Rect{ + X: position.X, + Y: position.Y, + W: texSize.W, + H: texSize.H, + }) +} + +func drawBorder(e render.Engine, w, h int) { + // Draw the decorative border. We're going for a "ridged" border + // style here. First draw the light and dark edges of the top/left + // sides of the border. + e.DrawBox(borderColor.Lighten(40), render.Rect{ + X: 0, + Y: 0, + W: int32(w), + H: int32(h), + }) + e.DrawBox(borderColor.Darken(40), render.Rect{ + X: borderSize / 2, + Y: borderSize / 2, + W: int32(w) - (borderSize / 2), + H: int32(h) - (borderSize / 2), + }) + + // Now inset a bit and draw the light/dark edges of the bottom/right. + e.DrawBox(borderColor.Darken(40), render.Rect{ + X: borderSize, + Y: borderSize, + W: int32(w), + H: int32(h), + }) + e.DrawBox(borderColor.Lighten(40), render.Rect{ + X: borderSize, + Y: borderSize, + W: int32(w) - borderSize - (borderSize / 2), + H: int32(h) - borderSize - (borderSize / 2), + }) + + e.DrawBox(bgColor, render.Rect{ + X: borderSize, + Y: borderSize, + W: int32(w) - (borderSize * 2), + H: int32(h) - (borderSize * 2), + }) +} diff --git a/examples/hello-world/screenshot.png b/examples/hello-world/screenshot.png new file mode 100644 index 0000000..fbdb3bb Binary files /dev/null and b/examples/hello-world/screenshot.png differ diff --git a/interface.go b/interface.go index b97e289..cfd6a8c 100644 --- a/interface.go +++ b/interface.go @@ -4,7 +4,7 @@ import ( "fmt" "image" - "git.kirsle.net/apps/doodle/lib/render/event" + "git.kirsle.net/go/render/event" ) // Engine is the interface for the rendering engine, keeping SDL-specific stuff diff --git a/point_test.go b/point_test.go index d40c5da..e9b9527 100644 --- a/point_test.go +++ b/point_test.go @@ -4,7 +4,7 @@ import ( "strconv" "testing" - "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/go/render" ) func TestPointInside(t *testing.T) { diff --git a/rect_test.go b/rect_test.go index f0528c1..25e64af 100644 --- a/rect_test.go +++ b/rect_test.go @@ -4,7 +4,7 @@ import ( "strconv" "testing" - "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/go/render" ) func TestIntersection(t *testing.T) { diff --git a/sdl/canvas.go b/sdl/canvas.go index 0095585..bc26818 100644 --- a/sdl/canvas.go +++ b/sdl/canvas.go @@ -2,7 +2,7 @@ package sdl import ( - "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/go/render" "github.com/veandco/go-sdl2/sdl" ) diff --git a/sdl/events.go b/sdl/events.go index a5b8014..923b49c 100644 --- a/sdl/events.go +++ b/sdl/events.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "git.kirsle.net/apps/doodle/lib/render/event" + "git.kirsle.net/go/render/event" "github.com/veandco/go-sdl2/sdl" ) diff --git a/sdl/sdl.go b/sdl/sdl.go index 35799ef..97ff6fd 100644 --- a/sdl/sdl.go +++ b/sdl/sdl.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "git.kirsle.net/apps/doodle/lib/render" - "git.kirsle.net/apps/doodle/lib/render/event" + "git.kirsle.net/go/render" + "git.kirsle.net/go/render/event" "github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/ttf" ) diff --git a/sdl/text.go b/sdl/text.go index 1347254..54182b3 100644 --- a/sdl/text.go +++ b/sdl/text.go @@ -4,7 +4,7 @@ import ( "fmt" "sync" - "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/go/render" "github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/ttf" ) diff --git a/sdl/texture.go b/sdl/texture.go index 27b0cc7..8c52e6b 100644 --- a/sdl/texture.go +++ b/sdl/texture.go @@ -5,7 +5,7 @@ import ( "fmt" "image" - "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/go/render" "github.com/veandco/go-sdl2/sdl" "golang.org/x/image/bmp" ) diff --git a/sdl/utils.go b/sdl/utils.go index 3c7f260..ee72a9b 100644 --- a/sdl/utils.go +++ b/sdl/utils.go @@ -1,7 +1,7 @@ package sdl import ( - "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/go/render" "github.com/veandco/go-sdl2/sdl" )