diff --git a/Ooui.AspNetCore/WebSocketHandler.cs b/Ooui.AspNetCore/WebSocketHandler.cs index 0c22f74..ce6f417 100644 --- a/Ooui.AspNetCore/WebSocketHandler.cs +++ b/Ooui.AspNetCore/WebSocketHandler.cs @@ -32,19 +32,30 @@ namespace Ooui.AspNetCore return id; } + + 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 // if (!context.Request.Query.TryGetValue ("id", out var idValues)) { - context.Response.StatusCode = StatusCodes.Status400BadRequest; + BadRequest ("Missing `id`"); return; } - var id = idValues.FirstOrDefault (); + var id = idValues.LastOrDefault (); if (id == null || id.Length != 32) { - context.Response.StatusCode = StatusCodes.Status400BadRequest; + BadRequest ("Invalid `id`"); return; } @@ -52,7 +63,7 @@ namespace Ooui.AspNetCore // Find the pending session // if (!pendingSessions.TryRemove (id, out var pendingSession)) { - context.Response.StatusCode = StatusCodes.Status400BadRequest; + BadRequest ("Unknown `id`"); return; } @@ -60,16 +71,32 @@ namespace Ooui.AspNetCore // Reject the session if it's old // if ((DateTime.UtcNow - pendingSession.RequestTimeUtc) > SessionTimeout) { - context.Response.StatusCode = StatusCodes.Status400BadRequest; + BadRequest ("Old `id`"); 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 // var token = CancellationToken.None; 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); } diff --git a/Ooui.Forms/Extensions/ElementExtensions.cs b/Ooui.Forms/Extensions/ElementExtensions.cs index eb64812..50b7cb6 100644 --- a/Ooui.Forms/Extensions/ElementExtensions.cs +++ b/Ooui.Forms/Extensions/ElementExtensions.cs @@ -8,15 +8,35 @@ namespace Ooui.Forms.Extensions public static SizeRequest GetSizeRequest (this Ooui.Element self, double widthConstraint, double heightConstraint, 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 ( - double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : s.Width, - double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : s.Height); - var minimum = new Size (minimumWidth < 0 ? request.Width : minimumWidth, - minimumHeight < 0 ? request.Height : minimumHeight); + if (self.Style.Width.Equals ("inherit")) { + s = self.Text.MeasureSize (self.Style); + measured = true; + rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : s.Width; + } + 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); } } } diff --git a/Ooui.Forms/Platform.cs b/Ooui.Forms/Platform.cs index 5295ac6..938adb2 100644 --- a/Ooui.Forms/Platform.cs +++ b/Ooui.Forms/Platform.cs @@ -33,16 +33,17 @@ namespace Ooui.Forms { _renderer = new PlatformRenderer (this); - MessagingCenter.Subscribe(this, Page.AlertSignalName, (Page sender, AlertArguments arguments) => - { - var alert = new DisplayAlert(arguments); + _renderer.Style.PropertyChanged += HandleRendererStyle_PropertyChanged; + + MessagingCenter.Subscribe (this, Page.AlertSignalName, (Page sender, AlertArguments arguments) => { + var alert = new DisplayAlert (arguments); alert.Clicked += CloseAlert; - _renderer.AppendChild(alert.Element); + _renderer.AppendChild (alert.Element); - void CloseAlert(object s, EventArgs e) + void CloseAlert (object s, EventArgs e) { - _renderer.RemoveChild(alert.Element); + _renderer.RemoveChild (alert.Element); } }); } @@ -128,6 +129,12 @@ namespace Ooui.Forms 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) { throw new NotImplementedException (); diff --git a/Ooui.Forms/PlatformRenderer.cs b/Ooui.Forms/PlatformRenderer.cs index 6e8d939..d555803 100644 --- a/Ooui.Forms/PlatformRenderer.cs +++ b/Ooui.Forms/PlatformRenderer.cs @@ -9,6 +9,8 @@ namespace Ooui.Forms public Platform Platform => platform; + public override bool WantsFullScreen => true; + public PlatformRenderer (Platform platform) { this.platform = platform; diff --git a/Ooui.Forms/VisualElementTracker.cs b/Ooui.Forms/VisualElementTracker.cs index 6efb363..1e570ee 100644 --- a/Ooui.Forms/VisualElementTracker.cs +++ b/Ooui.Forms/VisualElementTracker.cs @@ -142,10 +142,10 @@ namespace Ooui.Forms bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged); if (shouldUpdate) { uiview.Style.Position = "absolute"; - uiview.Style.Left = x + "px"; - uiview.Style.Top = y + "px"; - uiview.Style.Width = width + "px"; - uiview.Style.Height = height + "px"; + uiview.Style.Left = x; + uiview.Style.Top = y; + uiview.Style.Width = width; + uiview.Style.Height = height; Renderer.SetControlSize (new Size (width, height)); } else if (width <= 0 || height <= 0) { diff --git a/Ooui/Client.js b/Ooui/Client.js index 779ca36..108fc35 100644 --- a/Ooui/Client.js +++ b/Ooui/Client.js @@ -18,6 +18,7 @@ const mouseEvents = { wheel: true, }; +// Try to close the socket gracefully window.onbeforeunload = function() { if (socket != null) { socket.close (1001, "Unloading page"); @@ -27,10 +28,22 @@ window.onbeforeunload = function() { return null; } +function getSize () { + return { + height: window.innerHeight, + width: window.innerWidth + }; +} + +// Main entrypoint function ooui (rootElementPath) { 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) { console.log ("Web socket opened"); @@ -61,6 +74,34 @@ function ooui (rootElementPath) { }); 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) { diff --git a/Ooui/Element.cs b/Ooui/Element.cs index 253f57d..8ba85e9 100644 --- a/Ooui/Element.cs +++ b/Ooui/Element.cs @@ -90,6 +90,12 @@ namespace Ooui remove => RemoveEventListener ("wheel", value); } + /// + /// A signal to Ooui that this element should take up the + /// entire browser window. + /// + public virtual bool WantsFullScreen => false; + protected Element (string tagName) : base (tagName) { diff --git a/Ooui/Style.cs b/Ooui/Style.cs index 1a0fedb..cec8577 100644 --- a/Ooui/Style.cs +++ b/Ooui/Style.cs @@ -11,6 +11,10 @@ namespace Ooui readonly Dictionary properties = new Dictionary (); + static readonly private char[] numberChars = new char[] { + '0','1','2','3','4','5','6','7','8','9', + '.','-','+', + }; public Value AlignSelf { get => this["align-self"]; @@ -126,7 +130,7 @@ namespace Ooui public Value Bottom { get => this["bottom"]; - set => this["bottom"] = value; + set => this["bottom"] = AddNumberUnits (value, "px"); } public Value Clear { @@ -196,12 +200,12 @@ namespace Ooui public Value Height { get => this["height"]; - set => this["height"] = value; + set => this["height"] = AddNumberUnits (value, "px"); } public Value Left { get => this["left"]; - set => this["left"] = value; + set => this["left"] = AddNumberUnits (value, "px"); } public Value LineHeight { @@ -301,7 +305,7 @@ namespace Ooui public Value Top { get => this["top"]; - set => this["top"] = value; + set => this["top"] = AddNumberUnits (value, "px"); } public Value Transform { @@ -326,7 +330,7 @@ namespace Ooui public Value Width { get => this["width"]; - set => this["width"] = value; + set => this["width"] = AddNumberUnits (value, "px"); } public Value ZIndex { @@ -396,5 +400,34 @@ namespace Ooui } 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; + } } } diff --git a/Ooui/UI.cs b/Ooui/UI.cs index 079972b..b3a3ac9 100644 --- a/Ooui/UI.cs +++ b/Ooui/UI.cs @@ -436,7 +436,7 @@ namespace Ooui // Create a new session and let it handle everything from here // try { - var session = new Session (webSocket, element, serverToken); + var session = new Session (webSocket, element, 1024, 768, serverToken); await session.RunAsync ().ConfigureAwait (false); } catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) { @@ -473,11 +473,15 @@ namespace Ooui readonly System.Timers.Timer sendThrottle; DateTime lastTransmitTime = DateTime.MinValue; 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.element = element; + this.initialWidth = initialWidth; + this.initialHeight = initialHeight; // // Create a new session cancellation token that will trigger @@ -525,6 +529,10 @@ namespace Ooui // // Add it to the document body // + if (element.WantsFullScreen) { + element.Style.Width = initialWidth; + element.Style.Height = initialHeight; + } QueueMessage (Message.Call ("document.body", "appendChild", element)); //