Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
142 changes: 134 additions & 8 deletions Source/Fuse.Controls.Native/Android/ImageView.uno
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Uno;
using Uno.UX;
using Uno.IO;
using Uno.Compiler.ExportTargetInterop;
using Uno.Collections;
using Uno.Graphics;
Expand Down Expand Up @@ -104,6 +105,32 @@ namespace Fuse.Controls.Native.Android
}
}

string _symbols;
public string Symbols
{
set
{
if (_symbols != value)
{
_symbols = value;
UpdateImage();
}
}
}

bool _isFilled;
public bool IsFilled
{
set
{
if (_isFilled != value)
{
_isFilled = value;
UpdateImage();
}
}
}

void OnMultiDensityImageSourceActiveChanged()
{
if (ImageSource is MultiDensityImageSource)
Expand Down Expand Up @@ -147,14 +174,21 @@ namespace Fuse.Controls.Native.Android

public void UpdateImageTransform(float density, float2 origin, float2 scale, float2 drawSize)
{
var imagePos = (int2)Math.Ceil(origin * density);
var imageScale = scale * density;
UpdateImageTransform(
_imageView,
imagePos.X,
imagePos.Y,
imageScale.X,
imageScale.Y);
if (!string.IsNullOrEmpty(_symbols))
{
UpdateImage();
}
else
{
var imagePos = (int2)Math.Ceil(origin * density);
var imageScale = scale * density;
UpdateImageTransform(
_imageView,
imagePos.X,
imagePos.Y,
imageScale.X,
imageScale.Y);
}
}

IDisposable _imageHandle;
Expand All @@ -179,6 +213,67 @@ namespace Fuse.Controls.Native.Android
}
}

void UpdateImage()
{
if (!string.IsNullOrEmpty(_symbols) && Size.X > 0)
{
ImageHandle = null;
var colorArgb = (int)Color.ToArgb(_tintColor);
var bitmap = GetBitmapFromSymbol(_symbols, colorArgb, Size.X, _isFilled);
SetBitmap(_imageView, bitmap);
}
}

internal protected override void OnSizeChanged()
{
UpdateImage();
}

static Java.Object GetMaterialSymbolsTypeface()
{
var bundleFileSource = GetMaterialSymbolsBundleFileSource();
if (bundleFileSource != null)
return LoadTypefaceFromBundle(bundleFileSource.BundleFile);
else
return GetDefaultTypeface();
}

static BundleFileSource GetMaterialSymbolsBundleFileSource()
{
// Find the MaterialSymbols.ttf in the bundle
foreach (var file in Uno.IO.Bundle.AllFiles)
{
if (file.SourcePath.EndsWith("MaterialSymbols.ttf"))
{
return new BundleFileSource(file);
}
}
return null;
}

[Foreign(Language.Java)]
static Java.Object LoadTypefaceFromBundle(BundleFile bundleFile)
@{
try
{
String uri = @{BundleFile:of(bundleFile).BundlePath:get()};
android.content.res.AssetManager assetManager = com.fuse.Activity.getRootActivity().getAssets();
android.graphics.Typeface typeface = android.graphics.Typeface.createFromAsset(assetManager, uri);
return typeface;
}
catch (Exception e)
{
android.util.Log.e("ImageView", "Failed to load MaterialSymbols font: " + e.getMessage());
return @{GetDefaultTypeface():call()};
}
@}

[Foreign(Language.Java)]
static Java.Object GetDefaultTypeface()
@{
return android.graphics.Typeface.DEFAULT;
@}

void UpdateImage(FileImageSource fileImageSource)
{
if (_isLoad)
Expand Down Expand Up @@ -222,6 +317,37 @@ namespace Fuse.Controls.Native.Android
return float2((float)wh[0], (float)wh[1]);
}

