Update fork from latests changes

This commit is contained in:
Javier Suárez Ruiz 2017-11-26 17:14:42 +01:00
commit 5cbd9e42b5
50 changed files with 1225 additions and 383 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
# Autosave files # Autosave files
*~ *~
.vs/
# build # build
[Oo]bj/ [Oo]bj/

View File

@ -8,10 +8,12 @@ namespace Ooui.AspNetCore
public class ElementResult : ActionResult 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) public override async Task ExecuteResultAsync (ActionContext context)
@ -20,10 +22,11 @@ namespace Ooui.AspNetCore
response.StatusCode = 200; response.StatusCode = 200;
response.ContentType = "text/html; charset=utf-8"; response.ContentType = "text/html; charset=utf-8";
var sessionId = WebSocketHandler.BeginSession (context.HttpContext, element); var sessionId = WebSocketHandler.BeginSession (context.HttpContext, element);
var html = Encoding.UTF8.GetBytes (UI.RenderTemplate (WebSocketHandler.WebSocketPath + "?id=" + sessionId)); var html = UI.RenderTemplate (WebSocketHandler.WebSocketPath + "?id=" + sessionId, title: title);
response.ContentLength = html.Length; var htmlBytes = Encoding.UTF8.GetBytes (html);
response.ContentLength = htmlBytes.Length;
using (var s = response.Body) { using (var s = response.Body) {
await s.WriteAsync (html, 0, html.Length).ConfigureAwait (false); await s.WriteAsync (htmlBytes, 0, htmlBytes.Length).ConfigureAwait (false);
} }
} }
} }

View File

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

114
Ooui.Forms/DisplayAlert.cs Normal file
View File

@ -0,0 +1,114 @@
using System.Web;
using Xamarin.Forms.Internals;
namespace Ooui.Forms
{
public class DisplayAlert
{
private readonly Button _closeButton;
private readonly Button _acceptButton;
private readonly Button _cancelButton;
public DisplayAlert(AlertArguments arguments)
{
Element = new Div
{
ClassName = "modal-dialog"
};
var content = new Div
{
ClassName = "modal-content"
};
var header = new Div
{
ClassName = "modal-header"
};
_closeButton = new Button
{
ClassName = "close"
};
_closeButton.AppendChild(new Span(HttpUtility.HtmlDecode("&times;")));
var h4 = new Heading(4)
{
Text = arguments.Title
};
header.AppendChild(_closeButton);
header.AppendChild(h4);
content.AppendChild(header);
content.AppendChild(new Div()
{
ClassName = "modal-body",
Text = arguments.Message
});
if (!string.IsNullOrEmpty(arguments.Cancel))
{
var footer = new Div()
{
ClassName = "modal-footer"
};
_cancelButton = new Button(arguments.Cancel)
{
ClassName = "btn btn-default"
};
_cancelButton.Clicked += (s, e) => SetResult(false);
footer.AppendChild(_cancelButton);
if (!string.IsNullOrEmpty(arguments.Accept))
{
_acceptButton = new Button(arguments.Accept)
{
ClassName = "btn btn-default"
};
_acceptButton.Clicked += (s, e) => SetResult(true);
footer.AppendChild(_acceptButton);
}
content.AppendChild(footer);
}
Element.AppendChild(content);
void SetResult(bool result)
{
arguments.SetResult(result);
}
}
public event TargetEventHandler Clicked
{
add
{
_closeButton.Clicked += value;
if(_cancelButton != null)
_cancelButton.Clicked += value;
if(_acceptButton != null)
_acceptButton.Clicked += value;
}
remove
{
_closeButton.Clicked -= value;
if (_cancelButton != null)
_cancelButton.Clicked -= value;
if (_acceptButton != null)
_acceptButton.Clicked -= value;
}
}
public Element Element { get; private set; }
}
}

View File

@ -5,6 +5,7 @@ using Xamarin.Forms;
using Xamarin.Forms.Internals; using Xamarin.Forms.Internals;
[assembly: Dependency (typeof (ResourcesProvider))] [assembly: Dependency (typeof (ResourcesProvider))]
[assembly: ExportRenderer (typeof (BoxView), typeof (BoxRenderer))]
[assembly: ExportRenderer (typeof (Button), typeof (ButtonRenderer))] [assembly: ExportRenderer (typeof (Button), typeof (ButtonRenderer))]
[assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))] [assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))]

View File

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

View File

@ -5,8 +5,55 @@ namespace Ooui.Forms.Extensions
{ {
public static class FontExtensions public static class FontExtensions
{ {
public static void SetStyleFont (this View view, Style style) public static void SetStyleFont (this View view, string family, double size, FontAttributes attrs, Style style)
{ {
#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
if (size == 14.0) {
style.FontSize = null;
}
else {
style.FontSize = size;
}
#pragma warning restore RECS0018 // Comparison of floating point numbers with equality operator
if (string.IsNullOrEmpty (family)) {
style.FontFamily = null;
}
else {
style.FontFamily = family;
}
if (attrs.HasFlag (FontAttributes.Bold)) {
style.FontWeight = "bold";
}
else {
style.FontWeight = null;
}
if (attrs.HasFlag (FontAttributes.Italic)) {
style.FontStyle = "italic";
}
else {
style.FontStyle = null;
}
}
public static Size MeasureSize (this string text, string fontFamily, double fontSize, FontAttributes fontAttrs)
{
if (string.IsNullOrEmpty (text))
return Size.Zero;
var fontHeight = fontSize;
var charWidth = fontSize * 0.5;
var width = text.Length * charWidth;
return new Size (width, fontHeight);
}
public static Size MeasureSize (this string text, Style style)
{
return MeasureSize (text, "", 14, FontAttributes.None);
} }
public static string ToOouiTextAlign (this TextAlignment align) public static string ToOouiTextAlign (this TextAlignment align)
@ -22,17 +69,5 @@ namespace Ooui.Forms.Extensions
} }
} }
public static Size MeasureSize (this string text, Style style)
{
if (string.IsNullOrEmpty (text))
return Size.Zero;
var fontHeight = 16.0;
var charWidth = 8.0;
var width = text.Length * charWidth;
return new Size (width, fontHeight);
}
} }
} }

