Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: [VB.NET] Customizing button control - radial gradient /rounded rectangle /images  (Read 8995 times)

0 Members and 1 Guest are viewing this topic.

Recoil

  • Jr. Member
  • **
  • Posts: 76
    • View Profile
I guess I was posting about this in the wrong forum.  Here is what I was running into with the gradient issue:
http://en.sfml-dev.org/forums/index.php?topic=20324.0

So that is fixed though, and I was able to piece together how to do either a vertical or horizontal gradient for my button control.  TBH, I have been so unsuccessful getting any of the control libraries that are out there working with VB.NET, because there are virtually no tutorials.  Most of the tutorials and documentation I have found has been in C++, and very limited info in C#.

Anyways, I'm making some progress on a custom control library.  There are a few bugs I am still working out, and some functionality missing, but so far I am okay with what I have been able to come up with on my own:


I would like to extend this though, mainly the graphics for the buttons.  Still working out how to do it, but it can (originally did) show images for the buttons.  So, with the above issue solved for the gradient, I can make the gradient vertical or horizontal.  I would like to see about extending this to a radial gradient, and hopefully having a rounded rectangle.  I ran across this looking for an idea for a rounded rectangle shape:
https://github.com/SFML/SFML/wiki/Source%3A-Draw-Rounded-Rectangle

With my limited skills, I made a failed attempt to convert that to VB.NET, and I get a flag with really weird half circles on the sides of the flag.  I think it best I try to make something on my own instead, that way I may be more able to understand how it actually works.

I would like some help understanding how a rounded rectangle shape should be made?  I was able to find a limited number of docs online in C++ that suggests using triangle fans...but those are way over my head, and I was lucky to figure out how to make a rectangle on my own.

Second, instead of using either a vertical or horizontal gradient, I would like to be able to use a radial gradient.  The same documentation I found on this also dealt with using triangle fans...which doesn't help when it is C++.

I'm not necessarily looking for code, but it would be appreciated.  But if someone can provide me a few ideas how to achieve a radial gradient effect as well as a rounded rectangle effect for a custom button control I would really appreciate it.

Note: I am also after some general ideas for being able to skin these controls, like selecting a color scheme and it apply to every customizable aspect of that control, which would make things easier instead of setting every color for every object on the control.  Another thing I am looking for ideas on is the best approach to allowing the use of gradients, as well as custom images.  The only thing here I can think of is on the drawing sub for the control (If ImgTexture = Nothing Then DrawGradient, Else Draw ImageTexture)...but there has got to be a more logical approach that I have overlooked.

I really appreciate any help or ideas you guys can give me...Thanks!

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
A rounded rectangle would be a vertex array that is a rectangle with extra vertices at the corners to form the shape of a quarter of an ellipse.

Radial gradients inside a rectangle shape
One option is to use a shader as it can be applied to the shape as required.
Here's a Radient Gradient Shader that can do this for you.

If you need to avoid shaders, you'll need to modify the actual geometry since gradients are formed between points in a triangle. This is the sort of thing you'll need to get a radial gradient inside a rectangle:

You can either draw the circular section as a triangle fan over the top of the rectangle or you can break the rectangle down into a collection of triangles and draw it as a single object. Note that the outer quads in the image will also need to be broken down into triangles.
Note also that you cannot change the expansion of the gradient this way; it's always linear between points. If you need the red section (in the image) to be larger, you'll need another group of triangles of just red.

As you can see, the shader route can be much simpler.

If, however, you would consider using textures for the gradient, you could have just stretch it to match. You could even use something like Selba Ward's Nine Patch to draw a fixed-sized rounded rectangle and a stretched radial gradient on the centre section. A bit drawback here is that the gradient is stretched so the texture pixels may be visible.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Recoil

  • Jr. Member
  • **
  • Posts: 76
    • View Profile
Thanks Hapax.  I have tried working with shaders before, but ran into too many issues.  The only benefit of going the second route is that the code only has to be written once and ported to my class.  So I chose a midpoint between the second and third options you provided, and decided to draw 5 connected rectangle shapes that define the size of the gradient by the "thickness" value.

Warning: broken down redneck code:
(click to show/hide)

Here are those results:


Trying to get an idea of how to do the rounded corners, I came across this:


What I would like to do is be able to pass a variable for the number of points, say 3, for how many triangles that would make up the fan, and generate the fan that way, instead of trying to micro calculate each point.  The math involved is obviously above my head because it has been several year since I messed with this stuff.

But for each corner I can define the following:
'This triangle fan is going to be 90 degrees, with 4 triangles, 3 points, in between x1/y1 & x2/y2...

'number of triangles
Dim points as Integer = 3

'cx and cy are the center coordinates of the triangle fan.
Dim cx As Integer = loc.X + thickness                            ' 100 + 25
Dim cy As Integer = loc.Y + thickness                            ' 100 + 25

