Add `ITextButton` and related Mappers (#2868)

* textbutton_mapper

* - cleanup

* - fix stubs

* - fix stubs
This commit is contained in:
Shane Neuville 2021-10-22 09:02:29 -05:00 коммит произвёл GitHub
Родитель d9327d13ed
Коммит dcc4cf0f14
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 147 добавлений и 102 удалений

Просмотреть файл

@ -1,7 +1,9 @@
namespace Microsoft.Maui.Controls
{
public partial class Button : IButton, IText
public partial class Button : IButton, ITextButton, IImageButton
{
bool _wasImageLoading;
void IButton.Clicked()
{
(this as IButtonController).SendClicked();
@ -17,13 +19,22 @@ namespace Microsoft.Maui.Controls
(this as IButtonController).SendReleased();
}
void IButton.ImageSourceLoaded()
void IImageSourcePart.UpdateIsLoading(bool isLoading)
{
Handler?.UpdateValue(nameof(ContentLayout));
if (!isLoading && _wasImageLoading)
Handler?.UpdateValue(nameof(ContentLayout));
_wasImageLoading = isLoading;
}
IImageSource IButton.ImageSource => ImageSource;
Font ITextStyle.Font => (Font)GetValue(FontElement.FontProperty);
Aspect IImage.Aspect => Aspect.Fill;
bool IImage.IsOpaque => true;
IImageSource IImageSourcePart.Source => ImageSource;
bool IImageSourcePart.IsAnimationPlaying => false;
}
}

Просмотреть файл

@ -19,8 +19,8 @@ namespace Microsoft.Maui.Controls
[nameof(Padding)] = MapPadding,
#endif
#if WINDOWS
[nameof(IText.Text)] = MapText,
[nameof(IButton.ImageSource)] = MapImageSource
[nameof(IText.Text)] = MapText,
[nameof(ImageSource)] = MapImageSource
#endif
};

Просмотреть файл

@ -26,11 +26,5 @@ namespace Microsoft.Maui.Controls
{
(this as IButtonController).SendReleased();
}
void IButton.ImageSourceLoaded()
{
}
IImageSource IButton.ImageSource => Source;
}
}

Просмотреть файл