View File

@ -26,6 +26,7 @@ namespace Xamarin.Forms
Device.SetIdiom (TargetIdiom.Desktop); Device.SetIdiom (TargetIdiom.Desktop);
Device.PlatformServices = new OouiPlatformServices (); Device.PlatformServices = new OouiPlatformServices ();
Device.Info = new OouiDeviceInfo (); Device.Info = new OouiDeviceInfo ();
Color.SetAccent (Color.FromHex ("#0000EE")); // Safari Blue
Registrar.RegisterAll (new[] { Registrar.RegisterAll (new[] {
typeof(ExportRendererAttribute), typeof(ExportRendererAttribute),
@ -110,7 +111,13 @@ namespace Xamarin.Forms
public void StartTimer (TimeSpan interval, Func<bool> callback) public void StartTimer (TimeSpan interval, Func<bool> callback)
{ {
throw new NotImplementedException (); Timer timer = null;
timer = new Timer ((_ => {
if (!callback ()) {
timer?.Dispose ();
timer = null;
}
}), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds);
} }
} }

View File

@ -15,7 +15,6 @@
<PackageReference Include="Xamarin.Forms" Version="2.4.0.38779" /> <PackageReference Include="Xamarin.Forms" Version="2.4.0.38779" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Renderers\" />
<Folder Include="Extensions\" /> <Folder Include="Extensions\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -16,16 +16,24 @@ namespace Xamarin.Forms
Ooui.UI.Publish (path, () => lazyPage.Value); 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) if (!Xamarin.Forms.Forms.IsInitialized)
throw new InvalidOperationException ("call Forms.Init() before this"); 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)) { if (!(page.RealParent is Application)) {
var app = new DefaultApplication (); var app = new DefaultApplication ();
app.MainPage = page; app.MainPage = page;
} }
var result = new Ooui.Forms.Platform (); var result = new Ooui.Forms.Platform ();
result.SetPage (page); result.SetPage (page);
return result.Element; return result.Element;

