Yojimbo2000's Soda with Super Simple Buttons?

@LoopSpace Yes, it’s the “perfect mesh” rounded rectangle. Thanks for creating it. I credit you in the Soda source. I agree that attribution is a nice way of saying thanks (why not make your code CC-BY ?) I added a shader to it which adds a stroke, anti-aliasing, and texCoords for optional texture, and I changed the corner bit-flags to take advantage of Lua 5.3 bitcode.

I’ve looked at the cmodule code a few times, but have never got my head round what it’s for. I can see it’s supposed to be for just loading a single tab from a library project rather than the entire codebase, but we can do this anyway using load and readProjectTab. How is it different from, say, load(readProjectTab("Shapes library:RoundedRectangle"))() ?

@yojimbo2000: (as in the other thread) I figured out how to apply your blur to an arbitrary image, and it’s explained in your comments how to pass a texture to one of your roundedRects: how do I make the roundedRect show only the portion of the texture that matches the background it’s covering?

Well, given that creating large images and applying 2-pass Gaussian shaders to them are both quite expensive operations in terms of both memory and processing, I think the method I use in Soda is best, just draw what is under the panel (using translate and clip), to an image the size of the panel.

But… If you do want to pass texCoords into roundedRect, maybe have them as parameters, tcx, tcy, tcw, tch, (for X,y,w,h), or perhaps pack them into a single vec4. This line sets the texCoord:

t[i] = vec2((v.x + ww)/w, (v.y + hh)/h)

Where w,h are the total width, height of the rect, and ww, hh are half the width and height. So it first translates from the origin being the centre, to origin being the bottom left corner v.x+ww, then divides by total width/height to convert it to the 0.0 to 1.0 scale needed.

I’ll let you puzzle out what modifications need to be made!

Think I’ve got it. Haven’t tested, but should work. If the tc X,y,w,h are packed in a vec4 called texCoord (eg w,h are z,w, bit confusing that w swaps around, but, whatever), instead of ww = image.width * 0.5 have ww = image.width * (0.5 + texCoord.x) and instead of w = image.width have w = image.width / texCoord.z

The default value for texCoord should be this:

local texCoord = t.texCoord or vec4(0,0,1,1) --default to bottom-left-most corner, full with and height

Hmmm, the width calculation isn’t quite right…

@yojimbo2000: Re: your first suggestion, i.e. the Soda way: that sounds so much better.

It seems like I could do that pretty easily with my version of your blur function–pass it an image and also the clipping area to draw to. So I get back what you said, then I pass that as the texture to the rounded deck.

I know how to draw to a clipped area, but I don’t know how to cut that area out of the larger image to wind up with an image the size of the button.

I know how to draw to a clipped area, but I don’t know how to cut that area out of the larger image to wind up with an image the size of the button

Actually, you don’t need to clip. Set the image to the size you need (ie the clip you would have done), and then translate to the negative of the lower left corner of the clipped area, before you start drawing it.

This is how Soda.blur does that:

function Soda.Blur:drawImage()
    pushMatrix()

    translate(-self.parent:left(), -self.parent:bottom())
 
    drawing(self.parent) --draw all elements to the blur image, with the parent set as the breakpoint (so that the parent window itself does not show up in the blurred image)
    popMatrix()
end

@yojimbo2000 here’s my try:

function blurClipFromImage(sourceImage,x,y,width,height)
    local clippedRect = image(width,height)
    local clippedRectPosition = vec2(x,y)
    local clippedRectHalved = vec2(width/2,height/2)
    setContext(clippedRect)
    pushMatrix()
    translate(-clippedRectPosition.x+clippedRectHalved.x,-clippedRectPosition.y+clippedRectHalved.y)
    tint(220, 14, 14, 255)
    sprite(sourceImage,sourceImage.width/2,sourceImage.height/2)
    noTint()
    popMatrix()
    return imageWithGaussian2PassBlur(clippedRect)
end

It seems to work.

@yojimbo2000 : I don’t seem to be able to draw a roundedRect without a border. RoundedRect seems to ignore the alpha value when I set the stroke. So if I set stroke(255,0,0,0), I see a red border. Do you have the same result over in Soda?

Use noStroke() (which is the same as strokeWidth(0))to draw roundedRect without a border, same as the other primitives.

Sure, that’ll do the trick, but shouldnt the stroke color preserve the alpha value of the specified color?

I’ll look into it

It’s sort of working except for the fact that it’s totally crashing.

I’ve updated the gist. If anything jumps out at you please let me know. I have a feeling it’s requiring too many calls to setContext–probably at least three per screen element per draw right now. Not positive about that though.

@yojimbo2000 : I’ve got it working and not crashing by going back to feeding texCoord as a vec4 directly into roundedRect.

This was the tweak that worked for me:

            local w,h = t.tex.width,t.tex.height
            local textureOffsetX,textureOffsetY = texCoord.x,texCoord.y
            
            local t = {} 
            for i,v in ipairs(rr.vertices) do
                t[i] = vec2((v.x + textureOffsetX)/w, (v.y + textureOffsetY)/h)
            end

When I say “working,” I mean “basically working but still a lot of rough edges to work on”, of course. The gist is updated if you want to see.

