Skip to content

taublast/DrawnUi.Maui

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DrawnUI for .NET MAUI

License NuGet Version NuGet Downloads

Rendering engine to draw your UI on a Skia canvas, with gestures and animations, designed to draw pixel-perfect custom controls instead of using native ones, powered by SkiaSharp😍. Create and render your custom controls on a hardware-accelerated Skia canvas with an improved common MAUI layout system.

Supports iOS, MacCatalyst, Android, Windows.

  • To use inside a usual MAUI app, consume drawn controls here and there inside Canvas views.
  • Create a totally drawn app with just one Canvas as root view, SkiaShell is provided for navigation.
  • Drawn controls are totally virtual, these are commands for the engine on what and how to draw on a skia canvas.
  • Free to use under the MIT license, a nuget package is available.
  • A Light version for Xamarin is there too.

The current development state is ALPHA, features remain to be implemented, documentation incoming.

What's New

Nuget 1.2.9.9

for SkiaSharp 2.88.9-preview.2.2

  • ImageDoubleBuffered rendering gets faster
  • TextTransform property for SkiaLabel: Lowercase, Uppercase, Titlecase, Phrasecase
  • SkiaButton TinColor removed, applying Background and BackgroundColor to button frame
  • Nuget iOS 17 compatibility restored
  • Some more fixes
  • Still using SkiaSharp 2.88.9-preview.2.2 until scaling issue is solved for Windows
demoapp.mp4

Please star ⭐ if you like it!

Features

  • Draw your UI using SkiaSharp with hardware acceleration

  • Easily create your controls and animations

  • Design in MAUI XAML or code-behind

  • 2D and 3D Transforms

  • Animations targeting max fps

  • Visual effects for every control

  • Gestures support for panning, scrolling and zooming (rotation on the roadmap)

  • Caching system for elements and images

  • Optimized for performance, rendering only visible elements, recycling templates etc

  • Navigate on canvas using MAUI familiar Shell techniques

  • Designed for YOU to create your drawn controls with ease !

  • Port existing native controls to be drawn - easy as a pie!

  • Shipped with pre-built contols:

    • SkiaControl Your lego brick to create anything
    • SkiaShape Path, Rectangle, Circle, Ellipse, Gauge etc, can wrap other elements to be clipped inside
    • SkiaLabel, multiline with many options like dropshadow, gradients etc
    • SkiaImage with many options and filters
    • SkiaSvg with many options
    • SkiaLayout (Absolute, Grid, Vertical stack, Horizontal stack, todo Masonry) with templates support
    • SkiaScroll (Horizontal, Vertical, Both) with header, footer, zoom support and adjustable inertia, bounce, snap and much more. Can act like a collectionview with custom refresh indicator, load more etc
    • SkiaHotspot to handle gestures in a lazy way
    • SkiaBackdrop to apply effects to background below, like blur etc
    • SkiaMauiElement to embed maui controls in your canvas
    • SkiaScrollLooped for neverending scrolls
    • RefreshIndicator can use lottie and anything for your scroll refresh view
    • SkiaDrawer to swipe in and out your controls
    • SkiaCarousel swipe and slide controls inside a carousel
    • SkiaDecoratedGrid to draw shapes between rows and columns
    • ScrollPickerWheel for creating wheel pickers
    • SkiaTabsSelector create top and bottom tabs
    • SkiaViewSwitcher switch your views, pop, push and slide
    • SkiaLottie with tint customization
    • SkiaGif a dedicated lightweight GIF-player with playback properties
    • SkiaMediaImage a subclassed SkiaImage for displaying any kind of images (image/animated gif/more..)
    • SkiaRive (actually Windows only)
    • SkiaButton include anything inside, text, images etc
    • SkiaSlider incuding range selction capability
    • SkiaHoverMask to overlay a clipping shape
    • SkiaLabelFps for developement
    • Create your own!
  • Animated Effects

    • Ripple
    • Shimmer
    • BlinkColors
    • Commit yours!
  • Transforms

    • TranslationX
    • TranslationY
    • TranslationZ (none-affine)
    • ScaleX
    • ScaleY
    • Rotation
    • RotationX (none-affine)
    • RotationY (none-affine)
    • RotationZ (none-affine)
    • SkewX
    • SkewY
    • Perspective1
    • Perspective2
  • Keyboard support for desktop platforms.

    • Not just accelerators, but full keyboard support, usage example inside SpaceShooter game below. :)

About

A small article about the library and why it was created

Demo Apps

  • This repo includes a Sandbox project for some custom controls, with playground examples, custom controls, maps etc
  • More creating custom controls examples inside the Engine Demo 🤩 Updated with latest nuget!
  • A dynamic arcade game drawn with this engine, uses preview nuget with SkiaSharp v3.
  • A drawn CollectionView demo where you could see how simple and profitable it is to convert an existing recycled cells list into a drawn one
  • Shaders Carousel Demo featuring SkiaSharp v3 capabilities
  • For production published apps list - scroll to bottom!
ShaderEffect.webm

V3 preview: subclassed SkiaShaderEffect, implementing ISkiaGestureProcessor, IStateEffect and IPostRendererEffect when compiled for SkiaSharp v3 preview.

Development Notes

  • To compile the v3 version that supports NEW SHADERS you must set <UseSkiaSharp3>true</UseSkiaSharp3> inside Directory.Build.props file.
  • All files to be consumed (images etc) must be placed inside the MAUI app Resources/Raw folder, subfolders allowed. If you need to load from the native app folder use prefix "file://".
  • Accessibility support is compatible and is on the roadmap.

Installation

Install the package AppoMobi.Maui.DrawnUi from NuGet, please install stable versions only, avoid -pre.

After that initialize the library inside your MauiProgram.cs file:

builder.UseDrawnUi();

Docs are under construction!

Quick Start

You will be mainly using Maui view Canvas that will wrap your SkiaControls. Anywhere in your existing Maui app you can include a Canvas and start drawing your UI. The Canvas control is aware of its children's size and will resize accordingly. At the same time, you could set a fixed size for the Canvas and its children will adapt to it.

Xaml

Import the namespace:

  xmlns:draw="http://schemas.appomobi.com/drawnUi/2023/draw"

Consume:

<draw:Canvas>
     <draw:SkiaSvg
        Source="Svg/dotnet_bot.svg"
        LockRatio="1"
        TintColor="White"
        WidthRequest="44" />
</draw:Canvas>

As you can see in this example the Maui view Canvas will adapt its size to drawn content and should take 44x44 pts. LockRatio="1" tells the engine to take the highest calculated dimension and multiply it by 1, so even if we omitted HeightRequest it was set to 44.

Code behind

Can use basic FluentExtensions will be adding more

            Canvas = new Canvas()
            {
                Gestures = GesturesMode.Enabled,
                HardwareAcceleration = HardwareAccelerationMode.Enabled,
                HorizontalOptions = LayoutOptions.Fill,
                VerticalOptions = LayoutOptions.Fill,
                BackgroundColor = Colors.Black,
                Content = new SkiaLayout()
                {
                    HorizontalOptions = LayoutOptions.Fill,
                    VerticalOptions = LayoutOptions.Fill,
                    Children = new List<SkiaControl>()
                    {

                        new SkiaShape()
                            {
                                BackgroundColor = Colors.DodgerBlue,
                                CornerRadius = 16,
                                WidthRequest = 150,
                                HeightRequest = 150,
                                HorizontalOptions = LayoutOptions.Center,
                                VerticalOptions = LayoutOptions.Center,
                                Content = new SkiaLabel()
                                {
                                    TextColor = Colors.White,
                                    HorizontalOptions = LayoutOptions.Center,
                                    VerticalOptions = LayoutOptions.Center,
                                    Text="Oyee"
                                }
                            }
                    }
                }
            };

Please check the Sandbox and Demo apps, they contain many examples of usage.

Important differences between DrawnUI and Xamarin.Forms/Maui layouts:

  • HorizontalOptions and VerticalOptions defaults are not Fill but Start. Request size explicitly or set some options to Fill, otherwise your control will take zero space.
  • Grid layout type default Row- and ColumnSpacing are not 8 but 1.

MAUI Controls Replication

Draw a line or reserve space

Use a simple SkiaControl with height and background color set. For complex shapes use SkiaShape or SkiaPath.

Simulate MAUI Grid

SkiaLayout of Grid type, set children properties as usual (Grid.something)

Simulate MAUI StackLayout inside a ScrollView

SkiaScroll + SkiaLayout of type Column/Row. Its up to you to decide whether to cache the layout or its children only. It's best not to cache the layout for large stacks and the virtualisation will enter the game.

Simulate MAUI CollectionView

SkiaScroll + SkiaLayout of type Column/Row (ItemTemplate=...). Set cache of the cell to ImageDoubleBuffered or other appropriate. You might also what to order to create ItemTemplate in background not to freeze the UI by using InitializeTemplatesInBackgroundDelay property. An important property here is VirtualisationInflated how mocj of the content outside the visible bounds should still be rendered, better not be 0 to have more recycled cells in use and make scroll more smooth. IMPORTANT: Remember default layout properties are Start and not Fill like in standart MAUI.

Simulate MAUI StackLayout with a BidableLayout.ItemTemplate

SkiaScroll with Virtualisation=Disabled + SkiaLayout of type Column/Row (ItemTemplate=...) do not forget to cache your cell template.

MAUI Compatibility Limitations

  • Binding RelativeSource with FindAncestorBindingContext not working yet.

Images

  • Images loaded and converted for skia format are cached on a per-app run basis.

  • When an image fails to load then if the app was offline the image will get its method ReloadSource invoked. So when you go online your missing images will get automatically loaded!

  • 'SkiaScroll' can also control the loading of images via VelocityImageLoaderLock property, that would lock and unlock loading of images globally in case of a huge velocity scroll.

Base control for using images is SkiaImage with many virtuals to be easily subclassed for your needs. The Source property is a usual maui ImageSource. SkiaImageManager loads platform sources and converts them to skia format. It falls back to default MAUI methods for loading ImageSource in some cases.

On Android the Glide library is used for loading urls. It caches images, making it so that if the app is offline it still gets its images from cache if they were loaded previously.

Gestures

To make your root Canvas catch gestures you need to attach a TouchEffect to it. This can be done automatically by setting GesturesEnabled="True" for the Canvas. After that skia controls can process gestures in multiple ways:

  • At low level by implementing an ISkiaGestureListener interface and overriding OnGestureReceived.
  • At control level by overriding ProcessGestures, recommended for custom controls.
  • Attaching an AddGestures effect that has properties similar to SkiaHotspot.
  • Including a SkiaHotspot as a child.
  • Using a SkiaButton.

Parent controls have full control over gestures and passing them to children. In a base scenario, a gesture would be passed all along to the ends of a view tree to its ends for every top-level control. If a gesture is marked as consumed (by returning the reference of the consumer, not consumed if null) a control would typically stop processing gestures at its level.

By overriding ProcessGestures any control might process gestures with or without passing them to children.

When creating a custom control the standard code for the override would be to pass gestures below by calling base then processing at the current level. You might choose to do it differently according to your needs.

The engine is designed to pass the ending gestures to those who already returned "consumed" for preceding gestures even if the following gestures are out of their hitbox.

More docs will be added on this matter.

Caching System

! Without caching animations going beyond simple wouldn't be possible. Caching makes complicated processing needed just once for layout calculation and then the caching result is redrawn on every frame.

Caching is controlled using a property UseCache of the following type:

public enum SkiaCacheType
{
    /// <summary>
    /// True and old school
    /// </summary>
    None,

    /// <summary>
    /// Create and reuse SKPicture. Try this first for labels, svg etc. 
    /// Do not use this when dropping shadows or with other effects, better use Bitmap. 
    /// </summary>
    Operations,

    /// <summary>
    /// Will use simple SKBitmap cache type, will not use hardware acceleration.
    /// Slower but will work for sizes bigger than graphics memory if needed.
    /// </summary>
    Image,

    /// <summary>
    /// Using `Image` cache type with double buffering. Best for fast animated scenarios, this must be implemented by a specific control, not all controls support this, will fallback to 'Image' if anything.
    /// </summary>
    ImageDoubleBuffered,

