Init Xamarin.Forms pages with the browser size

This commit is contained in:
Frank A. Krueger 2017-11-19 14:19:44 -06:00
parent 010d1f75d0
commit 1f1c90c250
9 changed files with 175 additions and 31 deletions

View File

@ -32,19 +32,30 @@ namespace Ooui.AspNetCore
return id; return id;
} }
public static async Task HandleWebSocketRequestAsync (HttpContext context) public static async Task HandleWebSocketRequestAsync (HttpContext context)
{ {
void BadRequest (string message)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
context.Response.ContentType = "text/plain; charset=utf-8";
using (var sw = new System.IO.StreamWriter (context.Response.Body)) {
sw.WriteLine (message);
}
}
// //
// Make sure we get a good ID // Make sure we get a good ID
// //
if (!context.Request.Query.TryGetValue ("id", out var idValues)) { if (!context.Request.Query.TryGetValue ("id", out var idValues)) {
context.Response.StatusCode = StatusCodes.Status400BadRequest; BadRequest ("Missing `id`");
return; return;
} }
var id = idValues.FirstOrDefault (); var id = idValues.LastOrDefault ();
if (id == null || id.Length != 32) { if (id == null || id.Length != 32) {
context.Response.StatusCode = StatusCodes.Status400BadRequest; BadRequest ("Invalid `id`");
return; return;
} }
@ -52,7 +63,7 @@ namespace Ooui.AspNetCore
// Find the pending session // Find the pending session
// //
if (!pendingSessions.TryRemove (id, out var pendingSession)) { if (!pendingSessions.TryRemove (id, out var pendingSession)) {
context.Response.StatusCode = StatusCodes.Status400BadRequest; BadRequest ("Unknown `id`");
return; return;
} }
@ -60,16 +71,32 @@ namespace Ooui.AspNetCore
// Reject the session if it's old // Reject the session if it's old
// //
if ((DateTime.UtcNow - pendingSession.RequestTimeUtc) > SessionTimeout) { if ((DateTime.UtcNow - pendingSession.RequestTimeUtc) > SessionTimeout) {
context.Response.StatusCode = StatusCodes.Status400BadRequest; BadRequest ("Old `id`");
return; return;
} }
//
// Set the element's dimensions
//
if (!context.Request.Query.TryGetValue ("w", out var wValues) || wValues.Count < 1) {
BadRequest ("Missing `w`");
return;
}
if (!context.Request.Query.TryGetValue ("h", out var hValues) || hValues.Count < 1) {
BadRequest ("Missing `h`");
return;
}
if (!double.TryParse (wValues.Last (), out var w))
w = 640;
if (!double.TryParse (hValues.Last (), out var h))
h = 480;
// //
// OK, Run // OK, Run
// //
var token = CancellationToken.None; var token = CancellationToken.None;
var webSocket = await context.WebSockets.AcceptWebSocketAsync ("ooui"); var webSocket = await context.WebSockets.AcceptWebSocketAsync ("ooui");
var session = new Ooui.UI.Session (webSocket, pendingSession.Element, token); var session = new Ooui.UI.Session (webSocket, pendingSession.Element, w, h, token);
await session.RunAsync ().ConfigureAwait (false); await session.RunAsync ().ConfigureAwait (false);
} }

View File

@ -8,15 +8,35 @@ namespace Ooui.Forms.Extensions
public static SizeRequest GetSizeRequest (this Ooui.Element self, double widthConstraint, double heightConstraint, public static SizeRequest GetSizeRequest (this Ooui.Element self, double widthConstraint, double heightConstraint,
double minimumWidth = -1, double minimumHeight = -1) double minimumWidth = -1, double minimumHeight = -1)
{ {
var s = self.Text.MeasureSize (self.Style); var rw = 0.0;
var rh = 0.0;
Size s = new Size (0, 0);
var measured = false;
var request = new Size ( if (self.Style.Width.Equals ("inherit")) {
double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : s.Width, s = self.Text.MeasureSize (self.Style);
double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : s.Height); measured = true;
var minimum = new Size (minimumWidth < 0 ? request.Width : minimumWidth, rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : s.Width;
minimumHeight < 0 ? request.Height : minimumHeight); }
else {
rw = self.Style.GetNumberWithUnits ("width", "px", 640);
}
return new SizeRequest (request, minimum); if (self.Style.Height.Equals ("inherit")) {
if (!measured) {
s = self.Text.MeasureSize (self.Style);
measured = true;
}
rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : s.Height;
}
else {
rh = self.Style.GetNumberWithUnits ("height", "px", 480);
}
var minimum = new Size (minimumWidth < 0 ? rw : minimumWidth,
minimumHeight < 0 ? rh : minimumHeight);
return new SizeRequest (new Size (rw, rh), minimum);
} }
} }
} }

