Skip to content

Commit

Permalink
Scrollview handler (#1669)
Browse files Browse the repository at this point in the history
* A mostly-functioning WASDK ScrollView handler

* Forgot to check these in

* Add request method to IScrollView

* Working implementation of CommandMapper and working WinUI ScrollView scroll requests

* Remove Actions from PropertyMapper and into CommandMapper

* Ported Android ScrollViewRenderer to handler

* De-linqify

* iOS ScrollViewHandler

* ScrollViewHandler iOS (with ContentSize)

* Add device tests for ScrollViewHandlers

* Remove defunct tests

* Fix NRE

* Reinstate IntrinsicContentSize check - the bug this was removed to prevent
is fixed by PR #1700.

* Fix nullability

* Fix whitespace

* Whitespace

* Whitespace

* Yes, I'm being picky about whitespace

Co-authored-by: Jonathan Dick <jondick@gmail.com>
Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
  • Loading branch information
3 people committed Jul 20, 2021
1 parent 6230651 commit 16d56c6
Show file tree
Hide file tree
Showing 39 changed files with 991 additions and 187 deletions.
8 changes: 7 additions & 1 deletion src/Controls/src/Core/HandlerImpl/ScrollView.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public partial class ScrollView : IScrollView
}
}

void IScrollView.RequestScrollTo(double horizontalOffset, double verticalOffset, bool instant)
{
var request = new ScrollToRequest(horizontalOffset, verticalOffset, instant);
Handler?.Invoke(nameof(IScrollView.RequestScrollTo), request);
}

void IScrollView.ScrollFinished() => SendScrollFinished();
}
}
}
2 changes: 1 addition & 1 deletion src/Controls/src/Core/HandlerImpl/VisualElement.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ void IFrameworkElement.InvalidateMeasure()

// InvalidateMeasureOverride provides a way to allow subclasses (e.g., Layout) to override InvalidateMeasure even though
// the interface has to be explicitly implemented to avoid conflict with the VisualElement.InvalidateMeasure method
protected virtual void InvalidateMeasureOverride() => Handler?.UpdateValue(nameof(IFrameworkElement.InvalidateMeasure));
protected virtual void InvalidateMeasureOverride() => Handler?.Invoke(nameof(IFrameworkElement.InvalidateMeasure));

void IFrameworkElement.InvalidateArrange()
{
Expand Down
2 changes: 0 additions & 2 deletions src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ public static partial class AppHostBuilderExtensions
{ typeof(Layout2.Layout), typeof(LayoutHandler) },
{ typeof(Picker), typeof(PickerHandler) },
{ typeof(ProgressBar), typeof(ProgressBarHandler) },
#if WINDOWS
{ typeof(ScrollView), typeof(ScrollViewHandler) },
#endif
{ typeof(SearchBar), typeof(SearchBarHandler) },
{ typeof(Slider), typeof(SliderHandler) },
{ typeof(Stepper), typeof(StepperHandler) },
Expand Down
4 changes: 2 additions & 2 deletions src/Controls/src/Core/Picker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
// If the index has not changed, still need to change the selected item
if (newIndex == oldIndex)
UpdateSelectedItem(newIndex);
//This sends the notification to the Maui Handler to reload
OnPropertyChanged("Reload");

Handler?.Invoke("Reload");
}

static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
Expand Down
4 changes: 4 additions & 0 deletions src/Controls/src/Core/ScrollToRequestedEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,9 @@ object ITemplatedItemsListScrollToRequestedEventArgs.Item
}
}

public ScrollToRequest ToRequest()
{
return new ScrollToRequest(ScrollX, ScrollY, !ShouldAnimate);
}
}
}
8 changes: 6 additions & 2 deletions src/Controls/src/Core/ScrollView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ public View Content
if (_content != null)
InternalChildren.Add(_content);
OnPropertyChanged();

Handler?.UpdateValue(nameof(Content));
}
}

