// Copyright (c) 2026 Igor Hipólito Vieira
// SPDX-License-Identifier: MIT
//
// Runtime SVG Migration Examples
//
// This file demonstrates three common migration paths to Forma's bounded runtime SVG rendering:
//
// 1. Migrating a Texture2D atlas icon loaded at startup to an SvgImageSource with Image.
// 2. Migrating a ThemeIcon bitmap-atlas consumer to the RuntimeSvg theme policy.
// 3. Migrating a DrawingImage / ImageDrawing surface to an SvgImageSource.
//
// Prerequisites: add the runtime-matched companion alongside the core package.
//
// MonoGame:
//
//
//
// FNA:
//
//
//
// The package module initializer installs the backend automatically. Call
// SvgBackendDefaults.Verify() for an explicit startup probe.
using System;
using System.IO;
using Forma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Forma.Examples;
// ---------------------------------------------------------------------------
// Example 1: Migrating a Texture2D atlas icon to SvgImageSource + Image
//
// Before: load a pre-rasterized PNG at a fixed physical size.
// After: load an SVG source once; the cache rasterizes it at the exact
// physical size required by the current display scale.
// ---------------------------------------------------------------------------
public sealed class TextureIconMigrationExample : IDisposable
{
// Before: callers owned a Texture2D and drew it at a fixed logical size.
// private Texture2D _iconTexture;
// After: one immutable source shared across all consumers.
private readonly SvgImageSource _iconSource;
public UIContext Context { get; }
public TextureIconMigrationExample(string contentDirectory)
{
// FromFile validates the SVG immediately and stores an immutable private copy.
// The source lifetime is independent of the graphics device.
_iconSource = SvgImageSource.FromFile(Path.Combine(contentDirectory, "Icons/status.svg"));
Context = new UIContext();
// Before: Image with a Texture2D source at a fixed logical size.
// var icon = new Image { Texture = _iconTexture, CustomMinimumSize = new Vector2(32, 32) };
//
// After: Image with a ScalableImageSource at the same logical size.
// The renderer derives the exact physical dimensions from the current display scale and
// the complete logical transform, producing a crisp raster at every fractional DPI.
var icon = new Image
{
ScalableSource = _iconSource,
Stretch = ImageStretch.Contain,
CustomMinimumSize = new Vector2(32, 32),
};
var root = new VBoxContainer { Size = new Vector2(480, 240) };
root.AddChild(icon);
Context.Add(root);
}
public void Dispose()
{
Context.Dispose();
// SvgImageSource is a plain immutable object; no Dispose is required.
}
}
// ---------------------------------------------------------------------------
// Example 2: Migrating a ThemeIcon bitmap-atlas consumer to RuntimeSvg policy
//
// Before: ThemeIcon always draws from the embedded 1x/2x PNG atlas.
// After: RuntimeSvg policy renders authoritative companion SVG sources when the
// backend is healthy, with the PNG atlas retained as a per-icon fallback.
// ---------------------------------------------------------------------------
public sealed class ThemeIconPolicyMigrationExample : IDisposable
{
public UIContext Context { get; }
public ThemeIconPolicyMigrationExample()
{
Context = new UIContext();
// Before: policy was always BitmapAtlas (the shipped default).
// Context.ThemeIconRenderingPolicy = ThemeIconRenderingPolicy.BitmapAtlas;
//
// After: switch to RuntimeSvg. A missing companion or unhealthy backend never
// removes a default control icon; every icon carries its PNG atlas region as a
// per-icon fallback. ThemeIconDiagnostics distinguishes SVG sources, PNG
// fallbacks, and missing names.
//
// Note: BitmapAtlas remains the shipped default until the full release matrix
// (Phase 8) is approved. Set RuntimeSvg explicitly to opt in during development.
Context.ThemeIconRenderingPolicy = ThemeIconRenderingPolicy.RuntimeSvg;
// ThemeIcon usage is unchanged; policy selection happens at the context level.
var icon = new ThemeIconRect
{
ThemeTypeName = nameof(OptionButton),
ThemeItemName = "arrow",
CustomMinimumSize = new Vector2(32, 32),
};
var root = new HBoxContainer { Size = new Vector2(480, 64) };
root.AddChild(icon);
Context.Add(root);
}
public void Dispose() => Context.Dispose();
}
// ---------------------------------------------------------------------------
// Example 3: Migrating a DrawingImage / ImageDrawing surface to SvgImageSource
//
// Before: a DrawingImage assembled programmatically from GeometryDrawing primitives,
// then set as the source of an Image control.
// After: an SvgImageSource loaded from an authored SVG file that expresses the same
// geometry, giving the design team a standard authoring workflow.
//
// Source precedence: bitmap content wins over DrawingImage, which wins over scalable
// (SVG) content. Remove any Texture or DrawingImage assignment when migrating to SVG.
// ---------------------------------------------------------------------------
public sealed class DrawingImageMigrationExample : IDisposable
{
private readonly SvgImageSource _badgeSource;
public UIContext Context { get; }
public DrawingImageMigrationExample(ReadOnlyMemory svgBytes)
{
// Before: build a DrawingImage and assign it to Image.Drawing.
// var circle = new EllipseGeometry { Center = new Vector2(16, 16), RadiusX = 14, RadiusY = 14 };
// var drawing = new GeometryDrawing { Geometry = circle, Brush = new SolidColorBrush(Color.Green) };
// var drawingImage = new DrawingImage { Drawing = drawing };
// var badge = new Image { Drawing = drawingImage, CustomMinimumSize = new Vector2(32, 32) };
//
// After: load the equivalent authored SVG. FromMemory accepts a ReadOnlyMemory
// from a caller-owned buffer; the source copies the bytes on construction.
_badgeSource = SvgImageSource.FromMemory(svgBytes);
var badge = new Image
{
ScalableSource = _badgeSource,
Stretch = ImageStretch.Contain,
CustomMinimumSize = new Vector2(32, 32),
};
Context = new UIContext();
var root = new VBoxContainer { Size = new Vector2(480, 240) };
root.AddChild(badge);
Context.Add(root);
}
public void Dispose() => Context.Dispose();
}