What's the functional difference between a Border and a Frame in .NET MAUI?
The summary of a Border according to the documentation is
The .NET Multi-platform App UI (.NET MAUI) Border is a container control that draws a border, background, or both, around another control. A Border can only contain one child object. If you want to put a border around multiple objects, wrap them in a container object such as a layout.
And the summary of a Frame is as follows
The .NET Multi-platform App UI (.NET MAUI) Frame class is used to wrap a view or layout with a border that can be configured with color, shadow, and other options. Frames can be used to create borders around controls but can also be used to create more complex UI.
Sounds like they both do the same thing to me: drawing a border around another view (whether that's a layout or a single control doesn't matter). So why are there 2 different views? How do I decide which one to use?
I think this is due to the history of .NET MAUI. The Frame is a control that is in Xamarin.Forms. I'm not sure if it was ever intended to be the control to put a border around something, but since if was the only control that could do a shadow and border for a long time, a lot of people wrapped their controls in a Frame.
However, now with .NET MAUI there is the opportunity to fix some historical tech debt. That is why Border was introduced which is much more flexible. With Border you can give each corner an individual corner radius for instance. And instead of just a solid color you can give the Border a gradient.
So from a functional perspective the Border has more options and will probably outlive the Frame although there is no indication that Frame is going anywhere anytime soon.
There is probably more little differences here and there. Based on nothing more than a gut feeling I would think Border performs better, but I have no data to back that up.
Hope this makes it a bit more clear.
Related
Let's say you have some flutter widget, for example, a TabBar. It is rendered in a rectangular box. I need it to be rendered in an arc so that the text and the underline follow part of a circle (or better still, a bezier curve).
How can that be achieved?
This illustrates what I am trying to achieve - bent TabBar widget:
Please note that in this case, the TabBar bends following and an arc (edge of a circle). This is what I need immediately. However, in the future, I might need bending that follows the edge of an eclipse so a solution that allows it would be preferable.
Also, a solution that works for different widgets is preferred.
IMPORTANT: I do NOT want to clip a curved shape of the widget. The entire area of the widget should stay visible. Instead, I need to bend the content of the widget (=bend its render image).
Apologies for the bold font but without it, people will keep posting answers about how to clip a widget which is trivial in comparison and useless for my problem.
An answer "this is currently not supported by Flutter" from authority (e.g. high reputation in Flutter tag or Flutter a team member) will also be accepted.
One approach: Use a CustomPaint to draw lines/areas/etc freely
You can use a CustomPaint to draw whatever curved lines, areas, etc freely. Therefore, you can definitely achieve your goal - since they are nothing but curved lines, areas with curved edges, rotated texts, etc.
That will take a bit of work, but if you encapsulate it well enough, it is not that hard to implement. For example, draw a Path with filled red area, then draw a white arc, then draw several texts with calculated rotations, etc. Sounds like you can do that within maybe hundreds of lines. If you find difficulty implementing this please comment and I will explain.
Another approach: Hack Canvas such that the call to drawLine becomes indeed calls to drawPath, etc.
You may do the following to allow bend/crook for any widget (and their combination, of course), but this may need a bit of engineering. You can make your own Canvas, say, class BendCanvas implements Canvas {}. The BendCanvas.drawLine, for example, has an implementation of Canvas.drawPath with a curved path cooresponding to the input "line". Similar things hold for drawing rectangles, etc. Then you need to hack Flutter's painting logic, such that it takes in your own BendCanvas instead of the original canvas. This is a universal solution, and it is much more efficient (less code) if you plan to have a lot of bend UI; but if only on bend TabBar, maybe use the first approach.
As for the bend text
In the two approaches above, we have mainly described how you draw the bended rectangle (i.e. draw an arbitrary shaped widget). As for bending text, see: https://github.com/flutter/flutter/issues/16477 , which is still an open issue.
Remarks
It is not possible using transformations. As explained in the comments, you already see Transform does not work - it is affine or perspective, which, by definition, will not bend. Then what about other transforms? After some research there seems to be none. Then, what about looking under the scene. We know Flutter uses Skia under it to draw the UI with high performance, then let us search through Skia. However, it does not provide any transformations like bending. So there seems no use.
P.S. I am not that high reputation in Flutter tag, but I did answer >100 Flutter questions (including some dig-into-source-code style ones), and have developed >100k lines of Flutter code in the production app.
I use the .shadow(color:, radius:, x:, y:) to draw shadows in my application. This is the only way I know of drawing apps in SwiftUI. I use the .sheet(isPresented:, content:) method to pop up a view, which contains a lot of shadows, and when I debug view hierarchy, I saw these warnings:
But I don't know how to setting shadowPath, or pre-rendering the shadow into an image and putting it under the layer in SwiftUI, please help me.
This warning is not caused because your code is inherently bad, but as a way of telling you that there are much more performant ways of rendering shadows.
As your UI elements are currently written, SwiftUI is drawing the shadows around your view objects dynamically (at Runtime) based on wherever their positions and bounds are at the time, and that rendering will follow the view throughout it's lifecycle.
It's a math intensive process and involves many draw-calls to the GPU in the best of cases, and CPU bottlenecking as well in the worst of cases.
There are several different ways of rendering shadows in Swift. Most of them utilize frameworks OUTSIDE of SwiftUI (UIKit and CoreGraphics, usually, though Metal, Core Animation, and Core Image have been important in various applications.)
This warning is probably not a big deal if you're not seeing performance problems in the UI layer on target hardware, but if you're very motivated to solve the problem, there are some options:
Option 1
The absolute easiest thing to do if you just want to make the error go away would be to force a GPU call rasterization for the view + shadow by adding
.drawingGroup()
somewhere after the .shadow view. Be advised, this will likely look like crap compared to dynamic shadows. If you're familiar with UIKit, this is similar to the layer.shouldRasterize property on UIView.
Option 2
Speaking of UIKit, an alternative would be to head over there and use either a UIViewRepresentable of your SwiftUI drawing logic, or a completely separate UIView. Either way:
myView = UIView()
myView.layer.shadowPath = UIBezierPath(rect: myView.bounds.cgPath)
should get you started... the other shadow properties and stuff will help.
Option 3
You could render the shadow as an image, either programatically (hard) or in an imaging editing application (annoying) and load is as an image at a lower Z index than your view, and scale them to give the illusion of depth.
This is the kind of hacky work around that game developers used to do when they had crappy hardware but still wanted things to look good.
In the end... for MOST SwiftUI views this warning likely can be ignored. If you load the code in Instruments, you'll likely see that the dynamic rendering of drop shadows under a View is probably not impacting your view rendering performance significantly. This warning is only usually visible inside a UI Debug session.
Hope this helps set you on the path to a solution.
I ran into this this week. And I think I figured out a way that is as performant as the shadowPath option in UIKit. I think that when you use Shape().shadow(...) it draws an efficient shadow based on the path. So IF you know the shape of the thing you're giving a shadow to, you can do it like this:
content
// Use background so the shadow is the same size as the content
.background(
// I'm assuming rectangle but it can be anything like with rounded corners too
Rectangle()
// Add efficient shadow
.shadow()
// Add an inset so you don't see the ugly inner edge of the shadow, it will be under your content
.padding(1)
)
This was much much faster than what I had before!
I wonder is there any way to slice this sprite(dialog pop up thing) that could keep the bottom center (the upside-down triangle) not scaled? I'm using nGUI if it matters.
Nope
Sorry, but that's how 9-slice scaling works. You would need 25 slice scaling to do what you're looking for and that's overkill for most things, so I've never seen an implementation.
What to do instead...
Break up your sprite into two pieces: the 9-slice portion and the "notch" portion. Then just position the notch to be in the right place.
I haven't used nGUI (only iGUI and the Unity native--both old and new) so I'm not sure on the precise nature of how nGUI will let you do that, but you'd still need two sprites, one of which is scaled and the other one which isn't, positioned either manually or through parent-child relative relationship. If your dialog is always the same width, it'll be pretty straight forward. If not, it might be more challenging.
A few other things:
You'll probably want the notch sprite and the bubble sprite to the same native image size, but its not necessary (might make things easier, might not).
The notch will want to have some "overbleed" so that when the two stack the underlying rendering code doesn't go all squinty eyed and go "there's a gap here..." and draw through in some cases.
Depending on the bubble portion's drawn edge, you might want the notch to be in front or behind. In your precise case, I don't think it'll make a difference. It's a little hard to tell due to the colors, but when I did a selectable tab (which is built similarly), the tab sits on top of the container window so that the shaded edge flows nicely. The unselected version then has no overbleed so it looks like it sits "behind" (accurate pixel placement--2D game at a fixed size--insures that no "gap" is rendered).
It's a little tedious but pretty straightforward to implement this for UI images. I recently did it in order to make a slice stretch the left/right borders of a 9-slice instead of the center.
The trick is to subclass Image and override OnPopulateMesh, where you do the calculations you need and set positions/uvs to whatever you require.
Here's a helpful how-to article: https://www.hallgrimgames.com/blog/2018/11/25/custom-unity-ui-meshes
Things for a non-UI sprite will be harder. I think you'll have to create all your geometry in a script, and the calculations might be a little complicated because you're using an atlas.
I am working on a game using GTK3 as a rendering technique (terrible idea, but it's a school project).
My gameobjects are made of Image widgets and are placed in a Fixed container. It's working pretty well, however when i move widgets beoynd right or bottom border, the window automatically grows along with it.
I want the window to stay at the sam size, event if widget leaves its area and becomes invisible. It works when i move widget past the upper or left border.
I tried using gtk_widget_set_vexpand and gtk_widget_set_hexpand. My window is set as not resizable (gtk_window_set_resizable).
Is there any way I can achieve this?
This isn't the right way to use GTK+. GTK+ is intended for laying out widgets in a GUI program.
There are better options for animating 2D elements. One that works with GTK+ natively is the Clutter library. You can also integrate SDL or OpenGL or something like that if you so choose.
That being said, you can also use GtkLayout instead of GtkFixed, or put the GtkFixed in a GtkScrolledWindow and hide the scrollbars and set the scroll policy to prevent scrolling. It's still technically misuse, and in fact GtkFixed (and possibly GtkLayout too but the docs don't say) is really not supposed to be used anymore unless absolutely necessary because it doesn't give you automatic support for tricky UI layout problems, but it doesn't have extra dependencies.
I am trying to create a pseudo-3D square (like a scrabble tile) and I was thinking of faking it with borders. But then I couldn't find a way to set different width for different side.
Is this possible at all? If not, does anyone have any recommendation on how to do this in CALayer?
Oh, one caveat, I am already using shadow, so can't use that to fake the bevel.
Not possible with ordinary CALayer properties. You'd have to inset your layer and draw the different borders in the drawing method, or add a second larger layer to take care of that inside its drawing.
You could possibly do it with the layer's shadow properties, using -shadowOffset to only render it on two sides. However, to make it really nice you probably have to draw it yourself.