View File

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Ooui.Forms.Renderers; using Ooui.Forms.Renderers;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Internals; using Xamarin.Forms.Internals;
using System.Web;
namespace Ooui.Forms namespace Ooui.Forms
{ {
@ -31,6 +32,20 @@ namespace Ooui.Forms
public Platform () public Platform ()
{ {
_renderer = new PlatformRenderer (this); _renderer = new PlatformRenderer (this);
_renderer.Style.PropertyChanged += HandleRendererStyle_PropertyChanged;
MessagingCenter.Subscribe (this, Page.AlertSignalName, (Page sender, AlertArguments arguments) => {
var alert = new DisplayAlert (arguments);
alert.Clicked += CloseAlert;
_renderer.AppendChild (alert.Element);
void CloseAlert (object s, EventArgs e)
{
_renderer.RemoveChild (alert.Element);
}
});
} }
void IDisposable.Dispose () void IDisposable.Dispose ()
@ -114,6 +129,12 @@ namespace Ooui.Forms
Console.Error.WriteLine ("Potential view double add"); Console.Error.WriteLine ("Potential view double add");
} }
void HandleRendererStyle_PropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var pageRenderer = GetRenderer (Page);
pageRenderer?.SetElementSize (Ooui.Forms.Extensions.ElementExtensions.GetSizeRequest (_renderer, double.PositiveInfinity, double.PositiveInfinity).Request);
}
void INavigation.InsertPageBefore (Page page, Page before) void INavigation.InsertPageBefore (Page page, Page before)
{ {
throw new NotImplementedException (); throw new NotImplementedException ();

View File

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

View File

@ -0,0 +1,41 @@
using System;
using System.ComponentModel;
using Ooui.Forms.Extensions;
using Xamarin.Forms;
namespace Ooui.Forms.Renderers
{
public class BoxRenderer : VisualElementRenderer<BoxView>
{
Ooui.Color _colorToRenderer;
protected override void OnElementChanged (ElementChangedEventArgs<BoxView> 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;
}
}
}

View File

@ -12,6 +12,13 @@ namespace Ooui.Forms.Renderers
Ooui.Color _buttonTextColorDefaultHighlighted; Ooui.Color _buttonTextColorDefaultHighlighted;
Ooui.Color _buttonTextColorDefaultNormal; Ooui.Color _buttonTextColorDefaultNormal;
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
{
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
size = new Size (size.Width, size.Height * 1.428 + 14);
return new SizeRequest (size, size);
}
protected override void Dispose (bool disposing) protected override void Dispose (bool disposing)
{ {
if (Control != null) { if (Control != null) {
@ -31,6 +38,8 @@ namespace Ooui.Forms.Renderers
Debug.Assert (Control != null, "Control != null"); Debug.Assert (Control != null, "Control != null");
Control.ClassName = "btn btn-primary";
_buttonTextColorDefaultNormal = Ooui.Colors.Black; _buttonTextColorDefaultNormal = Ooui.Colors.Black;
_buttonTextColorDefaultHighlighted = Ooui.Colors.Black; _buttonTextColorDefaultHighlighted = Ooui.Colors.Black;
_buttonTextColorDefaultDisabled = Ooui.Colors.Black; _buttonTextColorDefaultDisabled = Ooui.Colors.Black;
@ -98,7 +107,7 @@ namespace Ooui.Forms.Renderers
void UpdateFont () void UpdateFont ()
{ {
Element.SetStyleFont (Control.Style); Element.SetStyleFont (Element.FontFamily, Element.FontSize, Element.FontAttributes, Control.Style);
} }
void UpdateImage () void UpdateImage ()
@ -141,14 +150,10 @@ namespace Ooui.Forms.Renderers
void UpdateTextColor () void UpdateTextColor ()
{ {
if (Element.TextColor == Xamarin.Forms.Color.Default) { if (Element.TextColor == Xamarin.Forms.Color.Default) {
Control.Style.Color = _buttonTextColorDefaultNormal; Control.Style.Color = null;
Control.Style.Color = _buttonTextColorDefaultHighlighted;
Control.Style.Color = _buttonTextColorDefaultDisabled;
} }
else { else {
Control.Style.Color = Element.TextColor.ToOouiColor (); Control.Style.Color = Element.TextColor.ToOouiColor ();
Control.Style.Color = Element.TextColor.ToOouiColor ();
Control.Style.Color = _buttonTextColorDefaultDisabled;
} }
} }
} }

View File

@ -0,0 +1,167 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using Ooui.Forms.Extensions;
using Xamarin.Forms;
namespace Ooui.Forms.Renderers
{
public class EntryRenderer : ViewRenderer<Entry, Ooui.Input>
{
Ooui.Color _defaultTextColor;
bool _disposed;
static Size initialSize = Size.Zero;
public EntryRenderer ()
{
}
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
{
return base.GetDesiredSize (widthConstraint, heightConstraint);
}
IElementController ElementController => Element as IElementController;
protected override void Dispose (bool disposing)
{
if (_disposed)
return;
_disposed = true;
if (disposing) {
if (Control != null) {
//Control.Inputted -= OnEditingBegan;
Control.Inputted -= OnEditingChanged;
Control.Changed -= OnEditingEnded;
}
}
base.Dispose (disposing);
}
protected override void OnElementChanged (ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged (e);
if (e.NewElement == null)
return;
if (Control == null) {
var textField = new Ooui.Input (InputType.Text);
SetNativeControl (textField);
_defaultTextColor = Colors.Black;
textField.Inputted += OnEditingChanged;
//textField.EditingDidBegin += OnEditingBegan;
textField.Changed += OnEditingEnded;
}
UpdatePlaceholder ();
UpdatePassword ();
UpdateText ();
UpdateColor ();
UpdateFont ();
UpdateKeyboard ();
UpdateAlignment ();
}
protected override void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == Entry.PlaceholderProperty.PropertyName || e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
UpdatePlaceholder ();
else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
UpdatePassword ();
else if (e.PropertyName == Entry.TextProperty.PropertyName)
UpdateText ();
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
UpdateColor ();
else if (e.PropertyName == Xamarin.Forms.InputView.KeyboardProperty.PropertyName)
UpdateKeyboard ();
else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName)
UpdateAlignment ();
else if (e.PropertyName == Entry.FontAttributesProperty.PropertyName)
UpdateFont ();
else if (e.PropertyName == Entry.FontFamilyProperty.PropertyName)
UpdateFont ();
else if (e.PropertyName == Entry.FontSizeProperty.PropertyName)
UpdateFont ();
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) {
UpdateColor ();
UpdatePlaceholder ();
}
base.OnElementPropertyChanged (sender, e);
}
void OnEditingBegan (object sender, EventArgs e)
{
ElementController.SetValueFromRenderer (VisualElement.IsFocusedPropertyKey, true);
}
void OnEditingChanged (object sender, EventArgs eventArgs)
{
ElementController.SetValueFromRenderer (Entry.TextProperty, Control.Value);
}
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);
}
ElementController.SetValueFromRenderer (VisualElement.IsFocusedPropertyKey, false);
}
void UpdateAlignment ()
{
Control.Style.TextAlign = Element.HorizontalTextAlignment.ToOouiTextAlign ();
}
void UpdateColor ()
{
var textColor = Element.TextColor;
if (textColor.IsDefault || !Element.IsEnabled)
Control.Style.Color = _defaultTextColor;
else
Control.Style.Color = textColor.ToOouiColor ();
}
void UpdateFont ()
{
if (initialSize == Size.Zero) {
var testString = "Tj";
initialSize = testString.MeasureSize (Control.Style);
}
Element.SetStyleFont (Element.FontFamily, Element.FontSize, Element.FontAttributes, Control.Style);
}
void UpdateKeyboard ()
{
}
void UpdatePassword ()
{
Control.Type = Element.IsPassword ? InputType.Password : InputType.Text;
}
void UpdatePlaceholder ()
{
Control.Placeholder = Element.Placeholder;
}
void UpdateText ()
{
// ReSharper disable once RedundantCheckBeforeAssignment
if (Control.Text != Element.Text)
Control.Text = Element.Text;
}
}
}

View File

