-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
draw rounded edges in Rectangle #1090
Comments
I’m a little torn here on whether the individual corner configuration is needed? Adding “CornerRadius” to Rectangle could be a nice addition. However if it was to be 4 new fields then it might hint towards a RoundedRectangle type that offers more flexibility? Just some thoughts really - no conclusion other than it sounds like a good idea in principle. |
Hm.. one I'd have no problem with an extra |
The way that we add APIs here is based on a user requirement and exposing the least possible changes to support that - so maximum configuration isn't really a mantra we follow. If such configuration is needed we could add it as long as the API is kept clean and simple. The |
@andydotxyz Isn't it possible to just add a new field to |
Would the dev need to implement anti-aliasing for this, or does a suitable API already exist? I'm still making a go/no-go decision about whether to implement my app in Go + Fyne. It's possible that I could add this feature, if it isn't done by then. |
To add new canvas primitives (or features to them) we need to add in the software and OpenGL renderers. |
In draw.go you are using module rasterx to draw Line, Rect and Circle. |
Yes that would work, but it is a fallback for when OpenGL rendering can’t deal with the object. As having no border is currently drawn as hardware accelerated texture you might need the fallback mechanism to work differently than you described. |
So far I understand the Circle object is always rendered as fallback and for OpenGL rendering of a RoundRect there would be a solution necessary like: Texture 9-slices (Triangle) or using Polygons. Is this correct? func (p *glPainter) drawTextureWithDetails(o fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture,
pos fyne.Position, size, frame fyne.Size, fill canvas.ImageFill, alpha float32, pad float32) {
texture := p.getTexture(o, creator)
if texture == NoTexture {
return
}
aspect := float32(0)
if img, ok := o.(*canvas.Image); ok {
aspect = painter.GetAspect(img)
if aspect == 0 {
aspect = 1 // fallback, should not occur - normally an image load error
}
}
points := p.rectCoords(size, pos, frame, fill, aspect, pad)
vbo := p.glCreateBuffer(points)
p.glDrawTexture(texture, alpha)
p.glFreeBuffer(vbo)
}
func (p *glPainter) drawCircle(circle *canvas.Circle, pos fyne.Position, frame fyne.Size) {
p.drawTextureWithDetails(circle, p.newGlCircleTexture, pos, circle.Size(), frame, canvas.ImageFillStretch,
1.0, painter.VectorPad(circle))
}
...
func (p *glPainter) drawRectangle(rect *canvas.Rectangle, pos fyne.Position, frame fyne.Size) {
if (rect.FillColor == color.Transparent || rect.FillColor == nil) && (rect.StrokeColor == color.Transparent || rect.StrokeColor == nil || rect.StrokeWidth == 0) {
return
}
p.drawTextureWithDetails(rect, p.newGlRectTexture, pos, rect.Size(), frame, canvas.ImageFillStretch,
1.0, painter.VectorPad(rect))
}
``` |
You are right that Circle still needs to have acceleration added. |
During my attempts to draw a rectangle as primitive GLObject I realized that drawLine(...), drawRectangle(...) and so on are called twice on first and following render process with same param values. Is this necessary for layout or rendering? I switched the original drawRectangle(...) to fragmentshader and vertexshader (GLSL) and the try seems to work. I saw there is the lib raylib, which offers all the core gl functionality in a easier way and many base shapes (RoundRec), that are rendered as vertexshader. Raylib supports many platforms and OpenGL/ES versions. Wouldn't it be easier to use core modules of raylib for all backend OpenGL rendering? |
They should not be called twice with identical values as we have a cache to handle this.
I don't know the library but it looks like C code and our OpenGL work is using the Go bindings - I would prefer to keep it out of C as much as possible, unless there is a sensible reason like a large performance gain. |
Do remember to double check that you have not invoked with a borderless rectangle, that already uses OpenGL shaders (it is only when a border radius is set with non-transparent border that it would use the old software fallback). |
I have changed only in https://github.com/fyne-io/fyne/blob/master/internal/painter/gl/draw.go the drawRectangle(...) to: func (p *glPainter) drawRectangle(rect *canvas.Rectangle, pos fyne.Position, frame fyne.Size) {
println("***Rectangle***")
points := p.rectCoords(pos, rect.Size(), rect.StrokeWidth, frame)
vbo := p.glCreateRectBuffer(points)
p.glDrawRect()
p.glFreeBuffer(vbo)
} And I use canvas.NewRectangle() or widget.NewButton(). You are right only the Button ist called twice, sorry. raylib has many language bindings and for golang too. I don't know what the overhead would be, but maybe a fallback would not be necessary then. |
That is not going to be a working solution in our case. Linking to C-projects unfortunately introduces another dependency that needs to be installed at both compile and runtime. It it does not quite work as regular Go code and because of our effort to be as statically linked as possible, we need to be very careful with new dependencies (or don’t add them at all). |
Unless it is statically compiled in like with GLFW :) |
I would like to point out that there are cost and risk to consider by pulling non go dependency in. The big one, is that you now depend on another community to provide what you need. You also need to know that other language. Past experience, tell me that crossing language barrier means few people will be able to contribute and help address issues. Would really prefer a go dependencies here for that reason. |
That might make the binaries significantly bigger if the projects are very large. I don’t know if it even can strip out the unused parts of the C code. Probably not. |
Last days i implemented an alpha version of a round renctangle as primitive GLObject in the fork: https://github.com/renlite/fyne
Some notes:
|
This is super cool, thanks for working on this. Agreed that percentage isn't intuitive. If you have a rectangle height 10 with radius 5 it should join in the middle. |
Just a few comments here. I agree with @andydotxyz, having segment in the rounded rectangle primitive seems to be a work around from not having a path primitive on the canvas and make the API harder than it should be. |
@renlite I quickly read through your change on your tree and it seems you have addressed our past concern. Would you mind explaining a little bit how you are doing things as the commit are a little bit difficult to follow at the moment. I would expect you would rebase/squash/make independent meaningful commits before doing a PR, but before doing that additional work just doing a quick description of what you have done will be easier and quicker. |
If the results are OK for you the next steps would be to comment the code and perhaps to paint a smaler inner shape to be able to define the stroke of the shape, which doesn't work at the moment. |
This is really amazing work thanks. |
@andydotxyz Alpha not 255.0: |
Thanks @renlite, that looks pretty good actually. Regarding your point about only supporting opaque colours, I would believe it would make the shader slightly more complex to address it, but should be doable. Is it something you can look at or will you need help looking at it? |
I think I didn't really share my idea well for the shader, but maybe adding another parameter that describe which of the 4 quarter of a circle need to be drawn would work for this. |
@Bluebugs Sorry for late reply I don't have much time at the moment to share my two ideas / directions to solve the transparent color problem. I think the problem is the overlapping of some line pixels with some circle segment pixels. That happens because I am using the original line implementation which paints the inner and outer stroke + feather of the line. Maybe a new line rendering painting only the outer side of a line could help to draw the line without overlapping the circle parts. I'm not sure if its possible to paint the line exactly along the edge of the circle without empty pixels at some positions. In other case the fragment shader should know if a pixel is overlapping and in this case no color should be painted? Maybe there is a solution with GLSL? |
@Bluebugs
|
It might not actually be possible. I was thinking of using discard in the shader to just not do anything which would mean there would not have been any conflict for that pixels, but I don't see how you could be precise enough to do so. I think the solution is only if the outline can be drawn in the same shader that does the rectangle. This way only one pixel is every pushed to the screen and the blending would work. Not to sure how that would fit in your work. |
I actually did get an idea last night that would work and shouldn't be to hard to implement. It does require shader change still. My idea would be to render the outline in a 8bits buffer and use it as a mask for the rounded rectangle shader. The rounded rectangle shader would receive both the color of the outline and the fill color too. This way when deciding to color a point, it can check if the mask color is set, in which case it would be a function of mask * outline color + (1 - mask) * gl_FragColor. |
Sorry, but I don't understand your idea "to render the outline in a 8bits buffer and use it as a mask for the rounded rectangle shader". Could you please explain it further or give me an example what you mean. Yesterday I started to implement a new version of the line rendering with stroke and feather only on one side. That should make it possible to draw the base line (of the line) exactly on the outline of the circle segement and then to use stroke + feather for antialiasing. In that case I would change the FragmentShader to transparent color (Alpha 0,0) and if necessary adapt the if statement for (distance <= ...). Do you think it could work?
But I would like to understand your solution. |
I was thinking of creating a FBO and use a texture with only one component, like GL_RED_INTEGER, as a render target. Using that FBO to render the outline of the button in that texture and then the resulting texture can be used in the rounded rectangle shader to render the outline and the inside content without "double" rendering and avoid the alpha problem when rendering the pixel twice. For reference a good introduction on FBO: https://learnopengl.com/Advanced-OpenGL/Framebuffers Your idea might work, I am not certain about rounding between two execution, but likely it should work too. At least at high level, I think it is likely to work. |
After some thinking I only changed the original method flexLineCoords(), to paint only the outside of the line (FramentShader is not changed): return []float32{
// coord x, y normal x, y
x1, y1, normalX, normalY,
x2, y2, normalX, normalY,
x2, y2, 0.0, 0.0, <<<
x2, y2, 0.0, 0.0, <<<
x1, y1, normalX, normalY,
x1, y1, 0.0, 0.0, <<<
}, halfWidth, featherWidth As far as I can see the transparent colors and anitaliasing seem to work too. https://github.com/renlite/fyne |
Nice! Great work :) |
https://github.com/renlite/fyne
Would you please inform me if the branch is ok for a pull request. |
This looks really great thanks - very promising indeed. To feed back on the |
I tried to compile but get this:
|
Thanks for your feedback. vertexRectShaderSource = `
#version 110
attribute vec2 vert;
attribute vec2 normal;
attribute float colorSwitch;
attribute float lineWidth;
attribute float feather; The reason I have selected the options pattern is not to break the original NewRectangle API and to offer further options in simple form. The new version could be used as before and can be extended with new and defined options. If I change the param to func NewRectangle(color color.Color) *Rectangle {}
// #1
func NewRectangleWithRadius(color color.Color, radius fyne.RectangleRadius) *Rectangle {}
func NewRectangleWithStroke(color color.Color, ...) *Rectangle {}
func NewRectangleWithRadiusAndStroke(color color.Color, ...) *Rectangle {}
// #2
func NewFlexRectangle(rect Rectangle) *Rectangle {}
// #3 options patter (WithStroke and WithRadius are optional)
NewRectangle(yellow, WithStroke(orange, 5.0), WithRadius(rectRad9)) Should you prefer an other implementation, please give me the names and a short API description. Button not working: func (b *Button) CreateRenderer() fyne.WidgetRenderer {
b.ExtendBaseWidget(b)
seg := &TextSegment{Text: b.Text, Style: RichTextStyleStrong}
seg.Style.Alignment = fyne.TextAlignCenter
text := NewRichText(seg)
text.inset = fyne.NewSize(theme.Padding()*2, theme.Padding()*2)
>>> background := canvas.NewRectangle(color.NRGBA{R: 255, G: 200, B: 0, A: 180}) <<<
...
r := &buttonRenderer{
ShadowingRenderer: widget.NewShadowingRenderer(objects, shadowLevel),
background: background,
tapBG: tapBG,
button: b,
label: text,
layout: layout.NewHBoxLayout(),
} To see if a button works I changed the |
Perhaps it can be debugged during a PR?
Did you remember to add the appropriate
I don't understand why you would have the same input and output type for a constructor function...?
Technically yes, but the last parameter would only be "Since: 2.3" but these annotations are per-function (or type). |
This is now fixed on developed - @renlite is a real star :) |
Is your feature request related to a problem? Please describe:
For better readability and a more diverse collection of shapes. Different Components in Material Design have them, like Material buttons, tags/chips, cards, ...
Is it possible to construct a solution with the existing API?
desired outcome not possible at this time
Describe the solution you'd like to see:
define in
canvas/Rectangle.go Rectangle struct
additional members: cornerX1, cornerX2, cornerY1, cornerY2,then define defaults in
theme/theme.go
per Widget: Makes sense for Button, Entry, Select, PopUp, TabContainer Button, ProgressBar, Spinner (wip). And other widgets which will probably come later: Frame, Switch.Maybe let the user overwrite them in code?
see border radius in CSS
The text was updated successfully, but these errors were encountered: