Comet is a .NET experiment inspired by Flutter and Swift UI for building cross-platform apps with C#.
All UI in Comet is a View
, which makes things really simple, right? Other toolkits may split them into controls, layouts, components, widgets, etc. That makes sense since some are best used for containing and organizing, others best for accepting user input, and others for creating beautiful UI.
Let's take a tour of what's available today in Comet and how you can quickly start composing your UI.
Container Views
The layouts in Comet extend from ContainerView
which itself is a View
. These managed views arrange and space child views on the screen, and all adapt to whatever size and density the app runs on. The current layout views include:
- HStack, VStack, ZStack - these views will display children horizontally, vertically, or in z-order
- Grid, HGrid, VGrid - these views use a powerful column/row system to arrange child views
- ScrollView - simply contains a single view that scrolls within the visible viewport
The stack family of views are super simple. You add your children inside them, and set some spacing. Here's the basic content of the Comet template:
[Body]
View body()
=> new VStack {
new Text(count.ToString()),
new Button("Increment", ()=>{
count.Value++;
})
};
Each view will accept the minimal set of constructor arguments to be useful. Keeping things simple and efficient is the name of the game. Notice the VStack
above doesn't require any constructor arguments. Optionally you can include VStack(HorizontalAlignment alignment = HorizontalAlignment.Center, float? spacing = null)
.
In C# the children of a view are within the curly braces, comma delimited. The children of our VStack
are a Text
and a Button
. The order is what determines what appears at the top of the screen moving down the screen.
As you can imagine HStack
does exactly the same only on the horizontal axis. The only different here will be if you're device is configured for a right-to-left (RTL) region. In that case the content will flow from the right instead of the default which is from the left. For this reason, our alignment options use the terms "Start" and "End" instead.
Now, ZStack
is a bit different. The first child in a ZStack
will be at the "back" or closest to the parent view surface. The subsequent views will all stack up on top of that view.
[Body]
View body()
=> new ZStack {
new ShapeView(
new Circle()
.Fill(Colors.OrangeRed)
).Frame(width:80,height:80, alignment: Alignment.Center)
.Background(Colors.Transparent),
new ShapeView(
new RoundedRectangle(2)
.Stroke(color: Colors.Brown, lineWidth:0)
.Fill(Colors.Orange)
).Frame(width:30,height:30, alignment: Alignment.Center)
.Background(Colors.Transparent)
}.Background(Color.FromHex("#101010"));
By default children are aligned to the center of ZStack
. To change alignment, the children's Frame
takes an alignment argument with many options:
TopLeading | Top | TopTrailing |
Leading | Center | Trailing |
BottomLeading | Bottom | BottomTrailing |
In order to move this ZStack
as a group around the screen, I can surround it with another view such as Grid
and then use the Alignment
property to move the stack without disturbing the 2 shapes stacked and centered.
[Body]
View body()
=> new Grid{
new ZStack {
new ShapeView(
new Circle()
.Fill(Colors.OrangeRed)
).Frame(width:80,height:80)
.Background(Colors.Transparent),
new ShapeView(
new RoundedRectangle(2)
.Stroke(color: Colors.Brown, lineWidth:0)
.Fill(Colors.Orange)
).Frame(width:30,height:30)
.Background(Colors.Transparent)
}.Frame(width:80,height:80,alignment:Alignment.Top)
}.Background(Color.FromHex("#101010"));
The Spacer
One of my favorite parts of layout in Comet is being able to use Spacer
to boss around the other elements shoving them into the spacing and position I want. This nifty view can take up a specific amount of space by setting the Frame
just like any other view, and by default it will expand to fill the space available.
Look at the example of this simple counter. This will display in a vertical view starting from the top of the screen.
new VStack(){
new Text(()=> $"Count is {Count}.")
.Color(Colors.White)
.FontSize(64)
.LineBreakMode(LineBreakMode.WordWrap)
.HorizontalTextAlignment(TextAlignment.Center)
.Margin(left:30,right:30),
new Spacer(),
new Button("Increment", ()=> Count.Value++)
.Frame(height:76)
.FontSize(32)
.Color(Colors.White)
.Background(Colors.OrangeRed)
.RoundedBorder(20)
.Margin(left:30,right:30)
}
If I now add new Spacer()
before the Text
it will fill the available space and push the content to the bottom of the view:
If I put a bookend spacer at the end of the VStack
, then my content is centered in the view. 😃
And to complete the story, if I put a spacer in the middle of the elements, they will space evenly. Another way to do this is to use the spacing property on stacks, but I find Spacer
to be a powerful way to achieve a variety of results.
Grids
The other major category of layout-base views are the grid family. Anyone with web and css experience has probably use a grid system before, and this may feel quite familiar. Starting with a Grid
you define the number of columns and rows you want by providing sizing. Column and row indexing are 0 based, so beware the off-by-one gremlin.
Here is a 3x3 Grid with Buttons
across the middle. I'm using the fill methods just to make the spacing and sizing obvious.
new Grid(
columns: new object[]{ 140, "*", 140},
rows: new []{"*", "*", "*"},
defaultRowHeight: 300
) {
new Button().Background(Colors.DarkRed)
.FillHorizontal()
.FillVertical()
.Cell(column:0, row: 1),
new Button().Background(Colors.LightSlateGray)
.FillHorizontal()
.FillVertical()
.Cell(column:1, row: 1),
new Button().Background(Colors.DarkRed)
.FillVertical()
.FillHorizontal()
.Cell(column:2, row: 1),
}.Background(Color.FromArgb("#1d1d1d"));
The Grid
is similar to ZStack
in that the z-index (or depth) of the views are determined by their order in the body.
Of course, you can span views across columns and rows as well. Here I'll add another Button
to the end of the Grid
and span it across three columns:
new Button().Background(Colors.DarkGreen)
.FillHorizontal()
.FillVertical()
.Cell(column:0, row: 0, colSpan:3),
Now for some power-ups! James Clancey recently added HGrid
and VGrid
as well as a few helpers. The concept here is that you want to just provide a set of child views and have them displayed uniformly. To do this you can provide the number of cross-axis cells to render, and just add views.
new VGrid(5) {
new Button().Background(Colors.DarkRed)
.FillHorizontal()
.FillVertical(),
new Button().Background(Colors.LightSlateGray)
.FillHorizontal()
.FillVertical(),
new Button().Background(Colors.DarkRed)
.FillVertical()
.FillHorizontal(),
new Button().Background(Colors.LightSlateGray)
.FillHorizontal()
.FillVertical(),
new Button().Background(Colors.DarkRed)
.FillVertical()
.FillHorizontal(),
new Button().Background(Colors.LightSlateGray)
.FillHorizontal()
.FillVertical(),
new Button().Background(Colors.DarkRed)
.FillVertical()
.FillHorizontal(),
new Button().Background(Colors.LightSlateGray)
.FillHorizontal()
.FillVertical(),
new Button().Background(Colors.DarkRed)
.FillVertical()
.FillHorizontal(),
new Button().Background(Colors.LightSlateGray)
.FillHorizontal()
.FillVertical(),
new Button().Background(Colors.DarkRed)
.FillVertical()
.FillHorizontal(),
new Button().Background(Colors.LightSlateGray)
.FillHorizontal()
.FillVertical(),
new Button().Background(Colors.DarkRed)
.FillVertical()
.FillHorizontal(),
new Button().Background(Colors.LightSlateGray)
.FillHorizontal()
.FillVertical(),
new Button().Background(Colors.DarkRed)
.FillVertical()
.FillHorizontal(),
}.Background(Color.FromArgb("#1d1d1d"));
Oof, despite not needing to declare the size of columns and rows, and not needing to specify the Cell for each view, that's a long block of repetitive code. 😖 Let me make that shorter while adding more content.
new VGrid(5) {
Enumerable.Range(0,20).Select(x=>
new Button().Background((x % 2 == 0) ? Colors.DarkRed : Colors.LightSlateGray)
.FillHorizontal()
.FillVertical()
),
}.Background(Color.FromArgb("#1d1d1d"));
Voilà! You can do the same with Grid
, and take advantage of some helpers like NextColumn()
(it bumps the view to the next column).
Basic UI Controls
Whether you're building a CRUD app or something more creative, you need a basic set of views to take user input and enable interaction. Comet ships today with:
ActivityIndicator | Button | CheckBox | DatePicker |
Image | IndicatorView | ListView | ProgressBar |
RadioButton | Section | SectionedListView | SecureField |
ShapeView | Slider | Stepper | TabView |
Text | TextEditor | TextField | Toggle |
Within a ShapeView
you can draw shapes: Capsule, Circle, Ellipse, Path, Pill, Rectangle, and RoundedRectangle.
Views support features such as shape clipping, setting borders (stroke), and shadows.
For colors you have your choice of solid paint, linear and radial gradient paint, and more provided by Microsoft.Maui.Graphics.
dotnet / Microsoft.Maui.Graphics
An experimental cross-platform native graphics library.
You've likely picked up on this from the previous examples, as I already know you're an astute developer since you are here with me looking at Comet: to begin customizing a view, Comet provides a fluent syntax that builds upon the base established in the constructor. Chain together your customizations like this:
new Text()
.Color(Colors.White)
.FontSize(64)
.LineBreakMode(LineBreakMode.WordWrap)
.HorizontalTextAlignment(TextAlignment.Center)
.Margin(left:30,right:30),
new Button("Increment")
.Frame(height:76)
.FontSize(32)
.Color(Colors.White)
.Background(Colors.OrangeRed)
.RoundedBorder(20)
.Margin(left:30,right:30),
In this example the Text
has a foreground color of white, a size of 64, enables word wrapping, centers the text, and spaces the view from the edges of the parent view. I don't need to "read" the Button
code to you, however I would like to call your attention to Frame
. This gives a view an explicit size, which on most any platform is a great idea to provide if you know the size in order to optimize layout performance.
Random Tips
While hot reloading put all your code in a single file to reduce the need to restart the app.
You can comment out a single line in the chain while editing without breaking the app.
Provide size in
Frame
if you know it to improve layout performance.Compose views into reusable functions like my
HangulText
view in order to create a template for a common style of control.Have fun and share your experiences on Twitter (I'm @davidortinau), GitHub, or with your 🦔.
Top comments (5)
Loving all this Comet content - thank you for showing what's possible in such an approachable and easily digestible way! 🎉
What's your advice for creating UI with Comet that follows standard looks/theming for each platform? Are there best practices for this yet?
I'll get into styles/themes in my next post. Right now everything uses the platform native UI, though there's also an option to use drawn UI. The latter gives you a uniform look across platforms.
I haven't mentioned that option yet because I'm not sure it currently works since Comet replaced the original handler implementation with the .NET MAUI core implementation.
Theoretically you should be able to take Microsoft.Maui.Graphics.Controls and initialize those handlers.
github.com/dotnet/Microsoft.Maui.G...
The shape feature is quite useful
Does Comet work with Tyzen?
If .NET Maui supports Tizen, Comet would support Tizen also. And the good thing is, a team at Samsumg is working towards that