@ -17,8 +17,8 @@ namespace Ooui.Forms.Renderers
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint) public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
{ {
if (!_perfectSizeValid) { if (!_perfectSizeValid) {
_perfectSize = base.GetDesiredSize (double.PositiveInfinity, double.PositiveInfinity); var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
_perfectSize.Minimum = new Size (Math.Min (10, _perfectSize.Request.Width), _perfectSize.Request.Height); _perfectSize = new SizeRequest (size, size);
_perfectSizeValid = true; _perfectSizeValid = true;
} }
@ -157,7 +157,7 @@ namespace Ooui.Forms.Renderers
return; return;
_perfectSizeValid = false; _perfectSizeValid = false;
Element.SetStyleFont (Control.Style); Element.SetStyleFont (Element.FontFamily, Element.FontSize, Element.FontAttributes, Control.Style);
} }
void UpdateTextColor () void UpdateTextColor ()

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Threading; using System.Threading;
using Xamarin.Forms; using Xamarin.Forms;
@ -141,10 +142,10 @@ namespace Ooui.Forms
bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged); bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged);
if (shouldUpdate) { if (shouldUpdate) {
uiview.Style.Position = "absolute"; uiview.Style.Position = "absolute";
uiview.Style.Left = x + "px"; uiview.Style.Left = x;
uiview.Style.Top = y + "px"; uiview.Style.Top = y;
uiview.Style.Width = width + "px"; uiview.Style.Width = width;
uiview.Style.Height = height + "px"; uiview.Style.Height = height;
Renderer.SetControlSize (new Size (width, height)); Renderer.SetControlSize (new Size (width, height));
} }
else if (width <= 0 || height <= 0) { else if (width <= 0 || height <= 0) {
@ -157,33 +158,33 @@ namespace Ooui.Forms
uiview.Style.Opacity = opacity; uiview.Style.Opacity = opacity;
} }
//var transform = 0; var transforms = "";
//const double epsilon = 0.001; var transformOrigin = default (string);
//caLayer.AnchorPoint = new PointF (anchorX - 0.5f, anchorY - 0.5f); const double epsilon = 0.001;
//// position is relative to anchor point var icult = System.Globalization.CultureInfo.InvariantCulture;
//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);
//if (Math.Abs (translationX) > epsilon || Math.Abs (translationY) > epsilon) // position is relative to anchor point
// transform = transform.Translate (translationX, translationY, 0); 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) if (Math.Abs (translationX) > epsilon || Math.Abs (translationY) > epsilon)
// transform = transform.Scale (scale); 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 (scale - 1) > epsilon)
//if (Math.Abs (rotationY % 180) > epsilon || Math.Abs (rotationX % 180) > epsilon) transforms = string.Format (icult, "{0} scale({1:0.######},{1:0.######})", transforms, scale);
// transform.m34 = 1.0f / -400f;
//if (Math.Abs (rotationX % 360) > epsilon) //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) //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); if (Math.Abs (rotation % 360) > epsilon)
//caLayer.Transform = transform; 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; _lastBounds = view.Bounds;
_lastParentBounds = viewParent?.Bounds ?? Rectangle.Zero; _lastParentBounds = viewParent?.Bounds ?? Rectangle.Zero;

View File

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

View File

@ -1,10 +1,11 @@
using System; using System;
using Newtonsoft.Json;
using StyleValue = System.Object; using StyleValue = System.Object;
namespace Ooui namespace Ooui
{ {
public struct Color [Newtonsoft.Json.JsonConverter (typeof (ColorJsonConverter))]
public struct Color : IEquatable<Color>
{ {
public byte R, G, B, A; public byte R, G, B, A;
@ -33,9 +34,70 @@ namespace Ooui
set => A = value >= 1.0 ? (byte)255 : ((value <= 0.0) ? (byte)0 : (byte)(value * 255.0 + 0.5)); set => A = value >= 1.0 ? (byte)255 : ((value <= 0.0) ? (byte)0 : (byte)(value * 255.0 + 0.5));
} }
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) public static Color FromStyleValue (StyleValue styleColor)
{ {
if (styleColor is Color c)
return c;
if (styleColor is string s)
return Parse (s);
return Colors.Clear; 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 ());
}
} }
} }

View File

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

View File

@ -40,6 +40,12 @@ namespace Ooui
remove => RemoveEventListener ("input", value); remove => RemoveEventListener ("input", value);
} }
string placeholder = "";
public string Placeholder {
get => placeholder;
set => SetProperty (ref placeholder, value, "placeholder");
}
bool isChecked = false; bool isChecked = false;
public bool IsChecked { public bool IsChecked {
get => isChecked; get => isChecked;

View File

@ -18,67 +18,60 @@ namespace Ooui
static readonly Type androidActivityType; static readonly Type androidActivityType;
static readonly Type androidWebViewType; static readonly Type androidWebViewType;
static Platform() static Platform ()
{ {
var asms = AppDomain.CurrentDomain.GetAssemblies().ToDictionary( var asms = AppDomain.CurrentDomain.GetAssemblies ().ToDictionary (
x => x.GetName().Name); x => x.GetName ().Name);
asms.TryGetValue("Xamarin.iOS", out iosAssembly); asms.TryGetValue ("Xamarin.iOS", out iosAssembly);
if (iosAssembly != null) if (iosAssembly != null) {
{ iosUIViewControllerType = iosAssembly.GetType ("UIKit.UIViewController");
iosUIViewControllerType = iosAssembly.GetType("UIKit.UIViewController"); iosUIApplicationType = iosAssembly.GetType ("UIKit.UIApplication");
iosUIApplicationType = iosAssembly.GetType("UIKit.UIApplication"); iosUIWebViewType = iosAssembly.GetType ("UIKit.UIWebView");
iosUIWebViewType = iosAssembly.GetType("UIKit.UIWebView"); iosNSUrl = iosAssembly.GetType ("Foundation.NSUrl");
iosNSUrl = iosAssembly.GetType("Foundation.NSUrl"); iosNSUrlRequest = iosAssembly.GetType ("Foundation.NSUrlRequest");
iosNSUrlRequest = iosAssembly.GetType("Foundation.NSUrlRequest");
} }
asms.TryGetValue("Mono.Android", out androidAssembly); asms.TryGetValue ("Mono.Android", out androidAssembly);
if (androidAssembly != null) if (androidAssembly != null) {
{ androidActivityType = androidAssembly.GetType ("Android.App.Activity");
androidActivityType = androidAssembly.GetType("Android.App.Activity"); androidWebViewType = androidAssembly.GetType ("Android.Webkit.WebView");
androidWebViewType = androidAssembly.GetType("Android.Webkit.WebView");
} }
} }
public static void OpenBrowser(string url, object presenter) public static void OpenBrowser (string url, object presenter)
{ {
if (iosAssembly != null) if (iosAssembly != null) {
{ OpenBrowserOniOS (url, presenter);
OpenBrowserOniOS(url, presenter);
} }
else if (androidAssembly != null) else if (androidAssembly != null) {
{ OpenBrowserOnAndroid (url, presenter);
OpenBrowserOnAndroid(url, presenter);
} }
else else {
{ StartBrowserProcess (url);
StartBrowserProcess(url);
} }
} }
static void OpenBrowserOnAndroid(string url, object presenter) static void OpenBrowserOnAndroid (string url, object presenter)
{ {
var presenterType = GetObjectType(presenter); var presenterType = GetObjectType (presenter);
object presenterWebView = null; object presenterWebView = null;
if (presenter != null && androidWebViewType.IsAssignableFrom(presenterType)) if (presenter != null && androidWebViewType.IsAssignableFrom (presenterType)) {
{
presenterWebView = presenter; presenterWebView = presenter;
} }
if (presenterWebView == null) if (presenterWebView == null) {
{ throw new ArgumentException ("Presenter must be a WebView", nameof(presenter));
throw new ArgumentException("Presenter must be a WebView", nameof(presenter));
} }
var m = androidWebViewType.GetMethod("LoadUrl", BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, new Type[] { typeof(string) }, null); var m = androidWebViewType.GetMethod ("LoadUrl", BindingFlags.Public|BindingFlags.Instance, null, CallingConventions.Any, new Type[] { typeof(string) }, null);
m.Invoke(presenterWebView, new object[] { url }); m.Invoke (presenterWebView, new object[] { url });
} }
static void OpenBrowserOniOS(string url, object presenter) static void OpenBrowserOniOS (string url, object presenter)
{ {
var presenterType = GetObjectType(presenter); var presenterType = GetObjectType (presenter);
// //
// Find a presenter view controller // Find a presenter view controller
@ -87,85 +80,58 @@ namespace Ooui
// 3. Create a window? // 3. Create a window?
// //
object presenterViewController = null; object presenterViewController = null;
if (presenter != null && iosUIViewControllerType.IsAssignableFrom(presenterType)) if (presenter != null && iosUIViewControllerType.IsAssignableFrom (presenterType)) {
{
presenterViewController = presenter; presenterViewController = presenter;
} }
if (presenterViewController == null) if (presenterViewController == null) {
{ var app = iosUIApplicationType.GetProperty ("SharedApplication").GetValue (null, null);
var app = iosUIApplicationType.GetProperty("SharedApplication").GetValue(null, null); var window = iosUIApplicationType.GetProperty ("KeyWindow").GetValue (app, null);
var window = iosUIApplicationType.GetProperty("KeyWindow").GetValue(app, null); if (window != null) {
if (window != null) var rvc = window.GetType ().GetProperty ("RootViewController").GetValue (window, null);
{ if (rvc != null) {
var rvc = window.GetType().GetProperty("RootViewController").GetValue(window, null); var pvc = rvc.GetType ().GetProperty ("PresentedViewController").GetValue (rvc, null);
if (rvc != null)
{
var pvc = rvc.GetType().GetProperty("PresentedViewController").GetValue(rvc, null);
presenterViewController = pvc ?? rvc; presenterViewController = pvc ?? rvc;
} }
} }
} }
if (presenterViewController == null) if (presenterViewController == null) {
{ throw new InvalidOperationException ("Cannot find a view controller from which to present");
throw new InvalidOperationException("Cannot find a view controller from which to present");
} }
// //
// Create the browser // Create the browser
// //
var browserVC = Activator.CreateInstance(iosUIViewControllerType); var browserVC = Activator.CreateInstance (iosUIViewControllerType);
var browserV = Activator.CreateInstance(iosUIWebViewType); var browserV = Activator.CreateInstance (iosUIWebViewType);
var nsUrl = iosNSUrl.GetMethod("FromString").Invoke(null, new object[] { url }); var nsUrl = iosNSUrl.GetMethod ("FromString").Invoke (null, new object[] { url });
var nsUrlRequest = iosNSUrlRequest.GetMethod("FromUrl").Invoke(null, new object[] { nsUrl }); var nsUrlRequest = iosNSUrlRequest.GetMethod ("FromUrl").Invoke (null, new object[] { nsUrl });
iosUIWebViewType.GetMethod("LoadRequest").Invoke(browserV, new object[] { nsUrlRequest }); iosUIWebViewType.GetMethod ("LoadRequest").Invoke (browserV, new object[] { nsUrlRequest });
iosUIViewControllerType.GetProperty("View").SetValue(browserVC, browserV, null); iosUIViewControllerType.GetProperty ("View").SetValue (browserVC, browserV, null);
var m = iosUIViewControllerType.GetMethod("PresentViewController"); var m = iosUIViewControllerType.GetMethod ("PresentViewController");
// Console.WriteLine (presenterViewController); // Console.WriteLine (presenterViewController);
// Console.WriteLine (browserVC); // Console.WriteLine (browserVC);
m.Invoke(presenterViewController, new object[] { browserVC, false, null }); m.Invoke (presenterViewController, new object[] { browserVC, false, null });
} }
static Type GetObjectType(object o) static Type GetObjectType (object o)
{ {
var t = typeof(object); var t = typeof (object);
if (o is IReflectableType rt) if (o is IReflectableType rt) {
{ t = rt.GetTypeInfo ().AsType ();
t = rt.GetTypeInfo().AsType();
} }
else if (o != null) else if (o != null) {
{ t = o.GetType ();
t = o.GetType();
} }
return t; return t;
} }
static Process StartBrowserProcess(string url) static Process StartBrowserProcess (string url)
{ {
var cmd = url;
var args = string.Empty;
var osv = Environment.OSVersion;
if (osv.Platform == PlatformID.Unix)
{
cmd = "open";
args = url;
}
var platform = (int)Environment.OSVersion.Platform;
var isWindows = ((platform != 4) && (platform != 6) && (platform != 128));
if (isWindows)
{
cmd = "explorer.exe";
args = url;
}
// var vs = Environment.GetEnvironmentVariables (); // var vs = Environment.GetEnvironmentVariables ();
// foreach (System.Collections.DictionaryEntry kv in vs) { // foreach (System.Collections.DictionaryEntry kv in vs) {
// System.Console.WriteLine($"K={kv.Key}, V={kv.Value}"); // System.Console.WriteLine($"K={kv.Key}, V={kv.Value}");
@ -173,8 +139,9 @@ namespace Ooui
// Console.WriteLine ($"Process.Start {cmd} {args}"); // 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 });
} }
} }
} }

View File

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

View File

@ -34,10 +34,11 @@ namespace Ooui
<head> <head>
<title>@Title</title> <title>@Title</title>
<meta name=""viewport"" content=""width=device-width, initial-scale=1"" /> <meta name=""viewport"" content=""width=device-width, initial-scale=1"" />
<link rel=""stylesheet"" href=""https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"" />
<style>@Styles</style> <style>@Styles</style>
</head> </head>
<body> <body>
<div id=""ooui-body""></div> <div id=""ooui-body"" class=""container-fluid""></div>
<script src=""/ooui.js""></script> <script src=""/ooui.js""></script>
<script>ooui(""@WebSocketPath"");</script> <script>ooui(""@WebSocketPath"");</script>
</body> </body>
@ -299,9 +300,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 class DataHandler : RequestHandler
@ -435,7 +436,7 @@ namespace Ooui
// Create a new session and let it handle everything from here // Create a new session and let it handle everything from here
// //
try { try {
var session = new Session (webSocket, element, serverToken); var session = new Session (webSocket, element, 1024, 768, serverToken);
await session.RunAsync ().ConfigureAwait (false); await session.RunAsync ().ConfigureAwait (false);
} }
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) { catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) {
@ -472,11 +473,15 @@ namespace Ooui
readonly System.Timers.Timer sendThrottle; readonly System.Timers.Timer sendThrottle;
DateTime lastTransmitTime = DateTime.MinValue; DateTime lastTransmitTime = DateTime.MinValue;
readonly TimeSpan throttleInterval = TimeSpan.FromSeconds (1.0 / 30); // 30 FPS max readonly TimeSpan throttleInterval = TimeSpan.FromSeconds (1.0 / 30); // 30 FPS max
readonly double initialWidth;
readonly double initialHeight;
public Session (WebSocket webSocket, Element element, CancellationToken serverToken) public Session (WebSocket webSocket, Element element, double initialWidth, double initialHeight, CancellationToken serverToken)
{ {
this.webSocket = webSocket; this.webSocket = webSocket;
this.element = element; this.element = element;
this.initialWidth = initialWidth;
this.initialHeight = initialHeight;
// //
// Create a new session cancellation token that will trigger // Create a new session cancellation token that will trigger
@ -524,6 +529,10 @@ namespace Ooui
// //
// Add it to the document body // Add it to the document body
// //
if (element.WantsFullScreen) {
element.Style.Width = initialWidth;
element.Style.Height = initialHeight;
}
QueueMessage (Message.Call ("document.body", "appendChild", element)); QueueMessage (Message.Call ("document.body", "appendChild", element));
// //
@ -570,34 +579,39 @@ namespace Ooui
} }
} }
void QueueStateMessages (EventTarget target) void QueueStateMessagesLocked (EventTarget target)
{ {
if (target == null) return; if (target == null) return;
var created = false;
foreach (var m in target.StateMessages) { foreach (var m in target.StateMessages) {
QueueMessage (m); if (m.MessageType == MessageType.Create) {
createdIds.Add (m.TargetId);
created = true;
}
if (created) {
QueueMessageLocked (m);
}
} }
} }
void QueueMessage (Message message) void QueueMessageLocked (Message message)
{ {
// //
// Make sure all the referenced objects have been created // Make sure all the referenced objects have been created
// //
if (message.MessageType == MessageType.Create) {
createdIds.Add (message.TargetId);
}
else {
if (!createdIds.Contains (message.TargetId)) { if (!createdIds.Contains (message.TargetId)) {
createdIds.Add (message.TargetId); QueueStateMessagesLocked (element.GetElementById (message.TargetId));
QueueStateMessages (element.GetElementById (message.TargetId));
} }
if (message.Value is Array a) { if (message.Value is EventTarget ve) {
if (!createdIds.Contains (ve.Id)) {
QueueStateMessagesLocked (ve);
}
}
else if (message.Value is Array a) {
for (var i = 0; i < a.Length; i++) { for (var i = 0; i < a.Length; i++) {
// Console.WriteLine ($"A{i} = {a.GetValue(i)}"); // Console.WriteLine ($"A{i} = {a.GetValue(i)}");
if (a.GetValue (i) is EventTarget e && !createdIds.Contains (e.Id)) { if (a.GetValue (i) is EventTarget e && !createdIds.Contains (e.Id)) {
createdIds.Add (e.Id); QueueStateMessagesLocked (e);
QueueStateMessages (e);
}
} }
} }
} }
@ -605,7 +619,14 @@ namespace Ooui
// //
// Add it to the queue // Add it to the queue
// //
lock (queuedMessages) queuedMessages.Add (message); queuedMessages.Add (message);
}
void QueueMessage (Message message)
{
lock (queuedMessages) {
QueueMessageLocked (message);
}
sendThrottle.Enabled = true; sendThrottle.Enabled = true;
} }

View File

@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Xamarin.Forms" Version="2.4.0.38779" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -19,5 +20,6 @@
<ProjectReference Include="..\..\Ooui.AspNetCore\Ooui.AspNetCore.csproj" /> <ProjectReference Include="..\..\Ooui.AspNetCore\Ooui.AspNetCore.csproj" />
<ProjectReference Include="..\..\Ooui\Ooui.csproj" /> <ProjectReference Include="..\..\Ooui\Ooui.csproj" />
<ProjectReference Include="..\..\Samples\Samples.csproj" /> <ProjectReference Include="..\..\Samples\Samples.csproj" />
<ProjectReference Include="..\..\Ooui.Forms\Ooui.Forms.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -18,33 +18,16 @@ namespace AspNetCoreMvc.Controllers
return View (); 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 () 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 (); return View ();
} }
public IActionResult Contact () public IActionResult Contact ()
{ {
ViewData["Message"] = "Your contact page."; ViewData["Message"] = "Find us on github.";
return View (); return View ();
} }

View File