    /// <summary>
    /// The way to go when dealing with images surrounded by shapes etc.
    /// The cached surface will use the same graphic context as your canvas.
    /// If hardware acceleration is enabled will try to cache as Bitmap inside graphics memory. Will fallback to simple Bitmap cache type if not possible. If you experience issues using it, switch to Memory cache type.
    /// </summary>
    GPU,
}

You should tweak your design caching to avoid unnecessary re-drawing of elements. The basic approach here is to cache small elements at some level. When you start using any kind of animations you should start using caching to max your FPS. You can check the DemoApp for such examples.

Caching Tips:

  • Cache shapes, svg and text as Operations.
  • Prefer caching shadows and gradients as Image instead of Operations.
  • Cache large static overlays as GPU, large static blocks as Image.
  • For dynamically changing controls consider ImageDoubleBuffered, it consumes double the memory as Image but doesn't slow down rendering: you would see the latest prepared cache until the actual state wouldn't finish rendering itsself to a hidden cache layer in background.
  • Do not include controls cached with GPU inside controls that use different type of cache, except for Disabled, will get a native crash otherwise.

Loaded Images

We are using the EasyCaching.InMemory library for caching loaded bitmaps. Its impact can much be seen when using recycled cells inside a scroll. todo add options and link to ImageLoader and SkiaImage docs

! When using images inside dynamic scenes, like a templated stack with scroll or other you should try to set the image cache to Image this would most probably climb your fps. This is due to the fact that image sources are usually of the wrong size and they need processing before being drawn. When using Image cache the image would be processed only once and then just redrawn.

Transforms

  • TranslationX
  • TranslationY
  • TranslationZ
  • ScaleX
  • ScaleY
  • Rotation
  • RotationX
  • RotationY
  • RotationZ
  • SkewX
  • SkewY
  • Perspective1
  • Perspective2

Animations

One would create animations and effects using animators. Animators are attached to controls, but technically register themselves at the root canvas level and their code is executed on every rendering frame. If the canvas is not redrawing then animators will not be executed. When the canvas has a registered animator running it would constantly force re-drawing itself until all animators are stopped.

There are two types of animators:

  • Animators are executed before the drawing, so you can move and transform elements before the are rendered on the canvas.
  • PostAnimators are executed after their parent element was drawn, so you can paint an effect over the existing result, or execute any other post-drawing logic.

Layout

For initial items positioning you would be using SkiaLayout. Its Absolute layout type is already built-in inside every skia control..

You can position your children using familiar properties like HorizonalOptions, VerticalOptions, Margin, parent Padding, WidthRequest, HeightRequest,MinimumWidthRequest, MinimumHeightRequest and additional MaximumWidthRequest, MaximumHeightRequest, HorizontalFillRatio, VerticalFillRatio and LockRatio properties.

  • LockRatio will be used to calculate the width when the height is set or vice versa. If it's above 0 the max value will be applied, if it's below 0 the min value will be applied. If it's 0 then the ratio will be ignored.
  • HorizontalFillRatio, VerticalFillRatio will let you take a percent and the available size if you don't set a numeric request. For example if HorizontalFillRatio is set to 0.5 you will take 50% of the available width, at the same time being able to align the control at start, center or at the and in a usual way.
  • HorizontalPositionOffsetRatio and VerticalPositionOffsetRatio let you offset the item after it was aligned by applying a ratio to its computed size. Defaults are 0.0. For example, HorizontalPositionOffsetRatio of -0.5 will offset the item by -Width/2, practically centering it horizontally along the current x-position. Works almost like translation but relatively to the current size.

For dynamic positioning or other precise cases use TranslationX and TranslationY.

When you need to layout children in a more arranged way you will want to wrap them with a SkiaLayout of different LayoutType : Grid, Colum, Row and others.

Layout supports ItemTemplate for most of layout types.

For grid, we have so useful feats like, instead of using RowDefinitions="32,32,32,32" you can just do i nice DefaultRowDefinition="32".