Re: stroke alpha. In my tests, stroke alpha is respected. When I set the stroke to 255,0,0,0 (transparent red) I see a tiny amount of red fringing around the shape. This is caused by the anti-aliasing, which is applied either side of the stroke line. As it fades from the fill color to the stroke color, some red becomes visible before the color becomes totally transparent. If the stroke width is very thin, it’s possible this fringing could resemble a stroke. You can adjust the thickness of the anti-aliasing in the shader. It’s supposed to be a pixel or less (ie only really in effect when the line is not axis-aligned). I think I’ve got it to the lowest it can be, and still be having an effect. When I worked on the wireframe shader I found out about a way to precisely control line thickness (using the GLES standard derivatives extensions), but I haven’t got round yet to trying to apply this o the rounded rect stroke/ aliasing.

Re your latest code: the program now makes much more sense when you’re using it (ie button positions are remembered between launches, functionality is retained). One thing I noticed: there’s some kind of artefact that stays on screen after the welcome button disappears (it looks like it’s that button’s blurred background image?)

Secondly, regarding performance, it looks as if you’re defining a closure for each button 60 times a second. I imagine that this is a bad idea, performance wise, based on stuff I’ve read, eg http://lua-users.org/wiki/OptimisationCodingTips

Memory allocation from the heap–e.g. repeatedly creating tables or closures–can slow things down.

It might be worth checking FPS and memory-usage.

Again, I don’t think ellipse is the model to go for with buttons, as they need to have logic attached to them, which should happen in a setup phase. I think you need a 2 or 3 part structure: define the button once (callback is attached here), draw the button, test for button touches. I suppose the model I was thinking of when creating Soda was less ellipse and more parameter, tween, http.request etc, anything where a callback is attached at the initiation stage.

In terms of blur crashing the project when you were using the small images, was there a particular reason to be creating the blur every frame? ie, is it because the background under the button is moving? If not, just do it once, and cache it. Soda only redoes the blur when the orientation changes. Now that Soda has a draggable window option, I suppose I should also get it to redo the blur once the window is dropped (and think about live updating as it’s being dragged I suppose). Were you defining a new image every frame as well? That would certainly crash the project, as it’s very memory intensive. Again, you need to have a once-only button setup where the image is defined, then just reuse that same image each frame. I’m not sure that setContext is expensive, but creating new images definitely is.

If you do want to have live updating of blurriness (ie if the underlying scene is moving a lot), then the approach you have here is probably best, one blurred image for the entire backdrop, and then passing in texCoords for each panel.

btw, @UberGoober whilst you’re working on blur, here’s an unsolved mystery for you (no-one ever replied!) https://codea.io/talk/discussion/6834/draw-scaling-mystery-plus-much-faster-gaussian-blur

@yojimbo2000 : the artifact is actually just a separate test of the blur function, not a bug. I should have removed it before posting the code.

Re: the ellipse model: I think we have to agree to disagree here. Consider this an experiment in how far the ellipse model can be pushed. For the purposes of this experiment the goal is the most efficient possible code without requiring the user to type more than one line of code per UI piece.

Re: defining a closure for every frame: I don’t think I am doing this. The first thing the button code does is look for an existing element defined by the same name. If one already exists, it uses that.

By the way, your roundedRect code is showing me how to use or in variable definitions–it’s so good! I plan to rewrite all the button-initializing code using it.

Re: defining a new image every frame: if I have to do it, then I should be manually de-allocating it at the end of every draw, right? How do I do that?

the artifact is actually just a separate test of the blur function, not a bug. I should have removed it before posting the code.

Oh, I should’ve spotted that.

defining a closure for every frame: I don’t think I am doing this. The first thing the button code does is look for an existing element defined by the same name. If one already exists, it uses that.

Yes, but you define the closure in the constructor of Button. This code runs every frame right?

function introductionUI()
    tintScreen(color(255,128,128))
    button("welcome", function() 
                showPlayModeUI()
                choicesActive = true
        end)
end

So that is a new closure each frame. This is a bad idea. Remember that a closure is not just a string with some code in it, it’s like a mini-class instance that stores its variables, and the variables of the enclosing function (“up values”). It’s not too bad here, because the enclosing function has no variables, but it’s worth checking that Lua garbage collection is collecting it. It’s far better to define the function once somewhere, eg

welcomeButton = function() 
    showPlayModeUI()
    choicesActive = true
end

and then point to that closure:

function introductionUI()
    tintScreen(color(255,128,128))
    button("welcome", welcomeButton)
end

Re: defining a new image every frame: if I have to do it, then I should be manually de-allocating it at the end of every draw, right? How do I do that?

What I would do is, cache the image, the same way that RoundedRectangle caches meshes (and it sounds like you’re caching button instances). That’s also, I believe, how the built-in primitive commands, rect, ellipse, sprite work. So you have some test for a unique button: position + size or whatever, then see if an image has already been cached for that button, and if not, create one and cache it.

To deallocate, remove all pointers to the image (set them to nil), then collectgarbage (but best not to call collectgarbage every frame). Better yet, don’t needlessly create images every frame! :wink: