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
*~
.vs/
# build
[Oo]bj/

View File

@ -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);
}
}
}

View File

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

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;
[assembly: Dependency (typeof (ResourcesProvider))]
[assembly: ExportRenderer (typeof (BoxView), typeof (BoxRenderer))]
[assembly: ExportRenderer (typeof (Button), typeof (ButtonRenderer))]
[assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))]

View File

@ -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)

View File

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

View File

@ -5,8 +5,55 @@ namespace Ooui.Forms.Extensions
{
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)
@ -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.PlatformServices = new OouiPlatformServices ();
Device.Info = new OouiDeviceInfo ();
Color.SetAccent (Color.FromHex ("#0000EE")); // Safari Blue
Registrar.RegisterAll (new[] {
typeof(ExportRendererAttribute),
@ -110,7 +111,13 @@ namespace Xamarin.Forms
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" />
</ItemGroup>
<ItemGroup>
<Folder Include="Renderers\" />
<Folder Include="Extensions\" />
</ItemGroup>
<ItemGroup>

View File

@ -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;

View File

@ -1,11 +1,12 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ooui.Forms.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Internals;
using System.Web;
namespace Ooui.Forms
namespace Ooui.Forms
{
public class Platform : BindableObject, IPlatform, INavigation, IDisposable
{
@ -31,6 +32,20 @@ namespace Ooui.Forms
public Platform ()
{
_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 ()
@ -70,10 +85,10 @@ namespace Ooui.Forms
public SizeRequest GetNativeSize (VisualElement view, double widthConstraint, double heightConstraint)
{
var renderView = GetRenderer (view);
if (renderView == null || renderView.NativeView == null)
return new SizeRequest (Size.Zero);
var renderView = GetRenderer (view);
if (renderView == null || renderView.NativeView == null)
return new SizeRequest (Size.Zero);
return renderView.GetDesiredSize (widthConstraint, heightConstraint);
}
@ -114,6 +129,12 @@ namespace Ooui.Forms
Console.Error.WriteLine ("Potential view double add");
}
void HandleRendererStyle_PropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var pageRenderer = GetRenderer (Page);
pageRenderer?.SetElementSize (Ooui.Forms.Extensions.ElementExtensions.GetSizeRequest (_renderer, double.PositiveInfinity, double.PositiveInfinity).Request);
}
void INavigation.InsertPageBefore (Page page, Page before)
{
throw new NotImplementedException ();
@ -173,5 +194,5 @@ namespace Ooui.Forms
{
throw new NotImplementedException ();
}
}
}
}
}

View File