Some differences from Xamarin.Forms/Maui to notice:

  • Layouts as other controls come with HorizontalOptions and VerticalOptions as Start and not Fill by default, so if your children do not request a size explicitly, the parent layout will not take any space at all unless you ask it to Fill the desired dimension, for example a Column needs its HorizontalOptions to be Fill or specified explicitly.

  • Actually for performance reasons in Column and Row layouts children cannot have Fill, End and Center in the direction of the layout, only Start. For example, if you have a Column children must have VerticalOptions set to Start. When you still need these options for children please use Absolute or Grid layouts.

! Layouts Column and Row, whether templated or not, will always check if child is out of the visible screen bounds and avoid rendering it in that case. That is especially useful when the layout is inside a SkiaScroll, this way we always render only the visible part. You can tweak this but setting a SkiaLayout property VirtualisationInflated in points, how many of the hidden amount outside the visible bounds should still be rendered. This system ensures that you can have an infinite-size layout inside a scroll and it will work just fine drawing only the visible area. At the same time if you want a SkiaScroll to lye communicate to its content that everything is visible on the screen you can set its VirtualizationEnabled="False".

! You can absolutely use the Margin property in a usual Maui way. In case if you would need to bind a specific margin you can switch to using MarginLeft, MarginTop, MarginRight, MarginBottom properties, -1.0 by default, if set they will override the specific value from Margin, and the result would be accessible via Margins read-only bindable static property.
Even more, sometimes you might want to bind your code to AddMarginTop, AddMarginLeft, AddMarginRight, AddMarginBottom..
When designing custom controls please use Margins property to read the final margin value.

BindingContext propagation in layout

When a parent has children attached it sets their binding content to its own by calling SetInheritedBindingContext of the child ONLY if child's BindingContext is actually null. So when the Parent property of the child gets set to null this child BindingContext is set to null too.

Any SkiaControl implements public virtual void SetInheritedBindingContext(object context) that you can override to prohibit changing binding context or apply any other logic.

Another case is when the control BindingContext changes for any reason, public virtual void ApplyBindingContext() method is invoked. Layouts override it to implement an additional logic to modify or not its children BindingContext. Some view containers use the following logic:

public override void ApplyBindingContext()
        {
            base.ApplyBindingContext();

			//preserve child context
            if (Content?.BindingContext == null)
                Content?.SetInheritedBindingContext(BindingContext);
        }

Loading sources

SkiaImage, SkiaLottie and other controls that have a Source property, can load data from web, from bundled resources and from native file system. The conventions are the following:

  • if a web url is detected the source is loaded from web
  • if a file path starts with file:// it will be loaded from native file system
  • otherwise will try to load from bundled resources with root folder 'Resources\Raw'.

The example below will load animation from Resources\Raw\Lottie\Loader.json.

            <draw:SkiaLottie
                InputTransparent="True"
                AutoPlay="True"
                ColorTint="{StaticResource ColorPrimary}"
                HorizontalOptions="Center"
                ="1"
                Opacity="0.85"
                Repeat="-1"
                Source="Lottie/Loader.json"
                Tag="Loader"
                VerticalOptions="Center"
                WidthRequest="56" />

Enhanced usage

When dealing with subviews in code behind you could typically use two ways.

Example for adding a subview:

  • Use method SetParent passing new parent. In this case parent layout will not be invalidated, use this for optimized logic when you know what you are doing. You can mainly use this way when just constructing parent, knowing it will be measured at start anyway.
  • Call parent's method AddSubView passing subview Parent's layout will be invalidated, and OnChildAdded will be called on parent.
  • When working with Children property use Add method, it will set Views to a new instance of appropriate collection, and call AddSubView for each item.

For removing a subview the usual options would be:

  • Call SetParent passing null, for soft removal.
  • Call parent's method RemoveSubView passing subview.Parent's layout will be invalidated, and OnChildRemoved will be called on parent.
  • When working with Children property use Remove method, it will set Views to a new instance of appropriate collection, and call RemoveSubView for each item.

In Deep

When XAML would be constructed it would fill Children property with views, this property is for high-level access. Views

Will be then filled internally. When you add or remove items in Children methods AddSubView and RemoveSubView will be called for managing Views.

Maui Views

Canvas

Events:

WillDraw

WillFirstTimeDraw

WasDrawn

Methods:

