oakmound / oak

A pure Go game engine
Apache License 2.0
1.53k stars 83 forks source link

Q: can text be clipped #146

Closed glycerine closed 3 years ago

glycerine commented 3 years ago

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

200sc commented 3 years ago

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

200sc commented 3 years ago

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

glycerine commented 3 years ago

awesome. thank you so much!

glycerine commented 3 years ago

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.

glycerine commented 3 years ago

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