@ -5,14 +5,6 @@ namespace Microsoft.Maui
/// </summary>
public interface IButton : IView, IPadding
{
/// <summary>
/// Allows you to display a bitmap image on the Button.
/// </summary>
IImageSource? ImageSource { get; }
void ImageSourceLoaded();
/// <summary>
/// Occurs when the Button is pressed.
/// </summary>

Просмотреть файл

@ -3,7 +3,7 @@ namespace Microsoft.Maui
/// <summary>
/// Provides functionality to be able to customize Text.
/// </summary>
public interface IText : ITextStyle, IElement
public interface IText : ITextStyle
{
/// <summary>
/// Gets the text.

Просмотреть файл

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Maui
{
public interface ITextButton : IView, IButton, IText
{
}
}

Просмотреть файл

@ -5,7 +5,7 @@ namespace Microsoft.Maui
/// <summary>
/// Provides functionality to be able to customize the appearance of text.
/// </summary>
public interface ITextStyle : IElement
public interface ITextStyle
{
/// <summary>
/// Gets the text color.

Просмотреть файл

@ -105,23 +105,17 @@ namespace Microsoft.Maui.Handlers
handler.TypedNativeView?.UpdatePadding(button, DefaultPadding);
}
public static void MapImageSource(IButtonHandler handler, IButton image) =>
public static void MapImageSource(IButtonHandler handler, IImageButton image) =>
MapImageSourceAsync(handler, image).FireAndForget(handler);
public static Task MapImageSourceAsync(IButtonHandler handler, IButton image)
public static Task MapImageSourceAsync(IButtonHandler handler, IImageButton image)
{
if (image.ImageSource == null)
{
return Task.CompletedTask;
}
return handler.ImageSourceLoader.UpdateImageSourceAsync();
}
void OnSetImageSource(Drawable? obj)
{
NativeView.Icon = obj;
VirtualView?.ImageSourceLoaded();
}
bool NeedsExactMeasure()

Просмотреть файл

@ -14,25 +14,25 @@ namespace Microsoft.Maui.Handlers
{
ImageSourcePartLoader? _imageSourcePartLoader;
public ImageSourcePartLoader ImageSourceLoader =>
_imageSourcePartLoader ??= new ImageSourcePartLoader(this, () => VirtualView?.ImageSource, OnSetImageSource);
_imageSourcePartLoader ??= new ImageSourcePartLoader(this, () => (VirtualView as IImageButton), OnSetImageSource);
public static IPropertyMapper<ITextStyle, IButtonHandler> TextStyleMapper = new PropertyMapper<ITextStyle, IButtonHandler>(ViewHandler.ViewMapper)
public static IPropertyMapper<IImageButton, IButtonHandler> ImageButtonMapper = new PropertyMapper<IImageButton, IButtonHandler>()
{
[nameof(IImageButton.Source)] = MapImageSource,
};
public static IPropertyMapper<ITextButton, IButtonHandler> TextButtonMapper = new PropertyMapper<ITextButton, IButtonHandler>()
{
[nameof(ITextStyle.CharacterSpacing)] = MapCharacterSpacing,
[nameof(ITextStyle.Font)] = MapFont,
[nameof(ITextStyle.TextColor)] = MapTextColor,
};
public static IPropertyMapper<IText, IButtonHandler> TextMapper = new PropertyMapper<IText, IButtonHandler>(TextStyleMapper)
{
[nameof(IText.Text)] = MapText,
};
public static IPropertyMapper<IButton, IButtonHandler> Mapper = new PropertyMapper<IButton, IButtonHandler>(TextMapper)
public static IPropertyMapper<IButton, IButtonHandler> Mapper = new PropertyMapper<IButton, IButtonHandler>(TextButtonMapper, ImageButtonMapper, ViewHandler.ViewMapper)
{
[nameof(IButton.Background)] = MapBackground,
[nameof(IButton.Padding)] = MapPadding,
[nameof(IButton.ImageSource)] = MapImageSource
};
public ButtonHandler() : base(Mapper)

Просмотреть файл

@ -92,16 +92,14 @@ namespace Microsoft.Maui.Handlers
{
NativeView.SetImage(null, UIControlState.Normal);
}
VirtualView.ImageSourceLoaded();
}
public static void MapImageSource(IButtonHandler handler, IButton image) =>
public static void MapImageSource(IButtonHandler handler, IImageButton image) =>
MapImageSourceAsync(handler, image).FireAndForget(handler);
public static Task MapImageSourceAsync(IButtonHandler handler, IButton image)
public static Task MapImageSourceAsync(IButtonHandler handler, IImageButton image)
{
if (image.ImageSource == null)
if (image.Source == null)
{
return Task.CompletedTask;
}

Просмотреть файл

@ -66,7 +66,7 @@ namespace Microsoft.Maui.Handlers
var map = imv.GetPropertyMapperOverrides();
if (map is not null)
{
map.Chained = _defaultMapper;
map.Chained = new[] { _defaultMapper };
_mapper = map;
}
}

Просмотреть файл

@ -41,7 +41,7 @@ namespace Microsoft.Maui.Handlers
#if __IOS__
UIKit.UIImageView IImageHandler.TypedNativeView => NativeView.ImageView;
#elif WINDOWS
UI.Xaml.Controls.Image IImageHandler.TypedNativeView => (UI.Xaml.Controls.Image)NativeView.Content;
UI.Xaml.Controls.Image IImageHandler.TypedNativeView => NativeView.GetImage() ?? (UI.Xaml.Controls.Image)NativeView.Content;
#elif __ANDROID__
Android.Widget.ImageView IImageHandler.TypedNativeView => NativeView;
#else

Просмотреть файл

@ -42,17 +42,6 @@ namespace Microsoft.Maui
SetImage = setImage;
}
internal ImageSourcePartLoader(
IElementHandler handler,
Func<IImageSource?> imageSource,
Action<NativeImage?> setImage)
{
Handler = handler;
var wrapper = new ImageSourcePartWrapper(imageSource);
_imageSourcePart = () => wrapper;
SetImage = setImage;
}
public void Reset()
{
SourceManager.Reset();
@ -83,26 +72,5 @@ namespace Microsoft.Maui
}
}
}
// TODO MAUI: This is currently here so that Button can continue to use IImageSource
// At a later point once we further define the interface for IButtonHandler we will probably
// change IButton to return an IImageSourcePart and we can get rid of this class
class ImageSourcePartWrapper : IImageSourcePart
{
readonly Func<IImageSource?> _imageSource;
public ImageSourcePartWrapper(Func<IImageSource?> imageSource)
{
_imageSource = imageSource;
}
IImageSource? IImageSourcePart.Source => _imageSource.Invoke();
bool IImageSourcePart.IsAnimationPlaying => false;
void IImageSourcePart.UpdateIsLoading(bool isLoading)
{
}
}
}
}