TakeScreenShot(Action<SKImage> callback) Takes screenshot on next draw. Passes an SKImage, can be null. If you need an SKBitmap use SKBitmap.FromImage on it.

Properties:

Surface

SkiaShell

The usage is almost the same as the standard Maui Shell, with some extra features.

SkiaShell is derived from FastShell that uses Maui interfaces and implements methods for standard maui navigation, then adds features to be able to navigate inside the Canvas.

Some additional features to be mentioned are actions that can be executed for specific routes. code example:

RegisterRoute("profile", typeof(ScreenUserProfile));

RegisterActionRoute("settings", () =>
{
    //select settings tab
    this.NavigationLayout.SelectedIndex = 4;
});

Usage

Please see the demo.

Drawn Controls

SkiaControl

Base drawn element, derived from Maui Element to assure basic Maui Xaml compatibility, could derive from a BindableObject if Maui would provide public interfaces for that matter.

Properties:

LockRatio a numeric value that will be used to calculate the width when the height is set or vice versa. If it's above 0 the max value will be applied, if it's below 0 the min value will be applied. If it's 0 then the ratio will be ignored.

Example 1:

  <draw:SkiaShape
                        LockRatio="1"
                        WidthRequest="40" />

HeightRequest wasn't specified but this control will request 40 by 40 pts.

Example 2:

  <draw:SkiaShape
	LockRatio="-1"
	HeightRequest="30"
	WidthRequest="40" />

This control will request 30 by 30 pts.

VisualEffects a list of effects you can attach to affect to change how your controls is drawing. It's easy to create effects for dirrerent tasks, would it be making your control black and white or animate it with conditions. Actually you can attach different types of effects to every control:

  • One effect changing the color filter, impements IColorEffect.
  • One effect changing the image filter, impements IImageEffect.
  • Any number of effects affecting the rendering before the controls drawing is finalized and eventually saved to cache, implementing IRenderEffect, applied in chain.
  • Any number of effects implementing IStateEffect, thoses can be used to change your control state, animate etc.
  • One post renderer, impements IPostRendererEffect, this one will render the cache in its own way, if you'd want to apply a shader etc.
  • Any effect can implement ISkiaGestureProcessor, to become gestures-aware.

SkiaScroll

SkiaScroll is a scrollable container that supports virtualization and recycling of its children.

If you include a SkiaLayout inside a SkiaScroll only visible on screen items will be rendered.

If the include a SkiaLayout that uses ItemTemplate this combination will automatically become virtualized and you will get sort of a CollectionView with recycled cells at your disposal. It is a good practice to use it for long lists of items.

Properties:

Orientation a value of type ScrollOrientation that can be Vertical or Horizontal.

FrictionScrolled Use this to control how fast the scroll will decelerate. Values 0.1f - 0.3f are the best, default is 0.1f.

IgnoreWrongDirection Will ignore gestures of the wrong direction, for example, if this Orientation is Horizontal will ignore gestures with vertical direction velocity. Might want to set it to true when you have a horizontal scroll inside a vertical scroll, this will let the parent scroll start scrolling vertically even if the gesture started inside its horizontal scroll child.

SkiaLayout

SkiaLayout is a container that supports various layout types: Absolute, Grid, Row, Column, and others.

It also supports virtualization and recycling of its children with ItemTemplate property.

Controls inside templated SkiaLayout can implement ISkiaCell interface to eventually receive information about their state:

  • OnAppearing
  • OnDisapearing
  • OnScrolled

This lets one to create custom controls that can react to scrolling and other events with animations etc.

SkiaShape

SkiaShape is a base class for all shapes. You could fill it, stroke, drop shadows, apply gradients, and even clip other controls with it.

SkiaImage

SkiaImage is a control that renders images. It can't apply filters and transformations.

SkiaSvg

SkiaSvg is a control that renders svg files. It can't tint the svg with a color or gradient, and apply some transforms to it.

