зеркало из https://github.com/DeGsoft/maui-linux.git
558 строки
15 KiB
C#
558 строки
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.ComponentModel;
|
|
using Xamarin.Forms.Internals;
|
|
using Xamarin.Forms.PlatformConfiguration.TizenSpecific;
|
|
using ElmSharp;
|
|
using EProgressBar = ElmSharp.ProgressBar;
|
|
using EButton = ElmSharp.Button;
|
|
using EColor = ElmSharp.Color;
|
|
|
|
namespace Xamarin.Forms.Platform.Tizen
|
|
{
|
|
public static class Platform
|
|
{
|
|
internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer", typeof(IVisualElementRenderer), typeof(Platform), default(IVisualElementRenderer),
|
|
propertyChanged: (bindable, oldvalue, newvalue) =>
|
|
{
|
|
var ve = bindable as VisualElement;
|
|
if (ve != null && newvalue == null)
|
|
ve.IsPlatformEnabled = false;
|
|
});
|
|
|
|
public static IVisualElementRenderer GetRenderer(BindableObject bindable)
|
|
{
|
|
return (IVisualElementRenderer)bindable.GetValue(Platform.RendererProperty);
|
|
}
|
|
|
|
public static void SetRenderer(BindableObject bindable, IVisualElementRenderer value)
|
|
{
|
|
bindable.SetValue(Platform.RendererProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the renderer associated with the <c>view</c>. If it doesn't exist, creates a new one.
|
|
/// </summary>
|
|
/// <returns>Renderer associated with the <c>view</c>.</returns>
|
|
/// <param name="element">VisualElement for which the renderer is going to be returned.</param>
|
|
public static IVisualElementRenderer GetOrCreateRenderer(VisualElement element)
|
|
{
|
|
return GetRenderer(element) ?? CreateRenderer(element);
|
|
}
|
|
|
|
internal static IVisualElementRenderer CreateRenderer(VisualElement element)
|
|
{
|
|
IVisualElementRenderer renderer = Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element) ?? new DefaultRenderer();
|
|
renderer.SetElement(element);
|
|
return renderer;
|
|
}
|
|
|
|
internal static ITizenPlatform CreatePlatform(EvasObject parent)
|
|
{
|
|
ITizenPlatform platform;
|
|
if (Forms.Flags.Contains(Flags.LightweightPlatformExperimental))
|
|
{
|
|
platform = new LightweightPlatform(parent);
|
|
}
|
|
else
|
|
{
|
|
platform = new DefaultPlatform(parent);
|
|
}
|
|
return platform;
|
|
}
|
|
|
|
public static SizeRequest GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint)
|
|
{
|
|
widthConstraint = widthConstraint <= -1 ? double.PositiveInfinity : widthConstraint;
|
|
heightConstraint = heightConstraint <= -1 ? double.PositiveInfinity : heightConstraint;
|
|
|
|
double width = !double.IsPositiveInfinity(widthConstraint) ? widthConstraint : Int32.MaxValue;
|
|
double height = !double.IsPositiveInfinity(heightConstraint) ? heightConstraint : Int32.MaxValue;
|
|
|
|
return Platform.GetRenderer(view).GetDesiredSize(width, height);
|
|
}
|
|
}
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public interface ITizenPlatform : IDisposable
|
|
{
|
|
void SetPage(Page page);
|
|
bool SendBackButtonPressed();
|
|
EvasObject GetRootNativeView();
|
|
bool HasAlpha { get; set; }
|
|
event EventHandler<RootNativeViewChangedEventArgs> RootNativeViewChanged;
|
|
}
|
|
|
|
public class RootNativeViewChangedEventArgs : EventArgs
|
|
{
|
|
public RootNativeViewChangedEventArgs(EvasObject view) => RootNativeView = view;
|
|
public EvasObject RootNativeView { get; private set; }
|
|
}
|
|
|
|
public class DefaultPlatform : BindableObject, ITizenPlatform, INavigation
|
|
{
|
|
NavigationModel _navModel = new NavigationModel();
|
|
bool _disposed;
|
|
Native.Dialog _pageBusyDialog;
|
|
int _pageBusyCount;
|
|
readonly Naviframe _internalNaviframe;
|
|
|
|
readonly HashSet<EvasObject> _alerts = new HashSet<EvasObject>();
|
|
|
|
public event EventHandler<RootNativeViewChangedEventArgs> RootNativeViewChanged;
|
|
|
|
internal DefaultPlatform(EvasObject parent)
|
|
{
|
|
Forms.NativeParent = parent;
|
|
_pageBusyCount = 0;
|
|
MessagingCenter.Subscribe<Page, bool>(this, Page.BusySetSignalName, BusySetSignalNameHandler);
|
|
MessagingCenter.Subscribe<Page, AlertArguments>(this, Page.AlertSignalName, AlertSignalNameHandler);
|
|
MessagingCenter.Subscribe<Page, ActionSheetArguments>(this, Page.ActionSheetSignalName, ActionSheetSignalNameHandler);
|
|
|
|
_internalNaviframe = new Naviframe(Forms.NativeParent)
|
|
{
|
|
PreserveContentOnPop = true,
|
|
DefaultBackButtonEnabled = false,
|
|
};
|
|
_internalNaviframe.SetAlignment(-1, -1);
|
|
_internalNaviframe.SetWeight(1.0, 1.0);
|
|
_internalNaviframe.Show();
|
|
_internalNaviframe.AnimationFinished += NaviAnimationFinished;
|
|
}
|
|
|
|
~DefaultPlatform()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
public Page Page { get; private set; }
|
|
|
|
public bool HasAlpha { get; set; }
|
|
|
|
Task CurrentModalNavigationTask { get; set; }
|
|
TaskCompletionSource<bool> CurrentTaskCompletionSource { get; set; }
|
|
IPageController CurrentPageController => _navModel.CurrentPage as IPageController;
|
|
IReadOnlyList<Page> INavigation.ModalStack => _navModel.Modals.ToList();
|
|
IReadOnlyList<Page> INavigation.NavigationStack => new List<Page>();
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
public void SetPage(Page newRoot)
|
|
{
|
|
if (Page != null)
|
|
{
|
|
var copyOfStack = new List<NaviItem>(_internalNaviframe.NavigationStack);
|
|
for (var i = 0; i < copyOfStack.Count; i++)
|
|
{
|
|
copyOfStack[i].Delete();
|
|
}
|
|
foreach (Page page in _navModel.Roots)
|
|
{
|
|
var renderer = Platform.GetRenderer(page);
|
|
renderer?.Dispose();
|
|
}
|
|
_navModel = new NavigationModel();
|
|
}
|
|
|
|
if (newRoot == null)
|
|
return;
|
|
|
|
_navModel.Push(newRoot, null);
|
|
|
|
Page = newRoot;
|
|
|
|
IVisualElementRenderer pageRenderer = Platform.CreateRenderer(Page);
|
|
var naviItem = _internalNaviframe.Push(pageRenderer.NativeView);
|
|
naviItem.TitleBarVisible = false;
|
|
|
|
// Make naviitem transparent if parent window is transparent.
|
|
// Make sure that this is only for _navModel._naviTree. (not for _navModel._modalStack)
|
|
// In addtion, the style of naviItem is only decided before the naviItem pushed into Naviframe. (not on-demand).
|
|
if (HasAlpha)
|
|
{
|
|
naviItem.Style = "default/transparent";
|
|
}
|
|
|
|
((Application)Page.RealParent).NavigationProxy.Inner = this;
|
|
|
|
Device.StartTimer(TimeSpan.Zero, () =>
|
|
{
|
|
CurrentPageController?.SendAppearing();
|
|
return false;
|
|
});
|
|
}
|
|
|
|
public bool SendBackButtonPressed()
|
|
{
|
|
bool handled = false;
|
|
if (_navModel.CurrentPage != null)
|
|
{
|
|
if (CurrentModalNavigationTask != null && !CurrentModalNavigationTask.IsCompleted)
|
|
{
|
|
handled = true;
|
|
}
|
|
else
|
|
{
|
|
handled = _navModel.CurrentPage.SendBackButtonPressed();
|
|
}
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
public EvasObject GetRootNativeView()
|
|
{
|
|
return _internalNaviframe as EvasObject;
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed) return;
|
|
if (disposing)
|
|
{
|
|
MessagingCenter.Unsubscribe<Page, AlertArguments>(this, "Xamarin.SendAlert");
|
|
MessagingCenter.Unsubscribe<Page, bool>(this, "Xamarin.BusySet");
|
|
MessagingCenter.Unsubscribe<Page, ActionSheetArguments>(this, "Xamarin.ShowActionSheet");
|
|
SetPage(null);
|
|
_internalNaviframe.Unrealize();
|
|
}
|
|
_disposed = true;
|
|
}
|
|
|
|
protected override void OnBindingContextChanged()
|
|
{
|
|
BindableObject.SetInheritedBindingContext(Page, base.BindingContext);
|
|
base.OnBindingContextChanged();
|
|
}
|
|
|
|
void INavigation.InsertPageBefore(Page page, Page before)
|
|
{
|
|
throw new InvalidOperationException("InsertPageBefore is not supported globally on Tizen, please use a NavigationPage.");
|
|
}
|
|
|
|
Task<Page> INavigation.PopAsync()
|
|
{
|
|
return ((INavigation)this).PopAsync(true);
|
|
}
|
|
|
|
Task<Page> INavigation.PopAsync(bool animated)
|
|
{
|
|
throw new InvalidOperationException("PopAsync is not supported globally on Tizen, please use a NavigationPage.");
|
|
}
|
|
|
|
Task INavigation.PopToRootAsync()
|
|
{
|
|
return ((INavigation)this).PopToRootAsync(true);
|
|
}
|
|
|
|
Task INavigation.PopToRootAsync(bool animated)
|
|
{
|
|
throw new InvalidOperationException("PopToRootAsync is not supported globally on Tizen, please use a NavigationPage.");
|
|
}
|
|
|
|
Task INavigation.PushAsync(Page root)
|
|
{
|
|
return ((INavigation)this).PushAsync(root, true);
|
|
}
|
|
|
|
Task INavigation.PushAsync(Page root, bool animated)
|
|
{
|
|
throw new InvalidOperationException("PushAsync is not supported globally on Tizen, please use a NavigationPage.");
|
|
}
|
|
|
|
void INavigation.RemovePage(Page page)
|
|
{
|
|
throw new InvalidOperationException("RemovePage is not supported globally on Tizen, please use a NavigationPage.");
|
|
}
|
|
|
|
Task INavigation.PushModalAsync(Page modal)
|
|
{
|
|
return ((INavigation)this).PushModalAsync(modal, true);
|
|
}
|
|
|
|
async Task INavigation.PushModalAsync(Page modal, bool animated)
|
|
{
|
|
var previousPage = CurrentPageController;
|
|
Device.BeginInvokeOnMainThread(() => previousPage?.SendDisappearing());
|
|
|
|
_navModel.PushModal(modal);
|
|
|
|
await PushModalInternal(modal, animated);
|
|
|
|
// Verify that the modal is still on the stack
|
|
if (_navModel.CurrentPage == modal)
|
|
CurrentPageController.SendAppearing();
|
|
}
|
|
|
|
Task<Page> INavigation.PopModalAsync()
|
|
{
|
|
return ((INavigation)this).PopModalAsync(true);
|
|
}
|
|
|
|
async Task<Page> INavigation.PopModalAsync(bool animated)
|
|
{
|
|
Page modal = _navModel.PopModal();
|
|
|
|
IVisualElementRenderer modalRenderer = Platform.GetRenderer(modal);
|
|
if (modalRenderer != null)
|
|
{
|
|
await PopModalInternal(animated);
|
|
modalRenderer.Dispose();
|
|
}
|
|
|
|
CurrentPageController?.SendAppearing();
|
|
return modal;
|
|
}
|
|
|
|
async Task PushModalInternal(Page modal, bool animated)
|
|
{
|
|
TaskCompletionSource<bool> tcs = null;
|
|
if (CurrentModalNavigationTask != null && !CurrentModalNavigationTask.IsCompleted)
|
|
{
|
|
var previousTask = CurrentModalNavigationTask;
|
|
tcs = new TaskCompletionSource<bool>();
|
|
CurrentModalNavigationTask = tcs.Task;
|
|
await previousTask;
|
|
}
|
|
|
|
var after = _internalNaviframe.NavigationStack.LastOrDefault();
|
|
NaviItem pushed = null;
|
|
if (animated || after == null)
|
|
{
|
|
pushed = _internalNaviframe.Push(Platform.GetOrCreateRenderer(modal).NativeView, modal.Title);
|
|
}
|
|
else
|
|
{
|
|
pushed = _internalNaviframe.InsertAfter(after, Platform.GetOrCreateRenderer(modal).NativeView, modal.Title);
|
|
}
|
|
pushed.TitleBarVisible = false;
|
|
|
|
bool shouldWait = animated && after != null;
|
|
await WaitForCompletion(shouldWait, tcs);
|
|
}
|
|
|
|
async Task PopModalInternal(bool animated)
|
|
{
|
|
TaskCompletionSource<bool> tcs = null;
|
|
if (CurrentModalNavigationTask != null && !CurrentModalNavigationTask.IsCompleted)
|
|
{
|
|
var previousTask = CurrentModalNavigationTask;
|
|
tcs = new TaskCompletionSource<bool>();
|
|
CurrentModalNavigationTask = tcs.Task;
|
|
await previousTask;
|
|
}
|
|
|
|
if (animated)
|
|
{
|
|
_internalNaviframe.Pop();
|
|
}
|
|
else
|
|
{
|
|
_internalNaviframe.NavigationStack.LastOrDefault()?.Delete();
|
|
}
|
|
|
|
bool shouldWait = animated && (_internalNaviframe.NavigationStack.Count != 0);
|
|
await WaitForCompletion(shouldWait, tcs);
|
|
}
|
|
|
|
async Task WaitForCompletion(bool shouldWait, TaskCompletionSource<bool> tcs)
|
|
{
|
|
if (shouldWait)
|
|
{
|
|
tcs = tcs ?? new TaskCompletionSource<bool>();
|
|
CurrentTaskCompletionSource = tcs;
|
|
if (CurrentModalNavigationTask == null || CurrentModalNavigationTask.IsCompleted)
|
|
{
|
|
CurrentModalNavigationTask = CurrentTaskCompletionSource.Task;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tcs?.SetResult(true);
|
|
}
|
|
|
|
if (tcs != null)
|
|
await tcs.Task;
|
|
}
|
|
|
|
void NaviAnimationFinished(object sender, EventArgs e)
|
|
{
|
|
var tcs = CurrentTaskCompletionSource;
|
|
CurrentTaskCompletionSource = null;
|
|
tcs?.SetResult(true);
|
|
}
|
|
|
|
void BusySetSignalNameHandler(Page sender, bool enabled)
|
|
{
|
|
// Verify that the page making the request is child of this platform
|
|
if (!PageIsChildOfPlatform(sender))
|
|
return;
|
|
|
|
if (null == _pageBusyDialog)
|
|
{
|
|
_pageBusyDialog = new Native.Dialog(Forms.NativeParent)
|
|
{
|
|
Orientation = PopupOrientation.Center,
|
|
BackgroundColor = EColor.Transparent
|
|
};
|
|
|
|
if (Device.Idiom == TargetIdiom.Phone)
|
|
{
|
|
_pageBusyDialog.SetPartColor("bg_title", EColor.Transparent);
|
|
_pageBusyDialog.SetPartColor("bg_content", EColor.Transparent);
|
|
}
|
|
else if (Device.Idiom == TargetIdiom.Watch)
|
|
{
|
|
_pageBusyDialog.Style = "circle";
|
|
}
|
|
|
|
var activity = new EProgressBar(_pageBusyDialog)
|
|
{
|
|
Style = "process_large",
|
|
IsPulseMode = true,
|
|
};
|
|
activity.PlayPulse();
|
|
activity.Show();
|
|
|
|
_pageBusyDialog.Content = activity;
|
|
|
|
}
|
|
_pageBusyCount = Math.Max(0, enabled ? _pageBusyCount + 1 : _pageBusyCount - 1);
|
|
if (_pageBusyCount > 0)
|
|
{
|
|
_pageBusyDialog.Show();
|
|
}
|
|
else
|
|
{
|
|
_pageBusyDialog.Dismiss();
|
|
_pageBusyDialog = null;
|
|
}
|
|
}
|
|
|
|
void AlertSignalNameHandler(Page sender, AlertArguments arguments)
|
|
{
|
|
// Verify that the page making the request is child of this platform
|
|
if (!PageIsChildOfPlatform(sender))
|
|
return;
|
|
|
|
Native.Dialog alert = Native.Dialog.CreateDialog(Forms.NativeParent, (arguments.Accept != null));
|
|
|
|
alert.Title = arguments.Title;
|
|
var message = arguments.Message.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace(Environment.NewLine, "<br>");
|
|
alert.Message = message;
|
|
|
|
EButton cancel = new EButton(alert) { Text = arguments.Cancel };
|
|
alert.NegativeButton = cancel;
|
|
cancel.Clicked += (s, evt) =>
|
|
{
|
|
arguments.SetResult(false);
|
|
alert.Dismiss();
|
|
};
|
|
|
|
if (arguments.Accept != null)
|
|
{
|
|
EButton ok = new EButton(alert) { Text = arguments.Accept };
|
|
alert.NeutralButton = ok;
|
|
ok.Clicked += (s, evt) =>
|
|
{
|
|
arguments.SetResult(true);
|
|
alert.Dismiss();
|
|
};
|
|
}
|
|
|
|
alert.BackButtonPressed += (s, evt) =>
|
|
{
|
|
arguments.SetResult(false);
|
|
alert.Dismiss();
|
|
};
|
|
|
|
alert.Show();
|
|
_alerts.Add(alert);
|
|
alert.Dismissed += (s, e) => _alerts.Remove(alert);
|
|
}
|
|
|
|
void ActionSheetSignalNameHandler(Page sender, ActionSheetArguments arguments)
|
|
{
|
|
// Verify that the page making the request is child of this platform
|
|
if (!PageIsChildOfPlatform(sender))
|
|
return;
|
|
|
|
Native.Dialog alert = Native.Dialog.CreateDialog(Forms.NativeParent);
|
|
|
|
alert.Title = arguments.Title;
|
|
Box box = new Box(alert);
|
|
|
|
if (null != arguments.Destruction)
|
|
{
|
|
Native.Button destruction = new Native.Button(alert)
|
|
{
|
|
Text = arguments.Destruction,
|
|
Style = ButtonStyle.Text,
|
|
TextColor = EColor.Red,
|
|
AlignmentX = -1
|
|
};
|
|
destruction.Clicked += (s, evt) =>
|
|
{
|
|
arguments.SetResult(arguments.Destruction);
|
|
alert.Dismiss();
|
|
};
|
|
destruction.Show();
|
|
box.PackEnd(destruction);
|
|
}
|
|
|
|
foreach (string buttonName in arguments.Buttons)
|
|
{
|
|
Native.Button button = new Native.Button(alert)
|
|
{
|
|
Text = buttonName,
|
|
Style = ButtonStyle.Text,
|
|
AlignmentX = -1
|
|
};
|
|
button.Clicked += (s, evt) =>
|
|
{
|
|
arguments.SetResult(buttonName);
|
|
alert.Dismiss();
|
|
};
|
|
button.Show();
|
|
box.PackEnd(button);
|
|
}
|
|
|
|
box.Show();
|
|
alert.Content = box;
|
|
|
|
if (null != arguments.Cancel)
|
|
{
|
|
EButton cancel = new EButton(Forms.NativeParent) { Text = arguments.Cancel };
|
|
alert.NegativeButton = cancel;
|
|
cancel.Clicked += (s, evt) =>
|
|
{
|
|
alert.Dismiss();
|
|
};
|
|
}
|
|
|
|
alert.BackButtonPressed += (s, evt) =>
|
|
{
|
|
alert.Dismiss();
|
|
};
|
|
|
|
alert.Show();
|
|
|
|
_alerts.Add(alert);
|
|
alert.Dismissed += (s, e) => _alerts.Remove(alert);
|
|
}
|
|
|
|
bool PageIsChildOfPlatform(Page page)
|
|
{
|
|
while (!Application.IsApplicationOrNull(page.RealParent))
|
|
page = (Page)page.RealParent;
|
|
|
|
return Page == page || _navModel.Roots.Contains(page);
|
|
}
|
|
}
|
|
}
|