View File

@ -33,8 +33,9 @@ namespace Ooui.Forms
{ {
_renderer = new PlatformRenderer (this); _renderer = new PlatformRenderer (this);
MessagingCenter.Subscribe(this, Page.AlertSignalName, (Page sender, AlertArguments arguments) => _renderer.Style.PropertyChanged += HandleRendererStyle_PropertyChanged;
{
MessagingCenter.Subscribe (this, Page.AlertSignalName, (Page sender, AlertArguments arguments) => {
var alert = new DisplayAlert (arguments); var alert = new DisplayAlert (arguments);
alert.Clicked += CloseAlert; alert.Clicked += CloseAlert;
@ -128,6 +129,12 @@ namespace Ooui.Forms
Console.Error.WriteLine ("Potential view double add"); Console.Error.WriteLine ("Potential view double add");
} }
void HandleRendererStyle_PropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var pageRenderer = GetRenderer (Page);
pageRenderer?.SetElementSize (Ooui.Forms.Extensions.ElementExtensions.GetSizeRequest (_renderer, double.PositiveInfinity, double.PositiveInfinity).Request);
}
void INavigation.InsertPageBefore (Page page, Page before) void INavigation.InsertPageBefore (Page page, Page before)
{ {
throw new NotImplementedException (); throw new NotImplementedException ();

View File

@ -9,6 +9,8 @@ namespace Ooui.Forms
public Platform Platform => platform; public Platform Platform => platform;
public override bool WantsFullScreen => true;
public PlatformRenderer (Platform platform) public PlatformRenderer (Platform platform)
{ {
this.platform = platform; this.platform = platform;

View File

@ -142,10 +142,10 @@ namespace Ooui.Forms
bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged); bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged);
if (shouldUpdate) { if (shouldUpdate) {
uiview.Style.Position = "absolute"; uiview.Style.Position = "absolute";
uiview.Style.Left = x + "px"; uiview.Style.Left = x;
uiview.Style.Top = y + "px"; uiview.Style.Top = y;
uiview.Style.Width = width + "px"; uiview.Style.Width = width;
uiview.Style.Height = height + "px"; uiview.Style.Height = height;
Renderer.SetControlSize (new Size (width, height)); Renderer.SetControlSize (new Size (width, height));
} }
else if (width <= 0 || height <= 0) { else if (width <= 0 || height <= 0) {

View File

@ -18,6 +18,7 @@ const mouseEvents = {
wheel: true, wheel: true,
}; };
// Try to close the socket gracefully
window.onbeforeunload = function() { window.onbeforeunload = function() {
if (socket != null) { if (socket != null) {
socket.close (1001, "Unloading page"); socket.close (1001, "Unloading page");
@ -27,10 +28,22 @@ window.onbeforeunload = function() {
return null; return null;
} }
function getSize () {
return {
height: window.innerHeight,
width: window.innerWidth
};
}
// Main entrypoint
function ooui (rootElementPath) { function ooui (rootElementPath) {
var opened = false; var opened = false;
socket = new WebSocket ("ws://" + document.location.host + rootElementPath, "ooui"); var initialSize = getSize ();
var wsArgs = (rootElementPath.indexOf("?") >= 0 ? "&" : "?") +
"w=" + initialSize.width + "&h=" + initialSize.height;
socket = new WebSocket ("ws://" + document.location.host + rootElementPath + wsArgs, "ooui");
socket.addEventListener ("open", function (event) { socket.addEventListener ("open", function (event) {
console.log ("Web socket opened"); console.log ("Web socket opened");
@ -61,6 +74,34 @@ function ooui (rootElementPath) {
}); });
console.log("Web socket created"); console.log("Web socket created");
// Throttled window resize event
(function() {
window.addEventListener("resize", resizeThrottler, false);
var resizeTimeout;
function resizeThrottler() {
if (!resizeTimeout) {
resizeTimeout = setTimeout(function() {
resizeTimeout = null;
actualResizeHandler();
}, 100);
}
}
function resizeHandler() {
const em = {
m: "event",
id: 42,
k: "window.resize",
v: getSize (),
};
const ems = JSON.stringify (em);
if (socket != null)
socket.send (ems);
if (debug) console.log ("Event", em);
}
}());
} }
function getNode (id) { function getNode (id) {

View File

@ -90,6 +90,12 @@ namespace Ooui
remove => RemoveEventListener ("wheel", value); remove => RemoveEventListener ("wheel", value);
} }
/// <summary>
/// A signal to Ooui that this element should take up the
/// entire browser window.
/// </summary>
public virtual bool WantsFullScreen => false;
protected Element (string tagName) protected Element (string tagName)
: base (tagName) : base (tagName)
{ {

View File

@ -11,6 +11,10 @@ namespace Ooui
readonly Dictionary<string, Value> properties = readonly Dictionary<string, Value> properties =
new Dictionary<string, Value> (); new Dictionary<string, Value> ();
static readonly private char[] numberChars = new char[] {
'0','1','2','3','4','5','6','7','8','9',
'.','-','+',
};
public Value AlignSelf { public Value AlignSelf {
get => this["align-self"]; get => this["align-self"];
@ -126,7 +130,7 @@ namespace Ooui
public Value Bottom { public Value Bottom {
get => this["bottom"]; get => this["bottom"];
set => this["bottom"] = value; set => this["bottom"] = AddNumberUnits (value, "px");
} }
public Value Clear { public Value Clear {
@ -196,12 +200,12 @@ namespace Ooui
public Value Height { public Value Height {
get => this["height"]; get => this["height"];
set => this["height"] = value; set => this["height"] = AddNumberUnits (value, "px");
} }
public Value Left { public Value Left {
get => this["left"]; get => this["left"];
set => this["left"] = value; set => this["left"] = AddNumberUnits (value, "px");
} }
public Value LineHeight { public Value LineHeight {
@ -301,7 +305,7 @@ namespace Ooui
public Value Top { public Value Top {
get => this["top"]; get => this["top"];
set => this["top"] = value; set => this["top"] = AddNumberUnits (value, "px");
} }
public Value Transform { public Value Transform {
@ -326,7 +330,7 @@ namespace Ooui
public Value Width { public Value Width {
get => this["width"]; get => this["width"];
set => this["width"] = value; set => this["width"] = AddNumberUnits (value, "px");
} }
public Value ZIndex { public Value ZIndex {
@ -396,5 +400,34 @@ namespace Ooui
} }
return o.ToString (); return o.ToString ();
} }
static string AddNumberUnits (object val, string units)
{
if (val is string s)
return s;
if (val is IConvertible c)
return c.ToString (System.Globalization.CultureInfo.InvariantCulture) + units;
return val.ToString ();
}
public double GetNumberWithUnits (string key, string units, double baseValue)
{
var v = this[key];
if (v == null)
return 0;
if (v is string s) {
var lastIndex = s.LastIndexOfAny (numberChars);
if (lastIndex < 0)
return 0;
var num = double.Parse (s.Substring (0, lastIndex + 1), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
return num;
}
if (v is IConvertible c)
return c.ToDouble (System.Globalization.CultureInfo.InvariantCulture);
return 0;
}
} }
} }

View File

@ -436,7 +436,7 @@ namespace Ooui
// Create a new session and let it handle everything from here // Create a new session and let it handle everything from here
// //
try { try {
var session = new Session (webSocket, element, serverToken); var session = new Session (webSocket, element, 1024, 768, serverToken);
await session.RunAsync ().ConfigureAwait (false); await session.RunAsync ().ConfigureAwait (false);
} }
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) { catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) {
@ -473,11 +473,15 @@ namespace Ooui
readonly System.Timers.Timer sendThrottle; readonly System.Timers.Timer sendThrottle;
DateTime lastTransmitTime = DateTime.MinValue; DateTime lastTransmitTime = DateTime.MinValue;
readonly TimeSpan throttleInterval = TimeSpan.FromSeconds (1.0 / 30); // 30 FPS max readonly TimeSpan throttleInterval = TimeSpan.FromSeconds (1.0 / 30); // 30 FPS max
readonly double initialWidth;
readonly double initialHeight;
public Session (WebSocket webSocket, Element element, CancellationToken serverToken) public Session (WebSocket webSocket, Element element, double initialWidth, double initialHeight, CancellationToken serverToken)
{ {
this.webSocket = webSocket; this.webSocket = webSocket;
this.element = element; this.element = element;
this.initialWidth = initialWidth;
this.initialHeight = initialHeight;
// //
// Create a new session cancellation token that will trigger // Create a new session cancellation token that will trigger
@ -525,6 +529,10 @@ namespace Ooui
// //
// Add it to the document body // Add it to the document body
// //
if (element.WantsFullScreen) {
element.Style.Width = initialWidth;
element.Style.Height = initialHeight;
}
QueueMessage (Message.Call ("document.body", "appendChild", element)); QueueMessage (Message.Call ("document.body", "appendChild", element));
// //