You can set the svg as string via SvgString property, or same is by including the string data as XAML content:

    <draw:SkiaSvg
        HeightRequest="110"
        HorizontalOptions="Center"
        LockRatio="1"
        Opacity="0.5"
        TintColor="{StaticResource Gray950}"
        UseCache="Operations"
        VerticalOptions="Center"
        ZIndex="-1">
        <![CDATA[                                      
        <svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M15 0H5C2.243 0 0 2.243 0 5V13C0 15.757 2.243 18 5 18H15C17.757 18 20 15.757 20 13V5C20 2.243 17.757 0 15 0ZM5 2H15C16.654 2 18 3.346 18 5V9.58594L14.417 6.00293C13.636 5.22193 12.364 5.22193 11.583 6.00293L7 10.5859L6.41701 10.0029C5.63601 9.22193 4.36399 9.22193 3.58299 10.0029L2 11.5859V5C2 3.346 3.346 2 5 2ZM4.5 6C4.5 5.172 5.172 4.5 6 4.5C6.828 4.5 7.5 5.172 7.5 6C7.5 6.828 6.828 7.5 6 7.5C5.172 7.5 4.5 6.828 4.5 6Z" fill="#41416E"/>
        </svg>
        ]]>
    </draw:SkiaSvg>

or just set the property directly, code-behind example:

var control = new SkiaSvg()
{
    SvgString = "<svg... whatever..></svg>"
}

Other properties of interest may be: Zoom, ZoomX, Zoomy, VerticalOffset, HorizontalOffset, HorizontalAlignment and more..

You can also apply a gradient to your svg, either like to any control with a VisualEffect (see VisualEffects) or by using following properties to apply a linear gradient:

UseGradient, StartXRatio, EndXRatio, StartYRatio, EndYRatio, StartColor, EndColor, GradientBlendMode

SkiaLabel

A multi-line label fighting for his place under the sun.

Properties:

FontWeight a numeric value used in case you have properly registered your fonts to support weights. You can use your font the usual Maui way but in the case of custom font files used from resources you might want to register them, using the following example:

.ConfigureFonts(fonts =>
{
   fonts.AddFont("Gilroy-Regular.ttf", "FontText", FontWeight.Regular);
   fonts.AddFont("Gilroy-Medium.ttf", "FontText", FontWeight.Medium);
});

Now if you set the FontWeight to 500 the control will use the Gilroy-Medium.ttf file. This might come in very handy when your Figma design shows you to use this weight and you want just to pass it over to SkiaLabel.

HorizontalTextAlignment :

public enum DrawTextAlignment
{
   Start,
   Center,
   End,
   FillWords,
   FillWordsFull,
   FillCharacters,
   FillCharactersFull,
}

SkiaLottie

SkiaLottie is a control that renders Lottie files. It can even tint some colors inside your animation!
You could fast-tint one color with ColorTint property. But if you find yourself in need to tint many colors use Colors, for example if we wanted to tint 2 distincts colors inside:

 <draw:SkiaLottie
    AutoPlay="True"
    HorizontalOptions="Center"
    WidthRequest="50"
    LockRatio="1"
    Repeat="-1"
    Source="Lottie/Loader.json"
    VerticalOptions="Center"                        >
    <draw:SkiaLottie.Colors>
        <Color>#FF0000</Color>
        <Color>#FFFFFF</Color>
    </draw:SkiaLottie.Colors>
</draw:SkiaLottie>

SkiaRive

Actually for Windows only, this plays and controls Rive animation files. Other platforms will be added soon, poke if you would like to help binding some c++;

SkiaHoverMask

A control deriving from SkiaShape that can be used to create hover effects. It will render a mask over its children when hovered, think of it as an inverted shape.

Docs under construction

Published Apps powered by DrawnUI For .Net MAUI

Racebox

MAUI pages with canvases, custom navigation. All scrolls, cells collections, maps, buttons, labels and custom controls are drawn.

iOS: https://apps.apple.com/us/app/racebox-vehicle-dynamics/id6444165250
GooglePlay: https://play.google.com/store/apps/details?id=com.raceboxcompanion.app

Bug ID: Insect Identifier AI

Totally drawn with just one root view Canvas and SkiaShell for navigation. First ever drawn MAUI app!

Ooops looks like the app API went dead, client maybe abandoned the project due to AI detecting stuff better than dedicated neural networks!

GooglePlay: https://play.google.com/store/apps/details?id=com.niroapps.insects