@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Mvc;
using AspNetCoreMvc.Models; using AspNetCoreMvc.Models;
using Ooui; using Ooui;
using Ooui.AspNetCore; using Ooui.AspNetCore;
using Samples;
using System.Collections.Concurrent;
namespace AspNetCoreMvc.Controllers namespace AspNetCoreMvc.Controllers
{ {
@ -35,14 +37,21 @@ namespace AspNetCoreMvc.Controllers
var sampleType = typeof (Samples.ISample); var sampleType = typeof (Samples.ISample);
var asm = sampleType.Assembly; var asm = sampleType.Assembly;
var sampleTypes = asm.GetTypes ().Where (x => x.Name.EndsWith ("Sample", StringComparison.Ordinal) && x != sampleType); 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 (); return samples.ToList ();
}), true); }), true);
static readonly ConcurrentDictionary<string, Element> sharedSamples =
new ConcurrentDictionary<string, Element> ();
public static List<Samples.ISample> Samples => lazySamples.Value; public static List<Samples.ISample> Samples => lazySamples.Value;
[Route("/Samples/Run/{name}")] [Route ("/Samples/Run/{name}")]
public IActionResult Run (string name) public IActionResult Run (string name, bool shared)
{ {
if (string.IsNullOrWhiteSpace (name) || name.Length > 32) if (string.IsNullOrWhiteSpace (name) || name.Length > 32)
return BadRequest (); return BadRequest ();
@ -51,7 +60,24 @@ namespace AspNetCoreMvc.Controllers
if (s == null) if (s == null)
return NotFound (); return NotFound ();
return new ElementResult (s.CreateElement ()); 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;
}
[Route ("/shared-button")]
public IActionResult SharedButton ()
{
return Run ("Button Counter", true);
} }
} }
} }

View File

@ -12,14 +12,15 @@ namespace AspNetCoreMvc
{ {
public class Program 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) => public static IWebHost BuildWebHost (string[] args) =>
WebHost.CreateDefaultBuilder(args) WebHost.CreateDefaultBuilder (args)
.UseStartup<Startup>() .UseConfiguration (new ConfigurationBuilder ().AddCommandLine (args).Build ())
.Build(); .UseStartup<Startup> ()
.Build ();
} }
} }

View File

@ -39,6 +39,8 @@ namespace AspNetCoreMvc
app.UseOoui (); app.UseOoui ();
Xamarin.Forms.Forms.Init ();
app.UseMvc (routes => { app.UseMvc (routes => {
routes.MapRoute ( routes.MapRoute (
name: "default", name: "default",

View File

@ -4,4 +4,4 @@
<h2>@ViewData["Title"].</h2> <h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3> <h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p> <p>Find out more <a href="https://github.com/praeclarum/Ooui">on github</a></p>

View File

@ -4,14 +4,3 @@
<h2>@ViewData["Title"].</h2> <h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3> <h3>@ViewData["Message"]</h3>
<address>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br />
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>

View File

@ -8,16 +8,19 @@
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<h1>Ooui</h1> <h1>Ooui</h1>
<p>Write interactive web apps in C#</p> <p>Write interactive web apps in C# and F#</p>
</div> </div>
</div> </div>
<div class="row" style="margin-top:4em;"> <div class="row" style="margin-top:4em;">
<div class="col-md-3"> <div class="col-md-4">
<h3>Samples</h3> <h3>Samples</h3>
<ul> <ul>
@foreach (var s in SamplesController.Samples) { @foreach (var s in SamplesController.Samples) {
<li><a asp-area="" asp-controller="Samples" asp-action="Run" asp-route-name="@s.Title">@s.Title</a></li> <li>
<a asp-area="" asp-controller="Samples" asp-action="Run" asp-route-name="@s.Title">@s.Title</a>
(<a asp-area="" asp-controller="Samples" asp-action="Run" asp-route-name="@s.Title" asp-route-shared="@true">Shared</a>)
</li>
} }
</ul> </ul>
</div> </div>

View File

@ -32,7 +32,6 @@
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li> <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li> <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@ It presents a classic object-oriented UI API that controls a dumb browser. With
## Try it Online ## 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 ## Try the Samples Locally

View File

@ -0,0 +1,138 @@
using System;
using Xamarin.Forms;
namespace Samples
{
public class BoxViewClockSample : ISample
{
public string Title => "Xamarin.Forms BoxViewClock";
BoxViewClockPage page;
public Ooui.Element 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
{
// 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<BoxView, HandParams> 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;
}
}
}
}

View File

@ -5,11 +5,14 @@ namespace Samples
{ {
public class ButtonSample : ISample public class ButtonSample : ISample
{ {
public string Title => "Button that count clicks"; public string Title => "Button Counter";
Button MakeButton () 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; var count = 0;
button.Clicked += (s, e) => { button.Clicked += (s, e) => {
count++; count++;

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ButtonXaml.ButtonXamlPage">
<StackLayout Padding="20">
<Label Text="Welcome to Xamarin.Forms!"/>
<Label x:Name="LabelCount" Text="Click Count: 0"/>
<Button Text="Tap for click count!"
Clicked="OnButtonClicked" />
</StackLayout>
</ContentPage>

22
Samples/ButtonXamlPage.xaml.cs Executable file
View File

@ -0,0 +1,22 @@
using System;
using Xamarin.Forms;
namespace ButtonXaml
{
public partial class ButtonXamlPage
{
int count = 0;
public ButtonXamlPage()
{
InitializeComponent();
}
public void OnButtonClicked(object sender, EventArgs args)
{
count++;
LabelCount.Text = $"Click Count: {count}";
}
}
}

View File

@ -0,0 +1,16 @@
using Ooui;
using Xamarin.Forms;
namespace Samples
{
public class ButtonXamlSample : ISample
{
public string Title => "Xamarin.Forms Button XAML";
public Ooui.Element CreateElement ()
{
var page = new ButtonXaml.ButtonXamlPage ();
return page.GetOouiElement ();
}
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Samples.DisplayAlertPage">
<ContentPage.Content>
<StackLayout>
<Label Text="Welcome to DisplayAlert Sample!" />
<Button Text="Tap for Display Alert"
Clicked="OnButtonClicked" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

View File

@ -0,0 +1,23 @@
using Ooui;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Samples
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DisplayAlertPage : ContentPage
{
public DisplayAlertPage ()
{
InitializeComponent ();
}
public async void OnButtonClicked(object sender, EventArgs args)
{
var result = await DisplayAlert("Alert Message", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa.", "YES", "NO");
await DisplayAlert("Alert Response", $"You selected value: {result}", "OK");
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using Ooui;
using Xamarin.Forms;
namespace Samples
{
public class DisplayAlertSample : ISample
{
public string Title => "Xamarin.Forms DisplayAlert";
public Ooui.Element CreateElement ()
{
var page = new DisplayAlertPage ();
return page.GetOouiElement ();
}
public void Publish ()
{
UI.Publish ("/display-alert", CreateElement);
}
}
}

View File

@ -7,7 +7,7 @@ namespace Samples
{ {
public class DrawSample : ISample public class DrawSample : ISample
{ {
public string Title => "Collaborative Drawing"; public string Title => "Drawing";
public void Publish () public void Publish ()
{ {
@ -17,7 +17,7 @@ namespace Samples
public Element CreateElement () public Element CreateElement ()
{ {
var heading = new Heading ("Draw"); 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 { var canvas = new Canvas {
Width = 320, Width = 320,
Height = 240, Height = 240,

View File

@ -6,7 +6,7 @@ using Ooui;
namespace Samples namespace Samples
{ {
public class FilesSample : ISample public class FilesSample //: ISample
{ {
public string Title => "Upload files"; public string Title => "Upload files";

View File

@ -7,6 +7,8 @@ namespace Samples
{ {
static void Main (string[] args) static void Main (string[] args)
{ {
Xamarin.Forms.Forms.Init ();
for (var i = 0; i < args.Length; i++) { for (var i = 0; i < args.Length; i++) {
var a = args[i]; var a = args[i];
switch (args[i]) { switch (args[i]) {
@ -27,9 +29,9 @@ namespace Samples
new TodoSample ().Publish (); new TodoSample ().Publish ();
new DrawSample ().Publish (); new DrawSample ().Publish ();
new FilesSample ().Publish (); new FilesSample ().Publish ();
new XamarinFormsSample ().Publish (); new DisplayAlertSample ().Publish ();
UI.Present ("/xamarin-forms"); UI.Present ("/display-alert");
Console.ReadLine (); Console.ReadLine ();
} }

View File

@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableDefaultEmbeddedResourceItems>False</EnableDefaultEmbeddedResourceItems>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ooui\Ooui.csproj" /> <ProjectReference Include="..\Ooui\Ooui.csproj" />
<ProjectReference Include="..\Ooui.Forms\Ooui.Forms.csproj" /> <ProjectReference Include="..\Ooui.Forms\Ooui.Forms.csproj" />
@ -8,6 +12,29 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Xamarin.Forms" Version="2.4.0.38779" /> <PackageReference Include="Xamarin.Forms" Version="2.4.0.38779" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="**/*.xaml" />
</ItemGroup>
<ItemGroup>
<Compile Condition=" '$(EnableDefaultCompileItems)' == 'true' " Update="ButtonXamlPage.xaml.cs">
<DependentUpon>*.xaml</DependentUpon>
</Compile>
<Compile Condition=" '$(EnableDefaultCompileItems)' == 'true' " Update="DisplayAlertPage.xaml.cs">
<DependentUpon>*.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="ButtonXamlPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Update="DisplayAlertPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>

View File

@ -7,16 +7,7 @@ namespace Samples
{ {
public class TodoSample : ISample public class TodoSample : ISample
{ {
public string Title => "Global TODO list"; public string Title => "Todo List";
List items = new List () {
ClassName = "list-group",
};
public TodoSample ()
{
items.Style.MarginTop = "1em";
}
class Item : ListItem class Item : ListItem
{ {
@ -48,6 +39,11 @@ namespace Samples
Element MakeTodo () Element MakeTodo ()
{ {
List items = new List () {
ClassName = "list-group",
};
items.Style.MarginTop = "1em";
var heading = new Heading ("Todo List"); var heading = new Heading ("Todo List");
var subtitle = new Paragraph ("This is the shared todo list of the world."); var subtitle = new Paragraph ("This is the shared todo list of the world.");
var inputForm = new Form { var inputForm = new Form {

View File

@ -1,57 +0,0 @@
using System;
using Xamarin.Forms;
using Ooui.Forms;
namespace Samples
{
public class XamarinFormsSample : ISample
{
public string Title => "Xamarin.Forms Button Counter";
Page MakePage ()
{
Forms.Init ();
var countLabel = new Label {
Text = "0",
BackgroundColor = Color.Gold,
HorizontalOptions = LayoutOptions.Center,
};
var countButton = new Button {
Text = "Increase",
HorizontalOptions = LayoutOptions.FillAndExpand,
};
countButton.Clicked += (sender, e) => {
var v = int.Parse (countLabel.Text);
countLabel.Text = (v + 1).ToString ();
};
return new ContentPage {
Content = new StackLayout {
BackgroundColor = Color.Khaki,
Children = {
new Label {
Text = "Hello World!",
FontSize = 32,
HorizontalOptions = LayoutOptions.Center,
},
countLabel,
countButton,
},
},
};
}
public void Publish ()
{
var page = MakePage ();
page.Publish ("/xamarin-forms-shared");
Ooui.UI.Publish ("/xamarin-forms", () => MakePage ().CreateElement ());
}
public Ooui.Element CreateElement ()
{
return MakePage ().CreateElement ();
}
}
}

13
ooui.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=Ooui Service
After=network.target
[Service]
Type=simple
Environment=HOME=/home/ubuntu
WorkingDirectory=/home/ubuntu/Ooui/PlatformSamples/AspNetCoreMvc
ExecStart=/usr/bin/dotnet run --no-build --server.urls=http://0.0.0.0:80/
Restart=on-abort
[Install]
WantedBy=multi-user.target