Просмотреть файл

@ -67,7 +67,7 @@ namespace Microsoft.Maui
image = contentImage;
}
// This means the users image hasn't loaded yet but we still want to setup the container for the user
else if (button.ImageSource != null)
else if (button is IImageButton ib && ib.Source != null)
{
image = nativeButton.GetImage() ?? new();
}

Просмотреть файл

@ -8,7 +8,7 @@ namespace Microsoft.Maui
{
readonly Dictionary<string, Action<IElementHandler, IElement>> _mapper = new();
IPropertyMapper? _chained;
IPropertyMapper[]? _chained;
// Keep a distinct list of the keys so we don't run any duplicate (overridden) updates more than once
// when we call UpdateProperties
@ -18,7 +18,7 @@ namespace Microsoft.Maui
{
}
public PropertyMapper(IPropertyMapper chained)
public PropertyMapper(params IPropertyMapper[]? chained)
{
Chained = chained;
}
@ -40,9 +40,16 @@ namespace Microsoft.Maui
if (_mapper.TryGetValue(key, out var action))
return action;
else if (Chained is not null)
return Chained.GetProperty(key);
else
return null;
{
foreach(var ch in Chained)
{
var returnValue = ch.GetProperty(key);
if (returnValue != null)
return returnValue;
}
}
return null;
}
public void UpdateProperty(IElementHandler viewHandler, IElement? virtualView, string property)
@ -64,7 +71,7 @@ namespace Microsoft.Maui
}
}
public IPropertyMapper? Chained
public IPropertyMapper[]? Chained
{
get => _chained;
set
@ -101,8 +108,9 @@ namespace Microsoft.Maui
if (Chained is not null)
{
foreach (var key in Chained.GetKeys())
yield return key;
foreach (var chain in Chained)
foreach (var key in chain.GetKeys())
yield return key;
}
}
}
@ -133,7 +141,7 @@ namespace Microsoft.Maui
{
}
public PropertyMapper(IPropertyMapper chained)
public PropertyMapper(params IPropertyMapper[] chained)
: base(chained)
{
}
@ -153,8 +161,17 @@ namespace Microsoft.Maui
{
if (v is TVirtualView vv)
action?.Invoke((TViewHandler)h, vv);
else
Chained?.UpdateProperty(h, v, key);
else if(Chained != null)
{
foreach(var chain in Chained)
{
if(chain.GetProperty(key) != null)
{
chain.UpdateProperty(h, v, key);
break;
}
}
}
});
}
@ -165,7 +182,7 @@ namespace Microsoft.Maui
{
}
public PropertyMapper(PropertyMapper chained)
public PropertyMapper(params PropertyMapper[] chained)
: base(chained)
{
}

Просмотреть файл

