oakmound / oak

A pure Go game engine

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Q: can text be clipped

glycerine opened this issue · comments

I'm just curious if (and how if so) it is possible in Oak to clip text to be inside a given rectangle bounding-box. Here's an example of what that looks like in Gio if its not clear what I mean:

https://github.com/glycerine/hello_gio/blob/master/screenshot.png

Yes-- it isn't built in (yet) but it's not too bad to make a renderable type that cuts off other renderables within it:

package main

import (
	"image"
	"image/color"
	"image/draw"

	"github.com/oakmound/oak/v2"
	"github.com/oakmound/oak/v2/render"
	"github.com/oakmound/oak/v2/scene"
)

func main() {
	var loop = true
	oak.AddScene("bounding-box-example", scene.Scene{
		Start: func(string, interface{}) {
			testBB := NewBoundingBox(130, 15, []render.Renderable{
				render.NewColorBox(130, 15, color.RGBA{50, 0, 0, 50}),
				render.NewStrText("a very long text string- a very long text string", 0, 10),
			})
			testBB.SetPos(100, 100)
			render.Draw(testBB, 0, 0)
		},
		Loop: scene.BooleanLoop(&loop),
		End:  scene.GoTo("bounding-box-example"),
	})

	oak.Init("bounding-box-example")
}

type BoundingBox struct {
	render.LayeredPoint
	rgba    *image.RGBA
	toBound []render.Renderable
}

func NewBoundingBox(w, h int, toBound []render.Renderable) *BoundingBox {
	return &BoundingBox{
		LayeredPoint: render.NewLayeredPoint(0, 0, 0),
		rgba:         image.NewRGBA(image.Rect(0, 0, w, h)),
		toBound:      toBound,
	}
}

func (bb *BoundingBox) Draw(buff draw.Image) {
	bb.DrawOffset(buff, 0, 0)
}
func (bb *BoundingBox) DrawOffset(buff draw.Image, xOff, yOff float64) {
	for _, bound := range bb.toBound {
		bound.Draw(bb.rgba)
	}
	render.ShinyDraw(buff, bb.rgba, int(bb.X()+xOff), int(bb.Y()+yOff))
}
func (bb *BoundingBox) GetDims() (int, int) {
	rect := bb.rgba.Rect
	return rect.Max.X, rect.Max.Y
}

Which displays as:
bounding-box-example

Another approach that treats transparency differently and has fewer pixel-drawing iterations, in exchange for some additional complexity:

type BoundingBox struct {
	render.LayeredPoint
	width, height int
	toBound       []render.Renderable
}

func NewBoundingBox(w, h int, toBound []render.Renderable) *BoundingBox {
	return &BoundingBox{
		LayeredPoint: render.NewLayeredPoint(0, 0, 0),
		width:        w,
		height:       h,
		toBound:      toBound,
	}
}

type boundImage struct {
	x, y          int
	width, height int
	draw.Image
}

func (b boundImage) Set(x, y int, c color.Color) {
	if x > b.width+b.x || y > b.height+b.x {
		return
	}
	if x < b.x || y < b.y {
		return
	}
	b.Image.Set(x, y, c)
}

func (bb *BoundingBox) Draw(buff draw.Image) {
	bb.DrawOffset(buff, 0, 0)
}
func (bb *BoundingBox) DrawOffset(buff draw.Image, xOff, yOff float64) {
	wrapperImg := boundImage{x: int(bb.X()), y: int(bb.Y()), width: bb.width, height: bb.height, Image: buff}
	for _, bound := range bb.toBound {
		bound.DrawOffset(wrapperImg, bb.X(), bb.Y())
	}
}
func (bb *BoundingBox) GetDims() (int, int) {
	return bb.width, bb.height
}

bounding-box-example

awesome. thank you so much!

By the way I'm using the first less efficient version because the 2nd one is buggy (most text isn't shown) and I haven't had time to debug it yet.

Ah. I see it.

This:

func (b boundImage) Set(x, y int, c color.Color) {
    if x > b.width+b.x || y > b.height+b.x {

should be

func (b boundImage) Set(x, y int, c color.Color) {
    if x > b.width+b.x || y > b.height+b.y {  // change b.x to b.y in the 2nd instance