diff --git a/Documentation/MeasureText.html b/Documentation/MeasureText.html
new file mode 100644
index 0000000..e645b1e
--- /dev/null
+++ b/Documentation/MeasureText.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Ooui.AspNetCore/Ooui.AspNetCore.csproj b/Ooui.AspNetCore/Ooui.AspNetCore.csproj
index bb62fe3..786c6ac 100644
--- a/Ooui.AspNetCore/Ooui.AspNetCore.csproj
+++ b/Ooui.AspNetCore/Ooui.AspNetCore.csproj
@@ -1,6 +1,14 @@
+ 1.0.0
+ praeclarum
+ ASP.NET Core MVC extensions to make working with Ooui easy.
+ Ooui;UI;CrossPlatform;ASP.NET
+ https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png
+ https://github.com/praeclarum/Ooui
+ https://github.com/praeclarum/Ooui/blob/master/LICENSE
+ https://github.com/praeclarum/Ooui.git
netstandard2.0
diff --git a/Ooui.AspNetCore/OouiMiddlewareExtensions.cs b/Ooui.AspNetCore/OouiMiddlewareExtensions.cs
index deaa51e..d07f8e9 100644
--- a/Ooui.AspNetCore/OouiMiddlewareExtensions.cs
+++ b/Ooui.AspNetCore/OouiMiddlewareExtensions.cs
@@ -25,17 +25,27 @@ namespace Microsoft.AspNetCore.Builder
};
app.UseWebSockets (webSocketOptions);
+ Ooui.UI.ServerEnabled = false;
+
app.Use (async (context, next) =>
{
+ var response = context.Response;
+
if (context.Request.Path == jsPath) {
- var response = context.Response;
var clientJsBytes = Ooui.UI.ClientJsBytes;
- response.StatusCode = 200;
- response.ContentLength = clientJsBytes.Length;
- response.ContentType = "application/javascript; charset=utf-8";
- response.Headers.Add ("Cache-Control", "public, max-age=3600");
- using (var s = response.Body) {
- await s.WriteAsync (clientJsBytes, 0, clientJsBytes.Length).ConfigureAwait (false);
+ var clientJsEtag = Ooui.UI.ClientJsEtag;
+ if (context.Request.Headers.TryGetValue ("If-None-Match", out var inms) && inms.Count > 0 && inms[0] == clientJsEtag) {
+ response.StatusCode = 304;
+ }
+ else {
+ response.StatusCode = 200;
+ response.ContentLength = clientJsBytes.Length;
+ response.ContentType = "application/javascript; charset=utf-8";
+ response.Headers.Add ("Cache-Control", "public, max-age=60");
+ response.Headers.Add ("Etag", clientJsEtag);
+ using (var s = response.Body) {
+ await s.WriteAsync (clientJsBytes, 0, clientJsBytes.Length).ConfigureAwait (false);
+ }
}
}
else if (context.Request.Path == WebSocketHandler.WebSocketPath) {
@@ -46,6 +56,21 @@ namespace Microsoft.AspNetCore.Builder
context.Response.StatusCode = 400;
}
}
+ else if (Ooui.UI.TryGetFileContentAtPath (context.Request.Path, out var file)) {
+ if (context.Request.Headers.TryGetValue ("If-None-Match", out var inms) && inms.Count > 0 && inms[0] == file.Etag) {
+ response.StatusCode = 304;
+ }
+ else {
+ response.StatusCode = 200;
+ response.ContentLength = file.Content.Length;
+ response.ContentType = file.ContentType;
+ response.Headers.Add ("Cache-Control", "public, max-age=60");
+ response.Headers.Add ("Etag", file.Etag);
+ using (var s = response.Body) {
+ await s.WriteAsync (file.Content, 0, file.Content.Length).ConfigureAwait (false);
+ }
+ }
+ }
else {
await next ().ConfigureAwait (false);
}
diff --git a/Ooui.AspNetCore/WebSocketHandler.cs b/Ooui.AspNetCore/WebSocketHandler.cs
index ce6f417..a483e01 100644
--- a/Ooui.AspNetCore/WebSocketHandler.cs
+++ b/Ooui.AspNetCore/WebSocketHandler.cs
@@ -86,9 +86,10 @@ namespace Ooui.AspNetCore
BadRequest ("Missing `h`");
return;
}
- if (!double.TryParse (wValues.Last (), out var w))
+ var icult = System.Globalization.CultureInfo.InvariantCulture;
+ if (!double.TryParse (wValues.Last (), System.Globalization.NumberStyles.Any, icult, out var w))
w = 640;
- if (!double.TryParse (hValues.Last (), out var h))
+ if (!double.TryParse (hValues.Last (), System.Globalization.NumberStyles.Any, icult, out var h))
h = 480;
//
diff --git a/Ooui.Forms/DisplayAlert.cs b/Ooui.Forms/DisplayAlert.cs
index d5f22f5..fae811a 100644
--- a/Ooui.Forms/DisplayAlert.cs
+++ b/Ooui.Forms/DisplayAlert.cs
@@ -59,7 +59,7 @@ namespace Ooui.Forms
{
ClassName = "btn btn-default"
};
- _cancelButton.Clicked += (s, e) => SetResult(false);
+ _cancelButton.Click += (s, e) => SetResult(false);
footer.AppendChild(_cancelButton);
@@ -70,7 +70,7 @@ namespace Ooui.Forms
ClassName = "btn btn-default"
};
- _acceptButton.Clicked += (s, e) => SetResult(true);
+ _acceptButton.Click += (s, e) => SetResult(true);
footer.AppendChild(_acceptButton);
}
@@ -90,23 +90,23 @@ namespace Ooui.Forms
{
add
{
- _closeButton.Clicked += value;
+ _closeButton.Click += value;
if(_cancelButton != null)
- _cancelButton.Clicked += value;
+ _cancelButton.Click += value;
if(_acceptButton != null)
- _acceptButton.Clicked += value;
+ _acceptButton.Click += value;
}
remove
{
- _closeButton.Clicked -= value;
+ _closeButton.Click -= value;
if (_cancelButton != null)
- _cancelButton.Clicked -= value;
+ _cancelButton.Click -= value;
if (_acceptButton != null)
- _acceptButton.Clicked -= value;
+ _acceptButton.Click -= value;
}
}
public Element Element { get; private set; }
diff --git a/Ooui.Forms/EventTracker.cs b/Ooui.Forms/EventTracker.cs
new file mode 100644
index 0000000..afa79f0
--- /dev/null
+++ b/Ooui.Forms/EventTracker.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using Xamarin.Forms;
+
+using NativeView = Ooui.Element;
+
+namespace Ooui.Forms
+{
+ public class EventTracker
+ {
+ readonly NotifyCollectionChangedEventHandler _collectionChangedHandler;
+
+ readonly Dictionary _gestureRecognizers = new Dictionary ();
+
+ readonly IVisualElementRenderer _renderer;
+ bool _disposed;
+ NativeView _handler;
+
+ public EventTracker (IVisualElementRenderer renderer)
+ {
+ if (renderer == null)
+ throw new ArgumentNullException (nameof (renderer));
+
+ _collectionChangedHandler = ModelGestureRecognizersOnCollectionChanged;
+
+ _renderer = renderer;
+ _renderer.ElementChanged += OnElementChanged;
+ }
+
+ ObservableCollection ElementGestureRecognizers {
+ get {
+ if (_renderer?.Element is View)
+ return ((View)_renderer.Element).GestureRecognizers as ObservableCollection;
+ return null;
+ }
+ }
+
+ public void Dispose ()
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ foreach (var kvp in _gestureRecognizers) {
+ RemoveGestureRecognizer (_handler, kvp.Value);
+ kvp.Value.Dispose ();
+ }
+
+ _gestureRecognizers.Clear ();
+
+ if (ElementGestureRecognizers != null)
+ ElementGestureRecognizers.CollectionChanged -= _collectionChangedHandler;
+
+ _handler = null;
+ }
+
+ void ModelGestureRecognizersOnCollectionChanged (object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+ {
+ LoadRecognizers ();
+ }
+
+ void OnElementChanged (object sender, VisualElementChangedEventArgs e)
+ {
+ if (e.OldElement != null) {
+ // unhook
+ var oldView = e.OldElement as View;
+ if (oldView != null) {
+ var oldRecognizers = (ObservableCollection)oldView.GestureRecognizers;
+ oldRecognizers.CollectionChanged -= _collectionChangedHandler;
+ }
+ }
+
+ if (e.NewElement != null) {
+ // hook
+ if (ElementGestureRecognizers != null) {
+ ElementGestureRecognizers.CollectionChanged += _collectionChangedHandler;
+ LoadRecognizers ();
+ }
+ }
+ }
+
+ void LoadRecognizers ()
+ {
+ if (ElementGestureRecognizers == null)
+ return;
+
+ foreach (var recognizer in ElementGestureRecognizers) {
+ if (_gestureRecognizers.ContainsKey (recognizer))
+ continue;
+
+ var nativeRecognizer = GetNativeRecognizer (recognizer);
+ if (nativeRecognizer != null) {
+ AddGestureRecognizer (_handler, nativeRecognizer);
+
+ _gestureRecognizers[recognizer] = nativeRecognizer;
+ }
+ }
+
+ var toRemove = _gestureRecognizers.Keys.Where (key => !ElementGestureRecognizers.Contains (key)).ToArray ();
+ foreach (var gestureRecognizer in toRemove) {
+ var uiRecognizer = _gestureRecognizers[gestureRecognizer];
+ _gestureRecognizers.Remove (gestureRecognizer);
+
+ RemoveGestureRecognizer (_handler, uiRecognizer);
+ uiRecognizer.Dispose ();
+ }
+ }
+
+ protected virtual NativeGestureRecognizer GetNativeRecognizer (IGestureRecognizer recognizer)
+ {
+ if (recognizer == null)
+ return null;
+
+ var weakRecognizer = new WeakReference (recognizer);
+ var weakEventTracker = new WeakReference (this);
+
+ var tapRecognizer = recognizer as TapGestureRecognizer;
+ if (tapRecognizer != null && tapRecognizer.NumberOfTapsRequired == 1) {
+ var returnAction = new TargetEventHandler ((s, e) => {
+ var tapGestureRecognizer = weakRecognizer.Target as TapGestureRecognizer;
+ var eventTracker = weakEventTracker.Target as EventTracker;
+ var view = eventTracker?._renderer?.Element as View;
+
+ if (tapGestureRecognizer != null && view != null)
+ tapGestureRecognizer.SendTapped (view);
+ });
+ var uiRecognizer = new NativeGestureRecognizer {
+ EventType = "click",
+ Handler = returnAction,
+ };
+ return uiRecognizer;
+ }
+ if (tapRecognizer != null && tapRecognizer.NumberOfTapsRequired == 2) {
+ var returnAction = new TargetEventHandler ((s, e) => {
+ var tapGestureRecognizer = weakRecognizer.Target as TapGestureRecognizer;
+ var eventTracker = weakEventTracker.Target as EventTracker;
+ var view = eventTracker?._renderer?.Element as View;
+
+ if (tapGestureRecognizer != null && view != null)
+ tapGestureRecognizer.SendTapped (view);
+ });
+ var uiRecognizer = new NativeGestureRecognizer {
+ EventType = "dblclick",
+ Handler = returnAction,
+ };
+ return uiRecognizer;
+ }
+
+ return null;
+ }
+
+ static void AddGestureRecognizer (Element element, NativeGestureRecognizer recognizer)
+ {
+ element.AddEventListener (recognizer.EventType, recognizer.Handler);
+ }
+
+ static void RemoveGestureRecognizer (Element element, NativeGestureRecognizer recognizer)
+ {
+ element.RemoveEventListener (recognizer.EventType, recognizer.Handler);
+ }
+
+ public void LoadEvents (NativeView handler)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (null);
+
+ _handler = handler;
+ OnElementChanged (this, new VisualElementChangedEventArgs (null, _renderer.Element));
+ }
+
+ protected class NativeGestureRecognizer : IDisposable
+ {
+ public string EventType;
+ public TargetEventHandler Handler;
+ public void Dispose ()
+ {
+ }
+ }
+ }
+}
diff --git a/Ooui.Forms/Exports.cs b/Ooui.Forms/Exports.cs
index c823d1b..b093983 100644
--- a/Ooui.Forms/Exports.cs
+++ b/Ooui.Forms/Exports.cs
@@ -11,8 +11,14 @@ using Xamarin.Forms.Internals;
[assembly: ExportRenderer (typeof (DatePicker), typeof (DatePickerRenderer))]
[assembly: ExportRenderer (typeof (Editor), typeof (EditorRenderer))]
[assembly: ExportRenderer (typeof (Entry), typeof (EntryRenderer))]
+[assembly: ExportRenderer (typeof (Frame), typeof (FrameRenderer))]
+[assembly: ExportRenderer (typeof (Image), typeof (ImageRenderer))]
[assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))]
[assembly: ExportRenderer (typeof (ProgressBar), typeof (ProgressBarRenderer))]
+[assembly: ExportRenderer (typeof (Switch), typeof (SwitchRenderer))]
+[assembly: ExportImageSourceHandler (typeof (FileImageSource), typeof (FileImageSourceHandler))]
+[assembly: ExportImageSourceHandler (typeof (StreamImageSource), typeof (StreamImagesourceHandler))]
+[assembly: ExportImageSourceHandler (typeof (UriImageSource), typeof (ImageLoaderSourceHandler))]
namespace Ooui.Forms
{
@@ -24,4 +30,13 @@ namespace Ooui.Forms
{
}
}
+
+ [AttributeUsage (AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute
+ {
+ public ExportImageSourceHandlerAttribute (Type handler, Type target)
+ : base (handler, target)
+ {
+ }
+ }
}
diff --git a/Ooui.Forms/Extensions/ElementExtensions.cs b/Ooui.Forms/Extensions/ElementExtensions.cs
index 50b7cb6..53c1366 100644
--- a/Ooui.Forms/Extensions/ElementExtensions.cs
+++ b/Ooui.Forms/Extensions/ElementExtensions.cs
@@ -16,7 +16,7 @@ namespace Ooui.Forms.Extensions
if (self.Style.Width.Equals ("inherit")) {
s = self.Text.MeasureSize (self.Style);
measured = true;
- rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : s.Width;
+ rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : Math.Ceiling (s.Width);
}
else {
rw = self.Style.GetNumberWithUnits ("width", "px", 640);
@@ -27,7 +27,7 @@ namespace Ooui.Forms.Extensions
s = self.Text.MeasureSize (self.Style);
measured = true;
}
- rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : s.Height;
+ rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : Math.Ceiling (s.Height * 1.4);
}
else {
rh = self.Style.GetNumberWithUnits ("height", "px", 480);
diff --git a/Ooui.Forms/Extensions/FontExtensions.cs b/Ooui.Forms/Extensions/FontExtensions.cs
index 310ece6..f364559 100644
--- a/Ooui.Forms/Extensions/FontExtensions.cs
+++ b/Ooui.Forms/Extensions/FontExtensions.cs
@@ -44,9 +44,23 @@ namespace Ooui.Forms.Extensions
return Size.Zero;
var fontHeight = fontSize;
- var charWidth = fontSize * 0.5;
- var width = text.Length * charWidth;
+ var isBold = fontAttrs.HasFlag (FontAttributes.Bold);
+
+ var props = isBold ? BoldCharacterProportions : CharacterProportions;
+ var avgp = isBold ? BoldAverageCharProportion : AverageCharProportion;
+
+ var pwidth = 1.0e-6; // Tiny little padding to account for sampling errors
+ for (var i = 0; i < text.Length; i++) {
+ var c = (int)text[i];
+ if (c < 128) {
+ pwidth += props[c];
+ }
+ else {
+ pwidth += avgp;
+ }
+ }
+ var width = fontSize * pwidth;
return new Size (width, fontHeight);
}
@@ -69,5 +83,57 @@ namespace Ooui.Forms.Extensions
}
}
+ public static string ToOouiVerticalAlign (this TextAlignment align)
+ {
+ switch (align) {
+ case TextAlignment.Start:
+ default:
+ return "top";
+ case TextAlignment.Center:
+ return "middle";
+ case TextAlignment.End:
+ return "bottom";
+ }
+ }
+
+ static readonly double[] CharacterProportions = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0.27799999713897705, 0.25899994373321533, 0.4259999990463257, 0.5560001134872437, 0.5560001134872437, 1.0000001192092896, 0.6299999952316284, 0.27799999713897705,
+ 0.25899994373321533, 0.25899994373321533, 0.3520001173019409, 0.6000000238418579, 0.27799999713897705, 0.3890000581741333, 0.27799999713897705, 0.3330000638961792,
+ 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437,
+ 0.5560001134872437, 0.5560001134872437, 0.27799999713897705, 0.27799999713897705, 0.6000000238418579, 0.6000000238418579, 0.6000000238418579, 0.5560001134872437,
+ 0.8000000715255737, 0.6480001211166382, 0.6850000619888306, 0.722000002861023, 0.7040001153945923, 0.6110001802444458, 0.5740000009536743, 0.7589999437332153,
+ 0.722000002861023, 0.25899994373321533, 0.5190001726150513, 0.6669999361038208, 0.5560001134872437, 0.8709999322891235, 0.722000002861023, 0.7600001096725464,
+ 0.6480001211166382, 0.7600001096725464, 0.6850000619888306, 0.6480001211166382, 0.5740000009536743, 0.722000002861023, 0.6110001802444458, 0.9259999990463257,
+ 0.6110001802444458, 0.6480001211166382, 0.6110001802444458, 0.25899994373321533, 0.3330000638961792, 0.25899994373321533, 0.6000000238418579, 0.5000001192092896,
+ 0.22200000286102295, 0.5370000600814819, 0.593000054359436, 0.5370000600814819, 0.593000054359436, 0.5370000600814819, 0.2960001230239868, 0.5740000009536743,
+ 0.5560001134872437, 0.22200000286102295, 0.22200000286102295, 0.5190001726150513, 0.22200000286102295, 0.8530000448226929, 0.5560001134872437, 0.5740000009536743,
+ 0.593000054359436, 0.593000054359436, 0.3330000638961792, 0.5000001192092896, 0.31500017642974854, 0.5560001134872437, 0.5000001192092896, 0.7580000162124634,
+ 0.5180000066757202, 0.5000001192092896, 0.4800001382827759, 0.3330000638961792, 0.22200000286102295, 0.3330000638961792, 0.6000000238418579, 0
+ };
+ const double AverageCharProportion = 0.5131400561332703;
+
+ static readonly double[] BoldCharacterProportions = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0.27799999713897705, 0.27799999713897705, 0.46299993991851807, 0.5560001134872437, 0.5560001134872437, 1.0000001192092896, 0.6850000619888306, 0.27799999713897705,
+ 0.2960001230239868, 0.2960001230239868, 0.40700018405914307, 0.6000000238418579, 0.27799999713897705, 0.40700018405914307, 0.27799999713897705, 0.37099993228912354,
+ 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437,
+ 0.5560001134872437, 0.5560001134872437, 0.27799999713897705, 0.27799999713897705, 0.6000000238418579, 0.6000000238418579, 0.6000000238418579, 0.5560001134872437,
+ 0.8000000715255737, 0.6850000619888306, 0.7040001153945923, 0.7410000562667847, 0.7410000562667847, 0.6480001211166382, 0.593000054359436, 0.7589999437332153,
+ 0.7410000562667847, 0.29499995708465576, 0.5560001134872437, 0.722000002861023, 0.593000054359436, 0.9070001840591431, 0.7410000562667847, 0.777999997138977,
+ 0.6669999361038208, 0.777999997138977, 0.722000002861023, 0.6490000486373901, 0.6110001802444458, 0.7410000562667847, 0.6299999952316284, 0.9440001249313354,
+ 0.6669999361038208, 0.6669999361038208, 0.6480001211166382, 0.3330000638961792, 0.37099993228912354, 0.3330000638961792, 0.6000000238418579, 0.5000001192092896,
+ 0.25899994373321533, 0.5740000009536743, 0.6110001802444458, 0.5740000009536743, 0.6110001802444458, 0.5740000009536743, 0.3330000638961792, 0.6110001802444458,
+ 0.593000054359436, 0.2580000162124634, 0.27799999713897705, 0.5740000009536743, 0.2580000162124634, 0.906000018119812, 0.593000054359436, 0.6110001802444458,
+ 0.6110001802444458, 0.6110001802444458, 0.3890000581741333, 0.5370000600814819, 0.3520001173019409, 0.593000054359436, 0.5200001001358032, 0.8140000104904175,
+ 0.5370000600814819, 0.5190001726150513, 0.5190001726150513, 0.3330000638961792, 0.223000168800354, 0.3330000638961792, 0.6000000238418579, 0
+ };
+ const double BoldAverageCharProportion = 0.5346300601959229;
}
}
diff --git a/Ooui.Forms/Forms.cs b/Ooui.Forms/Forms.cs
index c5a39f7..94bc20e 100644
--- a/Ooui.Forms/Forms.cs
+++ b/Ooui.Forms/Forms.cs
@@ -26,13 +26,13 @@ namespace Xamarin.Forms
Device.SetIdiom (TargetIdiom.Desktop);
Device.PlatformServices = new OouiPlatformServices ();
Device.Info = new OouiDeviceInfo ();
- Color.SetAccent (Color.FromHex ("#0000EE")); // Safari Blue
+ Color.SetAccent (Color.FromHex ("#337ab7")); // Bootstrap Blue
Registrar.RegisterAll (new[] {
typeof(ExportRendererAttribute),
- //typeof(ExportCellAttribute),
- //typeof(ExportImageSourceHandlerAttribute),
- });
+ //typeof(ExportCellAttribute),
+ typeof(ExportImageSourceHandlerAttribute),
+ });
}
public static event EventHandler ViewInitialized;
@@ -62,11 +62,6 @@ namespace Xamarin.Forms
Task.Run (action);
}
- public Ticker CreateTicker ()
- {
- throw new NotImplementedException ();
- }
-
public Assembly[] GetAssemblies ()
{
return AppDomain.CurrentDomain.GetAssemblies ();
@@ -119,6 +114,31 @@ namespace Xamarin.Forms
}
}), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds);
}
+
+ public Ticker CreateTicker ()
+ {
+ return new OouiTicker ();
+ }
+
+ class OouiTicker : Ticker
+ {
+ Timer timer;
+ protected override void DisableTimer ()
+ {
+ var t = timer;
+ timer = null;
+ t?.Dispose ();
+ }
+ protected override void EnableTimer ()
+ {
+ if (timer != null)
+ return;
+ var interval = TimeSpan.FromSeconds (1.0 / Ooui.UI.Session.MaxFps);
+ timer = new Timer ((_ => {
+ this.SendSignals ();
+ }), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds);
+ }
+ }
}
public class ViewInitializedEventArgs
diff --git a/Ooui.Forms/Ooui.Forms.csproj b/Ooui.Forms/Ooui.Forms.csproj
index e081c10..8bcdc99 100644
--- a/Ooui.Forms/Ooui.Forms.csproj
+++ b/Ooui.Forms/Ooui.Forms.csproj
@@ -1,6 +1,14 @@
+ 1.0.0
+ praeclarum
+ Xamarin.Forms backend for the web using Ooui technologies.
+ Ooui;UI;CrossPlatform;Xamarin.Forms
+ https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png
+ https://github.com/praeclarum/Ooui
+ https://github.com/praeclarum/Ooui/blob/master/LICENSE
+ https://github.com/praeclarum/Ooui.git
netstandard2.0
diff --git a/Ooui.Forms/Platform.cs b/Ooui.Forms/Platform.cs
index 938adb2..96197cb 100644
--- a/Ooui.Forms/Platform.cs
+++ b/Ooui.Forms/Platform.cs
@@ -57,6 +57,10 @@ namespace Ooui.Forms
MessagingCenter.Unsubscribe (this, Page.ActionSheetSignalName);
MessagingCenter.Unsubscribe (this, Page.AlertSignalName);
MessagingCenter.Unsubscribe (this, Page.BusySetSignalName);
+
+ DisposeModelAndChildrenRenderers (Page);
+ //foreach (var modal in _modals)
+ //DisposeModelAndChildrenRenderers (modal);
}
public static IVisualElementRenderer CreateRenderer (VisualElement element)
@@ -110,7 +114,29 @@ namespace Ooui.Forms
void HandleChildRemoved (object sender, ElementEventArgs e)
{
- throw new NotImplementedException ();
+ var view = e.Element;
+ DisposeModelAndChildrenRenderers (view);
+ }
+
+ void DisposeModelAndChildrenRenderers (Xamarin.Forms.Element view)
+ {
+ IVisualElementRenderer renderer;
+ foreach (VisualElement child in view.Descendants ()) {
+ renderer = GetRenderer (child);
+ child.ClearValue (RendererProperty);
+
+ if (renderer != null) {
+ //renderer.NativeView.RemoveFromSuperview ();
+ renderer.Dispose ();
+ }
+ }
+
+ renderer = GetRenderer ((VisualElement)view);
+ if (renderer != null) {
+ //renderer.NativeView.RemoveFromSuperview ();
+ renderer.Dispose ();
+ }
+ view.ClearValue (RendererProperty);
}
void AddChild (VisualElement view)
diff --git a/Ooui.Forms/PlatformRenderer.cs b/Ooui.Forms/PlatformRenderer.cs
index d555803..922d171 100644
--- a/Ooui.Forms/PlatformRenderer.cs
+++ b/Ooui.Forms/PlatformRenderer.cs
@@ -15,5 +15,19 @@ namespace Ooui.Forms
{
this.platform = platform;
}
+
+ protected override bool TriggerEventFromMessage (Message message)
+ {
+ if (message.TargetId == "window" && message.Key == "resize" && message.Value is Newtonsoft.Json.Linq.JObject j) {
+ var width = (double)j["width"];
+ var height = (double)j["height"];
+ Platform.Element.Style.Width = width;
+ Platform.Element.Style.Height = height;
+ return true;
+ }
+ else {
+ return base.TriggerEventFromMessage (message);
+ }
+ }
}
}
diff --git a/Ooui.Forms/Renderers/ButtonRenderer.cs b/Ooui.Forms/Renderers/ButtonRenderer.cs
index 727d835..04f30b2 100644
--- a/Ooui.Forms/Renderers/ButtonRenderer.cs
+++ b/Ooui.Forms/Renderers/ButtonRenderer.cs
@@ -22,7 +22,7 @@ namespace Ooui.Forms.Renderers
protected override void Dispose (bool disposing)
{
if (Control != null) {
- Control.Clicked -= OnButtonTouchUpInside;
+ Control.Click -= OnButtonTouchUpInside;
}
base.Dispose (disposing);
@@ -44,7 +44,7 @@ namespace Ooui.Forms.Renderers
_buttonTextColorDefaultHighlighted = Ooui.Colors.Black;
_buttonTextColorDefaultDisabled = Ooui.Colors.Black;
- Control.Clicked += OnButtonTouchUpInside;
+ Control.Click += OnButtonTouchUpInside;
}
UpdateText ();
diff --git a/Ooui.Forms/Renderers/DatePickerRenderer.cs b/Ooui.Forms/Renderers/DatePickerRenderer.cs
index 79bb422..3df8d05 100644
--- a/Ooui.Forms/Renderers/DatePickerRenderer.cs
+++ b/Ooui.Forms/Renderers/DatePickerRenderer.cs
@@ -31,8 +31,8 @@ namespace Ooui.Forms.Renderers
Type = InputType.Date,
};
- entry.Inputted += OnStarted;
- entry.Changed += OnEnded;
+ //entry.Input += OnStarted;
+ entry.Change += OnEnded;
SetNativeControl (entry);
}
@@ -100,8 +100,8 @@ namespace Ooui.Forms.Renderers
if (disposing) {
if (Control != null) {
- Control.Inputted -= OnStarted;
- Control.Changed -= OnEnded;
+ //Control.Input -= OnStarted;
+ Control.Change -= OnEnded;
}
}
diff --git a/Ooui.Forms/Renderers/EditorRenderer.cs b/Ooui.Forms/Renderers/EditorRenderer.cs
index 880c51e..84a8f60 100644
--- a/Ooui.Forms/Renderers/EditorRenderer.cs
+++ b/Ooui.Forms/Renderers/EditorRenderer.cs
@@ -10,6 +10,12 @@ namespace Ooui.Forms.Renderers
bool _disposed;
IEditorController ElementController => Element;
+ public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
+ {
+ var size = new Size (160, 100);
+ return new SizeRequest (size, size);
+ }
+
protected override void Dispose (bool disposing)
{
if (_disposed)
@@ -19,9 +25,9 @@ namespace Ooui.Forms.Renderers
if (disposing) {
if (Control != null) {
- Control.Changed -= HandleChanged;
+ Control.Input -= HandleChanged;
//Control.Started -= OnStarted;
- //Control.Ended -= OnEnded;
+ Control.Change -= OnEnded;
}
}
@@ -40,9 +46,9 @@ namespace Ooui.Forms.Renderers
ClassName = "form-control"
});
- Control.Changed += HandleChanged;
+ Control.Input += HandleChanged;
//Control.Started += OnStarted;
- //Control.Ended += OnEnded;
+ Control.Change += OnEnded;
}
UpdateText ();
@@ -75,13 +81,13 @@ namespace Ooui.Forms.Renderers
void HandleChanged (object sender, EventArgs e)
{
- ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Text);
+ ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Value);
}
void OnEnded (object sender, EventArgs eventArgs)
{
- if (Control.Text != Element.Text)
- ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Text);
+ if (Control.Value != Element.Text)
+ ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Value);
Element.SetValue (VisualElement.IsFocusedPropertyKey, false);
ElementController.SendCompleted ();
@@ -108,8 +114,8 @@ namespace Ooui.Forms.Renderers
void UpdateText ()
{
- if (Control.Text != Element.Text)
- Control.Text = Element.Text;
+ if (Control.Value != Element.Text)
+ Control.Value = Element.Text;
}
void UpdateTextAlignment ()
diff --git a/Ooui.Forms/Renderers/EntryRenderer.cs b/Ooui.Forms/Renderers/EntryRenderer.cs
index b4e5dfd..4e54dbd 100644
--- a/Ooui.Forms/Renderers/EntryRenderer.cs
+++ b/Ooui.Forms/Renderers/EntryRenderer.cs
@@ -6,7 +6,7 @@ using Xamarin.Forms;
namespace Ooui.Forms.Renderers
{
- public class EntryRenderer : ViewRenderer
+ public class EntryRenderer : ViewRenderer
{
Ooui.Color _defaultTextColor;
bool _disposed;
@@ -17,7 +17,17 @@ namespace Ooui.Forms.Renderers
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
{
- var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
+ var text = Element.Text;
+ if (text == null || text.Length == 0) {
+ text = Element.Placeholder;
+ }
+ Size size;
+ if (text == null || text.Length == 0) {
+ size = new Size (Element.FontSize * 0.25, Element.FontSize);
+ }
+ else {
+ size = text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
+ }
size = new Size (size.Width, size.Height * 1.428 + 14);
return new SizeRequest (size, size);
}
@@ -32,8 +42,8 @@ namespace Ooui.Forms.Renderers
if (disposing) {
if (Control != null) {
//Control.Inputted -= OnEditingBegan;
- Control.Inputted -= OnEditingChanged;
- Control.Changed -= OnEditingEnded;
+ Control.Input -= OnEditingChanged;
+ Control.Change -= OnEditingEnded;
}
}
@@ -48,7 +58,7 @@ namespace Ooui.Forms.Renderers
return;
if (Control == null) {
- var textField = new Ooui.Input (InputType.Text);
+ var textField = new Ooui.TextInput ();
SetNativeControl (textField);
Debug.Assert (Control != null, "Control != null");
@@ -57,10 +67,10 @@ namespace Ooui.Forms.Renderers
_defaultTextColor = Colors.Black;
- textField.Inputted += OnEditingChanged;
+ textField.Input += OnEditingChanged;
//textField.EditingDidBegin += OnEditingBegan;
- textField.Changed += OnEditingEnded;
+ textField.Change += OnEditingEnded;
}
UpdatePlaceholder ();
@@ -113,8 +123,8 @@ namespace Ooui.Forms.Renderers
void OnEditingEnded (object sender, EventArgs e)
{
// Typing aid changes don't always raise EditingChanged event
- if (Control.Text != Element.Text) {
- ElementController.SetValueFromRenderer (Entry.TextProperty, Control.Text);
+ if (Control.Value != Element.Text) {
+ ElementController.SetValueFromRenderer (Entry.TextProperty, Control.Value);
}
ElementController.SetValueFromRenderer (VisualElement.IsFocusedPropertyKey, false);
diff --git a/Ooui.Forms/Renderers/FrameRenderer.cs b/Ooui.Forms/Renderers/FrameRenderer.cs
new file mode 100644
index 0000000..752f534
--- /dev/null
+++ b/Ooui.Forms/Renderers/FrameRenderer.cs
@@ -0,0 +1,64 @@
+using System;
+using System.ComponentModel;
+using Xamarin.Forms;
+using Ooui.Forms.Extensions;
+
+namespace Ooui.Forms.Renderers
+{
+ public class FrameRenderer : VisualElementRenderer
+ {
+ protected override void OnElementChanged (ElementChangedEventArgs e)
+ {
+ base.OnElementChanged (e);
+
+ if (e.NewElement != null)
+ SetupLayer ();
+ }
+
+ protected override void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged (sender, e);
+
+ if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName ||
+ e.PropertyName == Xamarin.Forms.Frame.OutlineColorProperty.PropertyName ||
+ e.PropertyName == Xamarin.Forms.Frame.HasShadowProperty.PropertyName ||
+ e.PropertyName == Xamarin.Forms.Frame.CornerRadiusProperty.PropertyName)
+ SetupLayer ();
+ }
+
+ void SetupLayer ()
+ {
+ float cornerRadius = Element.CornerRadius;
+
+ if (cornerRadius == -1f)
+ cornerRadius = 5f; // default corner radius
+
+ var Layer = this.Style;
+
+ Layer.BorderRadius = cornerRadius;
+
+ if (Element.BackgroundColor == Xamarin.Forms.Color.Default)
+ Layer.BackgroundColor = "white";
+ else
+ Layer.BackgroundColor = Element.BackgroundColor.ToOouiColor ();
+
+ if (Element.HasShadow) {
+ //Layer.ShadowRadius = 5;
+ //Layer.ShadowColor = "black";
+ //Layer.ShadowOpacity = 0.8f;
+ //Layer.ShadowOffset = new SizeF ();
+ }
+ else {
+ //Layer.ShadowOpacity = 0;
+ }
+
+ if (Element.OutlineColor == Xamarin.Forms.Color.Default)
+ Layer.BorderColor = Colors.Clear;
+ else {
+ Layer.BorderColor = Element.OutlineColor.ToOouiColor ();
+ Layer.BorderWidth = 1;
+ Layer.BorderStyle = "solid";
+ }
+ }
+ }
+}
diff --git a/Ooui.Forms/Renderers/ImageRenderer.cs b/Ooui.Forms/Renderers/ImageRenderer.cs
new file mode 100644
index 0000000..3f9e6e2
--- /dev/null
+++ b/Ooui.Forms/Renderers/ImageRenderer.cs
@@ -0,0 +1,202 @@
+using System;
+using System.ComponentModel;
+using System.Threading;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+
+namespace Ooui.Forms.Renderers
+{
+ public class ImageRenderer : ViewRenderer
+ {
+ bool _isDisposed;
+
+ protected override void Dispose (bool disposing)
+ {
+ if (_isDisposed)
+ return;
+
+ if (disposing) {
+ }
+
+ _isDisposed = true;
+
+ base.Dispose (disposing);
+ }
+
+ protected override async void OnElementChanged (ElementChangedEventArgs e)
+ {
+ if (Control == null) {
+ var imageView = new Ooui.Image ();
+ SetNativeControl (imageView);
+ this.Style.Overflow = "hidden";
+ }
+
+ if (e.NewElement != null) {
+ SetAspect ();
+ await TrySetImage (e.OldElement);
+ SetOpacity ();
+ }
+
+ base.OnElementChanged (e);
+ }
+
+ protected override async void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged (sender, e);
+ if (e.PropertyName == Xamarin.Forms.Image.SourceProperty.PropertyName)
+ await TrySetImage ();
+ else if (e.PropertyName == Xamarin.Forms.Image.IsOpaqueProperty.PropertyName)
+ SetOpacity ();
+ else if (e.PropertyName == Xamarin.Forms.Image.AspectProperty.PropertyName)
+ SetAspect ();
+ }
+
+ void SetAspect ()
+ {
+ if (_isDisposed || Element == null || Control == null) {
+ return;
+ }
+ }
+
+ protected virtual async Task TrySetImage (Xamarin.Forms.Image previous = null)
+ {
+ // By default we'll just catch and log any exceptions thrown by SetImage so they don't bring down
+ // the application; a custom renderer can override this method and handle exceptions from
+ // SetImage differently if it wants to
+
+ try {
+ await SetImage (previous).ConfigureAwait (false);
+ }
+ catch (Exception ex) {
+ System.Diagnostics.Debug.WriteLine ("Error loading image: {0}", ex);
+ }
+ finally {
+ ((IImageController)Element)?.SetIsLoading (false);
+ }
+ }
+
+ protected async Task SetImage (Xamarin.Forms.Image oldElement = null)
+ {
+ if (_isDisposed || Element == null || Control == null) {
+ return;
+ }
+
+ var source = Element.Source;
+
+ if (oldElement != null) {
+ var oldSource = oldElement.Source;
+ if (Equals (oldSource, source))
+ return;
+
+ if (oldSource is FileImageSource && source is FileImageSource && ((FileImageSource)oldSource).File == ((FileImageSource)source).File)
+ return;
+
+ Control.Source = "";
+ }
+
+ IImageSourceHandler handler;
+
+ Element.SetIsLoading (true);
+
+ if (source != null &&
+ (handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandler (source.GetType ())) != null) {
+ string uiimage;
+ try {
+ uiimage = await handler.LoadImageAsync (source, scale: 1.0f);
+ }
+ catch (OperationCanceledException) {
+ uiimage = null;
+ }
+
+ if (_isDisposed)
+ return;
+
+ var imageView = Control;
+ if (imageView != null)
+ imageView.Source = uiimage;
+
+ ((IVisualElementController)Element).NativeSizeChanged ();
+ }
+ else {
+ Control.Source = "";
+ }
+
+ Element.SetIsLoading (false);
+ }
+
+ void SetOpacity ()
+ {
+ if (_isDisposed || Element == null || Control == null) {
+ return;
+ }
+ }
+ }
+
+ public interface IImageSourceHandler : IRegisterable
+ {
+ Task LoadImageAsync (ImageSource imagesource, CancellationToken cancelationToken = default (CancellationToken), float scale = 1);
+ }
+
+ public sealed class FileImageSourceHandler : IImageSourceHandler
+ {
+ public async Task LoadImageAsync (ImageSource imagesource, CancellationToken cancelationToken = default (CancellationToken), float scale = 1f)
+ {
+ string image = null;
+ var filesource = imagesource as FileImageSource;
+ var file = filesource?.File;
+ if (!string.IsNullOrEmpty (file)) {
+ var name = System.IO.Path.GetFileName (file);
+ image = "/images/" + name;
+ if (Ooui.UI.TryGetFileContentAtPath (image, out var f)) {
+ // Already published
+ }
+ else {
+ await Task.Run (() => Ooui.UI.PublishFile (image, file), cancelationToken);
+ }
+ }
+ return image;
+ }
+ }
+
+ public sealed class StreamImagesourceHandler : IImageSourceHandler
+ {
+ public async Task LoadImageAsync (ImageSource imagesource, CancellationToken cancelationToken = default (CancellationToken), float scale = 1f)
+ {
+ string image = null;
+ var streamsource = imagesource as StreamImageSource;
+ if (streamsource?.Stream != null) {
+ using (var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync (cancelationToken).ConfigureAwait (false)) {
+ if (streamImage != null) {
+ var data = new byte[streamImage.Length];
+ using (var outputStream = new System.IO.MemoryStream (data)) {
+ await streamImage.CopyToAsync (outputStream, 4096, cancelationToken).ConfigureAwait (false);
+ }
+ var hash = Ooui.UI.Hash (data);
+ var etag = "\"" + hash + "\"";
+ image = "/images/" + hash;
+ if (Ooui.UI.TryGetFileContentAtPath (image, out var file) && file.Etag == etag) {
+ // Already published
+ }
+ else {
+ Ooui.UI.PublishFile (image, data, etag, "image");
+ }
+ }
+ }
+ }
+
+ if (image == null) {
+ System.Diagnostics.Debug.WriteLine ("Could not load image: {0}", streamsource);
+ }
+ return image;
+ }
+ }
+
+ public sealed class ImageLoaderSourceHandler : IImageSourceHandler
+ {
+ public Task LoadImageAsync (ImageSource imagesource, CancellationToken cancelationToken = default (CancellationToken), float scale = 1f)
+ {
+ var imageLoader = imagesource as UriImageSource;
+ return Task.FromResult (imageLoader?.Uri.ToString () ?? "");
+ }
+ }
+}
diff --git a/Ooui.Forms/Renderers/LabelRenderer.cs b/Ooui.Forms/Renderers/LabelRenderer.cs
index 6a6104d..85ddc50 100644
--- a/Ooui.Forms/Renderers/LabelRenderer.cs
+++ b/Ooui.Forms/Renderers/LabelRenderer.cs
@@ -18,6 +18,8 @@ namespace Ooui.Forms.Renderers
{
if (!_perfectSizeValid) {
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
+ size.Width = Math.Ceiling (size.Width);
+ size.Height = Math.Ceiling (size.Height * 1.4);
_perfectSize = new SizeRequest (size, size);
_perfectSizeValid = true;
}
@@ -72,6 +74,9 @@ namespace Ooui.Forms.Renderers
{
base.OnElementPropertyChanged (sender, e);
+ if (Control == null)
+ return;
+
if (e.PropertyName == Xamarin.Forms.Label.HorizontalTextAlignmentProperty.PropertyName)
UpdateAlignment ();
else if (e.PropertyName == Xamarin.Forms.Label.VerticalTextAlignmentProperty.PropertyName)
@@ -98,8 +103,10 @@ namespace Ooui.Forms.Renderers
void UpdateAlignment ()
{
- Control.Style.TextAlign = Element.HorizontalTextAlignment.ToOouiTextAlign ();
- Control.Style.VerticalAlign = Element.VerticalTextAlignment.ToOouiTextAlign ();
+ this.Style.Display = "table";
+ Control.Style.Display = "table-cell";
+ this.Style.TextAlign = Element.HorizontalTextAlignment.ToOouiTextAlign ();
+ Control.Style.VerticalAlign = Element.VerticalTextAlignment.ToOouiVerticalAlign ();
}
void UpdateLineBreakMode ()
diff --git a/Ooui.Forms/Renderers/SwitchRenderer.cs b/Ooui.Forms/Renderers/SwitchRenderer.cs
new file mode 100644
index 0000000..737eb50
--- /dev/null
+++ b/Ooui.Forms/Renderers/SwitchRenderer.cs
@@ -0,0 +1,53 @@
+using System;
+using Xamarin.Forms;
+
+namespace Ooui.Forms.Renderers
+{
+ public class SwitchRenderer : ViewRenderer
+ {
+ public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
+ {
+ var size = new Size (54, 38);
+ return new SizeRequest (size, size);
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ if (disposing)
+ Control.Change -= OnControlValueChanged;
+
+ base.Dispose (disposing);
+ }
+
+ protected override void OnElementChanged (ElementChangedEventArgs e)
+ {
+ if (e.OldElement != null)
+ e.OldElement.Toggled -= OnElementToggled;
+
+ if (e.NewElement != null) {
+ if (Control == null) {
+ var input = new Input (InputType.Checkbox);
+ input.SetAttribute ("data-toggle", "toggle");
+ SetNativeControl (input);
+ input.Call ("$.bootstrapToggle");
+ Control.Change += OnControlValueChanged;
+ }
+
+ Control.IsChecked = Element.IsToggled;
+ e.NewElement.Toggled += OnElementToggled;
+ }
+
+ base.OnElementChanged (e);
+ }
+
+ void OnControlValueChanged (object sender, EventArgs e)
+ {
+ ((IElementController)Element).SetValueFromRenderer (Switch.IsToggledProperty, Control.IsChecked);
+ }
+
+ void OnElementToggled (object sender, EventArgs e)
+ {
+ Control.IsChecked = Element.IsToggled;
+ }
+ }
+}
diff --git a/Ooui.Forms/VisualElementRenderer.cs b/Ooui.Forms/VisualElementRenderer.cs
index a9f9af1..f6f4548 100644
--- a/Ooui.Forms/VisualElementRenderer.cs
+++ b/Ooui.Forms/VisualElementRenderer.cs
@@ -38,6 +38,7 @@ namespace Ooui.Forms
VisualElementRendererFlags _flags = VisualElementRendererFlags.AutoPackage | VisualElementRendererFlags.AutoTrack;
+ EventTracker _events;
VisualElementPackager _packager;
VisualElementTracker _tracker;
@@ -107,10 +108,10 @@ namespace Ooui.Forms
_packager.Load ();
}
- //if (AutoTrack && _events == null) {
- // _events = new EventTracker (this);
- // _events.LoadEvents (this);
- //}
+ if (AutoTrack && _events == null) {
+ _events = new EventTracker (this);
+ _events.LoadEvents (this);
+ }
element.PropertyChanged += _propertyChangedHandler;
}
diff --git a/Ooui/CanvasRenderingContext2D.cs b/Ooui/CanvasRenderingContext2D.cs
index 210017d..64d80f8 100644
--- a/Ooui/CanvasRenderingContext2D.cs
+++ b/Ooui/CanvasRenderingContext2D.cs
@@ -81,97 +81,97 @@ namespace Ooui
public void Save ()
{
- SendCall ("save");
+ Call ("save");
}
public void Restore ()
{
- SendCall ("restore");
+ Call ("restore");
}
public void ClearRect (double x, double y, double w, double h)
{
- SendCall ("clearRect", x, y, w, h);
+ Call ("clearRect", x, y, w, h);
}
public void FillRect (double x, double y, double w, double h)
{
- SendCall ("fillRect", x, y, w, h);
+ Call ("fillRect", x, y, w, h);
}
public void StrokeRect (double x, double y, double w, double h)
{
- SendCall ("strokeRect", x, y, w, h);
+ Call ("strokeRect", x, y, w, h);
}
public void BeginPath ()
{
- SendCall ("beginPath");
+ Call ("beginPath");
}
public void ClosePath ()
{
- SendCall ("closePath");
+ Call ("closePath");
}
public void MoveTo (double x, double y)
{
- SendCall ("moveTo", x, y);
+ Call ("moveTo", x, y);
}
public void LineTo (double x, double y)
{
- SendCall ("lineTo", x, y);
+ Call ("lineTo", x, y);
}
public void QuadraticCurveTo (double cpx, double cpy, double x, double y)
{
- SendCall ("quadraticCurveTo", cpx, cpy, x, y);
+ Call ("quadraticCurveTo", cpx, cpy, x, y);
}
public void BezierCurveTo (double cp1x, double cp1y, double cp2x, double cp2y, double x, double y)
{
- SendCall ("bezierCurveTo", cp1x, cp1y, cp2x, cp2y, x, y);
+ Call ("bezierCurveTo", cp1x, cp1y, cp2x, cp2y, x, y);
}
public void ArcTo (double x1, double y1, double x2, double y2, double radius)
{
- SendCall ("arcTo", x1, y1, x2, y2, radius);
+ Call ("arcTo", x1, y1, x2, y2, radius);
}
public void Rect (double x, double y, double w, double h)
{
- SendCall ("rect", x, y, w, h);
+ Call ("rect", x, y, w, h);
}
public void Arc (double x, double y, double radius, double startAngle, double endAngle, bool counterclockwise)
{
- SendCall ("arc", x, y, radius, startAngle, endAngle, counterclockwise);
+ Call ("arc", x, y, radius, startAngle, endAngle, counterclockwise);
}
public void Fill ()
{
- SendCall ("fill");
+ Call ("fill");
}
public void Stroke ()
{
- SendCall ("stroke");
+ Call ("stroke");
}
public void Clip ()
{
- SendCall ("clip");
+ Call ("clip");
}
public void FillText (string text, double x, double y, double? maxWidth)
{
- SendCall ("fillText", text, x, y, maxWidth);
+ Call ("fillText", text, x, y, maxWidth);
}
public void StrokeText (string text, double x, double y, double? maxWidth)
{
- SendCall ("strokeText", text, x, y, maxWidth);
+ Call ("strokeText", text, x, y, maxWidth);
}
}
diff --git a/Ooui/Client.js b/Ooui/Client.js
index e796069..f77d6e1 100644
--- a/Ooui/Client.js
+++ b/Ooui/Client.js
@@ -57,7 +57,8 @@ function ooui (rootElementPath) {
socket.addEventListener ("close", function (event) {
console.error ("Web socket close", event);
if (opened) {
- location.reload ();
+ alert ("Connection to the server has been lost. Please try refreshing the page.");
+ opened = false;
}
});
@@ -65,11 +66,19 @@ function ooui (rootElementPath) {
const messages = JSON.parse (event.data);
if (debug) console.log("Messages", messages);
if (Array.isArray (messages)) {
+ const jqs = []
messages.forEach (function (m) {
// console.log('Raw value from server', m.v);
m.v = fixupValue (m.v);
- processMessage (m);
+ if (m.k.startsWith ("$.")) {
+ jqs.push (m);
+ }
+ else {
+ processMessage (m);
+ }
});
+ // Run jQuery functions last since they usually require a fully built DOM
+ jqs.forEach (processMessage);
}
});
@@ -92,8 +101,8 @@ function ooui (rootElementPath) {
function resizeHandler() {
const em = {
m: "event",
- id: 42,
- k: "window.resize",
+ id: "window",
+ k: "resize",
v: getSize (),
};
const ems = JSON.stringify (em);
@@ -163,9 +172,11 @@ function msgCall (m) {
console.error ("Unknown node id", m);
return;
}
- const f = node[m.k];
+ const isJQuery = m.k.startsWith ("$.");
+ const target = isJQuery ? $(node) : node;
+ const f = isJQuery ? target[m.k.slice(2)] : target[m.k];
if (debug) console.log ("Call", node, f, m.v);
- const r = f.apply (node, m.v);
+ const r = f.apply (target, m.v);
if (typeof m.rid === 'string' || m.rid instanceof String) {
nodes[m.rid] = r;
}
diff --git a/Ooui/Div.cs b/Ooui/Div.cs
index 02012c5..d4d8009 100644
--- a/Ooui/Div.cs
+++ b/Ooui/Div.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace Ooui
{
@@ -8,5 +9,21 @@ namespace Ooui
: base ("div")
{
}
+
+ public Div (params Element[] children)
+ : this ()
+ {
+ foreach (var c in children) {
+ AppendChild (c);
+ }
+ }
+
+ public Div (IEnumerable children)
+ : this ()
+ {
+ foreach (var c in children) {
+ AppendChild (c);
+ }
+ }
}
}
diff --git a/Ooui/Element.cs b/Ooui/Element.cs
index a7287d1..bc08de7 100644
--- a/Ooui/Element.cs
+++ b/Ooui/Element.cs
@@ -25,12 +25,12 @@ namespace Ooui
set => SetProperty (ref hidden, value, "hidden");
}
- public event TargetEventHandler Clicked {
+ public event TargetEventHandler Click {
add => AddEventListener ("click", value);
remove => RemoveEventListener ("click", value);
}
- public event TargetEventHandler DoubleClicked {
+ public event TargetEventHandler DoubleClick {
add => AddEventListener ("dblclick", value);
remove => RemoveEventListener ("dblclick", value);
}
@@ -40,7 +40,7 @@ namespace Ooui
remove => RemoveEventListener ("keydown", value);
}
- public event TargetEventHandler KeyPressed {
+ public event TargetEventHandler KeyPress {
add => AddEventListener ("keypress", value);
remove => RemoveEventListener ("keypress", value);
}
@@ -55,17 +55,17 @@ namespace Ooui
remove => RemoveEventListener ("mousedown", value);
}
- public event TargetEventHandler MouseEntered {
+ public event TargetEventHandler MouseEnter {
add => AddEventListener ("mouseenter", value);
remove => RemoveEventListener ("mouseenter", value);
}
- public event TargetEventHandler MouseLeft {
+ public event TargetEventHandler MouseLeave {
add => AddEventListener ("mouseleave", value);
remove => RemoveEventListener ("mouseleave", value);
}
- public event TargetEventHandler MouseMoved {
+ public event TargetEventHandler MouseMove {
add => AddEventListener ("mousemove", value);
remove => RemoveEventListener ("mousemove", value);
}
@@ -85,7 +85,7 @@ namespace Ooui
remove => RemoveEventListener ("mouseup", value);
}
- public event TargetEventHandler Wheeled {
+ public event TargetEventHandler Wheel {
add => AddEventListener ("wheel", value);
remove => RemoveEventListener ("wheel", value);
}
@@ -116,5 +116,19 @@ namespace Ooui
{
SendSet ("style." + Style.GetJsName (e.PropertyName), Style[e.PropertyName]);
}
+
+ protected override bool SaveStateMessageIfNeeded (Message message)
+ {
+ if (message.TargetId != Id)
+ return false;
+
+ switch (message.MessageType) {
+ case MessageType.Call when message.Key.StartsWith ("$.", StringComparison.Ordinal):
+ AddStateMessage (message);
+ return true;
+ default:
+ return base.SaveStateMessageIfNeeded (message);
+ }
+ }
}
}
diff --git a/Ooui/EventTarget.cs b/Ooui/EventTarget.cs
index a7be894..e3e26c1 100644
--- a/Ooui/EventTarget.cs
+++ b/Ooui/EventTarget.cs
@@ -115,7 +115,7 @@ namespace Ooui
MessageSent?.Invoke (message);
}
- protected void SendCall (string methodName, params object[] args)
+ public void Call (string methodName, params object[] args)
{
Send (Message.Call (Id, methodName, args));
}
diff --git a/Ooui/Form.cs b/Ooui/Form.cs
index e23e22a..809397a 100644
--- a/Ooui/Form.cs
+++ b/Ooui/Form.cs
@@ -22,7 +22,7 @@ namespace Ooui
set => SetProperty (ref enctype, value ?? "", "enctype");
}
- public event TargetEventHandler Submitted {
+ public event TargetEventHandler Submit {
add => AddEventListener ("submit", value);
remove => RemoveEventListener ("submit", value);
}
diff --git a/Ooui/Input.cs b/Ooui/Input.cs
index 9a2d2f2..90f73d8 100644
--- a/Ooui/Input.cs
+++ b/Ooui/Input.cs
@@ -30,16 +30,11 @@ namespace Ooui
}
}
- public event TargetEventHandler Changed {
+ public event TargetEventHandler Change {
add => AddEventListener ("change", value);
remove => RemoveEventListener ("change", value);
}
- public event TargetEventHandler Inputted {
- add => AddEventListener ("input", value);
- remove => RemoveEventListener ("input", value);
- }
-
string placeholder = "";
public string Placeholder {
get => placeholder;
@@ -77,7 +72,7 @@ namespace Ooui
: base ("input")
{
// Subscribe to the change event so we always get up-to-date values
- Changed += (s, e) => {};
+ Change += (s, e) => {};
}
public Input (InputType type)
@@ -88,7 +83,7 @@ namespace Ooui
protected override bool TriggerEventFromMessage (Message message)
{
- if (message.TargetId == Id && message.MessageType == MessageType.Event && message.Key == "change") {
+ if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
// Don't need to notify here because the base implementation will fire the event
if (Type == InputType.Checkbox) {
isChecked = message.Value != null ? Convert.ToBoolean (message.Value) : false;
diff --git a/Ooui/Node.cs b/Ooui/Node.cs
index 5a80ca1..18d16a0 100644
--- a/Ooui/Node.cs
+++ b/Ooui/Node.cs
@@ -73,7 +73,8 @@ namespace Ooui
}
}
newChild.MessageSent += HandleChildMessageSent;
- SendCall ("insertBefore", newChild, referenceChild);
+ Call ("insertBefore", newChild, referenceChild);
+ OnChildInsertedBefore (newChild, referenceChild);
return newChild;
}
@@ -87,10 +88,19 @@ namespace Ooui
}
}
child.MessageSent -= HandleChildMessageSent;
- SendCall ("removeChild", child);
+ Call ("removeChild", child);
+ OnChildRemoved (child);
return child;
}
+ protected virtual void OnChildInsertedBefore (Node newChild, Node referenceChild)
+ {
+ }
+
+ protected virtual void OnChildRemoved (Node child)
+ {
+ }
+
protected void ReplaceAll (Node newNode)
{
var toRemove = new List ();
@@ -100,7 +110,7 @@ namespace Ooui
}
foreach (var child in toRemove) {
child.MessageSent -= HandleChildMessageSent;
- SendCall ("removeChild", child);
+ Call ("removeChild", child);
}
InsertBefore (newNode, null);
}
diff --git a/Ooui/Ooui.csproj b/Ooui/Ooui.csproj
index 3b692d5..98357fe 100644
--- a/Ooui/Ooui.csproj
+++ b/Ooui/Ooui.csproj
@@ -1,9 +1,9 @@
- 0.2.0
+ 1.0.0
praeclarum
Small cross-platform UI library for .NET that uses web technologies.
- UI;CrossPlatform
+ Ooui;UI;CrossPlatform
https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png
https://github.com/praeclarum/Ooui
https://github.com/praeclarum/Ooui/blob/master/LICENSE
diff --git a/Ooui/Option.cs b/Ooui/Option.cs
new file mode 100644
index 0000000..86d918e
--- /dev/null
+++ b/Ooui/Option.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace Ooui
+{
+ public class Option : Element
+ {
+ string val = "";
+ public string Value {
+ get => val;
+ set => SetProperty (ref val, value ?? "", "value");
+ }
+
+ string label = "";
+ public string Label {
+ get => label;
+ set => SetProperty (ref label, value ?? "", "label");
+ }
+
+ bool defaultSelected = false;
+ public bool DefaultSelected {
+ get => defaultSelected;
+ set => SetProperty (ref defaultSelected, value, "defaultSelected");
+ }
+
+ public Option ()
+ : base ("option")
+ {
+ }
+ }
+}
diff --git a/Ooui/Select.cs b/Ooui/Select.cs
index 7f53175..c88eed4 100644
--- a/Ooui/Select.cs
+++ b/Ooui/Select.cs
@@ -10,14 +10,42 @@ namespace Ooui
set => SetProperty (ref val, value ?? "", "value");
}
- public event TargetEventHandler Changed {
+ public event TargetEventHandler Change {
add => AddEventListener ("change", value);
remove => RemoveEventListener ("change", value);
}
+ public event TargetEventHandler Input {
+ add => AddEventListener ("input", value);
+ remove => RemoveEventListener ("input", value);
+ }
+
public Select ()
: base ("select")
{
+ // Subscribe to the change event so we always get up-to-date values
+ Change += (s, e) => { };
+ }
+
+ public void AddOption (string label, string value)
+ {
+ AppendChild (new Option { Label = label, Value = value });
+ }
+
+ protected override void OnChildInsertedBefore (Node newChild, Node referenceChild)
+ {
+ base.OnChildInsertedBefore (newChild, referenceChild);
+ if (string.IsNullOrEmpty (val) && newChild is Option o && !string.IsNullOrEmpty (o.Value)) {
+ val = o.Value;
+ }
+ }
+
+ protected override bool TriggerEventFromMessage (Message message)
+ {
+ if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
+ val = message.Value != null ? Convert.ToString (message.Value) : "";
+ }
+ return base.TriggerEventFromMessage (message);
}
}
}
diff --git a/Ooui/Style.cs b/Ooui/Style.cs
index 06b6149..6a6f307 100644
--- a/Ooui/Style.cs
+++ b/Ooui/Style.cs
@@ -93,38 +93,38 @@ namespace Ooui
public Value BorderTopWidth {
get => this["border-top-width"];
- set => this["border-top-width"] = value;
+ set => this["border-top-width"] = AddNumberUnits (value, "px");
}
public Value BorderRightWidth {
get => this["border-right-width"];
- set => this["border-right-width"] = value;
+ set => this["border-right-width"] = AddNumberUnits (value, "px");
}
public Value BorderBottomWidth {
get => this["border-bottom-width"];
- set => this["border-bottom-width"] = value;
+ set => this["border-bottom-width"] = AddNumberUnits (value, "px");
}
public Value BorderLeftWidth {
get => this["border-left-width"];
- set => this["border-left-width"] = value;
+ set => this["border-left-width"] = AddNumberUnits (value, "px");
}
public Value BorderRadius {
get => this["border-radius"];
set {
- this["border-radius"] = value;
+ this["border-radius"] = AddNumberUnits (value, "px");
}
}
public Value BorderWidth {
get => this["border-top-width"];
set {
- this["border-top-width"] = value;
- this["border-right-width"] = value;
- this["border-bottom-width"] = value;
- this["border-left-width"] = value;
+ this["border-top-width"] = AddNumberUnits (value, "px");
+ this["border-right-width"] = AddNumberUnits (value, "px");
+ this["border-bottom-width"] = AddNumberUnits (value, "px");
+ this["border-left-width"] = AddNumberUnits (value, "px");
}
}
@@ -253,6 +253,11 @@ namespace Ooui
set => this["order"] = value;
}
+ public Value Overflow {
+ get => this["overflow"];
+ set => this["overflow"] = value;
+ }
+
public Value PaddingTop {
get => this["padding-top"];
set => this["padding-top"] = value;
@@ -403,6 +408,8 @@ namespace Ooui
static string AddNumberUnits (object val, string units)
{
+ if (val == null)
+ return null;
if (val is string s)
return s;
if (val is IConvertible c)
diff --git a/Ooui/TextArea.cs b/Ooui/TextArea.cs
index 94ca6fe..a35b962 100644
--- a/Ooui/TextArea.cs
+++ b/Ooui/TextArea.cs
@@ -4,12 +4,12 @@ namespace Ooui
{
public class TextArea : FormControl
{
- public event TargetEventHandler Changed {
+ public event TargetEventHandler Change {
add => AddEventListener ("change", value);
remove => RemoveEventListener ("change", value);
}
- public event TargetEventHandler Inputted {
+ public event TargetEventHandler Input {
add => AddEventListener ("input", value);
remove => RemoveEventListener ("input", value);
}
@@ -36,18 +36,18 @@ namespace Ooui
: base ("textarea")
{
// Subscribe to the change event so we always get up-to-date values
- Changed += (s, e) => {};
+ Change += (s, e) => {};
}
public TextArea (string text)
: this ()
{
- Text = text;
+ Value = text;
}
protected override bool TriggerEventFromMessage (Message message)
{
- if (message.TargetId == Id && message.MessageType == MessageType.Event && message.Key == "change") {
+ if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
// Don't need to notify here because the base implementation will fire the event
val = message.Value != null ? Convert.ToString (message.Value) : "";
}
diff --git a/Ooui/TextInput.cs b/Ooui/TextInput.cs
new file mode 100644
index 0000000..a8fd587
--- /dev/null
+++ b/Ooui/TextInput.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Ooui
+{
+ public class TextInput : Input
+ {
+ public event TargetEventHandler Input {
+ add => AddEventListener ("input", value);
+ remove => RemoveEventListener ("input", value);
+ }
+
+ public TextInput ()
+ : base (InputType.Text)
+ {
+ }
+ }
+}
diff --git a/Ooui/UI.cs b/Ooui/UI.cs
index b3a3ac9..33c39ac 100644
--- a/Ooui/UI.cs
+++ b/Ooui/UI.cs
@@ -14,6 +14,9 @@ namespace Ooui
{
static readonly ManualResetEvent started = new ManualResetEvent (false);
+ [ThreadStatic]
+ static System.Security.Cryptography.SHA256 sha256;
+
static CancellationTokenSource serverCts;
static readonly Dictionary publishedPaths =
@@ -26,8 +29,10 @@ namespace Ooui
public static StyleSelectors Styles => rules;
static readonly byte[] clientJsBytes;
+ static readonly string clientJsEtag;
public static byte[] ClientJsBytes => clientJsBytes;
+ public static string ClientJsEtag => clientJsEtag;
public static string Template { get; set; } = $@"
@@ -35,10 +40,14 @@ namespace Ooui
@Title
+
+
+
+
@@ -64,6 +73,19 @@ namespace Ooui
}
}
}
+ static bool serverEnabled = true;
+ public static bool ServerEnabled {
+ get => serverEnabled;
+ set {
+ if (serverEnabled != value) {
+ serverEnabled = value;
+ if (serverEnabled)
+ Restart ();
+ else
+ Stop ();
+ }
+ }
+ }
static UI ()
{
@@ -79,6 +101,22 @@ namespace Ooui
clientJsBytes = Encoding.UTF8.GetBytes (r.ReadToEnd ());
}
}
+ clientJsEtag = "\"" + Hash (clientJsBytes) + "\"";
+ }
+
+ public static string Hash (byte[] bytes)
+ {
+ var sha = sha256;
+ if (sha == null) {
+ sha = System.Security.Cryptography.SHA256.Create ();
+ sha256 = sha;
+ }
+ var data = sha.ComputeHash (bytes);
+ StringBuilder sBuilder = new StringBuilder ();
+ for (int i = 0; i < data.Length; i++) {
+ sBuilder.Append (data[i].ToString ("x2"));
+ }
+ return sBuilder.ToString ();
}
static void Publish (string path, RequestHandler handler)
@@ -110,7 +148,47 @@ namespace Ooui
if (contentType == null) {
contentType = GuessContentType (path, filePath);
}
- Publish (path, new DataHandler (data, contentType));
+ var etag = "\"" + Hash (data) + "\"";
+ Publish (path, new DataHandler (data, etag, contentType));
+ }
+
+ public static void PublishFile (string path, byte[] data, string contentType)
+ {
+ var etag = "\"" + Hash (data) + "\"";
+ Publish (path, new DataHandler (data, etag, contentType));
+ }
+
+ public static void PublishFile (string path, byte[] data, string etag, string contentType)
+ {
+ Publish (path, new DataHandler (data, etag, contentType));
+ }
+
+ public static bool TryGetFileContentAtPath (string path, out FileContent file)
+ {
+ RequestHandler handler;
+ lock (publishedPaths) {
+ if (!publishedPaths.TryGetValue (path, out handler)) {
+ file = null;
+ return false;
+ }
+ }
+ if (handler is DataHandler dh) {
+ file = new FileContent {
+ Etag = dh.Etag,
+ Content = dh.Data,
+ ContentType = dh.ContentType,
+ };
+ return true;
+ }
+ file = null;
+ return false;
+ }
+
+ public class FileContent
+ {
+ public string ContentType { get; set; }
+ public string Etag { get; set; }
+ public byte[] Content { get; set; }
}
public static void PublishJson (string path, Func