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
48 changes: 44 additions & 4 deletions src/ComposeNet.Compose/ComposeBridges.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,19 @@ internal static IntPtr ModifierClipRoundedCorners(IntPtr modifier, float dp)
public static partial void Text(
string text,
IModifier? modifier,
long? color,
Sp? fontSize,
FontStyle? fontStyle,
FontWeight? fontWeight,
FontFamily? fontFamily,
Sp? letterSpacing,
TextDecoration? decoration,
TextAlign? align,
Sp? lineHeight,
TextOverflow? overflow,
bool? softWrap,
int? maxLines,
int? minLines,
IComposer composer);

// androidx.compose.material3.ButtonKt.Button
Expand Down Expand Up @@ -605,17 +613,49 @@ public static partial void IconPainter(
Signature = TextFieldStringSig,
Defaults = typeof(TextFieldDefault))]
[ComposeFacade]
public static partial void TextField(string value, [Callback(typeof(string))] IFunction1 onValueChange,
IModifier? modifier, IComposer composer);
public static partial void TextField(
string value,
[Callback(typeof(string))] IFunction1 onValueChange,
IModifier? modifier,
bool? enabled,
bool? readOnly,
IFunction2? label,
IFunction2? placeholder,
IFunction2? leadingIcon,
IFunction2? trailingIcon,
IFunction2? prefix,
IFunction2? suffix,
IFunction2? supportingText,
bool? isError,
bool? singleLine,
int? maxLines,
int? minLines,
IComposer composer);

[ComposeBridge(
Class = "androidx/compose/material3/OutlinedTextFieldKt",
JvmName = "OutlinedTextField",
Signature = TextFieldStringSig,
Defaults = typeof(TextFieldDefault))]
[ComposeFacade]
public static partial void OutlinedTextField(string value, [Callback(typeof(string))] IFunction1 onValueChange,
IModifier? modifier, IComposer composer);
public static partial void OutlinedTextField(
string value,
[Callback(typeof(string))] IFunction1 onValueChange,
IModifier? modifier,
bool? enabled,
bool? readOnly,
IFunction2? label,
IFunction2? placeholder,
IFunction2? leadingIcon,
IFunction2? trailingIcon,
IFunction2? prefix,
IFunction2? suffix,
IFunction2? supportingText,
bool? isError,
bool? singleLine,
int? maxLines,
int? minLines,
IComposer composer);

// androidx.compose.material3.SecureTextFieldKt.{SecureTextField,OutlinedSecureTextField}-XvU6IwQ.
// Both overloads have identical 23-user-param signatures: the
Expand Down
83 changes: 83 additions & 0 deletions src/ComposeNet.Compose/FontFamily.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Android.Runtime;

namespace ComposeNet;

/// <summary>
/// C# wrapper around <c>androidx.compose.ui.text.font.FontFamily</c>.
/// Compose's <c>FontFamily</c> is a real Kotlin class, but Compose
/// 1.11.2.1's <c>ui-text-android</c> package is shipped as a
/// Java-library-only stub with zero exported types — so the .NET
/// binding doesn't expose it yet. We subclass <see cref="Java.Lang.Object"/>
/// directly and resolve the Kotlin <c>Companion</c> instances via JNI;
/// the bridge generator's reference-type code path
/// (<c>x is null ? IntPtr.Zero : x.Handle</c>) passes the handle through
/// to the JNI <c>L</c> slot.
///
/// Will swap to bound <c>AndroidX.Compose.UI.Text.Font.FontFamily</c>
/// once <see href="https://github.com/dotnet/android-libraries/pull/1440"/>
/// ships and we adopt the next <c>Xamarin.AndroidX.Compose.UI.Text.Android</c>
/// release.
/// </summary>
public sealed class FontFamily : Java.Lang.Object
{
FontFamily(IntPtr handle, JniHandleOwnership transfer)
: base(handle, transfer) { }

// FontFamily.Companion exposes generic-font-family getters:
// FontFamily$Companion.getDefault() : FontFamily
// FontFamily$Companion.getSansSerif() : FontFamily
// FontFamily$Companion.getSerif() : FontFamily
// FontFamily$Companion.getMonospace() : FontFamily
// FontFamily$Companion.getCursive() : FontFamily
static IntPtr s_companion;
static unsafe IntPtr Companion()
{
if (s_companion == IntPtr.Zero)
{
IntPtr fontFamilyCls = JNIEnv.FindClass("androidx/compose/ui/text/font/FontFamily");
IntPtr fid = JNIEnv.GetStaticFieldID(fontFamilyCls, "Companion", "Landroidx/compose/ui/text/font/FontFamily$Companion;");
IntPtr local = JNIEnv.GetStaticObjectField(fontFamilyCls, fid);
s_companion = JNIEnv.NewGlobalRef(local);
JNIEnv.DeleteLocalRef(local);
}
return s_companion;
}

static FontFamily Resolve(string getterName, string returnDescriptor)
{
// FontFamily$Companion getters return concrete subtypes:
// getDefault() -> SystemFontFamily
// getSansSerif() -> GenericFontFamily
// getSerif() -> GenericFontFamily
// getMonospace() -> GenericFontFamily
// getCursive() -> GenericFontFamily
// All of these extend FontFamily, so we wrap as our base type.
IntPtr cls = JNIEnv.FindClass("androidx/compose/ui/text/font/FontFamily$Companion");
IntPtr mid = JNIEnv.GetMethodID(cls, getterName, $"(){returnDescriptor}");
IntPtr companion = Companion();
IntPtr value = JNIEnv.CallObjectMethod(companion, mid);
return new FontFamily(value, JniHandleOwnership.TransferLocalRef);
}

const string SystemFontFamilyDescriptor = "Landroidx/compose/ui/text/font/SystemFontFamily;";
const string GenericFontFamilyDescriptor = "Landroidx/compose/ui/text/font/GenericFontFamily;";

static FontFamily? s_default, s_sansSerif, s_serif, s_monospace, s_cursive;

/// <summary>
/// <c>FontFamily.Default</c> — the platform's system font family.
/// </summary>
public static FontFamily Default => s_default ??= Resolve("getDefault", SystemFontFamilyDescriptor);

/// <summary>Generic sans-serif family (<c>FontFamily.SansSerif</c>).</summary>
public static FontFamily SansSerif => s_sansSerif ??= Resolve("getSansSerif", GenericFontFamilyDescriptor);

/// <summary>Generic serif family (<c>FontFamily.Serif</c>).</summary>
public static FontFamily Serif => s_serif ??= Resolve("getSerif", GenericFontFamilyDescriptor);

/// <summary>Generic monospace family (<c>FontFamily.Monospace</c>).</summary>
public static FontFamily Monospace => s_monospace ??= Resolve("getMonospace", GenericFontFamilyDescriptor);

/// <summary>Generic cursive family (<c>FontFamily.Cursive</c>).</summary>
public static FontFamily Cursive => s_cursive ??= Resolve("getCursive", GenericFontFamilyDescriptor);
}
75 changes: 75 additions & 0 deletions src/ComposeNet.Compose/FontStyle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Android.Runtime;