'x1 and y1 are the coordinates for 270 deg point
Dim x1 As Integer = loc.X                                        ' 100
Dim y1 As Integer = loc.Y + thickness                            ' 100 + 25

'x2 and y2 are the coordinates for 0 deg point
Dim x2 As Integer = loc.X + thickness                            ' 100 + 25
Dim y2 As Integer = loc.Y                                        ' 100
 

What would be a formula for generating the points in between with their angles for that fan?
« Last Edit: May 16, 2016, 06:43:41 pm by Recoil »

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
It looks like I was mistaken. You don't actually seem to want radial gradient since your round rectangle has mostly straight and linear gradient :P

I can't help you with specific .NET code as I work with C++ but it looks like you're only really looking for sine/cosine.

For the top-right corner, consider this.
Loop from 0 to 90 (degrees) in however many steps you need.
"angle" should be the current degree angle converted to radians
each point is then:
(sin(angle) * width, cos(angle) * height)
where width and height are the width and height of the corner section.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Recoil

  • Jr. Member
  • **
  • Posts: 76
    • View Profile
No, you were correct, I did want a radial gradient.  But after your post it helped me achieve the appearance of a radial gradient on the button, in the most long and drawn out way I could possibly fathom. :D

I know you can't help me with specific code issue, I understand.  Maybe someone else can come along and tell me what I am supposed to do in my For/Next loop for my x/y values to increment them accordingly?  This is just wrong, and I know it.  But I'm unable to determine how to increment it properly.
Dim points As Integer = 5

' Size (thickness) of the corner piece is 25:
Dim radius As Double = thickness
Dim angle As Double = 90.0

Dim xOffset As Double
Dim yOffset As Double
Dim angleRadians As Double

' Convert degrees to radians
angleRadians = 2 * PI * angle / 360 '/ 180 '

' Calculate the X and Y offset to step
xOffset = Cos(angleRadians) * radius
yOffset = Sin(angleRadians) * radius


Dim tf As New VertexArray(PrimitiveType.TrianglesFan)

' Point0 - the inside corner
tf.Append(New Vertex(New Vector2f(cx, cy), col2))

' Point1 - the top 0deg
tf.Append(New Vertex(New Vector2f(x1, y1), col1))

For i = 1 To points

        tf.Append(New Vertex(New Vector2f(x1 + xOffset, y1 + yOffset), col1))

Next

' Point7 - 90degrees right
tf.Append(New Vertex(New Vector2f(x2, y2), col1))
MyWindow.Draw(tf)

 

Now I have found several examples to draw an arc between 2 points knowing the radius, but none of them work when trying to simply insert a point for the TriangleFan.  I know I'm so close...but probably off several dozen miles.

Recoil

  • Jr. Member
  • **
  • Posts: 76
    • View Profile
It literally took me all day, and a post in a math forum, to be able to get some help to come up with a solution.  This example is for the top right corner.  The [radius] is 25 pixels, and it sets the height of the top and bottom rectangles, and the width of the left and right ones...this also sets the size of the corner.

FYI, no I could not think of another way to make this happen...

'cx and cy are the center coordinates of the triangle fan.
        cx = loc.X + (size.Width - (radius))
        cy = loc.Y + radius

        'x1 and y1 are the coordinates for 270 deg point
        x1 = loc.X + (size.Width - (radius))
        y1 = loc.Y

        'x2 and y2 are the coordinates for 0 deg point
        x2 = loc.X + (size.Width)
        y2 = loc.Y + radius

        Dim trifan1 As New VertexArray(PrimitiveType.TrianglesFan)
        trifan1.Append(New Vertex(New Vector2f(cx, cy), col2))
        trifan1.Append(New Vertex(New Vector2f(x1, y1), col1))

        For i = 0 To points - 1

            Dim x As Double
            Dim y As Double
            Dim t As Double = (PI / 2) * (i / points - 1)

            x = cx + Cos(t) * (radius)
            y = cy + Sin(t) * (radius)

            trifan1.Append(New Vertex(New Vector2f(x, y), col1))

        Next
       
        trifan1.Append(New Vertex(New Vector2f(x2, y2), col1))
        DmWindow.Draw(trifan1)
 

This is the modification for the bottom left corner.  For each corner you have to offset the Cos/Sin by their inverses, depending on which corner you are working on.
            x = cx + -Cos(t) * (radius)
            y = cy + -Sin(t) * (radius)
 

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
It looks like you're complicating this a little.

The vertices should likely be specified in this way:


