Button widget : problem with 9-Slice (ImageSheet) method

@Brent,

Another observation I made regarding slices etc and combining images seamlessly is that I would not recommend using the center reference point - you just don’t know exactly where the edges will end up with.

What I do is make everything top left and work from there - again it helps with pixel-perfect positioning (and since, for example, with buttons you supply the width and height, it doesn’t make it any less centered (as you just position everything offset by -math.floor( width / 2 ) and -math.floor( height / 2 ), but you place your top left piece, and offset using the width and height from there by the pieces you’ve already placed.

Just to show my different approach, here is the part of my button code where I set up the 9 slice graphics. In case you are wondering, the line:[lua]class.getThemeImage( “button_” … sliceName )[/lua] is how I hook into the widget theme - it returns the imagesheet, the frame number, and the size of the requested sprite.

Point is, you can see where I set up my positions, and how I start ‘top left’ and work from there, with all positions relative to that, but taken directly from the sizes of things. I do do something a bit cheeky, that might potentially come back to haunt me but so far hasn’t caused problems, and that is when I need to create a sprite of a certain stretched size, I actually pass those required sizes as the parameters to newImageRect(), rather than create it at its default size, and then scale (has also proved better for getting specific pixel sizes - but might screw up the automatic content loading, so as far as that is concerned, procede with caution!).

[lua]        – Get the slice values

        local sheet

        local slices     = {}

        local sliceNames = {

            “topLeft”,

            “topMiddle”,

            “topRight”,

            “bottomLeft”,

            “bottomMiddle”,

            “bottomRight”,

            “middleLeft”,

            “middle”,

            “middleRight”,

            “topLeftOver”,

            “topMiddleOver”,

            “topRightOver”,

            “bottomLeftOver”,

            “bottomMiddleOver”,

            “bottomRightOver”,

            “middleLeftOver”,

            “middleOver”,

            “middleRightOver”,

        }

        for i = 1, #sliceNames do

            local frame, size

            local sliceName     = sliceNames[i]

            sheet, frame, size  = class.getThemeImage( “button_” … sliceName )

            local width, height = size.width, size.height

            slices[sliceName] = { frame = frame, size = size }

        end

        – Get sizes

        local width  = params.width

        local height = params.height

        local left   = -math.floor( width / 2 )

        local top    = -math.floor( height / 2 )

        

        local leftWidth    = slices.middleLeft.size.width

        local rightWidth   = slices.middleRight.size.width

        local topHeight    = slices.topMiddle.size.height

        local bottomHeight = slices.bottomMiddle.size.height

        local middleWidth  = width - leftWidth - rightWidth

        local middleHeight = height - topHeight - bottomHeight

        local xMid   = left + leftWidth

        local right  = left + leftWidth + middleWidth

        local yMid   = top + topHeight

        local bottom = top + topHeight + middleHeight

        – Create the actual image slices

        local slicesLayout = {

            topLeft          = { self._default, left,  top,    leftWidth,   topHeight }, 

            topMiddle        = { self._default, xMid,  top,    middleWidth, topHeight }, 

            topRight         = { self._default, right, top,    rightWidth,  topHeight }, 

            bottomLeft       = { self._default, left,  bottom, leftWidth,   bottomHeight }, 

            bottomMiddle     = { self._default, xMid,  bottom, middleWidth, bottomHeight }, 

            bottomRight      = { self._default, right, bottom, rightWidth,  bottomHeight }, 

            middleLeft       = { self._default, left,  yMid,   leftWidth,   middleHeight }, 

            middle           = { self._default, xMid,  yMid,   middleWidth, middleHeight }, 

            middleRight      = { self._default, right, yMid,   rightWidth,  middleHeight }, 

            topLeftOver      = { self._over, left,  top,    leftWidth,   topHeight }, 

            topMiddleOver    = { self._over, xMid,  top,    middleWidth, topHeight }, 

            topRightOver     = { self._over, right, top,    rightWidth,  topHeight }, 

            bottomLeftOver   = { self._over, left,  bottom, leftWidth,   bottomHeight }, 

            bottomMiddleOver = { self._over, xMid,  bottom, middleWidth, bottomHeight }, 

            bottomRightOver  = { self._over, right, bottom, rightWidth,  bottomHeight }, 

            middleLeftOver   = { self._over, left,  yMid,   leftWidth,   middleHeight }, 

            middleOver       = { self._over, xMid,  yMid,   middleWidth, middleHeight }, 

            middleRightOver  = { self._over, right, yMid,   rightWidth,  middleHeight }, 

        }

        for k, v in pairs( slicesLayout ) do

            local sliceData = slices[k]

            local image     = display.newImageRect( sheet, sliceData.frame, v[4], v[5] )

            image:setReferencePoint( display.TopLeftReferencePoint )

            v[1]:insert( image )

            image.x         = v[2]

            image.y         = v[3]

        end

    end

[/lua]

Hi Olivier,

Can you please post your code for the image sheet setup? Most likely it’s a simple fix.

Thanks,

Brent

Hi Brent

Here is my code :

local widget = require ("widget") local sheetInfo = require("slicesBt") local imageSheet = graphics.newImageSheet( "slicesBt.png", sheetInfo:getSheet() ) local mySliceBt = widget.newButton{ id = "bt", label = "Slice", sheet = imageSheet, font = "Arial", fontSize = 14, emboss = false, topLeftFrame = sheetInfo:getFrameIndex("topLeftFrame"), topMiddleFrame = sheetInfo:getFrameIndex("topMiddleFrame"), topRightFrame = sheetInfo:getFrameIndex("topRightFrame"), middleLeftFrame = sheetInfo:getFrameIndex("middleLeftFrame"), middleFrame = sheetInfo:getFrameIndex("middleFrame"), middleRightFrame = sheetInfo:getFrameIndex("middleRightFrame"), bottomLeftFrame = sheetInfo:getFrameIndex("bottomLeftFrame"), bottomMiddleFrame = sheetInfo:getFrameIndex("bottomMiddleFrame"), bottomRightFrame = sheetInfo:getFrameIndex("bottomRightFrame"), } mySliceBt.x = display.contentCenterX mySliceBt.y = display.contentCenterY

If you want I can post my imageSheet and my png files

I use TexturePacker.

Thanks for your help

best,

Olivier

Hi Olivier,

Thanks for the code. First, try setting a width and height for the button. Also, I notice that you don’t have the frames declared for the “over” state… for testing, you can just use the same frames for “default” like:

[lua]

topLeftOverFrame = sheetInfo:getFrameIndex(“topLeftFrame”),

topMiddleOverFrame = sheetInfo:getFrameIndex(“topMiddleFrame”),

–etc…

[/lua]

(notice that I just added the Over part in each one, but I’m setting the normal frame as “default” uses from the sheet)

Please try this and let me know what happens.

Best regards,

Brent

Hi, I’m having the same problem; space between sliced parts. I think it has to do with the way corona sdk scales the individual parts. I tried to set the sampling filter to either “nearest” or “linear” but problem persists.

Here’s the imagesheet:

Here’s the sheet info: (exported from TexturePacker)

SheetInfo.sheet = { frames = { { -- btn-green-up-1 x=90, y=45, width=8, height=8, }, { -- btn-green-up-2 x=2, y=45, width=76, height=8, }, { -- btn-green-up-3 x=80, y=45, width=8, height=8, }, { -- btn-green-up-4 x=90, y=2, width=8, height=30, }, { -- btn-green-up-5 x=2, y=2, width=76, height=30, }, { -- btn-green-up-6 x=80, y=2, width=8, height=30, }, { -- btn-green-up-7 x=90, y=34, width=8, height=9, }, { -- btn-green-up-8 x=2, y=34, width=76, height=9, }, { -- btn-green-up-9 x=80, y=34, width=8, height=9, }, }, sheetContentWidth = 100, sheetContentHeight = 55 }

And here’s the code:

local btn = widget.newButton{ id = "btn-start", width = 227, height = 44, emboss = false, font = "Arial", fontSize = 18, labelColor = { default = {0, 0, 0}, over = {255, 255, 255} }, labelYOffset = 2, label = "Play", sheet = self.sheet, topLeftFrame = skin:getFrameIndex("btn-green-up-1"), topMiddleFrame = skin:getFrameIndex("btn-green-up-2"), topRightFrame = skin:getFrameIndex("btn-green-up-3"), middleLeftFrame = skin:getFrameIndex("btn-green-up-4"), middleFrame = skin:getFrameIndex("btn-green-up-5"), middleRightFrame = skin:getFrameIndex("btn-green-up-6"), bottomLeftFrame = skin:getFrameIndex("btn-green-up-7"), bottomMiddleFrame = skin:getFrameIndex("btn-green-up-8"), bottomRightFrame = skin:getFrameIndex("btn-green-up-9"), topLeftOverFrame = skin:getFrameIndex("btn-green-up-1"), topMiddleOverFrame = skin:getFrameIndex("btn-green-up-2"), topRightOverFrame = skin:getFrameIndex("btn-green-up-3"), middleLeftOverFrame = skin:getFrameIndex("btn-green-up-4"), middleOverFrame = skin:getFrameIndex("btn-green-up-5"), middleRightOverFrame = skin:getFrameIndex("btn-green-up-6"), bottomLeftOverFrame = skin:getFrameIndex("btn-green-up-7"), bottomMiddleOverFrame = skin:getFrameIndex("btn-green-up-8"), bottomRightOverFrame = skin:getFrameIndex("btn-green-up-9") }

Here you can see a screengrab of the issue:

try adding 1 to the extrude value in TexturePacker before exporting that should fix your problem

Thanks, that almost did it for me. Testing with the corona simulator, in some resolutions it now looks just right, but in other resolutions (such as iPhone zoomed in) it looks like this:

Notice there are two 1-pixel horizontal gaps there. Can’t figure out how to get rid of them. Any idea?

That would suggest there is something slightly wrong with the images, what happens when you extrude by 2

Also do you have trim on? if so try turning that off. The top parts of the box have a transparent top line I’m wondering if that is causing the problem (top left is at 90x45 and is 8x8).

If all else fails have you tried it on a device, you do get occasionally different results.

Hi

@Brent : thank you for your answer, I tried setting a width and height for the button and I also declared the “over” state with  the same frames. But I still have my problem.

@Cublah : I tried adding 1 to the extrude value in TexturePacker before exporting and … it fixed the problem!!! Thank you Cublah for your help. How do you explain this? What is the role of this “extrude” parameter?

To all, thank you for your time and your help.

Best,

Olivier

@Oliver

It’s an openGL thing, the texture filtering is probably set to GL_LINEAR, setting it to GL_NEAREST fixes the problem but GL_LINEAR looks better. Extrude duplicates the outside of the texture all around by the value indicated (1 in this case)

Thanks Cublah for these explanations :slight_smile:

O.

I too just solved the problem! It appears that the height of the top parts has to be equal to the height of the bottom parts. Seems like an unnecessary constraint but oh well :slight_smile:

Thank you so much cublah

Hi :slight_smile:

another question

the widget button documentation says :

 

width, height (required)

Numbers. This is the width/height of the Button. If you are using Single frame button creation set these to the width/height of your image. If you are using 9-Piece Button Creation (default) you may set these to any width/height and the  button will scale accordingly.

 

I use 9-Pieces Button Creation but my button doesn’t scale accordingly like it says…

Have I miss something?

Thanks for your help!

Olivier

Hi @Olivier,

With 9-slice buttons, the width and height will specify the overall size of your button. This allows you to build buttons of basically any size, while the corner pieces remain smooth (and not stretched).

If this isn’t happening, please post your current code so I can inspect.

Thanks,

Brent

Hi Brent

thanks for your answer  :slight_smile:

when you say_ “the width and height will specify the overall size of your button”,_ I’m not sure to correctly understand.

In my case I have these dimensions for iPad Retina resolution :

  • bottomLeftFrame >  width : 74, height : 74
  • bottomMiddleFrame >  width : 2, height : 74
  • bottomRightFrame >  width : 74, height : 74
  • middleFrame >  width : 2, height : 2
  • middleLeftFrame >  width : 74, height : 2
  • middleRightFrame >  width : 74, height : 2
  • topLeftFrame >  width : 74, height : 74
  • topMiddleFrame >  width : 2, height : 74
  • topRightFrame >  width : 74, height : 74

I’m not sure, sorry, but In that case, I imagine that the overall size of my button may be :

width = bottomLeftFrame + bottomMiddleFrame + bottomRightFrame = 74 + 2 + 74 = 150

height = bottomLeftFrame + middleLeftFrame + topLeftFrame = 74 + 2 + 74 = 150

overall size of my button :

width = 150

height = 150

These are the dimensions I put on my code, but unfortunately, the result is a button of 150*150, regardless the label and font dimensions. No dynamic button size for me  :frowning:

If you want I can post you my imageSheet 

Thank you again

Olivier

----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- -- Your code here local widget = require ("widget") local sheetInfo = require("slicesBt") local imageSheet = graphics.newImageSheet( "slicesBt.png", sheetInfo:getSheet() ) local mySliceBt = widget.newButton{ id = "mySliceBt", label = "Slice button is there!", sheet = imageSheet, width = 150, height = 150, font = "Arial", fontSize = 14, emboss = false, labelColor = { default = { 0, 0, 0, 255 }, over = { 0, 0, 0, 150 }, }, topLeftFrame = sheetInfo:getFrameIndex("topLeftFrame"), topMiddleFrame = sheetInfo:getFrameIndex("topMiddleFrame"), topRightFrame = sheetInfo:getFrameIndex("topRightFrame"), middleLeftFrame = sheetInfo:getFrameIndex("middleLeftFrame"), middleFrame = sheetInfo:getFrameIndex("middleFrame"), middleRightFrame = sheetInfo:getFrameIndex("middleRightFrame"), bottomLeftFrame = sheetInfo:getFrameIndex("bottomLeftFrame"), bottomMiddleFrame = sheetInfo:getFrameIndex("bottomMiddleFrame"), bottomRightFrame = sheetInfo:getFrameIndex("bottomRightFrame"), topLeftOverFrame = sheetInfo:getFrameIndex("topLeftFrame"), topMiddleOverFrame = sheetInfo:getFrameIndex("topMiddleFrame"), topRightOverFrame = sheetInfo:getFrameIndex("topRightFrame"), middleLeftOverFrame = sheetInfo:getFrameIndex("middleLeftFrame"), middleOverFrame = sheetInfo:getFrameIndex("middleFrame"), middleRightOverFrame = sheetInfo:getFrameIndex("middleRightFrame"), bottomLeftOverFrame = sheetInfo:getFrameIndex("bottomLeftFrame"), bottomMiddleOverFrame = sheetInfo:getFrameIndex("bottomMiddleFrame"), bottomRightOverFrame = sheetInfo:getFrameIndex("bottomRightFrame"), } mySliceBt.x = display.contentCenterX mySliceBt.y = display.contentCenterY

Simply set the width and height to whatever size you want. It doesn’t need to be 150.

Thanks Wenzil for your answer.

But in my app, I have 11 different languages.

And labels have very different sizes from one language to another, for naming the same concept.

For example 

     [“en”] = “Try again”,

     [“fr”] = “Recommencer le défi”,

     [“es”] = “Volver a empezar el reto”,

     [“de”] = “Aufgabe neu beginnen”,

     [“pt”] = “Recomeçar o desafio”,

     [“it”] = “Ricomincia la sfida”,

     [“nl”] = “De opdracht opnieuw beginnen”,

     [“ru”] = “Выполнить задание еще раз”,

     [“ja”] = “チャレンジに再挑戦”,

     [“ko”] = " 문제 다시 풀기",

     [“zh”] = “重新开始”,

As you can see, “Try again” in English, and "“De opdracht opnieuw beginnen” in Netherlands, are quite different…

So I need something which is dynamic, accordingly to the label size.

I think slice button can do that, but I still haven’t found how to do it.

But I will find a solution soon I hope  :wink:

Olivier

Ah I see. I don’t think Corona’s widget 2.0 library supports auto-sized buttons. They have made it open-source though, so you could try adding that feature in. Alternatively you could try to find a 3rd-party ui library that supports that feature. WidgetCandy for example. (though it’s not free)

Of course the simplest solution would be to use a one-size-fits-all button. 

Cheers

I would like to point out that I see gaps in the default theme settings as supplied in the github version.

I believe pretty much all the widgets that use slices are prone to an error that can produce gaps: Rounding errors.

I have noticed a complete dearth of math.floor() etc for placing image slices on pixel boundaries, and I am fairly certain that this is the cause of gaps in between the slices.

Why am I fairly certain? Because due to bugs and lack of features I’ve had to write my own versions of pretty much all of the widgets, and I *always* math.floor() or math.ceil() my x, y, width and height values as appropriate, and I have yet to see a single gap between my pieces, even when using the same sprite sheet and images as the default widgets theme.

Gaps can happen between images in a sprite sheet if the relevant graphics don’t reach the edge of the tile (due to scaling up - I was the one who posted the solution to gaps between sprites in the various tile-based engine threads here), but this normally isn’t an issue here. For example, if someone creates a button using the default widget theme, they’ll notice that the button image is a single button. The slices are made in the spritesheet, but the important thing is that each button slice butts up against the correct other slices necessary - IE there is no gap in the image source at all. Hence the gaps can only come from badly positioned slices*.

* If you created your own sprite sheets for the widgets and you DO have the various slice parts with gaps between them, that is an ADDITIONAL potential source of gaps. Try using the default github theme and see if that still gives you gaps.

Hi @rakoonic,

Thanks for bringing up this observation. I’ve mentioned it to the engineers to investigate.

Brent