From 3f742c8e34f2aa3c66f350fbfd2d9d80bc421d47 Mon Sep 17 00:00:00 2001 From: Keith Pickford Date: Sun, 18 Feb 2024 22:12:30 +0000 Subject: [PATCH] MultiViewZero is born. Works as a POC. --- .../Controls/Expander/ExpanderZero.xaml.cs | 4 +- .../Controls/MaskZero/MaskZero.xaml.cs | 3 +- .../Controls/MultiView/MultiViewAnimation.cs | 118 ++++++++ .../Controls/MultiView/MultiViewZero.cs | 262 ++++++++++++++++++ .../MultiView/MultiViewZeroLayoutManager.cs | 46 +++ .../FunctionZero.Maui.Controls.csproj | 5 +- SampleApp/MauiProgram.cs | 7 + .../Mvvm/PageViewModels/AppFlyoutPageVm.cs | 8 +- .../MultiView/MultiViewModalPageVm.cs | 31 +++ SampleApp/Mvvm/Pages/AppFlyoutPage.xaml | 14 + .../Pages/MultiView/MultiViewModalPage.xaml | 91 ++++++ .../MultiView/MultiViewModalPage.xaml.cs | 9 + SampleApp/SampleApp.csproj | 15 +- 13 files changed, 604 insertions(+), 9 deletions(-) create mode 100644 FunctionZero.Maui.Controls/Controls/MultiView/MultiViewAnimation.cs create mode 100644 FunctionZero.Maui.Controls/Controls/MultiView/MultiViewZero.cs create mode 100644 FunctionZero.Maui.Controls/Controls/MultiView/MultiViewZeroLayoutManager.cs create mode 100644 SampleApp/Mvvm/PageViewModels/MultiView/MultiViewModalPageVm.cs create mode 100644 SampleApp/Mvvm/Pages/MultiView/MultiViewModalPage.xaml create mode 100644 SampleApp/Mvvm/Pages/MultiView/MultiViewModalPage.xaml.cs diff --git a/FunctionZero.Maui.Controls/Controls/Expander/ExpanderZero.xaml.cs b/FunctionZero.Maui.Controls/Controls/Expander/ExpanderZero.xaml.cs index b078472..a4ee292 100644 --- a/FunctionZero.Maui.Controls/Controls/Expander/ExpanderZero.xaml.cs +++ b/FunctionZero.Maui.Controls/Controls/Expander/ExpanderZero.xaml.cs @@ -111,7 +111,7 @@ private static void EaseInChanged(BindableObject bindable, object oldValue, obje public Easing EaseIn { get => (Easing)GetValue(EaseInProperty); - set => SetValue(HeaderProperty, value); + set => SetValue(EaseInProperty, value); } public static readonly BindableProperty EaseOutProperty = BindableProperty.Create(nameof(EaseOut), typeof(Easing), typeof(ExpanderZero), Easing.Linear, BindingMode.OneWay, null, EaseOutChanged); @@ -124,7 +124,7 @@ private static void EaseOutChanged(BindableObject bindable, object oldValue, obj public Easing EaseOut { get => (Easing)GetValue(EaseOutProperty); - set => SetValue(HeaderProperty, value); + set => SetValue(EaseInProperty, value); } public static readonly BindableProperty DurationMillisecondsProperty = BindableProperty.Create(nameof(DurationMilliseconds), typeof(uint), typeof(ExpanderZero), (uint)500, BindingMode.OneWay, null, DurationMillisecondsChanged); diff --git a/FunctionZero.Maui.Controls/Controls/MaskZero/MaskZero.xaml.cs b/FunctionZero.Maui.Controls/Controls/MaskZero/MaskZero.xaml.cs index d28d076..2dd1f0c 100644 --- a/FunctionZero.Maui.Controls/Controls/MaskZero/MaskZero.xaml.cs +++ b/FunctionZero.Maui.Controls/Controls/MaskZero/MaskZero.xaml.cs @@ -14,6 +14,7 @@ public partial class MaskZero : ContentView private bool _updateRequested = false; private View _actualTarget; private double _alphaMultiplier; + private bool _loaded; public MaskZero() { @@ -29,9 +30,9 @@ public MaskZero() DescendantAdded += MaskZero_DescendantAdded; DescendantRemoved += MaskZero_DescendantRemoved; + } - } protected override void OnSizeAllocated(double width, double height) { diff --git a/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewAnimation.cs b/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewAnimation.cs new file mode 100644 index 0000000..596037c --- /dev/null +++ b/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewAnimation.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FunctionZero.Maui.Controls +{ + public class MultiViewAnimation : BindableObject + { + #region FromProperty + + public static readonly BindableProperty FromProperty = BindableProperty.Create(nameof(From), typeof(double), typeof(MultiViewAnimation), 0.0, BindingMode.OneWay, null, FromChanged); + + public double From + { + get { return (double)GetValue(FromProperty); } + set { SetValue(FromProperty, value); } + } + + private static void FromChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewAnimation)bindable; + + } + + #endregion + + #region ToViewNameProperty + + public static readonly BindableProperty ToProperty = BindableProperty.Create(nameof(To), typeof(double), typeof(MultiViewAnimation), 0.0, BindingMode.OneWay, null, ToChanged); + + public double To + { + get { return (double)GetValue(ToProperty); } + set { SetValue(ToProperty, value); } + } + + private static void ToChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewAnimation)bindable; + + } + + #endregion + + //#region DurationProperty + + //public static readonly BindableProperty DurationProperty = BindableProperty.Create(nameof(Duration), typeof(double), typeof(MultiViewAnimation), 0.0, BindingMode.OneWay, null, DurationChanged); + + //public double Duration + //{ + // get { return (double)GetValue(DurationProperty); } + // set { SetValue(DurationProperty, value); } + //} + + //private static void DurationChanged(BindableObject bindable, object oldValue, object newValue) + //{ + // var self = (MultiViewAnimation)bindable; + + //} + + //#endregion + + #region EasingFuncProperty + + public static readonly BindableProperty EasingFuncProperty = BindableProperty.Create(nameof(EasingFunc), typeof(Easing), typeof(MultiViewAnimation), Easing.Linear, BindingMode.OneWay, null, EasingFuncChanged); + + public Easing EasingFunc + { + get { return (Easing)GetValue(EasingFuncProperty); } + set { SetValue(EasingFuncProperty, value); } + } + + private static void EasingFuncChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewAnimation)bindable; + + } + + #endregion + + #region ExpressionProperty + + public static readonly BindableProperty ExpressionProperty = BindableProperty.Create(nameof(Expression), typeof(string), typeof(MultiViewAnimation), string.Empty, BindingMode.OneWay, null, ExpressionChanged); + + public string Expression + { + get { return (string)GetValue(ExpressionProperty); } + set { SetValue(ExpressionProperty, value); } + } + + private static void ExpressionChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewAnimation)bindable; + } + + #endregion + + #region FinishedExpressionProperty + + public static readonly BindableProperty FinishedExpressionProperty = BindableProperty.Create(nameof(FinishedExpression), typeof(string), typeof(MultiViewAnimation), string.Empty, BindingMode.OneWay, null, FinishedExpressionChanged); + + public string FinishedExpression + { + get { return (string)GetValue(FinishedExpressionProperty); } + set { SetValue(FinishedExpressionProperty, value); } + } + + private static void FinishedExpressionChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewAnimation)bindable; + } + + #endregion + + } +} diff --git a/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewZero.cs b/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewZero.cs new file mode 100644 index 0000000..84bf5d6 --- /dev/null +++ b/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewZero.cs @@ -0,0 +1,262 @@ +using FunctionZero.ExpressionParserZero.BackingStore; +using FunctionZero.Maui.zBind.z; +using Microsoft.Maui.Layouts; +using System.Diagnostics; +using System.Linq.Expressions; + +namespace FunctionZero.Maui.Controls +{ + public class MultiViewZero : Layout + { + private readonly PocoBackingStore _backingStore; + + private static int _instanceCount = 0; + public MultiViewZero() + { + InAnimations = new List(); + OutAnimations = new List(); + + _backingStore = new PocoBackingStore(this); + _instanceCount++; + + } + protected override ILayoutManager CreateLayoutManager() + { + return new MultiViewZeroLayoutManager(this); + } + + public IView CurrentView { get; private set; } + public IView PreviousView { get; private set; } + + #region InDurationProperty + + public static readonly BindableProperty InDurationProperty = BindableProperty.Create(nameof(InDuration), typeof(uint), typeof(MultiViewZero), (uint)250, BindingMode.OneWay, null, InDurationChanged); + + public uint InDuration + { + get { return (uint)GetValue(InDurationProperty); } + set { SetValue(InDurationProperty, value); } + } + + private static void InDurationChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewZero)bindable; + } + + #endregion + + #region OutDurationProperty + + public static readonly BindableProperty OutDurationProperty = BindableProperty.Create(nameof(OutDuration), typeof(uint), typeof(MultiViewZero), (uint)250, BindingMode.OneWay, null, OutDurationChanged); + + public uint OutDuration + { + get { return (uint)GetValue(OutDurationProperty); } + set { SetValue(OutDurationProperty, value); } + } + + private static void OutDurationChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewZero)bindable; + } + + #endregion + + + public static readonly BindableProperty EaseInProperty = BindableProperty.Create(nameof(EaseIn), typeof(Easing), typeof(MultiViewZero), Easing.Linear, BindingMode.OneWay, null, EaseInChanged); + + private static void EaseInChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewZero)bindable; + } + + public Easing EaseIn + { + get => (Easing)GetValue(EaseInProperty); + set => SetValue(EaseInProperty, value); + } + + public static readonly BindableProperty EaseOutProperty = BindableProperty.Create(nameof(EaseOut), typeof(Easing), typeof(MultiViewZero), Easing.Linear, BindingMode.OneWay, null, EaseOutChanged); + + private static void EaseOutChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewZero)bindable; + } + + public Easing EaseOut + { + get => (Easing)GetValue(EaseOutProperty); + set => SetValue(EaseInProperty, value); + } + + + #region TopViewNameProperty + + public static readonly BindableProperty TopViewNameProperty = BindableProperty.Create(nameof(TopViewName), typeof(string), typeof(MultiViewZero), string.Empty, BindingMode.OneWay, null, TopViewNameChanged); + + public string TopViewName + { + get { return (string)GetValue(TopViewNameProperty); } + set { SetValue(TopViewNameProperty, value); } + } + + private static void TopViewNameChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewZero)bindable; + + // TODO: Find the current view. + + foreach (IView item in self) + { + if (item is View theChildView) + { + var itemName = GetMultiName(theChildView); + if (itemName != null) + { + if (itemName == self.TopViewName) + { + theChildView.IsVisible = true; + self.SetTopView(theChildView); + } + else if (item != self.PreviousView && item != self.CurrentView) + theChildView.IsVisible = false; + } + } + } + } + + private void SetTopView(View theChildView) + { + + PreviousView = CurrentView; + CurrentView = theChildView; + + var ep = ExpressionParserZero.Binding.ExpressionParserFactory.GetExpressionParser(); + + + if (PreviousView is View previousViewAsView) + { + // Workaround for https://github.com/dotnet/maui/issues/18433 + if (previousViewAsView.IsLoaded) + { + previousViewAsView.AbortAnimation("PreviousAnimation"+ _instanceCount.ToString()); + + var a = new Animation(); + + foreach (var anim in OutAnimations) + { + // TODO: Horribly inefficient! + var compiledExpression = ep.Parse(anim.Expression); + var compiledFinishedExpression = ep.Parse(anim.FinishedExpression); + + a.Add(0, 1, new Animation(val => { value = val; compiledExpression.Evaluate(_backingStore); }, anim.From, anim.To, anim.EasingFunc, () => compiledFinishedExpression.Evaluate(_backingStore))); + } + a.Commit(this, "PreviousAnimation" + _instanceCount.ToString(), 16, OutDuration, null, null, () => false); + } + else + { + foreach (var anim in OutAnimations) + { + // TODO: Horribly inefficient! + var compiledExpression = ep.Parse(anim.Expression); + value = 1.0; + compiledExpression.Evaluate(_backingStore); + } + } + } + if (CurrentView is View currentViewAsView) + { + if (currentViewAsView.IsLoaded) + { + currentViewAsView.AbortAnimation("CurrentAnimation" + _instanceCount.ToString()); + + var a = new Animation(); + + foreach (var anim in InAnimations) + { + // TODO: Horribly inefficient! + var compiledExpression = ep.Parse(anim.Expression); + var compiledFinishedExpression = ep.Parse(anim.FinishedExpression); + a.Add(0, 1, new Animation(val => { value = val; compiledExpression.Evaluate(_backingStore); }, anim.From, anim.To, anim.EasingFunc,()=> compiledFinishedExpression.Evaluate(_backingStore))); + } + a.Commit(this, "CurrentAnimation" + _instanceCount.ToString(), 16, InDuration, null, null, () => false); + + } + else + { + foreach (var anim in InAnimations) + { + // TODO: Horribly inefficient! + var compiledExpression = ep.Parse(anim.Expression); + value = 1.0; + compiledExpression.Evaluate(_backingStore); + } + } + } + + // Visibilty has changed, so arrange it, in case it wasn't visible for the initial arrange call. + (this as IView).InvalidateArrange(); + } + + #endregion + + public double value { get; protected set; } + + #region InAnimationsProperty + + public static readonly BindableProperty InAnimationsProperty = BindableProperty.Create(nameof(InAnimations), typeof(IList), typeof(MultiViewZero), null, BindingMode.OneWay, null, InAnimationsChanged); + + public IList InAnimations + { + get { return (IList)GetValue(InAnimationsProperty); } + set { SetValue(InAnimationsProperty, value); } + } + + private static void InAnimationsChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewZero)bindable; + } + + #endregion + + #region OutAnimationsProperty + + public static readonly BindableProperty OutAnimationsProperty = BindableProperty.Create(nameof(OutAnimations), typeof(IList), typeof(MultiViewZero), null, BindingMode.OneWay, null, OutAnimationsChanged); + + public IList OutAnimations + { + get { return (IList)GetValue(OutAnimationsProperty); } + set { SetValue(OutAnimationsProperty, value); } + } + + private static void OutAnimationsChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (MultiViewZero)bindable; + } + + #endregion + + + #region AttachedProperties + + public static readonly BindableProperty MultiNameProperty = + BindableProperty.CreateAttached("MultiName", typeof(string), typeof(MultiViewZero), "", BindingMode.OneWay, null, MultiNamePropertyChanged); + + private static void MultiNamePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + // This is the name assigned to each child. + Debug.WriteLine($"Object: {bindable}, oldValue: {oldValue}, newValue: {newValue}"); + } + + public static string GetMultiName(BindableObject view) + { + return (string)view.GetValue(MultiNameProperty); + } + + public static void SetMultiName(BindableObject view, string value) + { + view.SetValue(MultiNameProperty, value); + } + #endregion + } +} diff --git a/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewZeroLayoutManager.cs b/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewZeroLayoutManager.cs new file mode 100644 index 0000000..c30a62b --- /dev/null +++ b/FunctionZero.Maui.Controls/Controls/MultiView/MultiViewZeroLayoutManager.cs @@ -0,0 +1,46 @@ +using Microsoft.Maui.Layouts; + +namespace FunctionZero.Maui.Controls +{ + internal class MultiViewZeroLayoutManager : ILayoutManager + { + private MultiViewZero _multiView; + + public MultiViewZeroLayoutManager(MultiViewZero multiView) + { + _multiView = multiView; + } + + public Size ArrangeChildren(Rect bounds) + { + var padding = _multiView.Padding; + + double left = padding.Left + bounds.X; + double top = padding.Top + bounds.Y; + double width = bounds.Width - padding.HorizontalThickness; + double height = bounds.Height - padding.VerticalThickness; + + // Place all visible children to fill the bounds. + // Any animations will be done using transforms that affect only the render pass. + var destination = new Rect(left, top, width, height); + + foreach (var item in _multiView.Children) + if (item.Visibility != Visibility.Collapsed) + item.Arrange(destination); + + return new Size(width, height); + } + + public Size Measure(double widthConstraint, double heightConstraint) + { + var padding = _multiView.Padding; + + var size = new Size(widthConstraint - padding.Left - padding.Right, heightConstraint - padding.Top - padding.Bottom); + + foreach (var item in _multiView.Children) + item.Measure(size.Width, size.Height); + + return new Size(widthConstraint - padding.Left - padding.Right, heightConstraint - padding.Top - padding.Bottom); + } + } +} \ No newline at end of file diff --git a/FunctionZero.Maui.Controls/FunctionZero.Maui.Controls.csproj b/FunctionZero.Maui.Controls/FunctionZero.Maui.Controls.csproj index 735375d..58fb644 100644 --- a/FunctionZero.Maui.Controls/FunctionZero.Maui.Controls.csproj +++ b/FunctionZero.Maui.Controls/FunctionZero.Maui.Controls.csproj @@ -57,8 +57,9 @@ - - + + + diff --git a/SampleApp/MauiProgram.cs b/SampleApp/MauiProgram.cs index 183c3f1..142ff21 100644 --- a/SampleApp/MauiProgram.cs +++ b/SampleApp/MauiProgram.cs @@ -4,11 +4,13 @@ using SampleApp.Mvvm.Pages.Expander; using SampleApp.Mvvm.Pages.List; using SampleApp.Mvvm.Pages.Mask; +using SampleApp.Mvvm.Pages.MultiView; using SampleApp.Mvvm.Pages.Tree; using SampleApp.Mvvm.PageViewModels; using SampleApp.Mvvm.PageViewModels.Expander; using SampleApp.Mvvm.PageViewModels.List; using SampleApp.Mvvm.PageViewModels.Mask; +using SampleApp.Mvvm.PageViewModels.MultiView; using SampleApp.Mvvm.PageViewModels.Tree; namespace SampleApp @@ -37,6 +39,9 @@ public static MauiApp CreateMauiApp() .MapVmToView() .MapVmToView() .MapVmToView() + .MapVmToView() + + ; } ) @@ -84,6 +89,8 @@ public static MauiApp CreateMauiApp() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() ; diff --git a/SampleApp/Mvvm/PageViewModels/AppFlyoutPageVm.cs b/SampleApp/Mvvm/PageViewModels/AppFlyoutPageVm.cs index 73bb1bc..7e42948 100644 --- a/SampleApp/Mvvm/PageViewModels/AppFlyoutPageVm.cs +++ b/SampleApp/Mvvm/PageViewModels/AppFlyoutPageVm.cs @@ -3,6 +3,7 @@ using SampleApp.Mvvm.PageViewModels.Expander; using SampleApp.Mvvm.PageViewModels.List; using SampleApp.Mvvm.PageViewModels.Mask; +using SampleApp.Mvvm.PageViewModels.MultiView; using SampleApp.Mvvm.PageViewModels.Tree; using SampleApp.Mvvm.ViewModels; using System; @@ -32,7 +33,9 @@ public enum AppFlyoutItems Jay, ExpanderBar, - ExpanderBarTest + ExpanderBarTest, + + MultiViewModal } @@ -101,6 +104,9 @@ private void ItemTappedCommandExecute(object arg) case AppFlyoutItems.ExpanderBarTest: _pageService.FlyoutController.SetDetailVm(typeof(ExpanderTestPageVm), true); break; + case AppFlyoutItems.MultiViewModal: + _pageService.FlyoutController.SetDetailVm(typeof(MultiViewModalPageVm), true); + break; } } diff --git a/SampleApp/Mvvm/PageViewModels/MultiView/MultiViewModalPageVm.cs b/SampleApp/Mvvm/PageViewModels/MultiView/MultiViewModalPageVm.cs new file mode 100644 index 0000000..712e0a6 --- /dev/null +++ b/SampleApp/Mvvm/PageViewModels/MultiView/MultiViewModalPageVm.cs @@ -0,0 +1,31 @@ +using SampleApp.Mvvm.ViewModels; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SampleApp.Mvvm.PageViewModels.MultiView +{ + public class MultiViewModalPageVm : BasePageVm + { + private string[] _modalViewNames = { "first", "none", "second", "none" }; + private int _modalViewIndex = 0; + private string _topViewName; + + public string TopViewName { get => _topViewName; set => SetProperty(ref _topViewName, value); } + + public MultiViewModalPageVm() + { + this.AddPageTimer(1500, Tick, null, null); + } + + private void Tick(object obj) + { + _modalViewIndex = (_modalViewIndex + 1) % _modalViewNames.Length; + TopViewName = _modalViewNames[_modalViewIndex]; + Debug.WriteLine(_modalViewIndex); + } + } +} diff --git a/SampleApp/Mvvm/Pages/AppFlyoutPage.xaml b/SampleApp/Mvvm/Pages/AppFlyoutPage.xaml index 5624478..69944a2 100644 --- a/SampleApp/Mvvm/Pages/AppFlyoutPage.xaml +++ b/SampleApp/Mvvm/Pages/AppFlyoutPage.xaml @@ -96,6 +96,20 @@ + + + + + + + + + diff --git a/SampleApp/Mvvm/Pages/MultiView/MultiViewModalPage.xaml b/SampleApp/Mvvm/Pages/MultiView/MultiViewModalPage.xaml new file mode 100644 index 0000000..b0fe279 --- /dev/null +++ b/SampleApp/Mvvm/Pages/MultiView/MultiViewModalPage.xaml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleApp/Mvvm/Pages/MultiView/MultiViewModalPage.xaml.cs b/SampleApp/Mvvm/Pages/MultiView/MultiViewModalPage.xaml.cs new file mode 100644 index 0000000..6d35e8e --- /dev/null +++ b/SampleApp/Mvvm/Pages/MultiView/MultiViewModalPage.xaml.cs @@ -0,0 +1,9 @@ +namespace SampleApp.Mvvm.Pages.MultiView; + +public partial class MultiViewModalPage : ContentPage +{ + public MultiViewModalPage() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/SampleApp/SampleApp.csproj b/SampleApp/SampleApp.csproj index c07cb3b..da0e8f7 100644 --- a/SampleApp/SampleApp.csproj +++ b/SampleApp/SampleApp.csproj @@ -53,15 +53,21 @@ - - - + + + + + + MultiViewModalPage.xaml + + + MSBuild:Compile @@ -87,6 +93,9 @@ MSBuild:Compile + + MSBuild:Compile + MSBuild:Compile