@ -3,7 +3,7 @@ using Microsoft.Maui.Graphics;
namespace Microsoft.Maui.DeviceTests.Stubs
{
public partial class ButtonStub : StubBase, IButton, IText
public partial class ButtonStub : StubBase, IButton, ITextButton, IImageButton
{
public string Text { get; set; }
@ -17,6 +17,16 @@ namespace Microsoft.Maui.DeviceTests.Stubs
public IImageSource ImageSource { get; set; }
Aspect IImage.Aspect => Aspect.Fill;
bool IImage.IsOpaque => true;
IImageSource IImageSourcePart.Source => ImageSource;
bool IImageSourcePart.IsAnimationPlaying => false;
void IImageSourcePart.UpdateIsLoading(bool isLoading) { }
public event EventHandler Pressed;
public event EventHandler Released;
public event EventHandler Clicked;

Просмотреть файл

@ -47,10 +47,5 @@ namespace Microsoft.Maui.DeviceTests.Stubs
void IButton.Pressed() => Pressed?.Invoke(this, EventArgs.Empty);
void IButton.Released() => Released?.Invoke(this, EventArgs.Empty);
void IButton.Clicked() => Clicked?.Invoke(this, EventArgs.Empty);
void IButton.ImageSourceLoaded()
{
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -40,9 +40,9 @@ namespace Microsoft.Maui.UnitTests
[nameof(IView.Background)] = (r, v) => wasMapper1Called = true
};
var mapper2 = new PropertyMapper<ITextStyle>(mapper1)
var mapper2 = new PropertyMapper<ITextButton>(mapper1)
{
[nameof(ITextStyle.TextColor)] = (r, v) => wasMapper2Called = true
[nameof(ITextButton.TextColor)] = (r, v) => wasMapper2Called = true
};
mapper2.UpdateProperties(null, new Button());
@ -51,6 +51,53 @@ namespace Microsoft.Maui.UnitTests
Assert.True(wasMapper2Called);
}
[Fact]
public void ConstructorChainingMappersWorks()
{
bool wasMapper1Called = false;
bool wasMapper2Called = false;
var mapper1 = new PropertyMapper<IView>
{
[nameof(IView.Background)] = (r, v) => wasMapper1Called = true
};
var mapper2 = new PropertyMapper<ITextButton>()
{
[nameof(ITextButton.TextColor)] = (r, v) => wasMapper2Called = true
};
new PropertyMapper<ITextButton>(mapper2, mapper1)
.UpdateProperties(null, new Button());
Assert.True(wasMapper1Called);
Assert.True(wasMapper2Called);
}
[Fact]
public void ConstructorChainingMappersOverrideBase()
{
bool wasMapper1Called = false;
bool wasMapper2Called = false;
var mapper1 = new PropertyMapper<IView>
{
[nameof(IView.Background)] = (r, v) => wasMapper1Called = true
};
var mapper2 = new PropertyMapper<IButton>()
{
[nameof(IView.Background)] = (r, v) => wasMapper2Called = true
};
new PropertyMapper<ITextButton>(mapper2, mapper1)
.UpdateProperties(null, new Button());
Assert.False(wasMapper1Called);
Assert.True(wasMapper2Called);
}
[Fact]
public void ChainingMappersStillAllowReplacingChainedRoot()
{
@ -62,9 +109,9 @@ namespace Microsoft.Maui.UnitTests
[nameof(IView.Background)] = (r, v) => wasMapper1Called = true
};
var mapper2 = new PropertyMapper<ITextStyle>(mapper1)
var mapper2 = new PropertyMapper<ITextButton>(mapper1)
{
[nameof(ITextStyle.TextColor)] = (r, v) => wasMapper2Called = true
[nameof(ITextButton.TextColor)] = (r, v) => wasMapper2Called = true
};
mapper1[nameof(IView.Background)] = (r, v) => wasMapper3Called = true;

Просмотреть файл

@ -3,7 +3,7 @@ using Microsoft.Maui.Graphics;
namespace Microsoft.Maui.UnitTests
{
class ButtonStub : View, IButton, IText
class ButtonStub : View, IButton, ITextButton, IImageButton
{
public string Text { get; set; }
@ -22,6 +22,15 @@ namespace Microsoft.Maui.UnitTests
public Font Font { get; set; }
public IImageSource ImageSource { get; set; }
Aspect IImage.Aspect => Aspect.Fill;
bool IImage.IsOpaque => true;
IImageSource IImageSourcePart.Source => ImageSource;
bool IImageSourcePart.IsAnimationPlaying => false;
void IImageSourcePart.UpdateIsLoading(bool isLoading) { }
public void ImageSourceLoaded()
{