@ -9,6 +9,8 @@ namespace Ooui.Forms
public Platform Platform => platform;
public override bool WantsFullScreen => true;
public PlatformRenderer (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 _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)
{
if (Control != null) {
@ -31,6 +38,8 @@ namespace Ooui.Forms.Renderers
Debug.Assert (Control != null, "Control != null");
Control.ClassName = "btn btn-primary";
_buttonTextColorDefaultNormal = Ooui.Colors.Black;
_buttonTextColorDefaultHighlighted = Ooui.Colors.Black;
_buttonTextColorDefaultDisabled = Ooui.Colors.Black;
@ -98,7 +107,7 @@ namespace Ooui.Forms.Renderers
void UpdateFont ()
{
Element.SetStyleFont (Control.Style);
Element.SetStyleFont (Element.FontFamily, Element.FontSize, Element.FontAttributes, Control.Style);
}
void UpdateImage ()
@ -141,14 +150,10 @@ namespace Ooui.Forms.Renderers
void UpdateTextColor ()
{
if (Element.TextColor == Xamarin.Forms.Color.Default) {
Control.Style.Color = _buttonTextColorDefaultNormal;
Control.Style.Color = _buttonTextColorDefaultHighlighted;
Control.Style.Color = _buttonTextColorDefaultDisabled;
Control.Style.Color = null;
}
else {
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)
{
if (!_perfectSizeValid) {
_perfectSize = base.GetDesiredSize (double.PositiveInfinity, double.PositiveInfinity);
_perfectSize.Minimum = new Size (Math.Min (10, _perfectSize.Request.Width), _perfectSize.Request.Height);
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
_perfectSize = new SizeRequest (size, size);
_perfectSizeValid = true;
}
@ -157,7 +157,7 @@ namespace Ooui.Forms.Renderers
return;
_perfectSizeValid = false;
Element.SetStyleFont (Control.Style);
Element.SetStyleFont (Element.FontFamily, Element.FontSize, Element.FontAttributes, Control.Style);
}
void UpdateTextColor ()

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using Xamarin.Forms;
@ -141,10 +142,10 @@ namespace Ooui.Forms
bool shouldUpdate = width > 0 && height > 0 && parent != null && (boundsChanged || parentBoundsChanged);
if (shouldUpdate) {
uiview.Style.Position = "absolute";
uiview.Style.Left = x + "px";
uiview.Style.Top = y + "px";
uiview.Style.Width = width + "px";
uiview.Style.Height = height + "px";
uiview.Style.Left = x;
uiview.Style.Top = y;
uiview.Style.Width = width;
uiview.Style.Height = height;
Renderer.SetControlSize (new Size (width, height));
}
else if (width <= 0 || height <= 0) {
@ -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;

View File

@ -18,10 +18,32 @@ const mouseEvents = {
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) {
var opened = false;
socket = new WebSocket ("ws://" + document.location.host + rootElementPath, "ooui");
var initialSize = getSize ();
var wsArgs = (rootElementPath.indexOf("?") >= 0 ? "&" : "?") +
"w=" + initialSize.width + "&h=" + initialSize.height;
socket = new WebSocket ("ws://" + document.location.host + rootElementPath + wsArgs, "ooui");
socket.addEventListener ("open", function (event) {
console.log ("Web socket opened");
@ -52,6 +74,34 @@ function ooui (rootElementPath) {
});
console.log("Web socket created");
// Throttled window resize event
(function() {
window.addEventListener("resize", resizeThrottler, false);
var resizeTimeout;
function resizeThrottler() {
if (!resizeTimeout) {
resizeTimeout = setTimeout(function() {
resizeTimeout = null;
actualResizeHandler();
}, 100);
}
}
function resizeHandler() {
const em = {
m: "event",
id: 42,
k: "window.resize",
v: getSize (),
};
const ems = JSON.stringify (em);
if (socket != null)
socket.send (ems);
if (debug) console.log ("Event", em);
}
}());
}
function getNode (id) {
@ -135,7 +185,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 ();

View File

@ -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<Color>
{
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 ());
}
}
}

View File

@ -90,6 +90,12 @@ namespace Ooui
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)
: base (tagName)
{

View File

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

View File

@ -7,165 +7,131 @@ namespace Ooui
{
static class Platform
{
static readonly Assembly iosAssembly;
static readonly Type iosUIViewControllerType;
static readonly Type iosUIApplicationType;
static readonly Type iosUIWebViewType;
static readonly Type iosNSUrl;
static readonly Type iosNSUrlRequest;
static readonly Assembly iosAssembly;
static readonly Type iosUIViewControllerType;
static readonly Type iosUIApplicationType;
static readonly Type iosUIWebViewType;
static readonly Type iosNSUrl;
static readonly Type iosNSUrlRequest;
static readonly Assembly androidAssembly;
static readonly Type androidActivityType;
static readonly Type androidWebViewType;
static readonly Assembly androidAssembly;
static readonly Type androidActivityType;
static readonly Type androidWebViewType;
static Platform()
{
var asms = AppDomain.CurrentDomain.GetAssemblies().ToDictionary(
x => x.GetName().Name);
static Platform ()
{
var asms = AppDomain.CurrentDomain.GetAssemblies ().ToDictionary (
x => x.GetName ().Name);
asms.TryGetValue("Xamarin.iOS", out iosAssembly);
if (iosAssembly != null)
{
iosUIViewControllerType = iosAssembly.GetType("UIKit.UIViewController");
iosUIApplicationType = iosAssembly.GetType("UIKit.UIApplication");
iosUIWebViewType = iosAssembly.GetType("UIKit.UIWebView");
iosNSUrl = iosAssembly.GetType("Foundation.NSUrl");
iosNSUrlRequest = iosAssembly.GetType("Foundation.NSUrlRequest");
}
asms.TryGetValue ("Xamarin.iOS", out iosAssembly);
if (iosAssembly != null) {
iosUIViewControllerType = iosAssembly.GetType ("UIKit.UIViewController");
iosUIApplicationType = iosAssembly.GetType ("UIKit.UIApplication");
iosUIWebViewType = iosAssembly.GetType ("UIKit.UIWebView");
iosNSUrl = iosAssembly.GetType ("Foundation.NSUrl");
iosNSUrlRequest = iosAssembly.GetType ("Foundation.NSUrlRequest");
}
asms.TryGetValue("Mono.Android", out androidAssembly);
if (androidAssembly != null)
{
androidActivityType = androidAssembly.GetType("Android.App.Activity");
androidWebViewType = androidAssembly.GetType("Android.Webkit.WebView");
}
}
asms.TryGetValue ("Mono.Android", out androidAssembly);
if (androidAssembly != null) {
androidActivityType = androidAssembly.GetType ("Android.App.Activity");
androidWebViewType = androidAssembly.GetType ("Android.Webkit.WebView");
}
}
public static void OpenBrowser(string url, object presenter)
{
if (iosAssembly != null)
{
OpenBrowserOniOS(url, presenter);
}
else if (androidAssembly != null)
{
OpenBrowserOnAndroid(url, presenter);
}
else
{
StartBrowserProcess(url);
}
}
public static void OpenBrowser (string url, object presenter)
{
if (iosAssembly != null) {
OpenBrowserOniOS (url, presenter);
}
else if (androidAssembly != null) {
OpenBrowserOnAndroid (url, presenter);
}
else {
StartBrowserProcess (url);
}
}
static void OpenBrowserOnAndroid(string url, object presenter)
{
var presenterType = GetObjectType(presenter);
static void OpenBrowserOnAndroid (string url, object presenter)
{
var presenterType = GetObjectType (presenter);
object presenterWebView = null;
if (presenter != null && androidWebViewType.IsAssignableFrom(presenterType))
{
presenterWebView = presenter;
}
object presenterWebView = null;
if (presenter != null && androidWebViewType.IsAssignableFrom (presenterType)) {
presenterWebView = presenter;
}
if (presenterWebView == null)
{
throw new ArgumentException("Presenter must be a WebView", nameof(presenter));
}
if (presenterWebView == null) {
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);
m.Invoke(presenterWebView, new object[] { url });
}
var m = androidWebViewType.GetMethod ("LoadUrl", BindingFlags.Public|BindingFlags.Instance, null, CallingConventions.Any, new Type[] { typeof(string) }, null);
m.Invoke (presenterWebView, new object[] { url });
}
static void OpenBrowserOniOS(string url, object presenter)
{
var presenterType = GetObjectType(presenter);
static void OpenBrowserOniOS (string url, object presenter)
{
var presenterType = GetObjectType (presenter);
//
// Find a presenter view controller
// 1. Try the given presenter
// 2. Find the key window vc
// 3. Create a window?
//
object presenterViewController = null;
if (presenter != null && iosUIViewControllerType.IsAssignableFrom(presenterType))
{
presenterViewController = presenter;
}
//
// Find a presenter view controller
// 1. Try the given presenter
// 2. Find the key window vc
// 3. Create a window?
//
object presenterViewController = null;
if (presenter != null && iosUIViewControllerType.IsAssignableFrom (presenterType)) {
presenterViewController = presenter;
}
if (presenterViewController == null)
{
var app = iosUIApplicationType.GetProperty("SharedApplication").GetValue(null, null);
var window = iosUIApplicationType.GetProperty("KeyWindow").GetValue(app, null);
if (window != null)
{
var rvc = window.GetType().GetProperty("RootViewController").GetValue(window, null);
if (rvc != null)
{
var pvc = rvc.GetType().GetProperty("PresentedViewController").GetValue(rvc, null);
presenterViewController = pvc ?? rvc;
}
}
}
if (presenterViewController == null) {
var app = iosUIApplicationType.GetProperty ("SharedApplication").GetValue (null, null);
var window = iosUIApplicationType.GetProperty ("KeyWindow").GetValue (app, null);
if (window != null) {
var rvc = window.GetType ().GetProperty ("RootViewController").GetValue (window, null);
if (rvc != null) {
var pvc = rvc.GetType ().GetProperty ("PresentedViewController").GetValue (rvc, null);
presenterViewController = pvc ?? rvc;
}
}
}
if (presenterViewController == null)
{
throw new InvalidOperationException("Cannot find a view controller from which to present");
}
if (presenterViewController == null) {
throw new InvalidOperationException ("Cannot find a view controller from which to present");
}
//
// Create the browser
//
var browserVC = Activator.CreateInstance(iosUIViewControllerType);
var browserV = Activator.CreateInstance(iosUIWebViewType);
//
// Create the browser
//
var browserVC = Activator.CreateInstance (iosUIViewControllerType);
var browserV = Activator.CreateInstance (iosUIWebViewType);
var nsUrl = iosNSUrl.GetMethod("FromString").Invoke(null, new object[] { url });
var nsUrlRequest = iosNSUrlRequest.GetMethod("FromUrl").Invoke(null, new object[] { nsUrl });
iosUIWebViewType.GetMethod("LoadRequest").Invoke(browserV, new object[] { nsUrlRequest });
iosUIViewControllerType.GetProperty("View").SetValue(browserVC, browserV, null);
var nsUrl = iosNSUrl.GetMethod ("FromString").Invoke (null, new object[] { url });
var nsUrlRequest = iosNSUrlRequest.GetMethod ("FromUrl").Invoke (null, new object[] { nsUrl });
iosUIWebViewType.GetMethod ("LoadRequest").Invoke (browserV, new object[] { nsUrlRequest });
iosUIViewControllerType.GetProperty ("View").SetValue (browserVC, browserV, null);
var m = iosUIViewControllerType.GetMethod("PresentViewController");
var m = iosUIViewControllerType.GetMethod ("PresentViewController");
// Console.WriteLine (presenterViewController);
// Console.WriteLine (browserVC);
m.Invoke(presenterViewController, new object[] { browserVC, false, null });
}
// Console.WriteLine (presenterViewController);
// Console.WriteLine (browserVC);
m.Invoke (presenterViewController, new object[] { browserVC, false, null });
}
static Type GetObjectType(object o)
{
var t = typeof(object);
if (o is IReflectableType rt)
{
t = rt.GetTypeInfo().AsType();
}
else if (o != null)
{
t = o.GetType();
}
return t;
}
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;
}
static Type GetObjectType (object o)
{
var t = typeof (object);
if (o is IReflectableType rt) {
t = rt.GetTypeInfo ().AsType ();
}
else if (o != null) {
t = o.GetType ();
}
return t;
}
static Process StartBrowserProcess (string url)
{
// var vs = Environment.GetEnvironmentVariables ();
// foreach (System.Collections.DictionaryEntry kv in vs) {
// System.Console.WriteLine($"K={kv.Key}, V={kv.Value}");
@ -173,8 +139,9 @@ namespace Ooui
// 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 =
new Dictionary<string, Value> ();
static readonly private char[] numberChars = new char[] {
'0','1','2','3','4','5','6','7','8','9',
'.','-','+',
};
public Value AlignSelf {
get => this["align-self"];
@ -126,7 +130,7 @@ namespace Ooui
public Value Bottom {
get => this["bottom"];
set => this["bottom"] = value;
set => this["bottom"] = AddNumberUnits (value, "px");
}
public Value Clear {
@ -176,7 +180,7 @@ namespace Ooui
public Value FontSize {
get => this["font-size"];
set => this["font-size"] = value;
set => this["font-size"] = AddNumberUnits (value, "px");
}
public Value FontStyle {
@ -196,12 +200,12 @@ namespace Ooui
public Value Height {
get => this["height"];
set => this["height"] = value;
set => this["height"] = AddNumberUnits (value, "px");
}
public Value Left {
get => this["left"];
set => this["left"] = value;
set => this["left"] = AddNumberUnits (value, "px");
}
public Value LineHeight {
@ -301,7 +305,17 @@ namespace Ooui
public Value 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 {
@ -316,7 +330,7 @@ namespace Ooui
public Value Width {
get => this["width"];
set => this["width"] = value;
set => this["width"] = AddNumberUnits (value, "px");
}
public Value ZIndex {
@ -386,5 +400,34 @@ namespace Ooui
}
return o.ToString ();
}
static string AddNumberUnits (object val, string units)
{
if (val is string s)
return s;
if (val is IConvertible c)
return c.ToString (System.Globalization.CultureInfo.InvariantCulture) + units;
return val.ToString ();
}
public double GetNumberWithUnits (string key, string units, double baseValue)
{
var v = this[key];
if (v == null)
return 0;
if (v is string s) {
var lastIndex = s.LastIndexOfAny (numberChars);
if (lastIndex < 0)
return 0;
var num = double.Parse (s.Substring (0, lastIndex + 1), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
return num;
}
if (v is IConvertible c)
return c.ToDouble (System.Globalization.CultureInfo.InvariantCulture);
return 0;
}
}
}

View File

@ -34,10 +34,11 @@ namespace Ooui
<head>
<title>@Title</title>
<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>
</head>
<body>
<div id=""ooui-body""></div>
<div id=""ooui-body"" class=""container-fluid""></div>
<script src=""/ooui.js""></script>
<script>ooui(""@WebSocketPath"");</script>
</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
@ -435,7 +436,7 @@ namespace Ooui
// Create a new session and let it handle everything from here
//
try {
var session = new Session (webSocket, element, serverToken);
var session = new Session (webSocket, element, 1024, 768, serverToken);
await session.RunAsync ().ConfigureAwait (false);
}
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) {
@ -472,11 +473,15 @@ namespace Ooui
readonly System.Timers.Timer sendThrottle;
DateTime lastTransmitTime = DateTime.MinValue;
readonly TimeSpan throttleInterval = TimeSpan.FromSeconds (1.0 / 30); // 30 FPS max
readonly double initialWidth;
readonly double initialHeight;
public Session (WebSocket webSocket, Element element, CancellationToken serverToken)
public Session (WebSocket webSocket, Element element, double initialWidth, double initialHeight, CancellationToken serverToken)
{
this.webSocket = webSocket;
this.element = element;
this.initialWidth = initialWidth;
this.initialHeight = initialHeight;
//
// Create a new session cancellation token that will trigger
@ -524,6 +529,10 @@ namespace Ooui
//
// Add it to the document body
//
if (element.WantsFullScreen) {
element.Style.Width = initialWidth;
element.Style.Height = initialHeight;
}
QueueMessage (Message.Call ("document.body", "appendChild", element));
//
@ -570,34 +579,39 @@ namespace Ooui
}
}
void QueueStateMessages (EventTarget target)
void QueueStateMessagesLocked (EventTarget target)
{
if (target == null) return;
var created = false;
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
//
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);
QueueStateMessages (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);
QueueStateMessages (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);
}
}
}
@ -605,7 +619,14 @@ namespace Ooui
//
// Add it to the queue
//
lock (queuedMessages) queuedMessages.Add (message);
queuedMessages.Add (message);
}
void QueueMessage (Message message)
{
lock (queuedMessages) {
QueueMessageLocked (message);
}
sendThrottle.Enabled = true;
}

View File

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

View File

@ -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 ();
}

View File

@ -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
{
@ -35,23 +37,47 @@ 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);
static readonly ConcurrentDictionary<string, Element> sharedSamples =
new ConcurrentDictionary<string, Element> ();
public static List<Samples.ISample> Samples => lazySamples.Value;
[Route("/Samples/Run/{name}")]
public IActionResult Run (string 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 ();
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 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<Startup>()
.Build();
public static IWebHost BuildWebHost (string[] args) =>
WebHost.CreateDefaultBuilder (args)
.UseConfiguration (new ConfigurationBuilder ().AddCommandLine (args).Build ())
.UseStartup<Startup> ()
.Build ();
}
}

View File

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

View File

@ -4,4 +4,4 @@
<h2>@ViewData["Title"].</h2>
<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>
<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 class="col-md-2">
<h1>Ooui</h1>
<p>Write interactive web apps in C#</p>
<p>Write interactive web apps in C# and F#</p>
</div>
</div>
<div class="row" style="margin-top:4em;">
<div class="col-md-3">
<div class="col-md-4">
<h3>Samples</h3>
<ul>
@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>
</div>

View File

@ -32,7 +32,6 @@
<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="About">About</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
</ul>
</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
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

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 string Title => "Button that count clicks";
public string Title => "Button Counter";
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++;

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 string Title => "Collaborative Drawing";
public string Title => "Drawing";
public void Publish ()
{
@ -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,

View File

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

View File

@ -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]) {
@ -26,10 +28,10 @@ namespace Samples
new ButtonSample ().Publish ();
new TodoSample ().Publish ();
new DrawSample ().Publish ();
new FilesSample ().Publish ();
new XamarinFormsSample ().Publish ();
new FilesSample ().Publish ();
new DisplayAlertSample ().Publish ();
UI.Present ("/xamarin-forms");
UI.Present ("/display-alert");
Console.ReadLine ();
}

View File

@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableDefaultEmbeddedResourceItems>False</EnableDefaultEmbeddedResourceItems>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ooui\Ooui.csproj" />
<ProjectReference Include="..\Ooui.Forms\Ooui.Forms.csproj" />
@ -8,6 +12,29 @@
<ItemGroup>
<PackageReference Include="Xamarin.Forms" Version="2.4.0.38779" />
</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>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>

View File

@ -7,16 +7,7 @@ namespace Samples
{
public class TodoSample : ISample
{
public string Title => "Global TODO list";
List items = new List () {
ClassName = "list-group",
};
public TodoSample ()
{
items.Style.MarginTop = "1em";
}
public string Title => "Todo List";
class Item : ListItem
{
@ -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 {

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