[Foreign(Language.Java)]
static Java.Object GetBitmapFromSymbol(string symbol, int colorArgb, int size, bool isFilled)
@{
android.graphics.Typeface typeface = (android.graphics.Typeface)@{GetMaterialSymbolsTypeface():call()};
android.text.TextPaint paint = new android.text.TextPaint(android.graphics.Paint.ANTI_ALIAS_FLAG | android.graphics.Paint.SUBPIXEL_TEXT_FLAG);
paint.setTypeface(typeface);
paint.setColor(colorArgb);
paint.setTextSize(size);
paint.setTextAlign(android.graphics.Paint.Align.CENTER);

// Set font variation for filled style
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
try {
if (isFilled) {
paint.setFontVariationSettings("'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' " + size);
} else {
paint.setFontVariationSettings("'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' " + size);
}
} catch (Exception e) {
android.util.Log.w("ImageView", "Font variations not supported: " + e.getMessage());
}
}

android.graphics.Bitmap bitmap = android.graphics.Bitmap.createBitmap(size, size, android.graphics.Bitmap.Config.ARGB_8888);
android.graphics.Canvas canvas = new android.graphics.Canvas(bitmap);
float y = (size / 2.0f) - ((paint.descent() + paint.ascent()) / 2.0f);
canvas.drawText(symbol, size / 2.0f, y, paint);

return bitmap;
@}

[Foreign(Language.Java)]
static void GetImageSize(Java.Object handle, int[] wh)
@{
Expand Down
3 changes: 2 additions & 1 deletion Source/Fuse.Controls.Native/Fuse.Controls.Native.unoproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"Android/Java/CanvasViewGroup.java:java:Android",
"Android/Java/VideoView.java:java:Android",
"Android/Java/ScalableType.java:java:Android",
"Android/Java/ShadowDrawable.java:java:Android"
"Android/Java/ShadowDrawable.java:java:Android",
"Android/Fonts/MaterialSymbols.ttf:bundle:Android"
]
}
2 changes: 2 additions & 0 deletions Source/Fuse.Controls.Native/Interfaces.uno
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ namespace Fuse.Controls.Native
public interface IImageView : IView
{
ImageSource ImageSource { set; }
string Symbols { set; }
bool IsFilled { set; }
float4 TintColor { set; }
bool IsLoaded { set; }
void UpdateImageTransform(float density, float2 origin, float2 scale, float2 drawSize);
Expand Down
135 changes: 106 additions & 29 deletions Source/Fuse.Controls.Native/iOS/ImageView.uno
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ namespace Fuse.Controls.Native.iOS
}
}

float4 _tintColor = float4(1.0f);
float4 _tintColor = Uno.Color.FromRgba(0x000000FF);
public float4 TintColor
{
set
Expand All @@ -70,13 +70,48 @@ namespace Fuse.Controls.Native.iOS
set { _isLoad = value; }
}

string _symbols;
public string Symbols
{
set
{
if (_symbols != value)
{
_symbols = value;
UpdateImage();
}
}
}

bool _isFilled;
public bool IsFilled
{
set
{
_isFilled = value;
UpdateImage();
}
}

void UpdateImage()
{
var c = _tintColor;
var imageHandle = _uiImageHandle != null
? TintImage(_uiImageHandle, c.X, c.Y, c.Z, c.W)
: null;
SetImage(_uiImageView, imageHandle);
if (_uiImageView == null) return;

if (!string.IsNullOrEmpty(_symbols) && Size.X > 0)
{
var systemImage = GetSystemImage(_symbols, Size.X);
var tintedSystemImage = TintImage(systemImage, _tintColor.X, _tintColor.Y, _tintColor.Z, _tintColor.W);
SetImage(_uiImageView, tintedSystemImage);
SetContentMode(_uiImageView);
}
else
{
if (_uiImageHandle != null)
{
var tintedImage = TintImage(_uiImageHandle, _tintColor.X, _tintColor.Y, _tintColor.Z, _tintColor.W);
SetImage(_uiImageView, tintedImage);
}
}
}

ObjC.Object _uiImageView;
Expand All @@ -95,6 +130,7 @@ namespace Fuse.Controls.Native.iOS

