From 7601ba45decda95556c7513dc6475074d0542f52 Mon Sep 17 00:00:00 2001 From: Sami Date: Thu, 16 Nov 2017 00:10:58 +0100 Subject: [PATCH 01/32] Invoke default browser on Windows --- Ooui/Platform.cs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Ooui/Platform.cs b/Ooui/Platform.cs index 951bd7a..287bfa4 100644 --- a/Ooui/Platform.cs +++ b/Ooui/Platform.cs @@ -132,22 +132,16 @@ namespace Ooui static Process StartBrowserProcess (string url) { - var cmd = url; - var args = ""; + // var vs = Environment.GetEnvironmentVariables (); + // foreach (System.Collections.DictionaryEntry kv in vs) { + // System.Console.WriteLine($"K={kv.Key}, V={kv.Value}"); + // } - var osv = Environment.OSVersion; - if (osv.Platform == PlatformID.Unix) { - cmd = "open"; - args = url; - } + // Console.WriteLine ($"Process.Start {cmd} {args}"); - // var vs = Environment.GetEnvironmentVariables (); - // foreach (System.Collections.DictionaryEntry kv in vs) { - // System.Console.WriteLine($"K={kv.Key}, V={kv.Value}"); - // } - - // Console.WriteLine ($"Process.Start {cmd} {args}"); - return Process.Start (cmd, args); - } + return Environment.OSVersion.Platform == PlatformID.Unix + ? Process.Start ("open", url) + : Process.Start (new ProcessStartInfo (url) { UseShellExecute = true }); + } } } From 5575578c2d5b4cca25d0c399f4417c033746e6fa Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 10 Nov 2017 09:57:45 -0800 Subject: [PATCH 02/32] Add BoxViewClock sample --- Ooui.Forms/Forms.cs | 8 +- .../AspNetCoreMvc/AspNetCoreMvc.csproj | 2 + PlatformSamples/AspNetCoreMvc/Startup.cs | 2 + Samples/BoxViewClockSample.cs | 130 ++++++++++++++++++ Samples/Program.cs | 2 + Samples/XamarinFormsSample.cs | 2 - 6 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 Samples/BoxViewClockSample.cs diff --git a/Ooui.Forms/Forms.cs b/Ooui.Forms/Forms.cs index 5782113..cb0fefd 100644 --- a/Ooui.Forms/Forms.cs +++ b/Ooui.Forms/Forms.cs @@ -110,7 +110,13 @@ namespace Xamarin.Forms public void StartTimer (TimeSpan interval, Func callback) { - throw new NotImplementedException (); + Timer timer = null; + timer = new Timer ((_ => { + if (!callback ()) { + timer?.Dispose (); + timer = null; + } + }), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds); } } diff --git a/PlatformSamples/AspNetCoreMvc/AspNetCoreMvc.csproj b/PlatformSamples/AspNetCoreMvc/AspNetCoreMvc.csproj index 1e9ec6c..4e05f3e 100644 --- a/PlatformSamples/AspNetCoreMvc/AspNetCoreMvc.csproj +++ b/PlatformSamples/AspNetCoreMvc/AspNetCoreMvc.csproj @@ -9,6 +9,7 @@ + @@ -19,5 +20,6 @@ + diff --git a/PlatformSamples/AspNetCoreMvc/Startup.cs b/PlatformSamples/AspNetCoreMvc/Startup.cs index cc5ff26..babc056 100644 --- a/PlatformSamples/AspNetCoreMvc/Startup.cs +++ b/PlatformSamples/AspNetCoreMvc/Startup.cs @@ -39,6 +39,8 @@ namespace AspNetCoreMvc app.UseOoui (); + Xamarin.Forms.Forms.Init (); + app.UseMvc (routes => { routes.MapRoute ( name: "default", diff --git a/Samples/BoxViewClockSample.cs b/Samples/BoxViewClockSample.cs new file mode 100644 index 0000000..2fb684b --- /dev/null +++ b/Samples/BoxViewClockSample.cs @@ -0,0 +1,130 @@ +using System; +using Xamarin.Forms; + +namespace Samples +{ + public class BoxViewClockSample : ISample + { + public string Title => "Xamarin.Forms BoxViewClock"; + + public Ooui.Element CreateElement () + { + return new BoxViewClockPage ().CreateElement (); + } + + class BoxViewClockPage : ContentPage + { + // Structure for storing information about the three hands. + struct HandParams + { + public HandParams (double width, double height, double offset) : this () + { + Width = width; + Height = height; + Offset = offset; + } + + public double Width { private set; get; } // fraction of radius + public double Height { private set; get; } // ditto + public double Offset { private set; get; } // relative to center pivot + } + + static readonly HandParams secondParams = new HandParams (0.02, 1.1, 0.85); + static readonly HandParams minuteParams = new HandParams (0.05, 0.8, 0.9); + static readonly HandParams hourParams = new HandParams (0.125, 0.65, 0.9); + + BoxView[] tickMarks = new BoxView[60]; + BoxView secondHand, minuteHand, hourHand; + + public BoxViewClockPage () + { + AbsoluteLayout absoluteLayout = new AbsoluteLayout (); + + // Create the tick marks (to be sized and positioned later) + for (int i = 0; i < tickMarks.Length; i++) { + tickMarks[i] = new BoxView { + Color = Color.Accent + }; + absoluteLayout.Children.Add (tickMarks[i]); + } + + // Create the three hands. + absoluteLayout.Children.Add (hourHand = + new BoxView { + Color = Color.Accent + }); + absoluteLayout.Children.Add (minuteHand = + new BoxView { + Color = Color.Accent + }); + absoluteLayout.Children.Add (secondHand = + new BoxView { + Color = Color.Accent + }); + + Content = absoluteLayout; + + // Attach a couple event handlers. + Device.StartTimer (TimeSpan.FromMilliseconds (16), OnTimerTick); + SizeChanged += OnPageSizeChanged; + } + + void OnPageSizeChanged (object sender, EventArgs args) + { + // Size and position the 12 tick marks. + Point center = new Point (this.Width / 2, this.Height / 2); + double radius = 0.45 * Math.Min (this.Width, this.Height); + + for (int i = 0; i < tickMarks.Length; i++) { + double size = radius / (i % 5 == 0 ? 15 : 30); + double radians = i * 2 * Math.PI / tickMarks.Length; + double x = center.X + radius * Math.Sin (radians) - size / 2; + double y = center.Y - radius * Math.Cos (radians) - size / 2; + AbsoluteLayout.SetLayoutBounds (tickMarks[i], new Rectangle (x, y, size, size)); + + tickMarks[i].AnchorX = 0.51; // Anchor settings necessary for Android + tickMarks[i].AnchorY = 0.51; + tickMarks[i].Rotation = 180 * radians / Math.PI; + } + + // Function for positioning and sizing hands. + Action Layout = (boxView, handParams) => { + double width = handParams.Width * radius; + double height = handParams.Height * radius; + double offset = handParams.Offset; + + AbsoluteLayout.SetLayoutBounds (boxView, + new Rectangle (center.X - 0.5 * width, + center.Y - offset * height, + width, height)); + + boxView.AnchorX = 0.51; + boxView.AnchorY = handParams.Offset; + }; + + Layout (secondHand, secondParams); + Layout (minuteHand, minuteParams); + Layout (hourHand, hourParams); + } + + bool OnTimerTick () + { + // Set rotation angles for hour and minute hands. + DateTime dateTime = DateTime.Now; + hourHand.Rotation = 30 * (dateTime.Hour % 12) + 0.5 * dateTime.Minute; + minuteHand.Rotation = 6 * dateTime.Minute + 0.1 * dateTime.Second; + + // Do an animation for the second hand. + double t = dateTime.Millisecond / 1000.0; + if (t < 0.5) { + t = 0.5 * Easing.SpringIn.Ease (t / 0.5); + } + else { + t = 0.5 * (1 + Easing.SpringOut.Ease ((t - 0.5) / 0.5)); + } + secondHand.Rotation = 6 * (dateTime.Second + t); + return true; + } + } + } +} diff --git a/Samples/Program.cs b/Samples/Program.cs index 80723e5..dd017c4 100644 --- a/Samples/Program.cs +++ b/Samples/Program.cs @@ -7,6 +7,8 @@ namespace Samples { static void Main (string[] args) { + Xamarin.Forms.Forms.Init (); + for (var i = 0; i < args.Length; i++) { var a = args[i]; switch (args[i]) { diff --git a/Samples/XamarinFormsSample.cs b/Samples/XamarinFormsSample.cs index bb8b4f2..e555ac7 100644 --- a/Samples/XamarinFormsSample.cs +++ b/Samples/XamarinFormsSample.cs @@ -10,8 +10,6 @@ namespace Samples Page MakePage () { - Forms.Init (); - var countLabel = new Label { Text = "0", BackgroundColor = Color.Gold, From 4a51859da1d76580473803c44b9864844c875dc9 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 10 Nov 2017 10:14:28 -0800 Subject: [PATCH 03/32] Switch to reusing renderers on Forms elements --- Ooui.Forms/PageExtensions.cs | 12 ++++++++++-- Samples/BoxViewClockSample.cs | 10 +++++++++- Samples/XamarinFormsSample.cs | 4 ++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Ooui.Forms/PageExtensions.cs b/Ooui.Forms/PageExtensions.cs index 8f60bd1..4ffe01d 100644 --- a/Ooui.Forms/PageExtensions.cs +++ b/Ooui.Forms/PageExtensions.cs @@ -16,16 +16,24 @@ namespace Xamarin.Forms Ooui.UI.Publish (path, () => lazyPage.Value); } - public static Ooui.Element CreateElement (this Xamarin.Forms.Page page) + public static Ooui.Element GetOouiElement (this Xamarin.Forms.Page page) { if (!Xamarin.Forms.Forms.IsInitialized) throw new InvalidOperationException ("call Forms.Init() before this"); + + var existingRenderer = Ooui.Forms.Platform.GetRenderer (page); + if (existingRenderer != null) + return existingRenderer.NativeView; + return CreateElement (page); + } + + static Ooui.Element CreateElement (this Xamarin.Forms.Page page) + { if (!(page.RealParent is Application)) { var app = new DefaultApplication (); app.MainPage = page; } - var result = new Ooui.Forms.Platform (); result.SetPage (page); return result.Element; diff --git a/Samples/BoxViewClockSample.cs b/Samples/BoxViewClockSample.cs index 2fb684b..f576061 100644 --- a/Samples/BoxViewClockSample.cs +++ b/Samples/BoxViewClockSample.cs @@ -7,9 +7,17 @@ namespace Samples { public string Title => "Xamarin.Forms BoxViewClock"; + BoxViewClockPage page; + public Ooui.Element CreateElement () { - return new BoxViewClockPage ().CreateElement (); + // + // Always return the same page because the code never stops the timer + // and we don't want to create an unlimited number of them. + // + if (page == null) + page = new BoxViewClockPage (); + return page.GetOouiElement (); } class BoxViewClockPage : ContentPage diff --git a/Samples/XamarinFormsSample.cs b/Samples/XamarinFormsSample.cs index e555ac7..0adccdf 100644 --- a/Samples/XamarinFormsSample.cs +++ b/Samples/XamarinFormsSample.cs @@ -44,12 +44,12 @@ namespace Samples var page = MakePage (); page.Publish ("/xamarin-forms-shared"); - Ooui.UI.Publish ("/xamarin-forms", () => MakePage ().CreateElement ()); + Ooui.UI.Publish ("/xamarin-forms", () => MakePage ().GetOouiElement ()); } public Ooui.Element CreateElement () { - return MakePage ().CreateElement (); + return MakePage ().GetOouiElement (); } } } From d351b07be46191b461d4a8d4069f6ceaeb959212 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 10 Nov 2017 11:32:51 -0800 Subject: [PATCH 04/32] Add BoxRenderer and fix color messaging --- Ooui.Forms/Exports.cs | 1 + Ooui.Forms/Extensions/ColorExtensions.cs | 2 +- Ooui.Forms/Forms.cs | 1 + Ooui.Forms/Renderers/BoxRenderer.cs | 41 +++++++++++ Ooui/Client.js | 2 +- Ooui/Color.cs | 86 ++++++++++++++++++++---- 6 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 Ooui.Forms/Renderers/BoxRenderer.cs diff --git a/Ooui.Forms/Exports.cs b/Ooui.Forms/Exports.cs index fc910e4..566c468 100644 --- a/Ooui.Forms/Exports.cs +++ b/Ooui.Forms/Exports.cs @@ -5,6 +5,7 @@ using Xamarin.Forms; using Xamarin.Forms.Internals; [assembly: Dependency (typeof (ResourcesProvider))] +[assembly: ExportRenderer (typeof (BoxView), typeof (BoxRenderer))] [assembly: ExportRenderer (typeof (Button), typeof (ButtonRenderer))] [assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))] diff --git a/Ooui.Forms/Extensions/ColorExtensions.cs b/Ooui.Forms/Extensions/ColorExtensions.cs index 547fc56..ad432ab 100644 --- a/Ooui.Forms/Extensions/ColorExtensions.cs +++ b/Ooui.Forms/Extensions/ColorExtensions.cs @@ -6,7 +6,7 @@ namespace Ooui.Forms.Extensions { public static Color ToOouiColor (this Xamarin.Forms.Color color) { - return new Color ((byte)(color.R * 255.0 + 0.5), (byte)(color.G * 255.0 + 0.5), (byte)(color.B * 255.0 + 0.5), (byte)(color.A * 255.0 + 0.5)); + return new Color ((byte)(color.R * 255.0 + 0.5), (byte)(color.G * 255.0 + 0.5), (byte)(color.B * 255.0 + 0.5), (byte)(color.A * 255.0 + 0.5)); } public static Color ToOouiColor (this Xamarin.Forms.Color color, Xamarin.Forms.Color defaultColor) diff --git a/Ooui.Forms/Forms.cs b/Ooui.Forms/Forms.cs index cb0fefd..c5a39f7 100644 --- a/Ooui.Forms/Forms.cs +++ b/Ooui.Forms/Forms.cs @@ -26,6 +26,7 @@ namespace Xamarin.Forms Device.SetIdiom (TargetIdiom.Desktop); Device.PlatformServices = new OouiPlatformServices (); Device.Info = new OouiDeviceInfo (); + Color.SetAccent (Color.FromHex ("#0000EE")); // Safari Blue Registrar.RegisterAll (new[] { typeof(ExportRendererAttribute), diff --git a/Ooui.Forms/Renderers/BoxRenderer.cs b/Ooui.Forms/Renderers/BoxRenderer.cs new file mode 100644 index 0000000..ee64082 --- /dev/null +++ b/Ooui.Forms/Renderers/BoxRenderer.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel; +using Ooui.Forms.Extensions; +using Xamarin.Forms; + +namespace Ooui.Forms.Renderers +{ + public class BoxRenderer : VisualElementRenderer + { + Ooui.Color _colorToRenderer; + + protected override void OnElementChanged (ElementChangedEventArgs e) + { + base.OnElementChanged (e); + + if (Element != null) + SetBackgroundColor (Element.BackgroundColor); + } + + protected override void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged (sender, e); + if (e.PropertyName == BoxView.ColorProperty.PropertyName) + SetBackgroundColor (Element.BackgroundColor); + } + + protected override void SetBackgroundColor (Xamarin.Forms.Color color) + { + if (Element == null) + return; + + var elementColor = Element.Color; + if (!elementColor.IsDefault) + _colorToRenderer = elementColor.ToOouiColor (); + else + _colorToRenderer = Colors.Clear; + + Style.BackgroundColor = _colorToRenderer; + } + } +} diff --git a/Ooui/Client.js b/Ooui/Client.js index bff2f13..b6a1cd7 100644 --- a/Ooui/Client.js +++ b/Ooui/Client.js @@ -1,5 +1,5 @@ -var debug = false; +var debug = true; const nodes = {}; diff --git a/Ooui/Color.cs b/Ooui/Color.cs index 7b914ad..476e1cd 100644 --- a/Ooui/Color.cs +++ b/Ooui/Color.cs @@ -1,20 +1,21 @@ using System; - +using Newtonsoft.Json; using StyleValue = System.Object; namespace Ooui { - public struct Color + [Newtonsoft.Json.JsonConverter (typeof (ColorJsonConverter))] + public struct Color : IEquatable { public byte R, G, B, A; public Color (byte r, byte g, byte b, byte a) - { - R = r; - G = g; - B = b; - A = a; - } + { + R = r; + G = g; + B = b; + A = a; + } public double Red { get => R / 255.0; @@ -33,9 +34,70 @@ namespace Ooui set => A = value >= 1.0 ? (byte)255 : ((value <= 0.0) ? (byte)0 : (byte)(value * 255.0 + 0.5)); } - public static Color FromStyleValue (StyleValue styleColor) - { + public override bool Equals (object obj) + { + if (obj is Color other) + return R == other.R && G == other.G && B == other.B && A == other.A; + return false; + } + + public bool Equals (Color other) => R == other.R && G == other.G && B == other.B && A == other.A; + + public override int GetHashCode () => R.GetHashCode () + G.GetHashCode () * 2 + B.GetHashCode () * 3 + A.GetHashCode () * 5; + + public static Color FromStyleValue (StyleValue styleColor) + { + if (styleColor is Color c) + return c; + if (styleColor is string s) + return Parse (s); return Colors.Clear; - } - } + } + + public static Color Parse (string styleValue) + { + if (string.IsNullOrWhiteSpace (styleValue) || styleValue.Length < 4) + throw new ArgumentException ("Cannot parse empty strings", nameof (styleValue)); + + if (styleValue.Length > 32) + throw new ArgumentException ("Color string is too long", nameof (styleValue)); + + if (styleValue == "inherit") + return Colors.Clear; + + //if (styleValue[0] == '#' && styleValue.Length == 4) { + //} + + //if (styleValue[0] == '#' && styleValue.Length == 7) { + //} + + throw new ArgumentException ($"Cannot parse color string `{styleValue}`", nameof (styleValue)); + } + + public override string ToString () + { + if (A == 255) + return string.Format ("#{0:x2}{1:x2}{2:x2}", R, G, B); + return string.Format ("rgba({0},{1},{2},{3})", R, G, B, A / 255.0); + } + } + + class ColorJsonConverter : Newtonsoft.Json.JsonConverter + { + public override bool CanConvert (Type objectType) + { + return objectType == typeof (Color); + } + + public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var str = reader.ReadAsString (); + return Color.Parse (str); + } + + public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue (value.ToString ()); + } + } } From 75841b4a7eeacfced946d0b4361777c4b9148352 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 10 Nov 2017 12:51:35 -0800 Subject: [PATCH 05/32] Fix synchronization of the transmit queue --- Ooui/UI.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Ooui/UI.cs b/Ooui/UI.cs index 1e4bd24..35889f1 100644 --- a/Ooui/UI.cs +++ b/Ooui/UI.cs @@ -570,15 +570,15 @@ namespace Ooui } } - void QueueStateMessages (EventTarget target) + void QueueStateMessagesLocked (EventTarget target) { if (target == null) return; foreach (var m in target.StateMessages) { - QueueMessage (m); + QueueMessageLocked (m); } } - void QueueMessage (Message message) + void QueueMessageLocked (Message message) { // // Make sure all the referenced objects have been created @@ -589,23 +589,29 @@ namespace Ooui else { if (!createdIds.Contains (message.TargetId)) { createdIds.Add (message.TargetId); - QueueStateMessages (element.GetElementById (message.TargetId)); + QueueStateMessagesLocked (element.GetElementById (message.TargetId)); } if (message.Value is Array a) { for (var i = 0; i < a.Length; i++) { // Console.WriteLine ($"A{i} = {a.GetValue(i)}"); if (a.GetValue (i) is EventTarget e && !createdIds.Contains (e.Id)) { createdIds.Add (e.Id); - QueueStateMessages (e); + QueueStateMessagesLocked (e); } } } } - // // Add it to the queue // - lock (queuedMessages) queuedMessages.Add (message); + queuedMessages.Add (message); + } + + void QueueMessage (Message message) + { + lock (queuedMessages) { + QueueMessageLocked (message); + } sendThrottle.Enabled = true; } From 6ec5bbe5a31e334586088beab5031292616d6039 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 10 Nov 2017 13:41:51 -0800 Subject: [PATCH 06/32] Apply transforms --- Ooui.Forms/VisualElementTracker.cs | 39 +++++++++++++++--------------- Ooui/Client.js | 2 +- Ooui/Style.cs | 10 ++++++++ 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/Ooui.Forms/VisualElementTracker.cs b/Ooui.Forms/VisualElementTracker.cs index 6b1fdd7..6efb363 100644 --- a/Ooui.Forms/VisualElementTracker.cs +++ b/Ooui.Forms/VisualElementTracker.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Threading; using Xamarin.Forms; @@ -157,33 +158,33 @@ namespace Ooui.Forms uiview.Style.Opacity = opacity; } - //var transform = 0; - //const double epsilon = 0.001; - //caLayer.AnchorPoint = new PointF (anchorX - 0.5f, anchorY - 0.5f); + var transforms = ""; + var transformOrigin = default (string); + const double epsilon = 0.001; - //// position is relative to anchor point - //if (Math.Abs (anchorX - .5) > epsilon) - // transform = transform.Translate ((anchorX - .5f) * width, 0, 0); - //if (Math.Abs (anchorY - .5) > epsilon) - // transform = transform.Translate (0, (anchorY - .5f) * height, 0); + var icult = System.Globalization.CultureInfo.InvariantCulture; - //if (Math.Abs (translationX) > epsilon || Math.Abs (translationY) > epsilon) - // transform = transform.Translate (translationX, translationY, 0); + // position is relative to anchor point + if ((Math.Abs (anchorX - 0.5) > epsilon) || (Math.Abs (anchorY - 0.5) > epsilon)) { + transformOrigin = string.Format (icult, "{0:0.######}% {1:0.######}%", anchorX*100, anchorY*100); + } - //if (Math.Abs (scale - 1) > epsilon) - // transform = transform.Scale (scale); + if (Math.Abs (translationX) > epsilon || Math.Abs (translationY) > epsilon) + transforms = string.Format (icult, "{0} translate({1:0.######}px,{2:0.######}px)", transforms, translationX, translationY); - //// not just an optimization, iOS will not "pixel align" a view which has m34 set - //if (Math.Abs (rotationY % 180) > epsilon || Math.Abs (rotationX % 180) > epsilon) - // transform.m34 = 1.0f / -400f; + if (Math.Abs (scale - 1) > epsilon) + transforms = string.Format (icult, "{0} scale({1:0.######},{1:0.######})", transforms, scale); //if (Math.Abs (rotationX % 360) > epsilon) - // transform = transform.Rotate (rotationX * (float)Math.PI / 180.0f, 1.0f, 0.0f, 0.0f); + // RotateX (rotationX); //if (Math.Abs (rotationY % 360) > epsilon) - // transform = transform.Rotate (rotationY * (float)Math.PI / 180.0f, 0.0f, 1.0f, 0.0f); + //RotateY (rotationY); - //transform = transform.Rotate (rotation * (float)Math.PI / 180.0f, 0.0f, 0.0f, 1.0f); - //caLayer.Transform = transform; + if (Math.Abs (rotation % 360) > epsilon) + transforms = string.Format (icult, "{0} rotate({1:0.######}deg)", transforms, rotation); + + uiview.Style.Transform = transforms.Length > 0 ? transforms : null; + uiview.Style.TransformOrigin = transforms.Length > 0 ? transformOrigin : null; _lastBounds = view.Bounds; _lastParentBounds = viewParent?.Bounds ?? Rectangle.Zero; diff --git a/Ooui/Client.js b/Ooui/Client.js index b6a1cd7..bff2f13 100644 --- a/Ooui/Client.js +++ b/Ooui/Client.js @@ -1,5 +1,5 @@ -var debug = true; +var debug = false; const nodes = {}; diff --git a/Ooui/Style.cs b/Ooui/Style.cs index a92f6d3..1a0fedb 100644 --- a/Ooui/Style.cs +++ b/Ooui/Style.cs @@ -304,6 +304,16 @@ namespace Ooui set => this["top"] = value; } + public Value Transform { + get => this["transform"]; + set => this["transform"] = value; + } + + public Value TransformOrigin { + get => this["transform-origin"]; + set => this["transform-origin"] = value; + } + public Value VerticalAlign { get => this["vertical-align"]; set => this["vertical-align"] = value; From d63d7e7ffcff00bb9b3b2962ff6dccc5be42bbac Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 21:58:48 -0600 Subject: [PATCH 07/32] Improve initial state transfer locking --- Ooui/UI.cs | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/Ooui/UI.cs b/Ooui/UI.cs index 35889f1..17e6c77 100644 --- a/Ooui/UI.cs +++ b/Ooui/UI.cs @@ -573,8 +573,15 @@ namespace Ooui void QueueStateMessagesLocked (EventTarget target) { if (target == null) return; + var created = false; foreach (var m in target.StateMessages) { - QueueMessageLocked (m); + if (m.MessageType == MessageType.Create) { + createdIds.Add (m.TargetId); + created = true; + } + if (created) { + QueueMessageLocked (m); + } } } @@ -583,24 +590,23 @@ namespace Ooui // // Make sure all the referenced objects have been created // - if (message.MessageType == MessageType.Create) { - createdIds.Add (message.TargetId); + if (!createdIds.Contains (message.TargetId)) { + QueueStateMessagesLocked (element.GetElementById (message.TargetId)); } - else { - if (!createdIds.Contains (message.TargetId)) { - createdIds.Add (message.TargetId); - QueueStateMessagesLocked (element.GetElementById (message.TargetId)); + if (message.Value is EventTarget ve) { + if (!createdIds.Contains (ve.Id)) { + QueueStateMessagesLocked (ve); } - if (message.Value is Array a) { - for (var i = 0; i < a.Length; i++) { - // Console.WriteLine ($"A{i} = {a.GetValue(i)}"); - if (a.GetValue (i) is EventTarget e && !createdIds.Contains (e.Id)) { - createdIds.Add (e.Id); - QueueStateMessagesLocked (e); - } + } + else if (message.Value is Array a) { + for (var i = 0; i < a.Length; i++) { + // Console.WriteLine ($"A{i} = {a.GetValue(i)}"); + if (a.GetValue (i) is EventTarget e && !createdIds.Contains (e.Id)) { + QueueStateMessagesLocked (e); } } } + // // Add it to the queue // From b67e87d19e80b0f9e8b5c8d4753032ddb08c58ce Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 21:59:01 -0600 Subject: [PATCH 08/32] Attempt to gracefully close the socket --- Ooui/Client.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Ooui/Client.js b/Ooui/Client.js index bff2f13..779ca36 100644 --- a/Ooui/Client.js +++ b/Ooui/Client.js @@ -18,6 +18,15 @@ const mouseEvents = { wheel: true, }; +window.onbeforeunload = function() { + if (socket != null) { + socket.close (1001, "Unloading page"); + socket = null; + console.log ("Web socket closed"); + } + return null; +} + function ooui (rootElementPath) { var opened = false; @@ -135,7 +144,8 @@ function msgListen (m) { }; } const ems = JSON.stringify (em); - socket.send (ems); + if (socket != null) + socket.send (ems); if (debug) console.log ("Event", em); if (em.k === "submit") e.preventDefault (); From 3a7b01e747b431ff86c7f64ecb37d7e895da6d2d Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 22:01:51 -0600 Subject: [PATCH 09/32] Use command line args when configuring the server --- PlatformSamples/AspNetCoreMvc/Program.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/PlatformSamples/AspNetCoreMvc/Program.cs b/PlatformSamples/AspNetCoreMvc/Program.cs index c6632a9..ac9502b 100644 --- a/PlatformSamples/AspNetCoreMvc/Program.cs +++ b/PlatformSamples/AspNetCoreMvc/Program.cs @@ -12,14 +12,15 @@ namespace AspNetCoreMvc { public class Program { - public static void Main(string[] args) + public static void Main (string[] args) { - BuildWebHost(args).Run(); + BuildWebHost (args).Run (); } - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); + public static IWebHost BuildWebHost (string[] args) => + WebHost.CreateDefaultBuilder (args) + .UseConfiguration (new ConfigurationBuilder ().AddCommandLine (args).Build ()) + .UseStartup () + .Build (); } } From 83fceb7ed100997883fd525c953555e2d28af939 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 22:20:09 -0600 Subject: [PATCH 10/32] Add titles to ElementResults --- Ooui.AspNetCore/ElementResult.cs | 15 +++++++++------ Ooui/UI.cs | 4 ++-- .../Controllers/SamplesController.cs | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Ooui.AspNetCore/ElementResult.cs b/Ooui.AspNetCore/ElementResult.cs index f43d342..23234a6 100644 --- a/Ooui.AspNetCore/ElementResult.cs +++ b/Ooui.AspNetCore/ElementResult.cs @@ -7,11 +7,13 @@ namespace Ooui.AspNetCore { public class ElementResult : ActionResult { - readonly Element element; + readonly Element element; + readonly string title; - public ElementResult (Element element) + public ElementResult (Element element, string title = "") { - this.element = element; + this.element = element; + this.title = title; } public override async Task ExecuteResultAsync (ActionContext context) @@ -20,10 +22,11 @@ namespace Ooui.AspNetCore response.StatusCode = 200; response.ContentType = "text/html; charset=utf-8"; var sessionId = WebSocketHandler.BeginSession (context.HttpContext, element); - var html = Encoding.UTF8.GetBytes (UI.RenderTemplate (WebSocketHandler.WebSocketPath + "?id=" + sessionId)); - response.ContentLength = html.Length; + var html = UI.RenderTemplate (WebSocketHandler.WebSocketPath + "?id=" + sessionId, title: title); + var htmlBytes = Encoding.UTF8.GetBytes (html); + response.ContentLength = htmlBytes.Length; using (var s = response.Body) { - await s.WriteAsync (html, 0, html.Length).ConfigureAwait (false); + await s.WriteAsync (htmlBytes, 0, htmlBytes.Length).ConfigureAwait (false); } } } diff --git a/Ooui/UI.cs b/Ooui/UI.cs index 17e6c77..213143e 100644 --- a/Ooui/UI.cs +++ b/Ooui/UI.cs @@ -299,9 +299,9 @@ namespace Ooui } } - public static string RenderTemplate (string webSocketPath) + public static string RenderTemplate (string webSocketPath, string title = "") { - return Template.Replace ("@WebSocketPath", webSocketPath).Replace ("@Styles", rules.ToString ()); + return Template.Replace ("@WebSocketPath", webSocketPath).Replace ("@Styles", rules.ToString ()).Replace ("@Title", title); } class DataHandler : RequestHandler diff --git a/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs b/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs index 640f9b5..3503207 100644 --- a/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs +++ b/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs @@ -51,7 +51,7 @@ namespace AspNetCoreMvc.Controllers if (s == null) return NotFound (); - return new ElementResult (s.CreateElement ()); + return new ElementResult (s.CreateElement (), title: s.Title + " - Ooui Samples"); } } } From 7cf286846c657cf91f5dbecaacf2fb123b6c19d1 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 22:32:17 -0600 Subject: [PATCH 11/32] Cleanup the homepage --- .../Controllers/HomeController.cs | 21 ++----------------- .../Controllers/SamplesController.cs | 6 +++++- .../AspNetCoreMvc/Views/Home/About.cshtml | 2 +- .../AspNetCoreMvc/Views/Home/Contact.cshtml | 11 ---------- .../AspNetCoreMvc/Views/Home/Index.cshtml | 2 +- .../AspNetCoreMvc/Views/Shared/_Layout.cshtml | 1 - Samples/ButtonSample.cs | 2 +- Samples/DrawSample.cs | 2 +- Samples/FilesSample.cs | 2 +- Samples/TodoSample.cs | 2 +- 10 files changed, 13 insertions(+), 38 deletions(-) diff --git a/PlatformSamples/AspNetCoreMvc/Controllers/HomeController.cs b/PlatformSamples/AspNetCoreMvc/Controllers/HomeController.cs index 11687ab..d8433eb 100644 --- a/PlatformSamples/AspNetCoreMvc/Controllers/HomeController.cs +++ b/PlatformSamples/AspNetCoreMvc/Controllers/HomeController.cs @@ -18,33 +18,16 @@ namespace AspNetCoreMvc.Controllers return View (); } - public IActionResult Clicker () - { - var count = 0; - var head = new Heading { Text = "Click away!" }; - var label = new Label { Text = "0" }; - var btn = new Button { Text = "Increase" }; - btn.Clicked += (sender, e) => { - count++; - label.Text = count.ToString (); - }; - var div = new Div (); - div.AppendChild (head); - div.AppendChild (label); - div.AppendChild (btn); - return new ElementResult (div); - } - public IActionResult About () { - ViewData["Message"] = "Your application description page."; + ViewData["Message"] = "Ooui is a mini web framework to make programming interactive UIs easy."; return View (); } public IActionResult Contact () { - ViewData["Message"] = "Your contact page."; + ViewData["Message"] = "Find us on github."; return View (); } diff --git a/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs b/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs index 3503207..fc46088 100644 --- a/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs +++ b/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs @@ -35,7 +35,11 @@ namespace AspNetCoreMvc.Controllers var sampleType = typeof (Samples.ISample); var asm = sampleType.Assembly; var sampleTypes = asm.GetTypes ().Where (x => x.Name.EndsWith ("Sample", StringComparison.Ordinal) && x != sampleType); - var samples = from t in sampleTypes let s = Activator.CreateInstance (t) as Samples.ISample where s != null select s; + var samples = from t in sampleTypes + let s = Activator.CreateInstance (t) as Samples.ISample + where s != null + orderby s.Title + select s; return samples.ToList (); }), true); diff --git a/PlatformSamples/AspNetCoreMvc/Views/Home/About.cshtml b/PlatformSamples/AspNetCoreMvc/Views/Home/About.cshtml index a443e8b..c9f4798 100644 --- a/PlatformSamples/AspNetCoreMvc/Views/Home/About.cshtml +++ b/PlatformSamples/AspNetCoreMvc/Views/Home/About.cshtml @@ -4,4 +4,4 @@

@ViewData["Title"].

@ViewData["Message"]

-

Use this area to provide additional information.

+

Find out more on github

diff --git a/PlatformSamples/AspNetCoreMvc/Views/Home/Contact.cshtml b/PlatformSamples/AspNetCoreMvc/Views/Home/Contact.cshtml index 9d10bf9..d1ef9c9 100644 --- a/PlatformSamples/AspNetCoreMvc/Views/Home/Contact.cshtml +++ b/PlatformSamples/AspNetCoreMvc/Views/Home/Contact.cshtml @@ -4,14 +4,3 @@

@ViewData["Title"].

@ViewData["Message"]

-
- One Microsoft Way
- Redmond, WA 98052-6399
- P: - 425.555.0100 -
- -
- Support: Support@example.com
- Marketing: Marketing@example.com -
diff --git a/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml b/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml index 53b984e..baebc52 100644 --- a/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml +++ b/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml @@ -13,7 +13,7 @@
-
+

Samples

    @foreach (var s in SamplesController.Samples) { diff --git a/PlatformSamples/AspNetCoreMvc/Views/Shared/_Layout.cshtml b/PlatformSamples/AspNetCoreMvc/Views/Shared/_Layout.cshtml index 25513d3..ae68cea 100644 --- a/PlatformSamples/AspNetCoreMvc/Views/Shared/_Layout.cshtml +++ b/PlatformSamples/AspNetCoreMvc/Views/Shared/_Layout.cshtml @@ -32,7 +32,6 @@
diff --git a/Samples/ButtonSample.cs b/Samples/ButtonSample.cs index 2a2d685..455499a 100644 --- a/Samples/ButtonSample.cs +++ b/Samples/ButtonSample.cs @@ -5,7 +5,7 @@ namespace Samples { public class ButtonSample : ISample { - public string Title => "Button that count clicks"; + public string Title => "Button Counter"; Button MakeButton () { diff --git a/Samples/DrawSample.cs b/Samples/DrawSample.cs index e6db2bd..5e70226 100644 --- a/Samples/DrawSample.cs +++ b/Samples/DrawSample.cs @@ -7,7 +7,7 @@ namespace Samples { public class DrawSample : ISample { - public string Title => "Collaborative Drawing"; + public string Title => "Drawing"; public void Publish () { diff --git a/Samples/FilesSample.cs b/Samples/FilesSample.cs index d1bdfa8..fbb05d5 100644 --- a/Samples/FilesSample.cs +++ b/Samples/FilesSample.cs @@ -6,7 +6,7 @@ using Ooui; namespace Samples { - public class FilesSample : ISample + public class FilesSample //: ISample { public string Title => "Upload files"; diff --git a/Samples/TodoSample.cs b/Samples/TodoSample.cs index 7f58ac7..42e1faa 100644 --- a/Samples/TodoSample.cs +++ b/Samples/TodoSample.cs @@ -7,7 +7,7 @@ namespace Samples { public class TodoSample : ISample { - public string Title => "Global TODO list"; + public string Title => "Todo List"; List items = new List () { ClassName = "list-group", From a2aec7fdb344a9ff6ba63ee096f23ae8f0a99695 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 22:49:05 -0600 Subject: [PATCH 12/32] Use bootstrap for styling --- Ooui/UI.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Ooui/UI.cs b/Ooui/UI.cs index 213143e..079972b 100644 --- a/Ooui/UI.cs +++ b/Ooui/UI.cs @@ -34,10 +34,11 @@ namespace Ooui @Title + -
+
From a43508253eb3cd09e789b538df277a19a2c6835f Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 22:49:15 -0600 Subject: [PATCH 13/32] Remember F# --- PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml b/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml index baebc52..d7f81f0 100644 --- a/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml +++ b/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml @@ -8,7 +8,7 @@

Ooui

-

Write interactive web apps in C#

+

Write interactive web apps in C# and F#

From 813b48bbdbfefda0cc8655a203571debaef75b35 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 23:03:36 -0600 Subject: [PATCH 14/32] Add shared (collaborative) samples --- .../Controllers/SamplesController.cs | 20 +++++++++++++++++-- .../AspNetCoreMvc/Views/Home/Index.cshtml | 5 ++++- Samples/DrawSample.cs | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs b/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs index fc46088..8fdb508 100644 --- a/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs +++ b/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs @@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Mvc; using AspNetCoreMvc.Models; using Ooui; using Ooui.AspNetCore; +using Samples; +using System.Collections.Concurrent; namespace AspNetCoreMvc.Controllers { @@ -43,10 +45,13 @@ namespace AspNetCoreMvc.Controllers return samples.ToList (); }), true); + static readonly ConcurrentDictionary sharedSamples = + new ConcurrentDictionary (); + public static List Samples => lazySamples.Value; [Route("/Samples/Run/{name}")] - public IActionResult Run (string name) + public IActionResult Run (string name, bool shared) { if (string.IsNullOrWhiteSpace (name) || name.Length > 32) return BadRequest (); @@ -55,7 +60,18 @@ namespace AspNetCoreMvc.Controllers if (s == null) return NotFound (); - return new ElementResult (s.CreateElement (), title: s.Title + " - Ooui Samples"); + var element = shared ? GetSharedSample (s) : s.CreateElement (); + + return new ElementResult (element, title: s.Title + " - Ooui Samples"); + } + + private Element GetSharedSample (ISample s) + { + if (sharedSamples.TryGetValue (s.Title, out var e)) + return e; + e = s.CreateElement (); + sharedSamples[s.Title] = e; + return e; } } } diff --git a/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml b/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml index d7f81f0..6fb9f7f 100644 --- a/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml +++ b/PlatformSamples/AspNetCoreMvc/Views/Home/Index.cshtml @@ -17,7 +17,10 @@

Samples

diff --git a/Samples/DrawSample.cs b/Samples/DrawSample.cs index 5e70226..4adead0 100644 --- a/Samples/DrawSample.cs +++ b/Samples/DrawSample.cs @@ -17,7 +17,7 @@ namespace Samples public Element CreateElement () { var heading = new Heading ("Draw"); - var subtitle = new Paragraph ("Click to draw a collaborative masterpiece"); + var subtitle = new Paragraph ("Click to draw a masterpiece"); var canvas = new Canvas { Width = 320, Height = 240, From 5cc7f15909c5c1299959afa9e4a81cad19109959 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 23:48:12 -0600 Subject: [PATCH 15/32] Add /shared-button url --- .../AspNetCoreMvc/Controllers/SamplesController.cs | 10 ++++++++-- Samples/ButtonSample.cs | 5 ++++- Samples/TodoSample.cs | 14 +++++--------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs b/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs index 8fdb508..cf4aa7a 100644 --- a/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs +++ b/PlatformSamples/AspNetCoreMvc/Controllers/SamplesController.cs @@ -50,12 +50,12 @@ namespace AspNetCoreMvc.Controllers public static List Samples => lazySamples.Value; - [Route("/Samples/Run/{name}")] + [Route ("/Samples/Run/{name}")] public IActionResult Run (string name, bool shared) { if (string.IsNullOrWhiteSpace (name) || name.Length > 32) return BadRequest (); - + var s = Samples.FirstOrDefault (x => x.Title == name); if (s == null) return NotFound (); @@ -73,5 +73,11 @@ namespace AspNetCoreMvc.Controllers sharedSamples[s.Title] = e; return e; } + + [Route ("/shared-button")] + public IActionResult SharedButton () + { + return Run ("Button Counter", true); + } } } diff --git a/Samples/ButtonSample.cs b/Samples/ButtonSample.cs index 455499a..7d8a08f 100644 --- a/Samples/ButtonSample.cs +++ b/Samples/ButtonSample.cs @@ -9,7 +9,10 @@ namespace Samples Button MakeButton () { - var button = new Button ("Click me!"); + var button = new Button ("Click me!") { + ClassName = "btn btn-primary", // Some bootstrap styling + }; + button.Style.MarginTop = "2em"; var count = 0; button.Clicked += (s, e) => { count++; diff --git a/Samples/TodoSample.cs b/Samples/TodoSample.cs index 42e1faa..839c8ab 100644 --- a/Samples/TodoSample.cs +++ b/Samples/TodoSample.cs @@ -9,15 +9,6 @@ namespace Samples { public string Title => "Todo List"; - List items = new List () { - ClassName = "list-group", - }; - - public TodoSample () - { - items.Style.MarginTop = "1em"; - } - class Item : ListItem { Element label = new Div (); @@ -48,6 +39,11 @@ namespace Samples Element MakeTodo () { + List items = new List () { + ClassName = "list-group", + }; + items.Style.MarginTop = "1em"; + var heading = new Heading ("Todo List"); var subtitle = new Paragraph ("This is the shared todo list of the world."); var inputForm = new Form { From 64cc7a22d836aa16f2bddd5894442ab5c5f261b8 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 23:51:02 -0600 Subject: [PATCH 16/32] Update samples URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4599264..31e7782 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It presents a classic object-oriented UI API that controls a dumb browser. With ## Try it Online -Head on over to [http://ooui.mecha.parts:8080/shared-button](http://ooui.mecha.parts:8080/shared-button) and give it a click! +Head on over to [http://ooui.mecha.parts](http://ooui.mecha.parts) to tryout the samples. ## Try the Samples Locally From f550c55e909addad9ef920813ec95a570e36a8cb Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 15 Nov 2017 23:59:40 -0600 Subject: [PATCH 17/32] Add XAML example --- Samples/ButtonXamlPage.xaml | 9 +++++++++ Samples/ButtonXamlPage.xaml.cs | 23 +++++++++++++++++++++++ Samples/ButtonXamlPageSample.cs | 17 +++++++++++++++++ Samples/Samples.csproj | 3 +++ 4 files changed, 52 insertions(+) create mode 100755 Samples/ButtonXamlPage.xaml create mode 100755 Samples/ButtonXamlPage.xaml.cs create mode 100644 Samples/ButtonXamlPageSample.cs diff --git a/Samples/ButtonXamlPage.xaml b/Samples/ButtonXamlPage.xaml new file mode 100755 index 0000000..6f601c1 --- /dev/null +++ b/Samples/ButtonXamlPage.xaml @@ -0,0 +1,9 @@ + + + +