Expand Down Expand Up @@ -276,9 +278,9 @@ protected override SizeRequest OnSizeRequest(double widthConstraint, double heig

SizeRequest contentRequest;

if (Content is IFrameworkElement fe)
if (Content is IFrameworkElement fe && fe.Handler != null)
{
contentRequest = fe.Measure(widthConstraint, heightConstraint);
contentRequest = fe.Handler.GetDesiredSize(widthConstraint, heightConstraint);
}
else
{
Expand Down Expand Up @@ -361,6 +363,8 @@ void OnScrollToRequested(ScrollToRequestedEventArgs e)
{
CheckTaskCompletionSource();
ScrollToRequested?.Invoke(this, e);

Handler?.Invoke(nameof(IScrollView.RequestScrollTo), e.ToRequest());
}

protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
Expand Down
22 changes: 0 additions & 22 deletions src/Core/src/ActionMapper.cs

This file was deleted.

104 changes: 104 additions & 0 deletions src/Core/src/CommandMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using Command = System.Action<Microsoft.Maui.IElementHandler, Microsoft.Maui.IElement, object?>;

namespace Microsoft.Maui
{
public abstract class CommandMapper
{
readonly Dictionary<string, Command> _mapper = new();

CommandMapper? _chained;

public CommandMapper()
{
}

public CommandMapper(CommandMapper chained)
{
Chained = chained;
}

private protected virtual void SetPropertyCore(string key, Command action)
{
_mapper[key] = action;
}

private protected virtual void InvokeCore(string key, IElementHandler viewHandler, IElement virtualView, object? args)
{
var action = GetCommandCore(key);
action?.Invoke(viewHandler, virtualView, args);
}

private protected virtual Command? GetCommandCore(string key)
{
if (_mapper.TryGetValue(key, out var action))
return action;
else if (Chained is not null)
return Chained.GetCommandCore(key);
else
return null;
}

internal void Invoke(IElementHandler viewHandler, IElement? virtualView, string property, object? args)
{
if (virtualView == null)
return;

InvokeCore(property, viewHandler, virtualView, args);
}

public CommandMapper? Chained
{
get => _chained;
set
{
_chained = value;
}
}
}

public class CommandMapper<TVirtualView, TViewHandler> : CommandMapper
where TVirtualView : IElement
where TViewHandler : IElementHandler
{
public CommandMapper()
{
}

public CommandMapper(CommandMapper chained)
: base(chained)
{
}

public Action<TViewHandler, TVirtualView, object?> this[string key]
{
get
{
var action = GetCommandCore(key) ?? throw new IndexOutOfRangeException($"Unable to find mapping for '{nameof(key)}'.");
return new Action<TViewHandler, TVirtualView, object?>((h, v, o) => action.Invoke(h, v, o));
}
set => Add(key, value);
}


public void Add(string key, Action<TViewHandler, TVirtualView> action) =>
Add(key, action);

public void Add(string key, Action<TViewHandler, TVirtualView, object?> action) =>
SetPropertyCore(key, (h, v, o) => action?.Invoke((TViewHandler)h, (TVirtualView)v, o));
}

public class CommandMapper<TVirtualView> : PropertyMapper<TVirtualView, IElementHandler>
where TVirtualView : IElement
{
public CommandMapper()
{
}

public CommandMapper(PropertyMapper chained)
: base(chained)
{
}
}
}
4 changes: 3 additions & 1 deletion src/Core/src/Core/IScrollView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Microsoft.Maui
{
public interface IScrollView : IView
public interface IScrollView : IView
{
// TODO ezhart 2021-07-08 It might make sense for IPage and IScrollView to derive from (the not yet created) IContentView

Expand Down Expand Up @@ -46,5 +46,7 @@ public interface IScrollView : IView
/// Allows the native ScrollView to inform that cross-platform code that a scroll operation has completed.
/// </summary>
void ScrollFinished();

void RequestScrollTo(double horizontalOffset, double verticalOffset, bool instant);
}
}
12 changes: 11 additions & 1 deletion src/Core/src/Handlers/Element/ElementHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ public abstract partial class ElementHandler : IElementHandler
};

protected PropertyMapper _mapper;
protected CommandMapper? CommandMapper;
protected readonly PropertyMapper _defaultMapper;

protected ElementHandler(PropertyMapper mapper)
protected ElementHandler(PropertyMapper mapper, CommandMapper? commandMapper = null)
{
_ = mapper ?? throw new ArgumentNullException(nameof(mapper));
_defaultMapper = mapper;
_mapper = _defaultMapper;
CommandMapper = commandMapper;
}

public IMauiContext? MauiContext { get; private set; }
Expand Down Expand Up @@ -75,6 +77,14 @@ public virtual void UpdateValue(string property)
_mapper?.UpdateProperty(this, VirtualView, property);
}

public virtual void Invoke(string command, object? args)
{
if (VirtualView == null)
return;

CommandMapper?.Invoke(this, VirtualView, command, args);
}

private protected abstract object OnCreateNativeElement();

object CreateNativeElement() =>
Expand Down
2 changes: 2 additions & 0 deletions src/Core/src/Handlers/IElementHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public interface IElementHandler

void UpdateValue(string property);

void Invoke(string command, object? args = null);