namespace ComposeNet;

/// <summary>
/// C# wrapper around <c>androidx.compose.ui.text.font.FontStyle</c>.
/// In Kotlin source, <c>FontStyle</c> is a <c>@JvmInline value class</c>
/// wrapping an <c>Int</c>, but every <c>@Composable</c> function in
/// Material 3 declares it as <em>nullable</em> (<c>fontStyle: FontStyle?
/// = null</c>) — so at the JNI boundary it travels as a boxed
/// <c>androidx/compose/ui/text/font/FontStyle;</c> reference, not a
/// packed <c>int</c>. The bridge generator's reference-type path
/// passes the handle through to the JNI <c>L</c> slot.
///
/// Same trick as <see cref="TextAlign"/>: call the mangled
/// <c>Companion.getNormal-_-LCdwA()I</c> for the packed int, then
/// route through static <c>FontStyle.box-impl(I)LFontStyle;</c>.
///
/// Will swap to bound <c>AndroidX.Compose.UI.Text.Font.FontStyle</c>
/// once <see href="https://github.com/dotnet/android-libraries/pull/1440"/>
/// ships.
/// </summary>
public sealed class FontStyle : Java.Lang.Object
{
FontStyle(IntPtr handle, JniHandleOwnership transfer)
: base(handle, transfer) { }

static IntPtr s_companion;
static IntPtr s_box;

static unsafe IntPtr Companion()
{
if (s_companion == IntPtr.Zero)
{
IntPtr cls = JNIEnv.FindClass("androidx/compose/ui/text/font/FontStyle");
IntPtr fid = JNIEnv.GetStaticFieldID(cls, "Companion", "Landroidx/compose/ui/text/font/FontStyle$Companion;");
IntPtr local = JNIEnv.GetStaticObjectField(cls, fid);
s_companion = JNIEnv.NewGlobalRef(local);
JNIEnv.DeleteLocalRef(local);
}
return s_companion;
}

static IntPtr BoxMethod()
{
if (s_box == IntPtr.Zero)
{
IntPtr cls = JNIEnv.FindClass("androidx/compose/ui/text/font/FontStyle");
s_box = JNIEnv.GetStaticMethodID(cls, "box-impl", "(I)Landroidx/compose/ui/text/font/FontStyle;");
}
return s_box;
}

static unsafe FontStyle Resolve(string mangledGetter)
{
IntPtr companionCls = JNIEnv.FindClass("androidx/compose/ui/text/font/FontStyle$Companion");
IntPtr getter = JNIEnv.GetMethodID(companionCls, mangledGetter, "()I");
int packed = JNIEnv.CallIntMethod(Companion(), getter);

IntPtr cls = JNIEnv.FindClass("androidx/compose/ui/text/font/FontStyle");
JValue* args = stackalloc JValue[1];
args[0] = new JValue(packed);
IntPtr boxed = JNIEnv.CallStaticObjectMethod(cls, BoxMethod(), args);
return new FontStyle(boxed, JniHandleOwnership.TransferLocalRef);
}

static FontStyle? s_normal, s_italic;

/// <summary>Upright glyphs (the default).</summary>
public static FontStyle Normal => s_normal ??= Resolve("getNormal-_-LCdwA");

/// <summary>Italic / slanted glyphs.</summary>
public static FontStyle Italic => s_italic ??= Resolve("getItalic-_-LCdwA");
}

Loading
Loading