I'm not sure why you needed so many explicit points.
You need to know the corner point (lower-left for a top-right corner), the radius, and the number of points for the corner.
Some pseudo code (since we're using different languages):
cornerPoint = [x, y]
radius = r
edgePointCount = p
beginAngle = a1
endAngle = a2
triFan = vertexArrayTriangleFan
triFan.append(cornerPoint)

for i = 0 to (edgePointCount - 1)
    currentAngle = radiansFromDegrees((endAngle - beginAngle) * (i / (edgePointCount - 1)))
    tempPoint = [sin(currentAngle] * radius, -cos(currentAngle) * radius]
    triFan.append(tempPoint)
You can provide [x, y], r, p, a1 and a2 to change how this works. It can be used for all four corners by specifying different begin and end angles (a1 and a2) and its position [x, y]

Note that I used positive sine for x and negative cosine for y so that angles are clockwise and zero angle is pointing upwards.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Recoil

  • Jr. Member
  • **
  • Posts: 76
    • View Profile
2 points...

First, it had not dawned on me to use pseudo code for most of this...I probably could have gotten a lot more help over the years, LOL

Second, you are right.  Initially I knew I had to make the center point(0), but I also thought I had to set the point(1) and point(end), and just fill in between.  Because I am so used to breaking things down (putting all the points in variables instead of on one line) I ended up adding more lines than I probably should have.  However, looking back at my solution, I am really adding only 2 additional points...the rest of all that is just broken down lines into additional variables.

And due to it taking a full day and a lot of brain racking on why everything I tried it would not work (not adding the center points), I almost did not even try what you suggested, but am glad I did ;)

beginAngle = 0
endAngle = 90
radius = 25
cx = (center x)
cy = (center y)

trifan1= VertexArray(PrimitiveType.TrianglesFan)
trifan1.Append((cx, cy), col2))

For i = 0 To points - 1

currentAngle = ((endAngle - beginAngle) * (i / (points - 1)))
trifan1.Append((cx + Sin(currentAngle) * radius, cy + -Cos(currentAngle) * radius), col1))

Next

MyWindow.Draw(trifan1)
 

It looks like a lot of code there, but it's actually not, and really only 2 lines shorter from my original code (without everything being broken down).  I can even simplify this even more by just putting everything in the for loop on 1 line.  But every bit of efficiency I can learn to implement is appreciated ;)

Edit: after implementing this for the whole test sub, it will only work with 8 points right out of the box.  Adding more or less make the corners into stars and various shaped hexagons and octagons, LOL.  I'll have to tinker with it.

Edit#2:  This solved it for any number of points: currentAngle = ((PI / 2) * (i / (points - 1)))

Edit#2.83: However, I also remember why I wanted to set the additional 2 points...this is a test shape for a button.  I was originally testing to make not only the gradient, but the rounded corners as well.  Setting the other 2 points on the corner allowed this to have a 45 degree angle with no points set.

I'm still pondering over all the details for my final control, and what properties I have to setup so it can be fully customizable.  Setting a larger radius currently requires changing the thickness of the outside 4 rectangles, which also sets the size of the radial gradient effect.  Honestly this has not been the easiest to figure out how to make everything work, and look right as well.


« Last Edit: May 19, 2016, 02:43:06 am by Recoil »

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
That is why my pseudo code had "radiansFromDegrees"  ;)

The "endAngle - beginAngle" is the range, specified in degrees so it will work for any corner by changing those values.
My line of code with explicit conversion to radians:
    currentAngle = ((endAngle - beginAngle) * (i / (edgePointCount - 1))) * pi / 180

The angle ranges (begin, end) are, then, for clarity:
Top-right: (0, 90)
Bottom-right: (90, 180)
Bottom-left: (180, 270)
Top-left: (270, 360)
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Setting the other 2 points on the corner allowed this to have a 45 degree angle with no points set.
Simply add 2 to the number of points before passing to the code  :P
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Recoil

  • Jr. Member
  • **
  • Posts: 76
    • View Profile
Quote
The "endAngle - beginAngle" is the range, specified in degrees so it will work for any corner by changing those values.

To be honest it has been several years since my last math class in college.  But even when I convert radians to degrees, I still have to modify the Sin/Cos +/- for each corner:

currentAngle = ((endAngle - startAngle) * (i / (points - 1))) * PI / 180

Top left
tempX = cx + -Sin(currentAngle) * radius
tempY = cy + -Cos(currentAngle) * radius
 

Top right
tempX = cx + Sin(currentAngle) * radius
tempY = cy + -Cos(currentAngle) * radius
 

Bottom right
tempX = cx + Sin(currentAngle) * radius
tempY = cy + Cos(currentAngle) * radius
 

Bottom left
tempX = cx + -Sin(currentAngle) * radius
tempY = cy + Cos(currentAngle) * radius
 

And
Quote
PI / 2 = (endPoint - startPoint) * (PI / 180)
both provide the exact same results considering I have to modify the Sin/Cos values.  I am missing something, aren't I?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
You're right, it seems that you have to add beginAngle to currentAngle for Hapax code to work. Otherwise, currentAngle is in [0 .. 90] for all four corners.
Laurent Gomila - SFML developer

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Ah, that's true. beginAngle should be added (before multiplying by pi / 180):
    currentAngle = (beginAngle + (90 * (i / (edgePointCount - 1)))) * pi / 180
This locks it to always be 90 degrees so you no longer have to specify the endAngle, just the beginAngle.

Nice catch, Laurent :)
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*