void DisconnectHandler();

object? NativeView { get; }
Expand Down
10 changes: 6 additions & 4 deletions src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using NativeView = UIKit.UIView;

namespace Microsoft.Maui.Handlers
Expand Down Expand Up @@ -34,9 +33,12 @@ public override void SetVirtualView(IView view)
NativeView.CrossPlatformMeasure = VirtualView.Measure;
NativeView.CrossPlatformArrange = VirtualView.Arrange;

//Cleanup the old view when reused
var oldChildren = NativeView.Subviews.ToList();
oldChildren.ForEach(x => x.RemoveFromSuperview());
// Remove any previous children
var oldChildren = NativeView.Subviews;
foreach (var child in oldChildren)
{
child.RemoveFromSuperview();
}

foreach (var child in VirtualView.Children)
{
Expand Down
14 changes: 9 additions & 5 deletions src/Core/src/Handlers/Page/PageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ public partial class PageHandler : IViewHandler
{
[nameof(IPage.Title)] = MapTitle,
[nameof(IPage.Content)] = MapContent,
};

#if __IOS__
Actions =
{
[nameof(IFrameworkElement.Frame)] = MapFrame,
}
#endif
public static CommandMapper<IPicker, PickerHandler> PageCommandMapper = new(ViewCommandMapper)
{
[nameof(IFrameworkElement.Frame)] = MapFrame,
};

public PageHandler() : base(PageMapper, PageCommandMapper)
#else
public PageHandler() : base(PageMapper)
#endif

{

}
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Page/PageHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public static void MapContent(PageHandler handler, IPage page)

public static void MapFrame(PageHandler handler, IView view)
{
ViewHandler.MapFrame(handler, view);
ViewHandler.MapFrame(handler, view, null);

// TODO MAUI: Currently the background layer frame is tied to the layout system
// which needs to be investigated more
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Picker/PickerHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void Reload()

NativeView.UpdatePicker(VirtualView);
}
public static void MapReload(PickerHandler handler, IPicker picker) => handler.Reload();
public static void MapReload(PickerHandler handler, IPicker picker, object? args) => handler.Reload();

public static void MapTitle(PickerHandler handler, IPicker picker)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Picker/PickerHandler.Standard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public partial class PickerHandler : ViewHandler<IPicker, object>
{
protected override object CreateNativeView() => throw new NotImplementedException();

public static void MapReload(PickerHandler handler, IPicker picker) { }
public static void MapReload(PickerHandler handler, IPicker picker, object? args) { }
public static void MapTitle(PickerHandler handler, IPicker view) { }
public static void MapTitleColor(PickerHandler handler, IPicker view) { }
public static void MapSelectedIndex(PickerHandler handler, IPicker view) { }
Expand Down
5 changes: 2 additions & 3 deletions src/Core/src/Handlers/Picker/PickerHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ protected override void DisconnectHandler(MauiComboBox nativeView)
void SetupDefaults(MauiComboBox nativeView)
{
_defaultForeground = nativeView.Foreground;


}

void Reload()
{

Expand All @@ -42,7 +41,7 @@ void Reload()
NativeView.ItemsSource = new ItemDelegateList<string>(VirtualView);
}

public static void MapReload(PickerHandler handler, IPicker picker) => handler.Reload();
public static void MapReload(PickerHandler handler, IPicker picker, object? args) => handler.Reload();

public static void MapTitle(PickerHandler handler, IPicker picker)
{
Expand Down
13 changes: 7 additions & 6 deletions src/Core/src/Handlers/Picker/PickerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
public partial class PickerHandler
{
public static PropertyMapper<IPicker, PickerHandler> PickerMapper = new PropertyMapper<IPicker, PickerHandler>(ViewHandler.ViewMapper)
public static PropertyMapper<IPicker, PickerHandler> PickerMapper = new(ViewMapper)
{
#if __ANDROID__
[nameof(IPicker.Background)] = MapBackground,
Expand All @@ -14,13 +14,14 @@ public partial class PickerHandler
[nameof(IPicker.Title)] = MapTitle,
[nameof(IPicker.TitleColor)] = MapTitleColor,
[nameof(IPicker.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
Actions =
{
["Reload"] = MapReload,
}
};

public PickerHandler() : base(PickerMapper)
public static CommandMapper<IPicker, PickerHandler> PickerCommandMapper = new(ViewCommandMapper)
{
["Reload"] = MapReload
};

public PickerHandler() : base(PickerMapper, PickerCommandMapper)
{

}
Expand Down
Loading

0 comments on commit 16d56c6

Please sign in to comment.