public override void Dispose()
{
_uiImageView = null;
ImageHandle = null;
if (ImageSource != null && ImageSource is MultiDensityImageSource)
{
Expand Down Expand Up @@ -164,11 +200,24 @@ namespace Fuse.Controls.Native.iOS

public void UpdateImageTransform(float density, float2 origin, float2 scale, float2 drawSize)
{
SetTransform(_uiImageView, float4x4.Identity);
var imageSize = GetImageSize();
SetBounds(_uiImageView, 0.0f, 0.0f, imageSize.X, imageSize.Y);
var imageTransform = Matrix.Compose(float3(scale, 0.0f), float4.Identity, float3(origin, 0.0f));
SetTransform(_uiImageView, imageTransform);
if (!string.IsNullOrEmpty(_symbols))
{
UpdateImage();
}
else
{
SetTransform(_uiImageView, float4x4.Identity);
var imageTransform = Matrix.Compose(float3(scale, 0.0f), float4.Identity, float3(origin, 0.0f));
SetTransform(_uiImageView, imageTransform);
}
}

internal protected override void OnSizeChanged()
{
SetBounds(_uiImageView, 0.0f, 0.0f, Size.X, Size.Y);
UpdateImage();
}

static void SetTransform(ObjC.Object handle, float4x4 t)
Expand Down Expand Up @@ -237,26 +286,6 @@ namespace Fuse.Controls.Native.iOS
[imageView setImage:image];
@}

[Foreign(Language.ObjC)]
static ObjC.Object TintImage(ObjC.Object handle, float r, float g, float b, float a)
@{
UIImage* image = (UIImage*)handle;
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
CGRect imageRect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[[UIColor colorWithRed:r green:g blue:b alpha:a] setFill];
CGContextTranslateCTM(ctx, 0, image.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
CGContextDrawImage(ctx, imageRect, image.CGImage);
CGContextClipToMask(ctx, imageRect, image.CGImage);
CGContextAddRect(ctx, imageRect);
CGContextDrawPath(ctx, kCGPathFill);
UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
@}

[Foreign(Language.ObjC)]
static void ClearImage(ObjC.Object imageViewHandle)
@{
Expand All @@ -283,5 +312,53 @@ namespace Fuse.Controls.Native.iOS
return imageView;
@}

[Foreign(Language.ObjC)]
static ObjC.Object GetSystemImage(string symbolName, float size)
@{
if (@available(iOS 13.0, *)) {
UIImage* image = [UIImage systemImageNamed:symbolName];
UIImageSymbolConfiguration* config = [UIImageSymbolConfiguration configurationWithPointSize:size];
image = [image imageByApplyingSymbolConfiguration:config];
return image;
}
return nil;
@}

[Foreign(Language.ObjC)]
static void SetContentMode(ObjC.Object imageViewHandle)
@{
UIImageView* imageView = (UIImageView*)imageViewHandle;
imageView.contentMode = UIViewContentModeScaleAspectFit;
@}

[Foreign(Language.ObjC)]
static ObjC.Object TintImage(ObjC.Object imageHandle, float r, float g, float b, float a)
@{
UIImage* image = (UIImage*)imageHandle;
UIColor* tintColor = [UIColor colorWithRed:r green:g blue:b alpha:a];

// Use modern iOS 13+ method when available
if (@available(iOS 13.0, *)) {
UIImage* tintedImage = [image imageWithTintColor:tintColor renderingMode:UIImageRenderingModeAlwaysOriginal];
return tintedImage;
}

// Fallback to Core Graphics for older iOS versions
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
CGRect imageRect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[tintColor setFill];
CGContextTranslateCTM(ctx, 0, image.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
CGContextDrawImage(ctx, imageRect, image.CGImage);
CGContextClipToMask(ctx, imageRect, image.CGImage);
CGContextAddRect(ctx, imageRect);
CGContextDrawPath(ctx, kCGPathFill);
UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
@}

}
}
Loading