Merge pull request #168 from limefrogyank/minimal
(mostly) Implementation of NavigationPage
This commit is contained in:
commit
6a2ee75782
|
@ -25,6 +25,7 @@ using Xamarin.Forms;
|
||||||
[assembly: ExportRenderer (typeof (Switch), typeof (SwitchRenderer))]
|
[assembly: ExportRenderer (typeof (Switch), typeof (SwitchRenderer))]
|
||||||
[assembly: ExportRenderer (typeof (TimePicker), typeof (TimePickerRenderer))]
|
[assembly: ExportRenderer (typeof (TimePicker), typeof (TimePickerRenderer))]
|
||||||
[assembly: ExportRenderer (typeof (WebView), typeof (WebViewRenderer))]
|
[assembly: ExportRenderer (typeof (WebView), typeof (WebViewRenderer))]
|
||||||
|
[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer))]
|
||||||
[assembly: ExportImageSourceHandler (typeof (FileImageSource), typeof (FileImageSourceHandler))]
|
[assembly: ExportImageSourceHandler (typeof (FileImageSource), typeof (FileImageSourceHandler))]
|
||||||
[assembly: ExportImageSourceHandler (typeof (StreamImageSource), typeof (StreamImagesourceHandler))]
|
[assembly: ExportImageSourceHandler (typeof (StreamImageSource), typeof (StreamImagesourceHandler))]
|
||||||
[assembly: ExportImageSourceHandler (typeof (UriImageSource), typeof (ImageLoaderSourceHandler))]
|
[assembly: ExportImageSourceHandler (typeof (UriImageSource), typeof (ImageLoaderSourceHandler))]
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Internals;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ooui.Forms.Renderers
|
||||||
|
{
|
||||||
|
public class NavigationPageRenderer : VisualElementRenderer<NavigationPage>
|
||||||
|
{
|
||||||
|
private Stack<DefaultRenderer> backElementStack = new Stack<DefaultRenderer>();
|
||||||
|
private Stack<DefaultRenderer> forwardElementStack = new Stack<DefaultRenderer>();
|
||||||
|
// required hack to make sure a browser nav event (back and forward buttons) don't trigger the commands to pushState or go back on the browser AGAIN
|
||||||
|
bool ignoreNavEventFlag = false;
|
||||||
|
|
||||||
|
private string ns;
|
||||||
|
private string assemblyName;
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
if (e.OldElement != null)
|
||||||
|
{
|
||||||
|
e.OldElement.PushRequested -= OnPushRequested;
|
||||||
|
e.OldElement.PopRequested -= OnPopRequested;
|
||||||
|
e.OldElement.PopToRootRequested -= OnPopToRootRequested;
|
||||||
|
e.OldElement.InternalChildren.CollectionChanged -= OnChildrenChanged;
|
||||||
|
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
|
||||||
|
}
|
||||||
|
if (e.NewElement != null)
|
||||||
|
{
|
||||||
|
e.NewElement.PushRequested += OnPushRequested;
|
||||||
|
e.NewElement.PopRequested += OnPopRequested;
|
||||||
|
e.NewElement.PopToRootRequested += OnPopToRootRequested;
|
||||||
|
e.NewElement.InternalChildren.CollectionChanged += OnChildrenChanged;
|
||||||
|
e.NewElement.PropertyChanged += OnElementPropertyChanged;
|
||||||
|
|
||||||
|
GetAssemblyInfoForRootPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool TriggerEventFromMessage(Message message)
|
||||||
|
{
|
||||||
|
if (message.TargetId == "window" && message.Key == "hashchange" && message.Value is Newtonsoft.Json.Linq.JObject k)
|
||||||
|
{
|
||||||
|
ProcessHash((string)k["hash"]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return base.TriggerEventFromMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
//signaling doesn't seem to work until child has been inserted into document, so wait for this and then adjust the hash for direct navigation
|
||||||
|
protected override void OnChildInsertedBefore(Node newChild, Node referenceChild)
|
||||||
|
{
|
||||||
|
base.OnChildInsertedBefore(newChild, referenceChild);
|
||||||
|
|
||||||
|
var index = this.Children.IndexOf(newChild);
|
||||||
|
if (index - 1 >= 0)
|
||||||
|
(this.Children[index - 1] as Element).Style.Display = "none";
|
||||||
|
|
||||||
|
this.backElementStack.Push(newChild as DefaultRenderer);
|
||||||
|
|
||||||
|
if (this.ignoreNavEventFlag)
|
||||||
|
this.ignoreNavEventFlag = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.forwardElementStack.Clear();
|
||||||
|
this.NativeView.Document.Window.Call("history.pushState", null, null, GenerateFullHash());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnChildRemoved(Node child)
|
||||||
|
{
|
||||||
|
base.OnChildRemoved(child);
|
||||||
|
|
||||||
|
if (this.Children.Count > 0)
|
||||||
|
(this.Children.Last() as Element).Style.Display = "block";
|
||||||
|
|
||||||
|
DefaultRenderer popped = this.backElementStack.Pop();
|
||||||
|
this.forwardElementStack.Push(child as DefaultRenderer);
|
||||||
|
|
||||||
|
if (this.ignoreNavEventFlag)
|
||||||
|
this.ignoreNavEventFlag = false;
|
||||||
|
else
|
||||||
|
this.NativeView.Document.Window.Call("history.back");
|
||||||
|
}
|
||||||
|
|
||||||
|
//only called when user types url manually OR clicks forward or back in the browser OR very beginning of navigation (backStack has first element already though)
|
||||||
|
private void ProcessHash(string fullHash)
|
||||||
|
{
|
||||||
|
IEnumerable<string> browserPageArray;
|
||||||
|
if (fullHash.Length > 0)
|
||||||
|
browserPageArray = fullHash.Split('/').Select(x => x == "#" ? this.backElementStack.Last().Element.GetType().Name : x);
|
||||||
|
else
|
||||||
|
browserPageArray = new string[] { this.backElementStack.Last().Element.GetType().Name };
|
||||||
|
|
||||||
|
// if current hash doesn't match up with the current page displayed (which is the first/most recent one in the backHashStack)
|
||||||
|
if (backElementStack.First().Element.GetType().Name != browserPageArray.Last())
|
||||||
|
{
|
||||||
|
// see if the last hash item is in the backStack (nav backwards) OR in the forward stack (nav forwards) OR not there at all (regenerate stack)
|
||||||
|
var lastPageFromBackStack = backElementStack.FirstOrDefault(x => x.Element.GetType().Name == browserPageArray.Last());//.Cast<(Page page, string hash)?>().FirstOrDefault();
|
||||||
|
var lastPageFromForwardStack = forwardElementStack.FirstOrDefault(x => x.Element.GetType().Name == browserPageArray.Last());//.Cast<(Page page, string hash)?>().FirstOrDefault();
|
||||||
|
|
||||||
|
// just need to adjust internal stack and page view
|
||||||
|
// case clicked back button
|
||||||
|
if (lastPageFromBackStack != null)
|
||||||
|
{
|
||||||
|
var peeked = this.backElementStack.Peek();
|
||||||
|
this.ignoreNavEventFlag = true;
|
||||||
|
this.Element.PopAsync();
|
||||||
|
}
|
||||||
|
// case for clicking forward
|
||||||
|
else if (lastPageFromForwardStack != null)
|
||||||
|
{
|
||||||
|
var popped = this.forwardElementStack.Pop();
|
||||||
|
this.ignoreNavEventFlag = true;
|
||||||
|
|
||||||
|
// *** This should work, but the result is a page that doesn't format correctly. Instead, we'll have to create new pages from scratch.
|
||||||
|
//this.Element.PushAsync(popped.Element as Page);
|
||||||
|
this.Element.PushAsync((Page)Activator.CreateInstance(Type.GetType($"{this.ns}.{popped.Element.GetType().Name}, {this.assemblyName}")));
|
||||||
|
}
|
||||||
|
// case for someone typing in url from scratch with hash
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//assume someone put the url into the browser manually from scratch
|
||||||
|
//first replace the current state with # only.
|
||||||
|
this.Document.Window.Call("history.replaceState", null, null, GenerateFullHash());
|
||||||
|
foreach (var pageName in browserPageArray.Where(x=> x != backElementStack.Last().Element.GetType().Name))
|
||||||
|
{
|
||||||
|
var pageTypeName = WebUtility.HtmlDecode(pageName);
|
||||||
|
var pageInstance = Activator.CreateInstance(Type.GetType($"{this.ns}.{pageTypeName}, {this.assemblyName}"));
|
||||||
|
this.Element.PushAsync(pageInstance as Page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reflection needs more than just the name to create an instance from a string.
|
||||||
|
private void GetAssemblyInfoForRootPage()
|
||||||
|
{
|
||||||
|
if (this.Element.Navigation.NavigationStack.Count > 0)
|
||||||
|
{
|
||||||
|
var page = this.Element.Navigation.NavigationStack.Last();
|
||||||
|
var type = page.GetType();
|
||||||
|
this.ns = type.Namespace;
|
||||||
|
this.assemblyName = type.Assembly.FullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is where you would normally set back button state based on what's in the stack.
|
||||||
|
private void OnChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
// Not needed for browser... back button is always available.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPopToRootRequested(object sender, NavigationRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
e.Realize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPopRequested(object sender, NavigationRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
e.Realize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is where you would draw the new contents.
|
||||||
|
private void OnPushRequested(object sender, NavigationRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
e.Realize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateFullHash()
|
||||||
|
{
|
||||||
|
string hashString = "";
|
||||||
|
bool started = false;
|
||||||
|
foreach (var i in this.backElementStack.Reverse())
|
||||||
|
{
|
||||||
|
if (started)
|
||||||
|
hashString += "/" + i.Element.GetType().Name;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
started = true;
|
||||||
|
hashString += "#";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hashString;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,6 +59,20 @@ function saveSize (s) {
|
||||||
setCookie ("oouiWindowHeight", s.height, 7);
|
setCookie ("oouiWindowHeight", s.height, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initializeNavigation() {
|
||||||
|
monitorHashChanged();
|
||||||
|
const em = {
|
||||||
|
m: "event",
|
||||||
|
id: "window",
|
||||||
|
k: "hashchange",
|
||||||
|
v: window.location
|
||||||
|
};
|
||||||
|
saveSize(em.v);
|
||||||
|
const ems = JSON.stringify(em);
|
||||||
|
send(ems);
|
||||||
|
if (debug) console.log("Event", em);
|
||||||
|
}
|
||||||
|
|
||||||
// Main entrypoint
|
// Main entrypoint
|
||||||
function ooui (rootElementPath) {
|
function ooui (rootElementPath) {
|
||||||
|
|
||||||
|
@ -77,6 +91,7 @@ function ooui (rootElementPath) {
|
||||||
|
|
||||||
socket.addEventListener ("open", function (event) {
|
socket.addEventListener ("open", function (event) {
|
||||||
console.log ("Web socket opened");
|
console.log ("Web socket opened");
|
||||||
|
initializeNavigation();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.addEventListener ("error", function (event) {
|
socket.addEventListener ("error", function (event) {
|
||||||
|
@ -109,9 +124,27 @@ function oouiWasm (mainAsmName, mainNamespace, mainClassName, mainMethodName, as
|
||||||
Module.entryPoint = { "a": mainAsmName, "n": mainNamespace, "t": mainClassName, "m": mainMethodName };
|
Module.entryPoint = { "a": mainAsmName, "n": mainNamespace, "t": mainClassName, "m": mainMethodName };
|
||||||
Module.assemblies = assemblies;
|
Module.assemblies = assemblies;
|
||||||
|
|
||||||
|
initializeNavigation();
|
||||||
monitorSizeChanges (1000/30);
|
monitorSizeChanges (1000/30);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function monitorHashChanged() {
|
||||||
|
function hashChangeHandler() {
|
||||||
|
const em = {
|
||||||
|
m: "event",
|
||||||
|
id: "window",
|
||||||
|
k: "hashchange",
|
||||||
|
v: window.location
|
||||||
|
};
|
||||||
|
saveSize(em.v);
|
||||||
|
const ems = JSON.stringify(em);
|
||||||
|
send(ems);
|
||||||
|
if (debug) console.log("Event", em);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("hashchange", hashChangeHandler, false);
|
||||||
|
}
|
||||||
|
|
||||||
function monitorSizeChanges (millis)
|
function monitorSizeChanges (millis)
|
||||||
{
|
{
|
||||||
var resizeTimeout;
|
var resizeTimeout;
|
||||||
|
@ -213,6 +246,17 @@ function msgRemAttr (m) {
|
||||||
if (debug) console.log ("RemAttr", node, m.k);
|
if (debug) console.log ("RemAttr", node, m.k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCallerProperty(target, accessorStr) {
|
||||||
|
const arr = accessorStr.split('.');
|
||||||
|
var caller = target;
|
||||||
|
var property = target;
|
||||||
|
arr.forEach(function (v) {
|
||||||
|
caller = property;
|
||||||
|
property = caller[v];
|
||||||
|
});
|
||||||
|
return [caller, property];
|
||||||
|
}
|
||||||
|
|
||||||
function msgCall (m) {
|
function msgCall (m) {
|
||||||
const id = m.id;
|
const id = m.id;
|
||||||
const node = getNode (id);
|
const node = getNode (id);
|
||||||
|
@ -227,9 +271,10 @@ function msgCall (m) {
|
||||||
target.removeChild (target.firstChild);
|
target.removeChild (target.firstChild);
|
||||||
delete hasText[id];
|
delete hasText[id];
|
||||||
}
|
}
|
||||||
const f = target[m.k];
|
//const f = target[m.k];
|
||||||
|
const f = getCallerProperty(target, m.k);
|
||||||
if (debug) console.log ("Call", node, f, m.v);
|
if (debug) console.log ("Call", node, f, m.v);
|
||||||
const r = f.apply (target, m.v);
|
const r = f[1].apply (f[0], m.v);
|
||||||
if (typeof m.rid === 'string' || m.rid instanceof String) {
|
if (typeof m.rid === 'string' || m.rid instanceof String) {
|
||||||
nodes[m.rid] = r;
|
nodes[m.rid] = r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Samples.Navigation.NavigationFirstPage"
|
||||||
|
Title="The First Page">
|
||||||
|
<ContentPage.Content>
|
||||||
|
<StackLayout>
|
||||||
|
<Label Text="This is the first page."
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="CenterAndExpand" />
|
||||||
|
<Button Clicked="Button_Clicked"
|
||||||
|
Text="Navigate to Second Page" />
|
||||||
|
</StackLayout>
|
||||||
|
</ContentPage.Content>
|
||||||
|
</ContentPage>
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Xaml;
|
||||||
|
|
||||||
|
namespace Samples.Navigation
|
||||||
|
{
|
||||||
|
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||||
|
public partial class NavigationFirstPage : ContentPage
|
||||||
|
{
|
||||||
|
public NavigationFirstPage ()
|
||||||
|
{
|
||||||
|
InitializeComponent ();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Button_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
(this.Parent as NavigationPage).PushAsync(new Navigation.NavigationSecondPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Samples.Navigation.NavigationSecondPage"
|
||||||
|
Title="The Second Page">
|
||||||
|
<ContentPage.Content>
|
||||||
|
<StackLayout>
|
||||||
|
<Label Text="This is the second page."
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="CenterAndExpand" />
|
||||||
|
<Button Clicked="BackButton_Clicked"
|
||||||
|
Text="Go back to First Page" />
|
||||||
|
<Button Clicked="Button_Clicked"
|
||||||
|
Text="Navigate to Third Page" />
|
||||||
|
|
||||||
|
</StackLayout>
|
||||||
|
</ContentPage.Content>
|
||||||
|
</ContentPage>
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Xaml;
|
||||||
|
|
||||||
|
namespace Samples.Navigation
|
||||||
|
{
|
||||||
|
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||||
|
public partial class NavigationSecondPage : ContentPage
|
||||||
|
{
|
||||||
|
public NavigationSecondPage ()
|
||||||
|
{
|
||||||
|
InitializeComponent ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BackButton_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
(this.Parent as NavigationPage).PopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Button_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
(this.Parent as NavigationPage).PushAsync(new Navigation.NavigationThirdPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Samples.Navigation.NavigationThirdPage"
|
||||||
|
Title="The Third Page">
|
||||||
|
<ContentPage.Content>
|
||||||
|
<StackLayout>
|
||||||
|
<Label Text="This is the third page."
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="CenterAndExpand" />
|
||||||
|
<Button Clicked="BackButton_Clicked"
|
||||||
|
Text="Go back to Second Page" />
|
||||||
|
<Button Clicked="RootButton_Clicked"
|
||||||
|
Text="Go back to Root (1st page)" />
|
||||||
|
</StackLayout>
|
||||||
|
</ContentPage.Content>
|
||||||
|
</ContentPage>
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Xaml;
|
||||||
|
|
||||||
|
namespace Samples.Navigation
|
||||||
|
{
|
||||||
|
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||||
|
public partial class NavigationThirdPage : ContentPage
|
||||||
|
{
|
||||||
|
public NavigationThirdPage ()
|
||||||
|
{
|
||||||
|
InitializeComponent ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BackButton_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
(this.Parent as NavigationPage).PopAsync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RootButton_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
(this.Parent as NavigationPage).PopToRootAsync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
using Ooui;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Samples.Navigation
|
||||||
|
{
|
||||||
|
public class NavigationSample : ISample
|
||||||
|
{
|
||||||
|
public string Title => "Xamarin.Forms Navigation XAML";
|
||||||
|
|
||||||
|
public Ooui.Element CreateElement()
|
||||||
|
{
|
||||||
|
var page = new Navigation.NavigationFirstPage();
|
||||||
|
var root = new NavigationPage(page);
|
||||||
|
return root.GetOouiElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue