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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion src/Core/src/Platform/iOS/TextFieldExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,19 @@ internal static void UpdateClearButtonColor(this UITextField textField, IEntry e

return renderer.CreateImage((context) =>
{
image.Draw(CGPoint.Empty, CGBlendMode.Normal, 1.0f);
// Draw a bitmap-backed copy of the clear button's image rather than the image itself.
// Drawing the image directly produced reduced alpha, and since the SourceIn fill below
// caps the final color's alpha at the drawn shape's alpha, that reduced alpha made the
// tinted clear button appear dimmed. Drawing a plain CGImage-backed UIImage avoids this
// and renders at full opacity.
var bitmapImage = image.CGImage is CGImage cgImage
? new UIImage(cgImage, image.CurrentScale, image.Orientation)
: image;
// bitmapImage's Size can be smaller than the original image's Size, so drawing it at
// CGPoint.Empty shifts it to the top-left instead of centering it. Center it manually
// to preserve the original image's rendered position.
var origin = new CGPoint((size.Width - bitmapImage.Size.Width) / 2, (size.Height - bitmapImage.Size.Height) / 2);
bitmapImage.Draw(origin, CGBlendMode.Normal, 1.0f);
color.ColorWithAlpha(1.0f).SetFill();

var rect = new CGRect(CGPoint.Empty.X, CGPoint.Empty.Y, image.Size.Width, image.Size.Height);
Expand Down
71 changes: 71 additions & 0 deletions src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,40 @@ await AttachAndRun(entry, async (handler) =>
});
}

[Fact(DisplayName = "Clear button tint matches TextColor at full opacity")]
public async Task ClearButtonTintMatchesTextColorAtFullOpacity()
{
EntryStub entry = new EntryStub
{
Text = "MAUI",
ClearButtonVisibility = ClearButtonVisibility.WhileEditing,
TextColor = null
};

await AttachAndRun(entry, async (handler) =>
{
await AssertEventually(() => handler.PlatformView.IsLoaded());
Assert.True(handler.PlatformView.BecomeFirstResponder());
await AssertEventually(() => handler.PlatformView.IsFirstResponder);

var clearButton = GetNativeClearButton(handler);
Assert.NotNull(clearButton);

entry.TextColor = Colors.Blue;
handler.UpdateValue(nameof(IEntry.TextColor));

var tintedImage = clearButton.ImageForState(UIControlState.Normal);
Assert.NotNull(tintedImage);

Assert.Equal(UIImageRenderingMode.Automatic, tintedImage.RenderingMode);

// the clear button was tinted from the system symbol image, which UIImage.Draw re-rasterizes
// at reduced opacity (~20%), so the glyph never reached full opacity and looked dimmed.
// The tinted glyph must contain fully opaque pixels so it matches TextColor.
Assert.Equal(byte.MaxValue, GetMaxAlpha(tintedImage));
});
}

[Fact]
public async Task NextMovesToNextEntry()
{
Expand Down Expand Up @@ -885,6 +919,43 @@ bool GetNativeClearButtonVisibility(EntryHandler entryHandler) =>
static UIButton GetNativeClearButton(EntryHandler entryHandler) =>
GetNativeEntry(entryHandler).ValueForKey(new NSString("clearButton")) as UIButton;

static byte GetMaxAlpha(UIImage image)
{
const int bitsPerComponent = 8;
const int bytesPerPixel = 4; // R, G, B, A (CGImageAlphaInfo.PremultipliedLast)
const int alphaByteOffset = 3; // A is the 4th byte within each RGBA pixel

var cgImage = image.CGImage;
Assert.NotNull(cgImage);

var width = (int)cgImage.Width;
var height = (int)cgImage.Height;
var bytesPerRow = width * bytesPerPixel;
var pixels = new byte[height * bytesPerRow];

using var colorSpace = CGColorSpace.CreateDeviceRGB();
using (var context = new CGBitmapContext(pixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, CGImageAlphaInfo.PremultipliedLast))
{
context.DrawImage(new CGRect(0, 0, width, height), cgImage);
}

byte maxAlpha = 0;
for (int i = alphaByteOffset; i < pixels.Length; i += bytesPerPixel)
{
if (pixels[i] == byte.MaxValue)
{
return byte.MaxValue;
}

if (pixels[i] > maxAlpha)
{
maxAlpha = pixels[i];
}
}

return maxAlpha;
}

UITextAlignment GetNativeHorizontalTextAlignment(EntryHandler entryHandler) =>
GetNativeEntry(entryHandler).TextAlignment;

Expand Down
Loading