Merge remote-tracking branch 'refs/remotes/praeclarum/master'
This commit is contained in:
commit
dcec2428d5
|
@ -0,0 +1,56 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<textarea id="result" cols="200" rows="32"></textarea>
|
||||||
|
|
||||||
|
<canvas id="canvas" width="320" height="200"></canvas>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var canvas = document.getElementById('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
|
||||||
|
var r = "static readonly double[] CharacterProportions = {\n ";
|
||||||
|
var head = "";
|
||||||
|
let size = 16;
|
||||||
|
|
||||||
|
ctx.font = "bold " + size + "px \"Helvetica Neue\"";
|
||||||
|
var mmm = ctx.measureText("MM");
|
||||||
|
let sp = 0;
|
||||||
|
let np = 0;
|
||||||
|
let mw = 0;
|
||||||
|
for (let i = 0; i < 128; i++) {
|
||||||
|
if (i > 0 && i % 8 == 0) {
|
||||||
|
head = ",\n ";
|
||||||
|
}
|
||||||
|
else if (i > 0) {
|
||||||
|
head = ", ";
|
||||||
|
}
|
||||||
|
let c = String.fromCharCode(i);
|
||||||
|
let s = "M" + c + "M";
|
||||||
|
let m = ctx.measureText(s);
|
||||||
|
let w = m.width - mmm.width;
|
||||||
|
let p = w / size;
|
||||||
|
if (p > 1e-4) {
|
||||||
|
sp += p;
|
||||||
|
np++;
|
||||||
|
}
|
||||||
|
if (c == "M") {
|
||||||
|
mw = w;
|
||||||
|
}
|
||||||
|
r += head + p;
|
||||||
|
console.log (c + " = " + w);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ap = sp / np;
|
||||||
|
let padding = (mmm.width - mw*2)/size;
|
||||||
|
r += "\n};\nconst double AverageCharProportion = " + ap + ";";
|
||||||
|
r += "\nconst double StringWidthPaddingProportion = " + padding + ";";
|
||||||
|
console.log(r);
|
||||||
|
document.getElementById("result").innerText = r;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,6 +1,14 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<Authors>praeclarum</Authors>
|
||||||
|
<Description>ASP.NET Core MVC extensions to make working with Ooui easy.</Description>
|
||||||
|
<PackageTags>Ooui;UI;CrossPlatform;ASP.NET</PackageTags>
|
||||||
|
<PackageIconUrl>https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png</PackageIconUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/praeclarum/Ooui</PackageProjectUrl>
|
||||||
|
<PackageLicenseUrl>https://github.com/praeclarum/Ooui/blob/master/LICENSE</PackageLicenseUrl>
|
||||||
|
<RepositoryUrl>https://github.com/praeclarum/Ooui.git</RepositoryUrl>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -25,17 +25,27 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
};
|
};
|
||||||
app.UseWebSockets (webSocketOptions);
|
app.UseWebSockets (webSocketOptions);
|
||||||
|
|
||||||
|
Ooui.UI.ServerEnabled = false;
|
||||||
|
|
||||||
app.Use (async (context, next) =>
|
app.Use (async (context, next) =>
|
||||||
{
|
{
|
||||||
|
var response = context.Response;
|
||||||
|
|
||||||
if (context.Request.Path == jsPath) {
|
if (context.Request.Path == jsPath) {
|
||||||
var response = context.Response;
|
|
||||||
var clientJsBytes = Ooui.UI.ClientJsBytes;
|
var clientJsBytes = Ooui.UI.ClientJsBytes;
|
||||||
response.StatusCode = 200;
|
var clientJsEtag = Ooui.UI.ClientJsEtag;
|
||||||
response.ContentLength = clientJsBytes.Length;
|
if (context.Request.Headers.TryGetValue ("If-None-Match", out var inms) && inms.Count > 0 && inms[0] == clientJsEtag) {
|
||||||
response.ContentType = "application/javascript; charset=utf-8";
|
response.StatusCode = 304;
|
||||||
response.Headers.Add ("Cache-Control", "public, max-age=3600");
|
}
|
||||||
using (var s = response.Body) {
|
else {
|
||||||
await s.WriteAsync (clientJsBytes, 0, clientJsBytes.Length).ConfigureAwait (false);
|
response.StatusCode = 200;
|
||||||
|
response.ContentLength = clientJsBytes.Length;
|
||||||
|
response.ContentType = "application/javascript; charset=utf-8";
|
||||||
|
response.Headers.Add ("Cache-Control", "public, max-age=60");
|
||||||
|
response.Headers.Add ("Etag", clientJsEtag);
|
||||||
|
using (var s = response.Body) {
|
||||||
|
await s.WriteAsync (clientJsBytes, 0, clientJsBytes.Length).ConfigureAwait (false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (context.Request.Path == WebSocketHandler.WebSocketPath) {
|
else if (context.Request.Path == WebSocketHandler.WebSocketPath) {
|
||||||
|
@ -46,6 +56,21 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
context.Response.StatusCode = 400;
|
context.Response.StatusCode = 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Ooui.UI.TryGetFileContentAtPath (context.Request.Path, out var file)) {
|
||||||
|
if (context.Request.Headers.TryGetValue ("If-None-Match", out var inms) && inms.Count > 0 && inms[0] == file.Etag) {
|
||||||
|
response.StatusCode = 304;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.ContentLength = file.Content.Length;
|
||||||
|
response.ContentType = file.ContentType;
|
||||||
|
response.Headers.Add ("Cache-Control", "public, max-age=60");
|
||||||
|
response.Headers.Add ("Etag", file.Etag);
|
||||||
|
using (var s = response.Body) {
|
||||||
|
await s.WriteAsync (file.Content, 0, file.Content.Length).ConfigureAwait (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
await next ().ConfigureAwait (false);
|
await next ().ConfigureAwait (false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,9 +86,10 @@ namespace Ooui.AspNetCore
|
||||||
BadRequest ("Missing `h`");
|
BadRequest ("Missing `h`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!double.TryParse (wValues.Last (), out var w))
|
var icult = System.Globalization.CultureInfo.InvariantCulture;
|
||||||
|
if (!double.TryParse (wValues.Last (), System.Globalization.NumberStyles.Any, icult, out var w))
|
||||||
w = 640;
|
w = 640;
|
||||||
if (!double.TryParse (hValues.Last (), out var h))
|
if (!double.TryParse (hValues.Last (), System.Globalization.NumberStyles.Any, icult, out var h))
|
||||||
h = 480;
|
h = 480;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -59,7 +59,7 @@ namespace Ooui.Forms
|
||||||
{
|
{
|
||||||
ClassName = "btn btn-default"
|
ClassName = "btn btn-default"
|
||||||
};
|
};
|
||||||
_cancelButton.Clicked += (s, e) => SetResult(false);
|
_cancelButton.Click += (s, e) => SetResult(false);
|
||||||
|
|
||||||
footer.AppendChild(_cancelButton);
|
footer.AppendChild(_cancelButton);
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ namespace Ooui.Forms
|
||||||
ClassName = "btn btn-default"
|
ClassName = "btn btn-default"
|
||||||
};
|
};
|
||||||
|
|
||||||
_acceptButton.Clicked += (s, e) => SetResult(true);
|
_acceptButton.Click += (s, e) => SetResult(true);
|
||||||
footer.AppendChild(_acceptButton);
|
footer.AppendChild(_acceptButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,23 +90,23 @@ namespace Ooui.Forms
|
||||||
{
|
{
|
||||||
add
|
add
|
||||||
{
|
{
|
||||||
_closeButton.Clicked += value;
|
_closeButton.Click += value;
|
||||||
|
|
||||||
if(_cancelButton != null)
|
if(_cancelButton != null)
|
||||||
_cancelButton.Clicked += value;
|
_cancelButton.Click += value;
|
||||||
|
|
||||||
if(_acceptButton != null)
|
if(_acceptButton != null)
|
||||||
_acceptButton.Clicked += value;
|
_acceptButton.Click += value;
|
||||||
}
|
}
|
||||||
remove
|
remove
|
||||||
{
|
{
|
||||||
_closeButton.Clicked -= value;
|
_closeButton.Click -= value;
|
||||||
|
|
||||||
if (_cancelButton != null)
|
if (_cancelButton != null)
|
||||||
_cancelButton.Clicked -= value;
|
_cancelButton.Click -= value;
|
||||||
|
|
||||||
if (_acceptButton != null)
|
if (_acceptButton != null)
|
||||||
_acceptButton.Clicked -= value;
|
_acceptButton.Click -= value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public Element Element { get; private set; }
|
public Element Element { get; private set; }
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
using NativeView = Ooui.Element;
|
||||||
|
|
||||||
|
namespace Ooui.Forms
|
||||||
|
{
|
||||||
|
public class EventTracker
|
||||||
|
{
|
||||||
|
readonly NotifyCollectionChangedEventHandler _collectionChangedHandler;
|
||||||
|
|
||||||
|
readonly Dictionary<IGestureRecognizer, NativeGestureRecognizer> _gestureRecognizers = new Dictionary<IGestureRecognizer, NativeGestureRecognizer> ();
|
||||||
|
|
||||||
|
readonly IVisualElementRenderer _renderer;
|
||||||
|
bool _disposed;
|
||||||
|
NativeView _handler;
|
||||||
|
|
||||||
|
public EventTracker (IVisualElementRenderer renderer)
|
||||||
|
{
|
||||||
|
if (renderer == null)
|
||||||
|
throw new ArgumentNullException (nameof (renderer));
|
||||||
|
|
||||||
|
_collectionChangedHandler = ModelGestureRecognizersOnCollectionChanged;
|
||||||
|
|
||||||
|
_renderer = renderer;
|
||||||
|
_renderer.ElementChanged += OnElementChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObservableCollection<IGestureRecognizer> ElementGestureRecognizers {
|
||||||
|
get {
|
||||||
|
if (_renderer?.Element is View)
|
||||||
|
return ((View)_renderer.Element).GestureRecognizers as ObservableCollection<IGestureRecognizer>;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose ()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
foreach (var kvp in _gestureRecognizers) {
|
||||||
|
RemoveGestureRecognizer (_handler, kvp.Value);
|
||||||
|
kvp.Value.Dispose ();
|
||||||
|
}
|
||||||
|
|
||||||
|
_gestureRecognizers.Clear ();
|
||||||
|
|
||||||
|
if (ElementGestureRecognizers != null)
|
||||||
|
ElementGestureRecognizers.CollectionChanged -= _collectionChangedHandler;
|
||||||
|
|
||||||
|
_handler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelGestureRecognizersOnCollectionChanged (object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
|
||||||
|
{
|
||||||
|
LoadRecognizers ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnElementChanged (object sender, VisualElementChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.OldElement != null) {
|
||||||
|
// unhook
|
||||||
|
var oldView = e.OldElement as View;
|
||||||
|
if (oldView != null) {
|
||||||
|
var oldRecognizers = (ObservableCollection<IGestureRecognizer>)oldView.GestureRecognizers;
|
||||||
|
oldRecognizers.CollectionChanged -= _collectionChangedHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewElement != null) {
|
||||||
|
// hook
|
||||||
|
if (ElementGestureRecognizers != null) {
|
||||||
|
ElementGestureRecognizers.CollectionChanged += _collectionChangedHandler;
|
||||||
|
LoadRecognizers ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadRecognizers ()
|
||||||
|
{
|
||||||
|
if (ElementGestureRecognizers == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var recognizer in ElementGestureRecognizers) {
|
||||||
|
if (_gestureRecognizers.ContainsKey (recognizer))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var nativeRecognizer = GetNativeRecognizer (recognizer);
|
||||||
|
if (nativeRecognizer != null) {
|
||||||
|
AddGestureRecognizer (_handler, nativeRecognizer);
|
||||||
|
|
||||||
|
_gestureRecognizers[recognizer] = nativeRecognizer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toRemove = _gestureRecognizers.Keys.Where (key => !ElementGestureRecognizers.Contains (key)).ToArray ();
|
||||||
|
foreach (var gestureRecognizer in toRemove) {
|
||||||
|
var uiRecognizer = _gestureRecognizers[gestureRecognizer];
|
||||||
|
_gestureRecognizers.Remove (gestureRecognizer);
|
||||||
|
|
||||||
|
RemoveGestureRecognizer (_handler, uiRecognizer);
|
||||||
|
uiRecognizer.Dispose ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual NativeGestureRecognizer GetNativeRecognizer (IGestureRecognizer recognizer)
|
||||||
|
{
|
||||||
|
if (recognizer == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var weakRecognizer = new WeakReference (recognizer);
|
||||||
|
var weakEventTracker = new WeakReference (this);
|
||||||
|
|
||||||
|
var tapRecognizer = recognizer as TapGestureRecognizer;
|
||||||
|
if (tapRecognizer != null && tapRecognizer.NumberOfTapsRequired == 1) {
|
||||||
|
var returnAction = new TargetEventHandler ((s, e) => {
|
||||||
|
var tapGestureRecognizer = weakRecognizer.Target as TapGestureRecognizer;
|
||||||
|
var eventTracker = weakEventTracker.Target as EventTracker;
|
||||||
|
var view = eventTracker?._renderer?.Element as View;
|
||||||
|
|
||||||
|
if (tapGestureRecognizer != null && view != null)
|
||||||
|
tapGestureRecognizer.SendTapped (view);
|
||||||
|
});
|
||||||
|
var uiRecognizer = new NativeGestureRecognizer {
|
||||||
|
EventType = "click",
|
||||||
|
Handler = returnAction,
|
||||||
|
};
|
||||||
|
return uiRecognizer;
|
||||||
|
}
|
||||||
|
if (tapRecognizer != null && tapRecognizer.NumberOfTapsRequired == 2) {
|
||||||
|
var returnAction = new TargetEventHandler ((s, e) => {
|
||||||
|
var tapGestureRecognizer = weakRecognizer.Target as TapGestureRecognizer;
|
||||||
|
var eventTracker = weakEventTracker.Target as EventTracker;
|
||||||
|
var view = eventTracker?._renderer?.Element as View;
|
||||||
|
|
||||||
|
if (tapGestureRecognizer != null && view != null)
|
||||||
|
tapGestureRecognizer.SendTapped (view);
|
||||||
|
});
|
||||||
|
var uiRecognizer = new NativeGestureRecognizer {
|
||||||
|
EventType = "dblclick",
|
||||||
|
Handler = returnAction,
|
||||||
|
};
|
||||||
|
return uiRecognizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddGestureRecognizer (Element element, NativeGestureRecognizer recognizer)
|
||||||
|
{
|
||||||
|
element.AddEventListener (recognizer.EventType, recognizer.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RemoveGestureRecognizer (Element element, NativeGestureRecognizer recognizer)
|
||||||
|
{
|
||||||
|
element.RemoveEventListener (recognizer.EventType, recognizer.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadEvents (NativeView handler)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
throw new ObjectDisposedException (null);
|
||||||
|
|
||||||
|
_handler = handler;
|
||||||
|
OnElementChanged (this, new VisualElementChangedEventArgs (null, _renderer.Element));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class NativeGestureRecognizer : IDisposable
|
||||||
|
{
|
||||||
|
public string EventType;
|
||||||
|
public TargetEventHandler Handler;
|
||||||
|
public void Dispose ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,14 @@ using Xamarin.Forms.Internals;
|
||||||
[assembly: ExportRenderer (typeof (DatePicker), typeof (DatePickerRenderer))]
|
[assembly: ExportRenderer (typeof (DatePicker), typeof (DatePickerRenderer))]
|
||||||
[assembly: ExportRenderer (typeof (Editor), typeof (EditorRenderer))]
|
[assembly: ExportRenderer (typeof (Editor), typeof (EditorRenderer))]
|
||||||
[assembly: ExportRenderer (typeof (Entry), typeof (EntryRenderer))]
|
[assembly: ExportRenderer (typeof (Entry), typeof (EntryRenderer))]
|
||||||
|
[assembly: ExportRenderer (typeof (Frame), typeof (FrameRenderer))]
|
||||||
|
[assembly: ExportRenderer (typeof (Image), typeof (ImageRenderer))]
|
||||||
[assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))]
|
[assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))]
|
||||||
[assembly: ExportRenderer (typeof (ProgressBar), typeof (ProgressBarRenderer))]
|
[assembly: ExportRenderer (typeof (ProgressBar), typeof (ProgressBarRenderer))]
|
||||||
|
[assembly: ExportRenderer (typeof (Switch), typeof (SwitchRenderer))]
|
||||||
|
[assembly: ExportImageSourceHandler (typeof (FileImageSource), typeof (FileImageSourceHandler))]
|
||||||
|
[assembly: ExportImageSourceHandler (typeof (StreamImageSource), typeof (StreamImagesourceHandler))]
|
||||||
|
[assembly: ExportImageSourceHandler (typeof (UriImageSource), typeof (ImageLoaderSourceHandler))]
|
||||||
|
|
||||||
namespace Ooui.Forms
|
namespace Ooui.Forms
|
||||||
{
|
{
|
||||||
|
@ -24,4 +30,13 @@ namespace Ooui.Forms
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AttributeUsage (AttributeTargets.Assembly, AllowMultiple = true)]
|
||||||
|
public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute
|
||||||
|
{
|
||||||
|
public ExportImageSourceHandlerAttribute (Type handler, Type target)
|
||||||
|
: base (handler, target)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace Ooui.Forms.Extensions
|
||||||
if (self.Style.Width.Equals ("inherit")) {
|
if (self.Style.Width.Equals ("inherit")) {
|
||||||
s = self.Text.MeasureSize (self.Style);
|
s = self.Text.MeasureSize (self.Style);
|
||||||
measured = true;
|
measured = true;
|
||||||
rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : s.Width;
|
rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : Math.Ceiling (s.Width);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
rw = self.Style.GetNumberWithUnits ("width", "px", 640);
|
rw = self.Style.GetNumberWithUnits ("width", "px", 640);
|
||||||
|
@ -27,7 +27,7 @@ namespace Ooui.Forms.Extensions
|
||||||
s = self.Text.MeasureSize (self.Style);
|
s = self.Text.MeasureSize (self.Style);
|
||||||
measured = true;
|
measured = true;
|
||||||
}
|
}
|
||||||
rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : s.Height;
|
rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : Math.Ceiling (s.Height * 1.4);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
rh = self.Style.GetNumberWithUnits ("height", "px", 480);
|
rh = self.Style.GetNumberWithUnits ("height", "px", 480);
|
||||||
|
|
|
@ -44,9 +44,23 @@ namespace Ooui.Forms.Extensions
|
||||||
return Size.Zero;
|
return Size.Zero;
|
||||||
|
|
||||||
var fontHeight = fontSize;
|
var fontHeight = fontSize;
|
||||||
var charWidth = fontSize * 0.5;
|
|
||||||
|
|
||||||
var width = text.Length * charWidth;
|
var isBold = fontAttrs.HasFlag (FontAttributes.Bold);
|
||||||
|
|
||||||
|
var props = isBold ? BoldCharacterProportions : CharacterProportions;
|
||||||
|
var avgp = isBold ? BoldAverageCharProportion : AverageCharProportion;
|
||||||
|
|
||||||
|
var pwidth = 1.0e-6; // Tiny little padding to account for sampling errors
|
||||||
|
for (var i = 0; i < text.Length; i++) {
|
||||||
|
var c = (int)text[i];
|
||||||
|
if (c < 128) {
|
||||||
|
pwidth += props[c];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pwidth += avgp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var width = fontSize * pwidth;
|
||||||
|
|
||||||
return new Size (width, fontHeight);
|
return new Size (width, fontHeight);
|
||||||
}
|
}
|
||||||
|
@ -69,5 +83,57 @@ namespace Ooui.Forms.Extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ToOouiVerticalAlign (this TextAlignment align)
|
||||||
|
{
|
||||||
|
switch (align) {
|
||||||
|
case TextAlignment.Start:
|
||||||
|
default:
|
||||||
|
return "top";
|
||||||
|
case TextAlignment.Center:
|
||||||
|
return "middle";
|
||||||
|
case TextAlignment.End:
|
||||||
|
return "bottom";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly double[] CharacterProportions = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0.27799999713897705, 0.25899994373321533, 0.4259999990463257, 0.5560001134872437, 0.5560001134872437, 1.0000001192092896, 0.6299999952316284, 0.27799999713897705,
|
||||||
|
0.25899994373321533, 0.25899994373321533, 0.3520001173019409, 0.6000000238418579, 0.27799999713897705, 0.3890000581741333, 0.27799999713897705, 0.3330000638961792,
|
||||||
|
0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437,
|
||||||
|
0.5560001134872437, 0.5560001134872437, 0.27799999713897705, 0.27799999713897705, 0.6000000238418579, 0.6000000238418579, 0.6000000238418579, 0.5560001134872437,
|
||||||
|
0.8000000715255737, 0.6480001211166382, 0.6850000619888306, 0.722000002861023, 0.7040001153945923, 0.6110001802444458, 0.5740000009536743, 0.7589999437332153,
|
||||||
|
0.722000002861023, 0.25899994373321533, 0.5190001726150513, 0.6669999361038208, 0.5560001134872437, 0.8709999322891235, 0.722000002861023, 0.7600001096725464,
|
||||||
|
0.6480001211166382, 0.7600001096725464, 0.6850000619888306, 0.6480001211166382, 0.5740000009536743, 0.722000002861023, 0.6110001802444458, 0.9259999990463257,
|
||||||
|
0.6110001802444458, 0.6480001211166382, 0.6110001802444458, 0.25899994373321533, 0.3330000638961792, 0.25899994373321533, 0.6000000238418579, 0.5000001192092896,
|
||||||
|
0.22200000286102295, 0.5370000600814819, 0.593000054359436, 0.5370000600814819, 0.593000054359436, 0.5370000600814819, 0.2960001230239868, 0.5740000009536743,
|
||||||
|
0.5560001134872437, 0.22200000286102295, 0.22200000286102295, 0.5190001726150513, 0.22200000286102295, 0.8530000448226929, 0.5560001134872437, 0.5740000009536743,
|
||||||
|
0.593000054359436, 0.593000054359436, 0.3330000638961792, 0.5000001192092896, 0.31500017642974854, 0.5560001134872437, 0.5000001192092896, 0.7580000162124634,
|
||||||
|
0.5180000066757202, 0.5000001192092896, 0.4800001382827759, 0.3330000638961792, 0.22200000286102295, 0.3330000638961792, 0.6000000238418579, 0
|
||||||
|
};
|
||||||
|
const double AverageCharProportion = 0.5131400561332703;
|
||||||
|
|
||||||
|
static readonly double[] BoldCharacterProportions = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0.27799999713897705, 0.27799999713897705, 0.46299993991851807, 0.5560001134872437, 0.5560001134872437, 1.0000001192092896, 0.6850000619888306, 0.27799999713897705,
|
||||||
|
0.2960001230239868, 0.2960001230239868, 0.40700018405914307, 0.6000000238418579, 0.27799999713897705, 0.40700018405914307, 0.27799999713897705, 0.37099993228912354,
|
||||||
|
0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437,
|
||||||
|
0.5560001134872437, 0.5560001134872437, 0.27799999713897705, 0.27799999713897705, 0.6000000238418579, 0.6000000238418579, 0.6000000238418579, 0.5560001134872437,
|
||||||
|
0.8000000715255737, 0.6850000619888306, 0.7040001153945923, 0.7410000562667847, 0.7410000562667847, 0.6480001211166382, 0.593000054359436, 0.7589999437332153,
|
||||||
|
0.7410000562667847, 0.29499995708465576, 0.5560001134872437, 0.722000002861023, 0.593000054359436, 0.9070001840591431, 0.7410000562667847, 0.777999997138977,
|
||||||
|
0.6669999361038208, 0.777999997138977, 0.722000002861023, 0.6490000486373901, 0.6110001802444458, 0.7410000562667847, 0.6299999952316284, 0.9440001249313354,
|
||||||
|
0.6669999361038208, 0.6669999361038208, 0.6480001211166382, 0.3330000638961792, 0.37099993228912354, 0.3330000638961792, 0.6000000238418579, 0.5000001192092896,
|
||||||
|
0.25899994373321533, 0.5740000009536743, 0.6110001802444458, 0.5740000009536743, 0.6110001802444458, 0.5740000009536743, 0.3330000638961792, 0.6110001802444458,
|
||||||
|
0.593000054359436, 0.2580000162124634, 0.27799999713897705, 0.5740000009536743, 0.2580000162124634, 0.906000018119812, 0.593000054359436, 0.6110001802444458,
|
||||||
|
0.6110001802444458, 0.6110001802444458, 0.3890000581741333, 0.5370000600814819, 0.3520001173019409, 0.593000054359436, 0.5200001001358032, 0.8140000104904175,
|
||||||
|
0.5370000600814819, 0.5190001726150513, 0.5190001726150513, 0.3330000638961792, 0.223000168800354, 0.3330000638961792, 0.6000000238418579, 0
|
||||||
|
};
|
||||||
|
const double BoldAverageCharProportion = 0.5346300601959229;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,13 +26,13 @@ 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
|
Color.SetAccent (Color.FromHex ("#337ab7")); // Bootstrap Blue
|
||||||
|
|
||||||
Registrar.RegisterAll (new[] {
|
Registrar.RegisterAll (new[] {
|
||||||
typeof(ExportRendererAttribute),
|
typeof(ExportRendererAttribute),
|
||||||
//typeof(ExportCellAttribute),
|
//typeof(ExportCellAttribute),
|
||||||
//typeof(ExportImageSourceHandlerAttribute),
|
typeof(ExportImageSourceHandlerAttribute),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static event EventHandler<ViewInitializedEventArgs> ViewInitialized;
|
public static event EventHandler<ViewInitializedEventArgs> ViewInitialized;
|
||||||
|
@ -62,11 +62,6 @@ namespace Xamarin.Forms
|
||||||
Task.Run (action);
|
Task.Run (action);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ticker CreateTicker ()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Assembly[] GetAssemblies ()
|
public Assembly[] GetAssemblies ()
|
||||||
{
|
{
|
||||||
return AppDomain.CurrentDomain.GetAssemblies ();
|
return AppDomain.CurrentDomain.GetAssemblies ();
|
||||||
|
@ -119,6 +114,31 @@ namespace Xamarin.Forms
|
||||||
}
|
}
|
||||||
}), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds);
|
}), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Ticker CreateTicker ()
|
||||||
|
{
|
||||||
|
return new OouiTicker ();
|
||||||
|
}
|
||||||
|
|
||||||
|
class OouiTicker : Ticker
|
||||||
|
{
|
||||||
|
Timer timer;
|
||||||
|
protected override void DisableTimer ()
|
||||||
|
{
|
||||||
|
var t = timer;
|
||||||
|
timer = null;
|
||||||
|
t?.Dispose ();
|
||||||
|
}
|
||||||
|
protected override void EnableTimer ()
|
||||||
|
{
|
||||||
|
if (timer != null)
|
||||||
|
return;
|
||||||
|
var interval = TimeSpan.FromSeconds (1.0 / Ooui.UI.Session.MaxFps);
|
||||||
|
timer = new Timer ((_ => {
|
||||||
|
this.SendSignals ();
|
||||||
|
}), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ViewInitializedEventArgs
|
public class ViewInitializedEventArgs
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<Authors>praeclarum</Authors>
|
||||||
|
<Description>Xamarin.Forms backend for the web using Ooui technologies.</Description>
|
||||||
|
<PackageTags>Ooui;UI;CrossPlatform;Xamarin.Forms</PackageTags>
|
||||||
|
<PackageIconUrl>https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png</PackageIconUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/praeclarum/Ooui</PackageProjectUrl>
|
||||||
|
<PackageLicenseUrl>https://github.com/praeclarum/Ooui/blob/master/LICENSE</PackageLicenseUrl>
|
||||||
|
<RepositoryUrl>https://github.com/praeclarum/Ooui.git</RepositoryUrl>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,10 @@ namespace Ooui.Forms
|
||||||
MessagingCenter.Unsubscribe<Page, ActionSheetArguments> (this, Page.ActionSheetSignalName);
|
MessagingCenter.Unsubscribe<Page, ActionSheetArguments> (this, Page.ActionSheetSignalName);
|
||||||
MessagingCenter.Unsubscribe<Page, AlertArguments> (this, Page.AlertSignalName);
|
MessagingCenter.Unsubscribe<Page, AlertArguments> (this, Page.AlertSignalName);
|
||||||
MessagingCenter.Unsubscribe<Page, bool> (this, Page.BusySetSignalName);
|
MessagingCenter.Unsubscribe<Page, bool> (this, Page.BusySetSignalName);
|
||||||
|
|
||||||
|
DisposeModelAndChildrenRenderers (Page);
|
||||||
|
//foreach (var modal in _modals)
|
||||||
|
//DisposeModelAndChildrenRenderers (modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IVisualElementRenderer CreateRenderer (VisualElement element)
|
public static IVisualElementRenderer CreateRenderer (VisualElement element)
|
||||||
|
@ -110,7 +114,29 @@ namespace Ooui.Forms
|
||||||
|
|
||||||
void HandleChildRemoved (object sender, ElementEventArgs e)
|
void HandleChildRemoved (object sender, ElementEventArgs e)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException ();
|
var view = e.Element;
|
||||||
|
DisposeModelAndChildrenRenderers (view);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisposeModelAndChildrenRenderers (Xamarin.Forms.Element view)
|
||||||
|
{
|
||||||
|
IVisualElementRenderer renderer;
|
||||||
|
foreach (VisualElement child in view.Descendants ()) {
|
||||||
|
renderer = GetRenderer (child);
|
||||||
|
child.ClearValue (RendererProperty);
|
||||||
|
|
||||||
|
if (renderer != null) {
|
||||||
|
//renderer.NativeView.RemoveFromSuperview ();
|
||||||
|
renderer.Dispose ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer = GetRenderer ((VisualElement)view);
|
||||||
|
if (renderer != null) {
|
||||||
|
//renderer.NativeView.RemoveFromSuperview ();
|
||||||
|
renderer.Dispose ();
|
||||||
|
}
|
||||||
|
view.ClearValue (RendererProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddChild (VisualElement view)
|
void AddChild (VisualElement view)
|
||||||
|
|
|
@ -15,5 +15,19 @@ namespace Ooui.Forms
|
||||||
{
|
{
|
||||||
this.platform = platform;
|
this.platform = platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool TriggerEventFromMessage (Message message)
|
||||||
|
{
|
||||||
|
if (message.TargetId == "window" && message.Key == "resize" && message.Value is Newtonsoft.Json.Linq.JObject j) {
|
||||||
|
var width = (double)j["width"];
|
||||||
|
var height = (double)j["height"];
|
||||||
|
Platform.Element.Style.Width = width;
|
||||||
|
Platform.Element.Style.Height = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return base.TriggerEventFromMessage (message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Ooui.Forms.Renderers
|
||||||
protected override void Dispose (bool disposing)
|
protected override void Dispose (bool disposing)
|
||||||
{
|
{
|
||||||
if (Control != null) {
|
if (Control != null) {
|
||||||
Control.Clicked -= OnButtonTouchUpInside;
|
Control.Click -= OnButtonTouchUpInside;
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Dispose (disposing);
|
base.Dispose (disposing);
|
||||||
|
@ -44,7 +44,7 @@ namespace Ooui.Forms.Renderers
|
||||||
_buttonTextColorDefaultHighlighted = Ooui.Colors.Black;
|
_buttonTextColorDefaultHighlighted = Ooui.Colors.Black;
|
||||||
_buttonTextColorDefaultDisabled = Ooui.Colors.Black;
|
_buttonTextColorDefaultDisabled = Ooui.Colors.Black;
|
||||||
|
|
||||||
Control.Clicked += OnButtonTouchUpInside;
|
Control.Click += OnButtonTouchUpInside;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateText ();
|
UpdateText ();
|
||||||
|
|
|
@ -31,8 +31,8 @@ namespace Ooui.Forms.Renderers
|
||||||
Type = InputType.Date,
|
Type = InputType.Date,
|
||||||
};
|
};
|
||||||
|
|
||||||
entry.Inputted += OnStarted;
|
//entry.Input += OnStarted;
|
||||||
entry.Changed += OnEnded;
|
entry.Change += OnEnded;
|
||||||
|
|
||||||
SetNativeControl (entry);
|
SetNativeControl (entry);
|
||||||
}
|
}
|
||||||
|
@ -100,8 +100,8 @@ namespace Ooui.Forms.Renderers
|
||||||
|
|
||||||
if (disposing) {
|
if (disposing) {
|
||||||
if (Control != null) {
|
if (Control != null) {
|
||||||
Control.Inputted -= OnStarted;
|
//Control.Input -= OnStarted;
|
||||||
Control.Changed -= OnEnded;
|
Control.Change -= OnEnded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,12 @@ namespace Ooui.Forms.Renderers
|
||||||
bool _disposed;
|
bool _disposed;
|
||||||
IEditorController ElementController => Element;
|
IEditorController ElementController => Element;
|
||||||
|
|
||||||
|
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
|
||||||
|
{
|
||||||
|
var size = new Size (160, 100);
|
||||||
|
return new SizeRequest (size, size);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose (bool disposing)
|
protected override void Dispose (bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
@ -19,9 +25,9 @@ namespace Ooui.Forms.Renderers
|
||||||
|
|
||||||
if (disposing) {
|
if (disposing) {
|
||||||
if (Control != null) {
|
if (Control != null) {
|
||||||
Control.Changed -= HandleChanged;
|
Control.Input -= HandleChanged;
|
||||||
//Control.Started -= OnStarted;
|
//Control.Started -= OnStarted;
|
||||||
//Control.Ended -= OnEnded;
|
Control.Change -= OnEnded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +46,9 @@ namespace Ooui.Forms.Renderers
|
||||||
ClassName = "form-control"
|
ClassName = "form-control"
|
||||||
});
|
});
|
||||||
|
|
||||||
Control.Changed += HandleChanged;
|
Control.Input += HandleChanged;
|
||||||
//Control.Started += OnStarted;
|
//Control.Started += OnStarted;
|
||||||
//Control.Ended += OnEnded;
|
Control.Change += OnEnded;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateText ();
|
UpdateText ();
|
||||||
|
@ -75,13 +81,13 @@ namespace Ooui.Forms.Renderers
|
||||||
|
|
||||||
void HandleChanged (object sender, EventArgs e)
|
void HandleChanged (object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Text);
|
ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnEnded (object sender, EventArgs eventArgs)
|
void OnEnded (object sender, EventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (Control.Text != Element.Text)
|
if (Control.Value != Element.Text)
|
||||||
ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Text);
|
ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Value);
|
||||||
|
|
||||||
Element.SetValue (VisualElement.IsFocusedPropertyKey, false);
|
Element.SetValue (VisualElement.IsFocusedPropertyKey, false);
|
||||||
ElementController.SendCompleted ();
|
ElementController.SendCompleted ();
|
||||||
|
@ -108,8 +114,8 @@ namespace Ooui.Forms.Renderers
|
||||||
|
|
||||||
void UpdateText ()
|
void UpdateText ()
|
||||||
{
|
{
|
||||||
if (Control.Text != Element.Text)
|
if (Control.Value != Element.Text)
|
||||||
Control.Text = Element.Text;
|
Control.Value = Element.Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateTextAlignment ()
|
void UpdateTextAlignment ()
|
||||||
|
|
|
@ -6,7 +6,7 @@ using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Ooui.Forms.Renderers
|
namespace Ooui.Forms.Renderers
|
||||||
{
|
{
|
||||||
public class EntryRenderer : ViewRenderer<Entry, Ooui.Input>
|
public class EntryRenderer : ViewRenderer<Entry, Ooui.TextInput>
|
||||||
{
|
{
|
||||||
Ooui.Color _defaultTextColor;
|
Ooui.Color _defaultTextColor;
|
||||||
bool _disposed;
|
bool _disposed;
|
||||||
|
@ -17,7 +17,17 @@ namespace Ooui.Forms.Renderers
|
||||||
|
|
||||||
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
|
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
|
||||||
{
|
{
|
||||||
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
|
var text = Element.Text;
|
||||||
|
if (text == null || text.Length == 0) {
|
||||||
|
text = Element.Placeholder;
|
||||||
|
}
|
||||||
|
Size size;
|
||||||
|
if (text == null || text.Length == 0) {
|
||||||
|
size = new Size (Element.FontSize * 0.25, Element.FontSize);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
size = text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
|
||||||
|
}
|
||||||
size = new Size (size.Width, size.Height * 1.428 + 14);
|
size = new Size (size.Width, size.Height * 1.428 + 14);
|
||||||
return new SizeRequest (size, size);
|
return new SizeRequest (size, size);
|
||||||
}
|
}
|
||||||
|
@ -32,8 +42,8 @@ namespace Ooui.Forms.Renderers
|
||||||
if (disposing) {
|
if (disposing) {
|
||||||
if (Control != null) {
|
if (Control != null) {
|
||||||
//Control.Inputted -= OnEditingBegan;
|
//Control.Inputted -= OnEditingBegan;
|
||||||
Control.Inputted -= OnEditingChanged;
|
Control.Input -= OnEditingChanged;
|
||||||
Control.Changed -= OnEditingEnded;
|
Control.Change -= OnEditingEnded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +58,7 @@ namespace Ooui.Forms.Renderers
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Control == null) {
|
if (Control == null) {
|
||||||
var textField = new Ooui.Input (InputType.Text);
|
var textField = new Ooui.TextInput ();
|
||||||
SetNativeControl (textField);
|
SetNativeControl (textField);
|
||||||
|
|
||||||
Debug.Assert (Control != null, "Control != null");
|
Debug.Assert (Control != null, "Control != null");
|
||||||
|
@ -57,10 +67,10 @@ namespace Ooui.Forms.Renderers
|
||||||
|
|
||||||
_defaultTextColor = Colors.Black;
|
_defaultTextColor = Colors.Black;
|
||||||
|
|
||||||
textField.Inputted += OnEditingChanged;
|
textField.Input += OnEditingChanged;
|
||||||
|
|
||||||
//textField.EditingDidBegin += OnEditingBegan;
|
//textField.EditingDidBegin += OnEditingBegan;
|
||||||
textField.Changed += OnEditingEnded;
|
textField.Change += OnEditingEnded;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdatePlaceholder ();
|
UpdatePlaceholder ();
|
||||||
|
@ -113,8 +123,8 @@ namespace Ooui.Forms.Renderers
|
||||||
void OnEditingEnded (object sender, EventArgs e)
|
void OnEditingEnded (object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// Typing aid changes don't always raise EditingChanged event
|
// Typing aid changes don't always raise EditingChanged event
|
||||||
if (Control.Text != Element.Text) {
|
if (Control.Value != Element.Text) {
|
||||||
ElementController.SetValueFromRenderer (Entry.TextProperty, Control.Text);
|
ElementController.SetValueFromRenderer (Entry.TextProperty, Control.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementController.SetValueFromRenderer (VisualElement.IsFocusedPropertyKey, false);
|
ElementController.SetValueFromRenderer (VisualElement.IsFocusedPropertyKey, false);
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Ooui.Forms.Extensions;
|
||||||
|
|
||||||
|
namespace Ooui.Forms.Renderers
|
||||||
|
{
|
||||||
|
public class FrameRenderer : VisualElementRenderer<Frame>
|
||||||
|
{
|
||||||
|
protected override void OnElementChanged (ElementChangedEventArgs<Frame> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged (e);
|
||||||
|
|
||||||
|
if (e.NewElement != null)
|
||||||
|
SetupLayer ();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged (sender, e);
|
||||||
|
|
||||||
|
if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName ||
|
||||||
|
e.PropertyName == Xamarin.Forms.Frame.OutlineColorProperty.PropertyName ||
|
||||||
|
e.PropertyName == Xamarin.Forms.Frame.HasShadowProperty.PropertyName ||
|
||||||
|
e.PropertyName == Xamarin.Forms.Frame.CornerRadiusProperty.PropertyName)
|
||||||
|
SetupLayer ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupLayer ()
|
||||||
|
{
|
||||||
|
float cornerRadius = Element.CornerRadius;
|
||||||
|
|
||||||
|
if (cornerRadius == -1f)
|
||||||
|
cornerRadius = 5f; // default corner radius
|
||||||
|
|
||||||
|
var Layer = this.Style;
|
||||||
|
|
||||||
|
Layer.BorderRadius = cornerRadius;
|
||||||
|
|
||||||
|
if (Element.BackgroundColor == Xamarin.Forms.Color.Default)
|
||||||
|
Layer.BackgroundColor = "white";
|
||||||
|
else
|
||||||
|
Layer.BackgroundColor = Element.BackgroundColor.ToOouiColor ();
|
||||||
|
|
||||||
|
if (Element.HasShadow) {
|
||||||
|
//Layer.ShadowRadius = 5;
|
||||||
|
//Layer.ShadowColor = "black";
|
||||||
|
//Layer.ShadowOpacity = 0.8f;
|
||||||
|
//Layer.ShadowOffset = new SizeF ();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//Layer.ShadowOpacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Element.OutlineColor == Xamarin.Forms.Color.Default)
|
||||||
|
Layer.BorderColor = Colors.Clear;
|
||||||
|
else {
|
||||||
|
Layer.BorderColor = Element.OutlineColor.ToOouiColor ();
|
||||||
|
Layer.BorderWidth = 1;
|
||||||
|
Layer.BorderStyle = "solid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Ooui.Forms.Renderers
|
||||||
|
{
|
||||||
|
public class ImageRenderer : ViewRenderer<Xamarin.Forms.Image, Ooui.Image>
|
||||||
|
{
|
||||||
|
bool _isDisposed;
|
||||||
|
|
||||||
|
protected override void Dispose (bool disposing)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (disposing) {
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
|
||||||
|
base.Dispose (disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnElementChanged (ElementChangedEventArgs<Xamarin.Forms.Image> e)
|
||||||
|
{
|
||||||
|
if (Control == null) {
|
||||||
|
var imageView = new Ooui.Image ();
|
||||||
|
SetNativeControl (imageView);
|
||||||
|
this.Style.Overflow = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewElement != null) {
|
||||||
|
SetAspect ();
|
||||||
|
await TrySetImage (e.OldElement);
|
||||||
|
SetOpacity ();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnElementChanged (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnElementPropertyChanged (object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged (sender, e);
|
||||||
|
if (e.PropertyName == Xamarin.Forms.Image.SourceProperty.PropertyName)
|
||||||
|
await TrySetImage ();
|
||||||
|
else if (e.PropertyName == Xamarin.Forms.Image.IsOpaqueProperty.PropertyName)
|
||||||
|
SetOpacity ();
|
||||||
|
else if (e.PropertyName == Xamarin.Forms.Image.AspectProperty.PropertyName)
|
||||||
|
SetAspect ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAspect ()
|
||||||
|
{
|
||||||
|
if (_isDisposed || Element == null || Control == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual async Task TrySetImage (Xamarin.Forms.Image previous = null)
|
||||||
|
{
|
||||||
|
// By default we'll just catch and log any exceptions thrown by SetImage so they don't bring down
|
||||||
|
// the application; a custom renderer can override this method and handle exceptions from
|
||||||
|
// SetImage differently if it wants to
|
||||||
|
|
||||||
|
try {
|
||||||
|
await SetImage (previous).ConfigureAwait (false);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.Diagnostics.Debug.WriteLine ("Error loading image: {0}", ex);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
((IImageController)Element)?.SetIsLoading (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task SetImage (Xamarin.Forms.Image oldElement = null)
|
||||||
|
{
|
||||||
|
if (_isDisposed || Element == null || Control == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var source = Element.Source;
|
||||||
|
|
||||||
|
if (oldElement != null) {
|
||||||
|
var oldSource = oldElement.Source;
|
||||||
|
if (Equals (oldSource, source))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (oldSource is FileImageSource && source is FileImageSource && ((FileImageSource)oldSource).File == ((FileImageSource)source).File)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Control.Source = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
IImageSourceHandler handler;
|
||||||
|
|
||||||
|
Element.SetIsLoading (true);
|
||||||
|
|
||||||
|
if (source != null &&
|
||||||
|
(handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandler<IImageSourceHandler> (source.GetType ())) != null) {
|
||||||
|
string uiimage;
|
||||||
|
try {
|
||||||
|
uiimage = await handler.LoadImageAsync (source, scale: 1.0f);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) {
|
||||||
|
uiimage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var imageView = Control;
|
||||||
|
if (imageView != null)
|
||||||
|
imageView.Source = uiimage;
|
||||||
|
|
||||||
|
((IVisualElementController)Element).NativeSizeChanged ();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Control.Source = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Element.SetIsLoading (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetOpacity ()
|
||||||
|
{
|
||||||
|
if (_isDisposed || Element == null || Control == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IImageSourceHandler : IRegisterable
|
||||||
|
{
|
||||||
|
Task<string> LoadImageAsync (ImageSource imagesource, CancellationToken cancelationToken = default (CancellationToken), float scale = 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class FileImageSourceHandler : IImageSourceHandler
|
||||||
|
{
|
||||||
|
public async Task<string> LoadImageAsync (ImageSource imagesource, CancellationToken cancelationToken = default (CancellationToken), float scale = 1f)
|
||||||
|
{
|
||||||
|
string image = null;
|
||||||
|
var filesource = imagesource as FileImageSource;
|
||||||
|
var file = filesource?.File;
|
||||||
|
if (!string.IsNullOrEmpty (file)) {
|
||||||
|
var name = System.IO.Path.GetFileName (file);
|
||||||
|
image = "/images/" + name;
|
||||||
|
if (Ooui.UI.TryGetFileContentAtPath (image, out var f)) {
|
||||||
|
// Already published
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await Task.Run (() => Ooui.UI.PublishFile (image, file), cancelationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class StreamImagesourceHandler : IImageSourceHandler
|
||||||
|
{
|
||||||
|
public async Task<string> LoadImageAsync (ImageSource imagesource, CancellationToken cancelationToken = default (CancellationToken), float scale = 1f)
|
||||||
|
{
|
||||||
|
string image = null;
|
||||||
|
var streamsource = imagesource as StreamImageSource;
|
||||||
|
if (streamsource?.Stream != null) {
|
||||||
|
using (var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync (cancelationToken).ConfigureAwait (false)) {
|
||||||
|
if (streamImage != null) {
|
||||||
|
var data = new byte[streamImage.Length];
|
||||||
|
using (var outputStream = new System.IO.MemoryStream (data)) {
|
||||||
|
await streamImage.CopyToAsync (outputStream, 4096, cancelationToken).ConfigureAwait (false);
|
||||||
|
}
|
||||||
|
var hash = Ooui.UI.Hash (data);
|
||||||
|
var etag = "\"" + hash + "\"";
|
||||||
|
image = "/images/" + hash;
|
||||||
|
if (Ooui.UI.TryGetFileContentAtPath (image, out var file) && file.Etag == etag) {
|
||||||
|
// Already published
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ooui.UI.PublishFile (image, data, etag, "image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image == null) {
|
||||||
|
System.Diagnostics.Debug.WriteLine ("Could not load image: {0}", streamsource);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ImageLoaderSourceHandler : IImageSourceHandler
|
||||||
|
{
|
||||||
|
public Task<string> LoadImageAsync (ImageSource imagesource, CancellationToken cancelationToken = default (CancellationToken), float scale = 1f)
|
||||||
|
{
|
||||||
|
var imageLoader = imagesource as UriImageSource;
|
||||||
|
return Task.FromResult (imageLoader?.Uri.ToString () ?? "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ namespace Ooui.Forms.Renderers
|
||||||
{
|
{
|
||||||
if (!_perfectSizeValid) {
|
if (!_perfectSizeValid) {
|
||||||
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
|
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
|
||||||
|
size.Width = Math.Ceiling (size.Width);
|
||||||
|
size.Height = Math.Ceiling (size.Height * 1.4);
|
||||||
_perfectSize = new SizeRequest (size, size);
|
_perfectSize = new SizeRequest (size, size);
|
||||||
_perfectSizeValid = true;
|
_perfectSizeValid = true;
|
||||||
}
|
}
|
||||||
|
@ -72,6 +74,9 @@ namespace Ooui.Forms.Renderers
|
||||||
{
|
{
|
||||||
base.OnElementPropertyChanged (sender, e);
|
base.OnElementPropertyChanged (sender, e);
|
||||||
|
|
||||||
|
if (Control == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (e.PropertyName == Xamarin.Forms.Label.HorizontalTextAlignmentProperty.PropertyName)
|
if (e.PropertyName == Xamarin.Forms.Label.HorizontalTextAlignmentProperty.PropertyName)
|
||||||
UpdateAlignment ();
|
UpdateAlignment ();
|
||||||
else if (e.PropertyName == Xamarin.Forms.Label.VerticalTextAlignmentProperty.PropertyName)
|
else if (e.PropertyName == Xamarin.Forms.Label.VerticalTextAlignmentProperty.PropertyName)
|
||||||
|
@ -98,8 +103,10 @@ namespace Ooui.Forms.Renderers
|
||||||
|
|
||||||
void UpdateAlignment ()
|
void UpdateAlignment ()
|
||||||
{
|
{
|
||||||
Control.Style.TextAlign = Element.HorizontalTextAlignment.ToOouiTextAlign ();
|
this.Style.Display = "table";
|
||||||
Control.Style.VerticalAlign = Element.VerticalTextAlignment.ToOouiTextAlign ();
|
Control.Style.Display = "table-cell";
|
||||||
|
this.Style.TextAlign = Element.HorizontalTextAlignment.ToOouiTextAlign ();
|
||||||
|
Control.Style.VerticalAlign = Element.VerticalTextAlignment.ToOouiVerticalAlign ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateLineBreakMode ()
|
void UpdateLineBreakMode ()
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Ooui.Forms.Renderers
|
||||||
|
{
|
||||||
|
public class SwitchRenderer : ViewRenderer<Switch, Input>
|
||||||
|
{
|
||||||
|
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
|
||||||
|
{
|
||||||
|
var size = new Size (54, 38);
|
||||||
|
return new SizeRequest (size, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose (bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
Control.Change -= OnControlValueChanged;
|
||||||
|
|
||||||
|
base.Dispose (disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementChanged (ElementChangedEventArgs<Switch> e)
|
||||||
|
{
|
||||||
|
if (e.OldElement != null)
|
||||||
|
e.OldElement.Toggled -= OnElementToggled;
|
||||||
|
|
||||||
|
if (e.NewElement != null) {
|
||||||
|
if (Control == null) {
|
||||||
|
var input = new Input (InputType.Checkbox);
|
||||||
|
input.SetAttribute ("data-toggle", "toggle");
|
||||||
|
SetNativeControl (input);
|
||||||
|
input.Call ("$.bootstrapToggle");
|
||||||
|
Control.Change += OnControlValueChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
Control.IsChecked = Element.IsToggled;
|
||||||
|
e.NewElement.Toggled += OnElementToggled;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnElementChanged (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnControlValueChanged (object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
((IElementController)Element).SetValueFromRenderer (Switch.IsToggledProperty, Control.IsChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnElementToggled (object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Control.IsChecked = Element.IsToggled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ namespace Ooui.Forms
|
||||||
|
|
||||||
VisualElementRendererFlags _flags = VisualElementRendererFlags.AutoPackage | VisualElementRendererFlags.AutoTrack;
|
VisualElementRendererFlags _flags = VisualElementRendererFlags.AutoPackage | VisualElementRendererFlags.AutoTrack;
|
||||||
|
|
||||||
|
EventTracker _events;
|
||||||
VisualElementPackager _packager;
|
VisualElementPackager _packager;
|
||||||
VisualElementTracker _tracker;
|
VisualElementTracker _tracker;
|
||||||
|
|
||||||
|
@ -107,10 +108,10 @@ namespace Ooui.Forms
|
||||||
_packager.Load ();
|
_packager.Load ();
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (AutoTrack && _events == null) {
|
if (AutoTrack && _events == null) {
|
||||||
// _events = new EventTracker (this);
|
_events = new EventTracker (this);
|
||||||
// _events.LoadEvents (this);
|
_events.LoadEvents (this);
|
||||||
//}
|
}
|
||||||
|
|
||||||
element.PropertyChanged += _propertyChangedHandler;
|
element.PropertyChanged += _propertyChangedHandler;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,97 +81,97 @@ namespace Ooui
|
||||||
|
|
||||||
public void Save ()
|
public void Save ()
|
||||||
{
|
{
|
||||||
SendCall ("save");
|
Call ("save");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Restore ()
|
public void Restore ()
|
||||||
{
|
{
|
||||||
SendCall ("restore");
|
Call ("restore");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearRect (double x, double y, double w, double h)
|
public void ClearRect (double x, double y, double w, double h)
|
||||||
{
|
{
|
||||||
SendCall ("clearRect", x, y, w, h);
|
Call ("clearRect", x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillRect (double x, double y, double w, double h)
|
public void FillRect (double x, double y, double w, double h)
|
||||||
{
|
{
|
||||||
SendCall ("fillRect", x, y, w, h);
|
Call ("fillRect", x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StrokeRect (double x, double y, double w, double h)
|
public void StrokeRect (double x, double y, double w, double h)
|
||||||
{
|
{
|
||||||
SendCall ("strokeRect", x, y, w, h);
|
Call ("strokeRect", x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginPath ()
|
public void BeginPath ()
|
||||||
{
|
{
|
||||||
SendCall ("beginPath");
|
Call ("beginPath");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClosePath ()
|
public void ClosePath ()
|
||||||
{
|
{
|
||||||
SendCall ("closePath");
|
Call ("closePath");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MoveTo (double x, double y)
|
public void MoveTo (double x, double y)
|
||||||
{
|
{
|
||||||
SendCall ("moveTo", x, y);
|
Call ("moveTo", x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LineTo (double x, double y)
|
public void LineTo (double x, double y)
|
||||||
{
|
{
|
||||||
SendCall ("lineTo", x, y);
|
Call ("lineTo", x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QuadraticCurveTo (double cpx, double cpy, double x, double y)
|
public void QuadraticCurveTo (double cpx, double cpy, double x, double y)
|
||||||
{
|
{
|
||||||
SendCall ("quadraticCurveTo", cpx, cpy, x, y);
|
Call ("quadraticCurveTo", cpx, cpy, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BezierCurveTo (double cp1x, double cp1y, double cp2x, double cp2y, double x, double y)
|
public void BezierCurveTo (double cp1x, double cp1y, double cp2x, double cp2y, double x, double y)
|
||||||
{
|
{
|
||||||
SendCall ("bezierCurveTo", cp1x, cp1y, cp2x, cp2y, x, y);
|
Call ("bezierCurveTo", cp1x, cp1y, cp2x, cp2y, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ArcTo (double x1, double y1, double x2, double y2, double radius)
|
public void ArcTo (double x1, double y1, double x2, double y2, double radius)
|
||||||
{
|
{
|
||||||
SendCall ("arcTo", x1, y1, x2, y2, radius);
|
Call ("arcTo", x1, y1, x2, y2, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rect (double x, double y, double w, double h)
|
public void Rect (double x, double y, double w, double h)
|
||||||
{
|
{
|
||||||
SendCall ("rect", x, y, w, h);
|
Call ("rect", x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Arc (double x, double y, double radius, double startAngle, double endAngle, bool counterclockwise)
|
public void Arc (double x, double y, double radius, double startAngle, double endAngle, bool counterclockwise)
|
||||||
{
|
{
|
||||||
SendCall ("arc", x, y, radius, startAngle, endAngle, counterclockwise);
|
Call ("arc", x, y, radius, startAngle, endAngle, counterclockwise);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Fill ()
|
public void Fill ()
|
||||||
{
|
{
|
||||||
SendCall ("fill");
|
Call ("fill");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stroke ()
|
public void Stroke ()
|
||||||
{
|
{
|
||||||
SendCall ("stroke");
|
Call ("stroke");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clip ()
|
public void Clip ()
|
||||||
{
|
{
|
||||||
SendCall ("clip");
|
Call ("clip");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillText (string text, double x, double y, double? maxWidth)
|
public void FillText (string text, double x, double y, double? maxWidth)
|
||||||
{
|
{
|
||||||
SendCall ("fillText", text, x, y, maxWidth);
|
Call ("fillText", text, x, y, maxWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StrokeText (string text, double x, double y, double? maxWidth)
|
public void StrokeText (string text, double x, double y, double? maxWidth)
|
||||||
{
|
{
|
||||||
SendCall ("strokeText", text, x, y, maxWidth);
|
Call ("strokeText", text, x, y, maxWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,8 @@ function ooui (rootElementPath) {
|
||||||
socket.addEventListener ("close", function (event) {
|
socket.addEventListener ("close", function (event) {
|
||||||
console.error ("Web socket close", event);
|
console.error ("Web socket close", event);
|
||||||
if (opened) {
|
if (opened) {
|
||||||
location.reload ();
|
alert ("Connection to the server has been lost. Please try refreshing the page.");
|
||||||
|
opened = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -65,11 +66,19 @@ function ooui (rootElementPath) {
|
||||||
const messages = JSON.parse (event.data);
|
const messages = JSON.parse (event.data);
|
||||||
if (debug) console.log("Messages", messages);
|
if (debug) console.log("Messages", messages);
|
||||||
if (Array.isArray (messages)) {
|
if (Array.isArray (messages)) {
|
||||||
|
const jqs = []
|
||||||
messages.forEach (function (m) {
|
messages.forEach (function (m) {
|
||||||
// console.log('Raw value from server', m.v);
|
// console.log('Raw value from server', m.v);
|
||||||
m.v = fixupValue (m.v);
|
m.v = fixupValue (m.v);
|
||||||
processMessage (m);
|
if (m.k.startsWith ("$.")) {
|
||||||
|
jqs.push (m);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
processMessage (m);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
// Run jQuery functions last since they usually require a fully built DOM
|
||||||
|
jqs.forEach (processMessage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -92,8 +101,8 @@ function ooui (rootElementPath) {
|
||||||
function resizeHandler() {
|
function resizeHandler() {
|
||||||
const em = {
|
const em = {
|
||||||
m: "event",
|
m: "event",
|
||||||
id: 42,
|
id: "window",
|
||||||
k: "window.resize",
|
k: "resize",
|
||||||
v: getSize (),
|
v: getSize (),
|
||||||
};
|
};
|
||||||
const ems = JSON.stringify (em);
|
const ems = JSON.stringify (em);
|
||||||
|
@ -163,9 +172,11 @@ function msgCall (m) {
|
||||||
console.error ("Unknown node id", m);
|
console.error ("Unknown node id", m);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const f = node[m.k];
|
const isJQuery = m.k.startsWith ("$.");
|
||||||
|
const target = isJQuery ? $(node) : node;
|
||||||
|
const f = isJQuery ? target[m.k.slice(2)] : target[m.k];
|
||||||
if (debug) console.log ("Call", node, f, m.v);
|
if (debug) console.log ("Call", node, f, m.v);
|
||||||
const r = f.apply (node, m.v);
|
const r = f.apply (target, m.v);
|
||||||
if (typeof m.rid === 'string' || m.rid instanceof String) {
|
if (typeof m.rid === 'string' || m.rid instanceof String) {
|
||||||
nodes[m.rid] = r;
|
nodes[m.rid] = r;
|
||||||
}
|
}
|
||||||
|
|
17
Ooui/Div.cs
17
Ooui/Div.cs
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ooui
|
namespace Ooui
|
||||||
{
|
{
|
||||||
|
@ -8,5 +9,21 @@ namespace Ooui
|
||||||
: base ("div")
|
: base ("div")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Div (params Element[] children)
|
||||||
|
: this ()
|
||||||
|
{
|
||||||
|
foreach (var c in children) {
|
||||||
|
AppendChild (c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Div (IEnumerable<Element> children)
|
||||||
|
: this ()
|
||||||
|
{
|
||||||
|
foreach (var c in children) {
|
||||||
|
AppendChild (c);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,12 @@ namespace Ooui
|
||||||
set => SetProperty (ref hidden, value, "hidden");
|
set => SetProperty (ref hidden, value, "hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler Clicked {
|
public event TargetEventHandler Click {
|
||||||
add => AddEventListener ("click", value);
|
add => AddEventListener ("click", value);
|
||||||
remove => RemoveEventListener ("click", value);
|
remove => RemoveEventListener ("click", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler DoubleClicked {
|
public event TargetEventHandler DoubleClick {
|
||||||
add => AddEventListener ("dblclick", value);
|
add => AddEventListener ("dblclick", value);
|
||||||
remove => RemoveEventListener ("dblclick", value);
|
remove => RemoveEventListener ("dblclick", value);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ namespace Ooui
|
||||||
remove => RemoveEventListener ("keydown", value);
|
remove => RemoveEventListener ("keydown", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler KeyPressed {
|
public event TargetEventHandler KeyPress {
|
||||||
add => AddEventListener ("keypress", value);
|
add => AddEventListener ("keypress", value);
|
||||||
remove => RemoveEventListener ("keypress", value);
|
remove => RemoveEventListener ("keypress", value);
|
||||||
}
|
}
|
||||||
|
@ -55,17 +55,17 @@ namespace Ooui
|
||||||
remove => RemoveEventListener ("mousedown", value);
|
remove => RemoveEventListener ("mousedown", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler MouseEntered {
|
public event TargetEventHandler MouseEnter {
|
||||||
add => AddEventListener ("mouseenter", value);
|
add => AddEventListener ("mouseenter", value);
|
||||||
remove => RemoveEventListener ("mouseenter", value);
|
remove => RemoveEventListener ("mouseenter", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler MouseLeft {
|
public event TargetEventHandler MouseLeave {
|
||||||
add => AddEventListener ("mouseleave", value);
|
add => AddEventListener ("mouseleave", value);
|
||||||
remove => RemoveEventListener ("mouseleave", value);
|
remove => RemoveEventListener ("mouseleave", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler MouseMoved {
|
public event TargetEventHandler MouseMove {
|
||||||
add => AddEventListener ("mousemove", value);
|
add => AddEventListener ("mousemove", value);
|
||||||
remove => RemoveEventListener ("mousemove", value);
|
remove => RemoveEventListener ("mousemove", value);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ namespace Ooui
|
||||||
remove => RemoveEventListener ("mouseup", value);
|
remove => RemoveEventListener ("mouseup", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler Wheeled {
|
public event TargetEventHandler Wheel {
|
||||||
add => AddEventListener ("wheel", value);
|
add => AddEventListener ("wheel", value);
|
||||||
remove => RemoveEventListener ("wheel", value);
|
remove => RemoveEventListener ("wheel", value);
|
||||||
}
|
}
|
||||||
|
@ -116,5 +116,19 @@ namespace Ooui
|
||||||
{
|
{
|
||||||
SendSet ("style." + Style.GetJsName (e.PropertyName), Style[e.PropertyName]);
|
SendSet ("style." + Style.GetJsName (e.PropertyName), Style[e.PropertyName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool SaveStateMessageIfNeeded (Message message)
|
||||||
|
{
|
||||||
|
if (message.TargetId != Id)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (message.MessageType) {
|
||||||
|
case MessageType.Call when message.Key.StartsWith ("$.", StringComparison.Ordinal):
|
||||||
|
AddStateMessage (message);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return base.SaveStateMessageIfNeeded (message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ namespace Ooui
|
||||||
MessageSent?.Invoke (message);
|
MessageSent?.Invoke (message);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SendCall (string methodName, params object[] args)
|
public void Call (string methodName, params object[] args)
|
||||||
{
|
{
|
||||||
Send (Message.Call (Id, methodName, args));
|
Send (Message.Call (Id, methodName, args));
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Ooui
|
||||||
set => SetProperty (ref enctype, value ?? "", "enctype");
|
set => SetProperty (ref enctype, value ?? "", "enctype");
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler Submitted {
|
public event TargetEventHandler Submit {
|
||||||
add => AddEventListener ("submit", value);
|
add => AddEventListener ("submit", value);
|
||||||
remove => RemoveEventListener ("submit", value);
|
remove => RemoveEventListener ("submit", value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,16 +30,11 @@ namespace Ooui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler Changed {
|
public event TargetEventHandler Change {
|
||||||
add => AddEventListener ("change", value);
|
add => AddEventListener ("change", value);
|
||||||
remove => RemoveEventListener ("change", value);
|
remove => RemoveEventListener ("change", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler Inputted {
|
|
||||||
add => AddEventListener ("input", value);
|
|
||||||
remove => RemoveEventListener ("input", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
string placeholder = "";
|
string placeholder = "";
|
||||||
public string Placeholder {
|
public string Placeholder {
|
||||||
get => placeholder;
|
get => placeholder;
|
||||||
|
@ -77,7 +72,7 @@ namespace Ooui
|
||||||
: base ("input")
|
: base ("input")
|
||||||
{
|
{
|
||||||
// Subscribe to the change event so we always get up-to-date values
|
// Subscribe to the change event so we always get up-to-date values
|
||||||
Changed += (s, e) => {};
|
Change += (s, e) => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Input (InputType type)
|
public Input (InputType type)
|
||||||
|
@ -88,7 +83,7 @@ namespace Ooui
|
||||||
|
|
||||||
protected override bool TriggerEventFromMessage (Message message)
|
protected override bool TriggerEventFromMessage (Message message)
|
||||||
{
|
{
|
||||||
if (message.TargetId == Id && message.MessageType == MessageType.Event && message.Key == "change") {
|
if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
|
||||||
// Don't need to notify here because the base implementation will fire the event
|
// Don't need to notify here because the base implementation will fire the event
|
||||||
if (Type == InputType.Checkbox) {
|
if (Type == InputType.Checkbox) {
|
||||||
isChecked = message.Value != null ? Convert.ToBoolean (message.Value) : false;
|
isChecked = message.Value != null ? Convert.ToBoolean (message.Value) : false;
|
||||||
|
|
16
Ooui/Node.cs
16
Ooui/Node.cs
|
@ -73,7 +73,8 @@ namespace Ooui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newChild.MessageSent += HandleChildMessageSent;
|
newChild.MessageSent += HandleChildMessageSent;
|
||||||
SendCall ("insertBefore", newChild, referenceChild);
|
Call ("insertBefore", newChild, referenceChild);
|
||||||
|
OnChildInsertedBefore (newChild, referenceChild);
|
||||||
return newChild;
|
return newChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +88,19 @@ namespace Ooui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
child.MessageSent -= HandleChildMessageSent;
|
child.MessageSent -= HandleChildMessageSent;
|
||||||
SendCall ("removeChild", child);
|
Call ("removeChild", child);
|
||||||
|
OnChildRemoved (child);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnChildInsertedBefore (Node newChild, Node referenceChild)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnChildRemoved (Node child)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected void ReplaceAll (Node newNode)
|
protected void ReplaceAll (Node newNode)
|
||||||
{
|
{
|
||||||
var toRemove = new List<Node> ();
|
var toRemove = new List<Node> ();
|
||||||
|
@ -100,7 +110,7 @@ namespace Ooui
|
||||||
}
|
}
|
||||||
foreach (var child in toRemove) {
|
foreach (var child in toRemove) {
|
||||||
child.MessageSent -= HandleChildMessageSent;
|
child.MessageSent -= HandleChildMessageSent;
|
||||||
SendCall ("removeChild", child);
|
Call ("removeChild", child);
|
||||||
}
|
}
|
||||||
InsertBefore (newNode, null);
|
InsertBefore (newNode, null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>0.2.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<Authors>praeclarum</Authors>
|
<Authors>praeclarum</Authors>
|
||||||
<Description>Small cross-platform UI library for .NET that uses web technologies.</Description>
|
<Description>Small cross-platform UI library for .NET that uses web technologies.</Description>
|
||||||
<PackageTags>UI;CrossPlatform</PackageTags>
|
<PackageTags>Ooui;UI;CrossPlatform</PackageTags>
|
||||||
<PackageIconUrl>https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png</PackageIconUrl>
|
<PackageIconUrl>https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png</PackageIconUrl>
|
||||||
<PackageProjectUrl>https://github.com/praeclarum/Ooui</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/praeclarum/Ooui</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/praeclarum/Ooui/blob/master/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/praeclarum/Ooui/blob/master/LICENSE</PackageLicenseUrl>
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ooui
|
||||||
|
{
|
||||||
|
public class Option : Element
|
||||||
|
{
|
||||||
|
string val = "";
|
||||||
|
public string Value {
|
||||||
|
get => val;
|
||||||
|
set => SetProperty (ref val, value ?? "", "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
string label = "";
|
||||||
|
public string Label {
|
||||||
|
get => label;
|
||||||
|
set => SetProperty (ref label, value ?? "", "label");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool defaultSelected = false;
|
||||||
|
public bool DefaultSelected {
|
||||||
|
get => defaultSelected;
|
||||||
|
set => SetProperty (ref defaultSelected, value, "defaultSelected");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Option ()
|
||||||
|
: base ("option")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,14 +10,42 @@ namespace Ooui
|
||||||
set => SetProperty (ref val, value ?? "", "value");
|
set => SetProperty (ref val, value ?? "", "value");
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler Changed {
|
public event TargetEventHandler Change {
|
||||||
add => AddEventListener ("change", value);
|
add => AddEventListener ("change", value);
|
||||||
remove => RemoveEventListener ("change", value);
|
remove => RemoveEventListener ("change", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event TargetEventHandler Input {
|
||||||
|
add => AddEventListener ("input", value);
|
||||||
|
remove => RemoveEventListener ("input", value);
|
||||||
|
}
|
||||||
|
|
||||||
public Select ()
|
public Select ()
|
||||||
: base ("select")
|
: base ("select")
|
||||||
{
|
{
|
||||||
|
// Subscribe to the change event so we always get up-to-date values
|
||||||
|
Change += (s, e) => { };
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOption (string label, string value)
|
||||||
|
{
|
||||||
|
AppendChild (new Option { Label = label, Value = value });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnChildInsertedBefore (Node newChild, Node referenceChild)
|
||||||
|
{
|
||||||
|
base.OnChildInsertedBefore (newChild, referenceChild);
|
||||||
|
if (string.IsNullOrEmpty (val) && newChild is Option o && !string.IsNullOrEmpty (o.Value)) {
|
||||||
|
val = o.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool TriggerEventFromMessage (Message message)
|
||||||
|
{
|
||||||
|
if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
|
||||||
|
val = message.Value != null ? Convert.ToString (message.Value) : "";
|
||||||
|
}
|
||||||
|
return base.TriggerEventFromMessage (message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,38 +93,38 @@ namespace Ooui
|
||||||
|
|
||||||
public Value BorderTopWidth {
|
public Value BorderTopWidth {
|
||||||
get => this["border-top-width"];
|
get => this["border-top-width"];
|
||||||
set => this["border-top-width"] = value;
|
set => this["border-top-width"] = AddNumberUnits (value, "px");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value BorderRightWidth {
|
public Value BorderRightWidth {
|
||||||
get => this["border-right-width"];
|
get => this["border-right-width"];
|
||||||
set => this["border-right-width"] = value;
|
set => this["border-right-width"] = AddNumberUnits (value, "px");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value BorderBottomWidth {
|
public Value BorderBottomWidth {
|
||||||
get => this["border-bottom-width"];
|
get => this["border-bottom-width"];
|
||||||
set => this["border-bottom-width"] = value;
|
set => this["border-bottom-width"] = AddNumberUnits (value, "px");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value BorderLeftWidth {
|
public Value BorderLeftWidth {
|
||||||
get => this["border-left-width"];
|
get => this["border-left-width"];
|
||||||
set => this["border-left-width"] = value;
|
set => this["border-left-width"] = AddNumberUnits (value, "px");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value BorderRadius {
|
public Value BorderRadius {
|
||||||
get => this["border-radius"];
|
get => this["border-radius"];
|
||||||
set {
|
set {
|
||||||
this["border-radius"] = value;
|
this["border-radius"] = AddNumberUnits (value, "px");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value BorderWidth {
|
public Value BorderWidth {
|
||||||
get => this["border-top-width"];
|
get => this["border-top-width"];
|
||||||
set {
|
set {
|
||||||
this["border-top-width"] = value;
|
this["border-top-width"] = AddNumberUnits (value, "px");
|
||||||
this["border-right-width"] = value;
|
this["border-right-width"] = AddNumberUnits (value, "px");
|
||||||
this["border-bottom-width"] = value;
|
this["border-bottom-width"] = AddNumberUnits (value, "px");
|
||||||
this["border-left-width"] = value;
|
this["border-left-width"] = AddNumberUnits (value, "px");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +253,11 @@ namespace Ooui
|
||||||
set => this["order"] = value;
|
set => this["order"] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Value Overflow {
|
||||||
|
get => this["overflow"];
|
||||||
|
set => this["overflow"] = value;
|
||||||
|
}
|
||||||
|
|
||||||
public Value PaddingTop {
|
public Value PaddingTop {
|
||||||
get => this["padding-top"];
|
get => this["padding-top"];
|
||||||
set => this["padding-top"] = value;
|
set => this["padding-top"] = value;
|
||||||
|
@ -403,6 +408,8 @@ namespace Ooui
|
||||||
|
|
||||||
static string AddNumberUnits (object val, string units)
|
static string AddNumberUnits (object val, string units)
|
||||||
{
|
{
|
||||||
|
if (val == null)
|
||||||
|
return null;
|
||||||
if (val is string s)
|
if (val is string s)
|
||||||
return s;
|
return s;
|
||||||
if (val is IConvertible c)
|
if (val is IConvertible c)
|
||||||
|
|
|
@ -4,12 +4,12 @@ namespace Ooui
|
||||||
{
|
{
|
||||||
public class TextArea : FormControl
|
public class TextArea : FormControl
|
||||||
{
|
{
|
||||||
public event TargetEventHandler Changed {
|
public event TargetEventHandler Change {
|
||||||
add => AddEventListener ("change", value);
|
add => AddEventListener ("change", value);
|
||||||
remove => RemoveEventListener ("change", value);
|
remove => RemoveEventListener ("change", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TargetEventHandler Inputted {
|
public event TargetEventHandler Input {
|
||||||
add => AddEventListener ("input", value);
|
add => AddEventListener ("input", value);
|
||||||
remove => RemoveEventListener ("input", value);
|
remove => RemoveEventListener ("input", value);
|
||||||
}
|
}
|
||||||
|
@ -36,18 +36,18 @@ namespace Ooui
|
||||||
: base ("textarea")
|
: base ("textarea")
|
||||||
{
|
{
|
||||||
// Subscribe to the change event so we always get up-to-date values
|
// Subscribe to the change event so we always get up-to-date values
|
||||||
Changed += (s, e) => {};
|
Change += (s, e) => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextArea (string text)
|
public TextArea (string text)
|
||||||
: this ()
|
: this ()
|
||||||
{
|
{
|
||||||
Text = text;
|
Value = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool TriggerEventFromMessage (Message message)
|
protected override bool TriggerEventFromMessage (Message message)
|
||||||
{
|
{
|
||||||
if (message.TargetId == Id && message.MessageType == MessageType.Event && message.Key == "change") {
|
if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
|
||||||
// Don't need to notify here because the base implementation will fire the event
|
// Don't need to notify here because the base implementation will fire the event
|
||||||
val = message.Value != null ? Convert.ToString (message.Value) : "";
|
val = message.Value != null ? Convert.ToString (message.Value) : "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ooui
|
||||||
|
{
|
||||||
|
public class TextInput : Input
|
||||||
|
{
|
||||||
|
public event TargetEventHandler Input {
|
||||||
|
add => AddEventListener ("input", value);
|
||||||
|
remove => RemoveEventListener ("input", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextInput ()
|
||||||
|
: base (InputType.Text)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
184
Ooui/UI.cs
184
Ooui/UI.cs
|
@ -14,6 +14,9 @@ namespace Ooui
|
||||||
{
|
{
|
||||||
static readonly ManualResetEvent started = new ManualResetEvent (false);
|
static readonly ManualResetEvent started = new ManualResetEvent (false);
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
static System.Security.Cryptography.SHA256 sha256;
|
||||||
|
|
||||||
static CancellationTokenSource serverCts;
|
static CancellationTokenSource serverCts;
|
||||||
|
|
||||||
static readonly Dictionary<string, RequestHandler> publishedPaths =
|
static readonly Dictionary<string, RequestHandler> publishedPaths =
|
||||||
|
@ -26,8 +29,10 @@ namespace Ooui
|
||||||
public static StyleSelectors Styles => rules;
|
public static StyleSelectors Styles => rules;
|
||||||
|
|
||||||
static readonly byte[] clientJsBytes;
|
static readonly byte[] clientJsBytes;
|
||||||
|
static readonly string clientJsEtag;
|
||||||
|
|
||||||
public static byte[] ClientJsBytes => clientJsBytes;
|
public static byte[] ClientJsBytes => clientJsBytes;
|
||||||
|
public static string ClientJsEtag => clientJsEtag;
|
||||||
|
|
||||||
public static string Template { get; set; } = $@"<!DOCTYPE html>
|
public static string Template { get; set; } = $@"<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -35,10 +40,14 @@ namespace Ooui
|
||||||
<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"" />
|
<link rel=""stylesheet"" href=""https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"" />
|
||||||
|
<link rel=""stylesheet"" href=""https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css"" />
|
||||||
<style>@Styles</style>
|
<style>@Styles</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id=""ooui-body"" class=""container-fluid""></div>
|
<div id=""ooui-body"" class=""container-fluid""></div>
|
||||||
|
|
||||||
|
<script type=""text/javascript"" src=""https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js""></script>
|
||||||
|
<script type=""text/javascript"" src=""https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js""></script>
|
||||||
<script src=""/ooui.js""></script>
|
<script src=""/ooui.js""></script>
|
||||||
<script>ooui(""@WebSocketPath"");</script>
|
<script>ooui(""@WebSocketPath"");</script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -64,6 +73,19 @@ namespace Ooui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static bool serverEnabled = true;
|
||||||
|
public static bool ServerEnabled {
|
||||||
|
get => serverEnabled;
|
||||||
|
set {
|
||||||
|
if (serverEnabled != value) {
|
||||||
|
serverEnabled = value;
|
||||||
|
if (serverEnabled)
|
||||||
|
Restart ();
|
||||||
|
else
|
||||||
|
Stop ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static UI ()
|
static UI ()
|
||||||
{
|
{
|
||||||
|
@ -79,6 +101,22 @@ namespace Ooui
|
||||||
clientJsBytes = Encoding.UTF8.GetBytes (r.ReadToEnd ());
|
clientJsBytes = Encoding.UTF8.GetBytes (r.ReadToEnd ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clientJsEtag = "\"" + Hash (clientJsBytes) + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Hash (byte[] bytes)
|
||||||
|
{
|
||||||
|
var sha = sha256;
|
||||||
|
if (sha == null) {
|
||||||
|
sha = System.Security.Cryptography.SHA256.Create ();
|
||||||
|
sha256 = sha;
|
||||||
|
}
|
||||||
|
var data = sha.ComputeHash (bytes);
|
||||||
|
StringBuilder sBuilder = new StringBuilder ();
|
||||||
|
for (int i = 0; i < data.Length; i++) {
|
||||||
|
sBuilder.Append (data[i].ToString ("x2"));
|
||||||
|
}
|
||||||
|
return sBuilder.ToString ();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Publish (string path, RequestHandler handler)
|
static void Publish (string path, RequestHandler handler)
|
||||||
|
@ -110,7 +148,47 @@ namespace Ooui
|
||||||
if (contentType == null) {
|
if (contentType == null) {
|
||||||
contentType = GuessContentType (path, filePath);
|
contentType = GuessContentType (path, filePath);
|
||||||
}
|
}
|
||||||
Publish (path, new DataHandler (data, contentType));
|
var etag = "\"" + Hash (data) + "\"";
|
||||||
|
Publish (path, new DataHandler (data, etag, contentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PublishFile (string path, byte[] data, string contentType)
|
||||||
|
{
|
||||||
|
var etag = "\"" + Hash (data) + "\"";
|
||||||
|
Publish (path, new DataHandler (data, etag, contentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PublishFile (string path, byte[] data, string etag, string contentType)
|
||||||
|
{
|
||||||
|
Publish (path, new DataHandler (data, etag, contentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetFileContentAtPath (string path, out FileContent file)
|
||||||
|
{
|
||||||
|
RequestHandler handler;
|
||||||
|
lock (publishedPaths) {
|
||||||
|
if (!publishedPaths.TryGetValue (path, out handler)) {
|
||||||
|
file = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handler is DataHandler dh) {
|
||||||
|
file = new FileContent {
|
||||||
|
Etag = dh.Etag,
|
||||||
|
Content = dh.Data,
|
||||||
|
ContentType = dh.ContentType,
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
file = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileContent
|
||||||
|
{
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
public string Etag { get; set; }
|
||||||
|
public byte[] Content { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void PublishJson (string path, Func<object> ctor)
|
public static void PublishJson (string path, Func<object> ctor)
|
||||||
|
@ -121,7 +199,8 @@ namespace Ooui
|
||||||
public static void PublishJson (string path, object value)
|
public static void PublishJson (string path, object value)
|
||||||
{
|
{
|
||||||
var data = JsonHandler.GetData (value);
|
var data = JsonHandler.GetData (value);
|
||||||
Publish (path, new DataHandler (data, JsonHandler.ContentType));
|
var etag = "\"" + Hash (data) + "\"";
|
||||||
|
Publish (path, new DataHandler (data, etag, JsonHandler.ContentType));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void PublishCustomResponse (string path, Action<HttpListenerContext, CancellationToken> responder)
|
public static void PublishCustomResponse (string path, Action<HttpListenerContext, CancellationToken> responder)
|
||||||
|
@ -153,6 +232,7 @@ namespace Ooui
|
||||||
|
|
||||||
static void Start ()
|
static void Start ()
|
||||||
{
|
{
|
||||||
|
if (!serverEnabled) return;
|
||||||
if (serverCts != null) return;
|
if (serverCts != null) return;
|
||||||
serverCts = new CancellationTokenSource ();
|
serverCts = new CancellationTokenSource ();
|
||||||
var token = serverCts.Token;
|
var token = serverCts.Token;
|
||||||
|
@ -232,12 +312,22 @@ namespace Ooui
|
||||||
var response = listenerContext.Response;
|
var response = listenerContext.Response;
|
||||||
|
|
||||||
if (path == "/ooui.js") {
|
if (path == "/ooui.js") {
|
||||||
response.ContentLength64 = clientJsBytes.LongLength;
|
var inm = listenerContext.Request.Headers.Get ("If-None-Match");
|
||||||
response.ContentType = "application/javascript";
|
if (string.IsNullOrEmpty (inm) || inm != clientJsEtag) {
|
||||||
response.ContentEncoding = Encoding.UTF8;
|
response.StatusCode = 200;
|
||||||
response.AddHeader ("Cache-Control", "public, max-age=3600");
|
response.ContentLength64 = clientJsBytes.LongLength;
|
||||||
using (var s = response.OutputStream) {
|
response.ContentType = "application/javascript";
|
||||||
s.Write (clientJsBytes, 0, clientJsBytes.Length);
|
response.ContentEncoding = Encoding.UTF8;
|
||||||
|
response.AddHeader ("Cache-Control", "public, max-age=60");
|
||||||
|
response.AddHeader ("Etag", clientJsEtag);
|
||||||
|
using (var s = response.OutputStream) {
|
||||||
|
s.Write (clientJsBytes, 0, clientJsBytes.Length);
|
||||||
|
}
|
||||||
|
response.Close ();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response.StatusCode = 304;
|
||||||
|
response.Close ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -308,11 +398,17 @@ namespace Ooui
|
||||||
class DataHandler : RequestHandler
|
class DataHandler : RequestHandler
|
||||||
{
|
{
|
||||||
readonly byte[] data;
|
readonly byte[] data;
|
||||||
|
readonly string etag;
|
||||||
readonly string contentType;
|
readonly string contentType;
|
||||||
|
|
||||||
public DataHandler (byte[] data, string contentType = null)
|
public byte[] Data => data;
|
||||||
|
public string Etag => etag;
|
||||||
|
public string ContentType => contentType;
|
||||||
|
|
||||||
|
public DataHandler (byte[] data, string etag, string contentType = null)
|
||||||
{
|
{
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
this.etag = etag;
|
||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,13 +418,20 @@ namespace Ooui
|
||||||
var path = url.LocalPath;
|
var path = url.LocalPath;
|
||||||
var response = listenerContext.Response;
|
var response = listenerContext.Response;
|
||||||
|
|
||||||
response.StatusCode = 200;
|
var inm = listenerContext.Request.Headers.Get ("If-None-Match");
|
||||||
if (!string.IsNullOrEmpty (contentType))
|
if (!string.IsNullOrEmpty (inm) && inm == etag) {
|
||||||
response.ContentType = contentType;
|
response.StatusCode = 304;
|
||||||
response.ContentLength64 = data.LongLength;
|
}
|
||||||
|
else {
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.AddHeader ("Etag", etag);
|
||||||
|
if (!string.IsNullOrEmpty (contentType))
|
||||||
|
response.ContentType = contentType;
|
||||||
|
response.ContentLength64 = data.LongLength;
|
||||||
|
|
||||||
using (var s = response.OutputStream) {
|
using (var s = response.OutputStream) {
|
||||||
s.Write (data, 0, data.Length);
|
s.Write (data, 0, data.Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
response.Close ();
|
response.Close ();
|
||||||
}
|
}
|
||||||
|
@ -432,11 +535,32 @@ namespace Ooui
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set the element's dimensions
|
||||||
|
//
|
||||||
|
var query =
|
||||||
|
(from part in listenerContext.Request.Url.Query.Split (new[] { '?', '&' })
|
||||||
|
where part.Length > 0
|
||||||
|
let kvs = part.Split ('=')
|
||||||
|
where kvs.Length == 2
|
||||||
|
select kvs).ToDictionary (x => Uri.UnescapeDataString (x[0]), x => Uri.UnescapeDataString (x[1]));
|
||||||
|
if (!query.TryGetValue ("w", out var wValue) || string.IsNullOrEmpty (wValue)) {
|
||||||
|
wValue = "640";
|
||||||
|
}
|
||||||
|
if (!query.TryGetValue ("h", out var hValue) || string.IsNullOrEmpty (hValue)) {
|
||||||
|
hValue = "480";
|
||||||
|
}
|
||||||
|
var icult = System.Globalization.CultureInfo.InvariantCulture;
|
||||||
|
if (!double.TryParse (wValue, System.Globalization.NumberStyles.Any, icult, out var w))
|
||||||
|
w = 640;
|
||||||
|
if (!double.TryParse (hValue, System.Globalization.NumberStyles.Any, icult, out var h))
|
||||||
|
h = 480;
|
||||||
|
|
||||||
//
|
//
|
||||||
// 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, 1024, 768, serverToken);
|
var session = new Session (webSocket, element, w, h, 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) {
|
||||||
|
@ -470,9 +594,11 @@ namespace Ooui
|
||||||
readonly HashSet<string> createdIds;
|
readonly HashSet<string> createdIds;
|
||||||
readonly List<Message> queuedMessages = new List<Message> ();
|
readonly List<Message> queuedMessages = new List<Message> ();
|
||||||
|
|
||||||
|
public const int MaxFps = 30;
|
||||||
|
|
||||||
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 / MaxFps);
|
||||||
readonly double initialWidth;
|
readonly double initialWidth;
|
||||||
readonly double initialHeight;
|
readonly double initialHeight;
|
||||||
|
|
||||||
|
@ -619,6 +745,7 @@ namespace Ooui
|
||||||
//
|
//
|
||||||
// Add it to the queue
|
// Add it to the queue
|
||||||
//
|
//
|
||||||
|
//Console.WriteLine ($"QM {message.MessageType} {message.TargetId} {message.Key} {message.Value}");
|
||||||
queuedMessages.Add (message);
|
queuedMessages.Add (message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,19 +764,24 @@ namespace Ooui
|
||||||
// Dequeue as many messages as we can
|
// Dequeue as many messages as we can
|
||||||
//
|
//
|
||||||
var messagesToSend = new List<Message> ();
|
var messagesToSend = new List<Message> ();
|
||||||
|
System.Runtime.CompilerServices.ConfiguredTaskAwaitable task;
|
||||||
lock (queuedMessages) {
|
lock (queuedMessages) {
|
||||||
messagesToSend.AddRange (queuedMessages);
|
messagesToSend.AddRange (queuedMessages);
|
||||||
queuedMessages.Clear ();
|
queuedMessages.Clear ();
|
||||||
}
|
|
||||||
if (messagesToSend.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//
|
if (messagesToSend.Count == 0)
|
||||||
// Now actually send this message
|
return;
|
||||||
//
|
|
||||||
var json = Newtonsoft.Json.JsonConvert.SerializeObject (messagesToSend);
|
//
|
||||||
var outputBuffer = new ArraySegment<byte> (Encoding.UTF8.GetBytes (json));
|
// Now actually send this message
|
||||||
await webSocket.SendAsync (outputBuffer, WebSocketMessageType.Text, true, token).ConfigureAwait (false);
|
// Do this while locked to make sure SendAsync is called in the right order
|
||||||
|
//
|
||||||
|
var json = Newtonsoft.Json.JsonConvert.SerializeObject (messagesToSend);
|
||||||
|
var outputBuffer = new ArraySegment<byte> (Encoding.UTF8.GetBytes (json));
|
||||||
|
//Console.WriteLine ("TRANSMIT " + json);
|
||||||
|
task = webSocket.SendAsync (outputBuffer, WebSocketMessageType.Text, true, token).ConfigureAwait (false);
|
||||||
|
}
|
||||||
|
await task;
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
Error ("Failed to send queued messages, aborting session", ex);
|
Error ("Failed to send queued messages, aborting session", ex);
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace AspNetCoreMvc.Controllers
|
||||||
var head = new Heading { Text = "Click away!" };
|
var head = new Heading { Text = "Click away!" };
|
||||||
var label = new Label { Text = "0" };
|
var label = new Label { Text = "0" };
|
||||||
var btn = new Button { Text = "Increase" };
|
var btn = new Button { Text = "Increase" };
|
||||||
btn.Clicked += (sender, e) => {
|
btn.Click += (sender, e) => {
|
||||||
count++;
|
count++;
|
||||||
label.Text = count.ToString ();
|
label.Text = count.ToString ();
|
||||||
};
|
};
|
||||||
|
|
12
README.md
12
README.md
|
@ -1,6 +1,10 @@
|
||||||
# Ooui
|
# Ooui Web Framework <img src="https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png" height="32"> [![Build Status](https://www.bitrise.io/app/86585e168136767d/status.svg?token=G9Svvnv_NvG40gcqu48RNQ)](https://www.bitrise.io/app/86585e168136767d)
|
||||||
|
|
||||||
[![Build Status](https://www.bitrise.io/app/86585e168136767d/status.svg?token=G9Svvnv_NvG40gcqu48RNQ)](https://www.bitrise.io/app/86585e168136767d) [![NuGet Package](https://img.shields.io/nuget/v/Ooui.svg)](https://www.nuget.org/packages/Ooui)
|
| Version | Package | Description |
|
||||||
|
| ------- | ------- | ----------- |
|
||||||
|
| [![NuGet Package](https://img.shields.io/nuget/v/Ooui.svg)](https://www.nuget.org/packages/Ooui) | [Ooui](https://www.nuget.org/packages/Ooui) | Core library with HTML elements and a server. |
|
||||||
|
| [![NuGet Package](https://img.shields.io/nuget/v/Ooui.Forms.svg)](https://www.nuget.org/packages/Ooui.Forms) | [Ooui.Forms](https://www.nuget.org/packages/Ooui.Forms) | Xamarin.Forms backend using Ooui |
|
||||||
|
| [![NuGet Package](https://img.shields.io/nuget/v/Ooui.AspNetCore.svg)](https://www.nuget.org/packages/Ooui.AspNetCore) | [Ooui.AspNetCore](https://www.nuget.org/packages/Ooui.AspNetCore) | Integration with ASP.NET Core MVC |
|
||||||
|
|
||||||
Ooui (pronounced *weeee!*) is a small cross-platform UI library for .NET that uses web technologies.
|
Ooui (pronounced *weeee!*) is a small cross-platform UI library for .NET that uses web technologies.
|
||||||
|
|
||||||
|
@ -45,7 +49,7 @@ class Program
|
||||||
|
|
||||||
// Add some logic to it
|
// Add some logic to it
|
||||||
var count = 0;
|
var count = 0;
|
||||||
button.Clicked += (s, e) => {
|
button.Click += (s, e) => {
|
||||||
count++;
|
count++;
|
||||||
button.Text = $"Clicked {count} times";
|
button.Text = $"Clicked {count} times";
|
||||||
};
|
};
|
||||||
|
@ -77,7 +81,7 @@ Button MakeButton()
|
||||||
{
|
{
|
||||||
var button = new Button("Click me!");
|
var button = new Button("Click me!");
|
||||||
var count = 0;
|
var count = 0;
|
||||||
button.Clicked += (s, e) => {
|
button.Click += (s, e) => {
|
||||||
count++;
|
count++;
|
||||||
button.Text = $"Clicked {count} times";
|
button.Text = $"Clicked {count} times";
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace BugSweeper
|
||||||
|
{
|
||||||
|
public class App : Application
|
||||||
|
{
|
||||||
|
public App ()
|
||||||
|
{
|
||||||
|
MainPage = new BugSweeperPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
using System;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace BugSweeper
|
||||||
|
{
|
||||||
|
class Board : AbsoluteLayout
|
||||||
|
{
|
||||||
|
// Alternative sizes make the tiles a tad small.
|
||||||
|
const int COLS = 9; // 16
|
||||||
|
const int ROWS = 9; // 16
|
||||||
|
const int BUGS = 10; // 40
|
||||||
|
|
||||||
|
Tile[,] tiles = new Tile[ROWS, COLS];
|
||||||
|
int flaggedTileCount;
|
||||||
|
bool isGameInProgress; // on first tap
|
||||||
|
bool isGameInitialized; // on first double-tap
|
||||||
|
bool isGameEnded;
|
||||||
|
|
||||||
|
// Events to notify page.
|
||||||
|
public event EventHandler GameStarted;
|
||||||
|
public event EventHandler<bool> GameEnded;
|
||||||
|
|
||||||
|
public Board()
|
||||||
|
{
|
||||||
|
for (int row = 0; row < ROWS; row++)
|
||||||
|
for (int col = 0; col < COLS; col++)
|
||||||
|
{
|
||||||
|
Tile tile = new Tile(row, col);
|
||||||
|
tile.TileStatusChanged += OnTileStatusChanged;
|
||||||
|
this.Children.Add(tile);
|
||||||
|
tiles[row, col] = tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeChanged += (sender, args) =>
|
||||||
|
{
|
||||||
|
double tileWidth = this.Width / COLS;
|
||||||
|
double tileHeight = this.Height / ROWS;
|
||||||
|
|
||||||
|
foreach (Tile tile in tiles)
|
||||||
|
{
|
||||||
|
Rectangle bounds = new Rectangle(tile.Col * tileWidth,
|
||||||
|
tile.Row * tileHeight,
|
||||||
|
tileWidth, tileHeight);
|
||||||
|
AbsoluteLayout.SetLayoutBounds(tile, bounds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NewGameInitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NewGameInitialize()
|
||||||
|
{
|
||||||
|
// Clear all the tiles.
|
||||||
|
foreach (Tile tile in tiles)
|
||||||
|
tile.Initialize();
|
||||||
|
|
||||||
|
isGameInProgress = false;
|
||||||
|
isGameInitialized = false;
|
||||||
|
isGameEnded = false;
|
||||||
|
this.FlaggedTileCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int FlaggedTileCount
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (flaggedTileCount != value)
|
||||||
|
{
|
||||||
|
flaggedTileCount = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return flaggedTileCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int BugCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return BUGS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Not called until the first tile is double-tapped.
|
||||||
|
void DefineNewBoard(int tappedRow, int tappedCol)
|
||||||
|
{
|
||||||
|
// Begin the assignment of bugs.
|
||||||
|
Random random = new Random();
|
||||||
|
int bugCount = 0;
|
||||||
|
|
||||||
|
while (bugCount < BUGS)
|
||||||
|
{
|
||||||
|
// Get random row and column.
|
||||||
|
int row = random.Next(ROWS);
|
||||||
|
int col = random.Next(COLS);
|
||||||
|
|
||||||
|
// Skip it if it's already a bug.
|
||||||
|
if (tiles[row, col].IsBug)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid the tappedRow & Col & surrounding ones.
|
||||||
|
if (row >= tappedRow - 1 &&
|
||||||
|
row <= tappedRow + 1 &&
|
||||||
|
col >= tappedCol - 1 &&
|
||||||
|
col <= tappedCol + 1)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's a bug!
|
||||||
|
tiles[row, col].IsBug = true;
|
||||||
|
|
||||||
|
// Calculate the surrounding bug count.
|
||||||
|
CycleThroughNeighbors(row, col,
|
||||||
|
(neighborRow, neighborCol) =>
|
||||||
|
{
|
||||||
|
++tiles[neighborRow, neighborCol].SurroundingBugCount;
|
||||||
|
});
|
||||||
|
|
||||||
|
bugCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CycleThroughNeighbors(int row, int col, Action<int, int> callback)
|
||||||
|
{
|
||||||
|
int minRow = Math.Max(0, row - 1);
|
||||||
|
int maxRow = Math.Min(ROWS - 1, row + 1);
|
||||||
|
int minCol = Math.Max(0, col - 1);
|
||||||
|
int maxCol = Math.Min(COLS - 1, col + 1);
|
||||||
|
|
||||||
|
for (int neighborRow = minRow; neighborRow <= maxRow; neighborRow++)
|
||||||
|
for (int neighborCol = minCol; neighborCol <= maxCol; neighborCol++)
|
||||||
|
{
|
||||||
|
if (neighborRow != row || neighborCol != col)
|
||||||
|
callback(neighborRow, neighborCol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnTileStatusChanged(object sender, TileStatus tileStatus)
|
||||||
|
{
|
||||||
|
if (isGameEnded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// With a first tile tapped, the game is now in progress.
|
||||||
|
if (!isGameInProgress)
|
||||||
|
{
|
||||||
|
isGameInProgress = true;
|
||||||
|
|
||||||
|
// Fire the GameStarted event.
|
||||||
|
if (GameStarted != null)
|
||||||
|
{
|
||||||
|
GameStarted(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the "flagged" bug count before checking for a loss.
|
||||||
|
int flaggedCount = 0;
|
||||||
|
|
||||||
|
foreach (Tile tile in tiles)
|
||||||
|
if (tile.Status == TileStatus.Flagged)
|
||||||
|
flaggedCount++;
|
||||||
|
|
||||||
|
this.FlaggedTileCount = flaggedCount;
|
||||||
|
|
||||||
|
// Get the tile whose status has changed.
|
||||||
|
Tile changedTile = (Tile)sender;
|
||||||
|
|
||||||
|
// If it's exposed, some actions are required.
|
||||||
|
if (tileStatus == TileStatus.Exposed)
|
||||||
|
{
|
||||||
|
if (!isGameInitialized)
|
||||||
|
{
|
||||||
|
DefineNewBoard(changedTile.Row, changedTile.Col);
|
||||||
|
isGameInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedTile.IsBug)
|
||||||
|
{
|
||||||
|
isGameInProgress = false;
|
||||||
|
isGameEnded = true;
|
||||||
|
|
||||||
|
// Fire the GameEnded event!
|
||||||
|
if (GameEnded != null)
|
||||||
|
{
|
||||||
|
GameEnded(this, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto expose for zero surrounding bugs.
|
||||||
|
if (changedTile.SurroundingBugCount == 0)
|
||||||
|
{
|
||||||
|
CycleThroughNeighbors(changedTile.Row, changedTile.Col,
|
||||||
|
(neighborRow, neighborCol) =>
|
||||||
|
{
|
||||||
|
// Expose all the neighbors.
|
||||||
|
tiles[neighborRow, neighborCol].Status = TileStatus.Exposed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a win.
|
||||||
|
bool hasWon = true;
|
||||||
|
|
||||||
|
foreach (Tile til in tiles)
|
||||||
|
{
|
||||||
|
if (til.IsBug && til.Status != TileStatus.Flagged)
|
||||||
|
hasWon = false;
|
||||||
|
|
||||||
|
if (!til.IsBug && til.Status != TileStatus.Exposed)
|
||||||
|
hasWon = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a win, celebrate!
|
||||||
|
if (hasWon)
|
||||||
|
{
|
||||||
|
isGameInProgress = false;
|
||||||
|
isGameEnded = true;
|
||||||
|
|
||||||
|
// Fire the GameEnded event!
|
||||||
|
if (GameEnded != null)
|
||||||
|
{
|
||||||
|
GameEnded(this, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:BugSweeper"
|
||||||
|
x:Class="BugSweeper.BugSweeperPage"
|
||||||
|
Title="BugSweeper">
|
||||||
|
<ContentPage.Padding>
|
||||||
|
<OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" Android="0, 0, 0, 0" WinPhone="0, 0, 0, 0" />
|
||||||
|
</ContentPage.Padding>
|
||||||
|
<ContentView SizeChanged="OnMainContentViewSizeChanged">
|
||||||
|
<Grid x:Name="mainGrid" ColumnSpacing="0" RowSpacing="0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="7*" />
|
||||||
|
<RowDefinition Height="4*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="0" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackLayout x:Name="textStack" Grid.Row="0" Grid.Column="1" Spacing="0">
|
||||||
|
<StackLayout HorizontalOptions="Center" Spacing="0">
|
||||||
|
<Label Text="BugSweeper" Font="Bold, Large" TextColor="Accent" />
|
||||||
|
<BoxView Color="Accent" HeightRequest="3" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label Text="Tap to flag/unflag a potential bug." VerticalOptions="CenterAndExpand" HorizontalTextAlignment="Center" />
|
||||||
|
<Label Text="Double-tap if you're sure it's not a bug.
The first double-tap is always safe!" VerticalOptions="CenterAndExpand" HorizontalTextAlignment="Center" />
|
||||||
|
<StackLayout Orientation="Horizontal" Spacing="0" VerticalOptions="CenterAndExpand" HorizontalOptions="Center">
|
||||||
|
<Label BindingContext="{x:Reference board}" Text="{Binding FlaggedTileCount, StringFormat='Flagged {0} '}" />
|
||||||
|
<Label BindingContext="{x:Reference board}" Text="{Binding BugCount, StringFormat=' out of {0} bugs.'}" />
|
||||||
|
</StackLayout>
|
||||||
|
<!-- Make this a binding??? -->
|
||||||
|
<Label x:Name="timeLabel" Text="0:00" VerticalOptions="CenterAndExpand" HorizontalTextAlignment="Center" />
|
||||||
|
</StackLayout>
|
||||||
|
<ContentView Grid.Row="1" Grid.Column="1" SizeChanged="OnBoardContentViewSizeChanged">
|
||||||
|
<!-- Single-cell Grid for Board and overlays. -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<local:Board x:Name="board" />
|
||||||
|
<StackLayout x:Name="congratulationsText" Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center" Spacing="0">
|
||||||
|
<Label Text="C" TextColor="Red" />
|
||||||
|
<Label Text="O" TextColor="Red" />
|
||||||
|
<Label Text="N" TextColor="Red" />
|
||||||
|
<Label Text="G" TextColor="Red" />
|
||||||
|
<Label Text="R" TextColor="Red" />
|
||||||
|
<Label Text="A" TextColor="Red" />
|
||||||
|
<Label Text="T" TextColor="Red" />
|
||||||
|
<Label Text="U" TextColor="Red" />
|
||||||
|
<Label Text="L" TextColor="Red" />
|
||||||
|
<Label Text="A" TextColor="Red" />
|
||||||
|
<Label Text="T" TextColor="Red" />
|
||||||
|
<Label Text="I" TextColor="Red" />
|
||||||
|
<Label Text="O" TextColor="Red" />
|
||||||
|
<Label Text="N" TextColor="Red" />
|
||||||
|
<Label Text="S" TextColor="Red" />
|
||||||
|
<Label Text="!" TextColor="Red" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout x:Name="consolationText" Orientation="Horizontal" Spacing="0" HorizontalOptions="Center" VerticalOptions="Center">
|
||||||
|
<Label Text="T" TextColor="Red" />
|
||||||
|
<Label Text="O" TextColor="Red" />
|
||||||
|
<Label Text="O" TextColor="Red" />
|
||||||
|
<Label Text=" " TextColor="Red" />
|
||||||
|
<Label Text="B" TextColor="Red" />
|
||||||
|
<Label Text="A" TextColor="Red" />
|
||||||
|
<Label Text="D" TextColor="Red" />
|
||||||
|
<Label Text="!" TextColor="Red" />
|
||||||
|
</StackLayout>
|
||||||
|
<Button x:Name="playAgainButton" Text=" Play Another Game? " HorizontalOptions="Center" VerticalOptions="Center" Clicked="OnplayAgainButtonClicked"
|
||||||
|
BorderColor="Black" BorderWidth="2" BackgroundColor="White" TextColor="Black" />
|
||||||
|
</Grid>
|
||||||
|
</ContentView>
|
||||||
|
</Grid>
|
||||||
|
</ContentView>
|
||||||
|
</ContentPage>
|
|
@ -0,0 +1,174 @@
|
||||||
|
#define FIX_WINPHONE_BUTTON // IsEnabled = false doesn't disable button
|
||||||
|
|
||||||
|
#pragma warning disable 4014 // for non-await'ed async call
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace BugSweeper
|
||||||
|
{
|
||||||
|
public partial class BugSweeperPage : ContentPage
|
||||||
|
{
|
||||||
|
const string timeFormat = @"%m\:ss";
|
||||||
|
|
||||||
|
bool isGameInProgress;
|
||||||
|
DateTime gameStartTime;
|
||||||
|
|
||||||
|
public BugSweeperPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
board.GameStarted += (sender, args) =>
|
||||||
|
{
|
||||||
|
isGameInProgress = true;
|
||||||
|
gameStartTime = DateTime.Now;
|
||||||
|
|
||||||
|
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
|
||||||
|
{
|
||||||
|
timeLabel.Text = (DateTime.Now - gameStartTime).ToString(timeFormat);
|
||||||
|
return isGameInProgress;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
board.GameEnded += (sender, hasWon) =>
|
||||||
|
{
|
||||||
|
isGameInProgress = false;
|
||||||
|
|
||||||
|
if (hasWon)
|
||||||
|
{
|
||||||
|
DisplayWonAnimation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayLostAnimation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PrepareForNewGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrepareForNewGame()
|
||||||
|
{
|
||||||
|
board.NewGameInitialize();
|
||||||
|
|
||||||
|
congratulationsText.IsVisible = false;
|
||||||
|
consolationText.IsVisible = false;
|
||||||
|
playAgainButton.IsVisible = false;
|
||||||
|
playAgainButton.IsEnabled = false;
|
||||||
|
|
||||||
|
timeLabel.Text = new TimeSpan().ToString(timeFormat);
|
||||||
|
isGameInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnMainContentViewSizeChanged(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
ContentView contentView = (ContentView)sender;
|
||||||
|
double width = contentView.Width;
|
||||||
|
double height = contentView.Height;
|
||||||
|
|
||||||
|
bool isLandscape = width > height;
|
||||||
|
|
||||||
|
if (isLandscape)
|
||||||
|
{
|
||||||
|
mainGrid.RowDefinitions[0].Height = 0;
|
||||||
|
mainGrid.RowDefinitions[1].Height = new GridLength(1, GridUnitType.Star);
|
||||||
|
|
||||||
|
mainGrid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
|
||||||
|
mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
|
||||||
|
|
||||||
|
Grid.SetRow(textStack, 1);
|
||||||
|
Grid.SetColumn(textStack, 0);
|
||||||
|
}
|
||||||
|
else // portrait
|
||||||
|
{
|
||||||
|
mainGrid.RowDefinitions[0].Height = new GridLength(3, GridUnitType.Star);
|
||||||
|
mainGrid.RowDefinitions[1].Height = new GridLength(5, GridUnitType.Star);
|
||||||
|
|
||||||
|
mainGrid.ColumnDefinitions[0].Width = 0;
|
||||||
|
mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
|
||||||
|
|
||||||
|
Grid.SetRow(textStack, 0);
|
||||||
|
Grid.SetColumn(textStack, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maintains a square aspect ratio for the board.
|
||||||
|
void OnBoardContentViewSizeChanged(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
ContentView contentView = (ContentView)sender;
|
||||||
|
double width = contentView.Width;
|
||||||
|
double height = contentView.Height;
|
||||||
|
double dimension = Math.Min(width, height);
|
||||||
|
double horzPadding = (width - dimension) / 2;
|
||||||
|
double vertPadding = (height - dimension) / 2;
|
||||||
|
contentView.Padding = new Thickness(horzPadding, vertPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
async void DisplayWonAnimation()
|
||||||
|
{
|
||||||
|
congratulationsText.Scale = 0;
|
||||||
|
congratulationsText.IsVisible = true;
|
||||||
|
|
||||||
|
// Because IsVisible has been false, the text might not have a size yet,
|
||||||
|
// in which case Measure will return a size.
|
||||||
|
double congratulationsTextWidth = congratulationsText.Measure(Double.PositiveInfinity, Double.PositiveInfinity).Request.Width;
|
||||||
|
|
||||||
|
congratulationsText.Rotation = 0;
|
||||||
|
congratulationsText.RotateTo(3 * 360, 1000, Easing.CubicOut);
|
||||||
|
|
||||||
|
double maxScale = 0.9 * board.Width / congratulationsTextWidth;
|
||||||
|
await congratulationsText.ScaleTo(maxScale, 1000);
|
||||||
|
|
||||||
|
foreach (View view in congratulationsText.Children)
|
||||||
|
{
|
||||||
|
view.Rotation = 0;
|
||||||
|
view.RotateTo(180);
|
||||||
|
await view.ScaleTo(3, 100);
|
||||||
|
view.RotateTo(360);
|
||||||
|
await view.ScaleTo(1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
await DisplayPlayAgainButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
async void DisplayLostAnimation()
|
||||||
|
{
|
||||||
|
consolationText.Scale = 0;
|
||||||
|
consolationText.IsVisible = true;
|
||||||
|
|
||||||
|
// (See above for rationale)
|
||||||
|
double consolationTextWidth = consolationText.Measure(Double.PositiveInfinity, Double.PositiveInfinity).Request.Width;
|
||||||
|
|
||||||
|
double maxScale = 0.9 * board.Width / consolationTextWidth;
|
||||||
|
await consolationText.ScaleTo(maxScale, 1000);
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await DisplayPlayAgainButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task DisplayPlayAgainButton()
|
||||||
|
{
|
||||||
|
playAgainButton.Scale = 0;
|
||||||
|
playAgainButton.IsVisible = true;
|
||||||
|
playAgainButton.IsEnabled = true;
|
||||||
|
|
||||||
|
// (See above for rationale)
|
||||||
|
double playAgainButtonWidth = playAgainButton.Measure(Double.PositiveInfinity, Double.PositiveInfinity).Request.Width;
|
||||||
|
|
||||||
|
double maxScale = board.Width / playAgainButtonWidth;
|
||||||
|
await playAgainButton.ScaleTo(maxScale, 1000, Easing.SpringOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnplayAgainButtonClicked(object sender, object EventArgs)
|
||||||
|
{
|
||||||
|
#if FIX_WINPHONE_BUTTON
|
||||||
|
|
||||||
|
if (Device.OS == TargetPlatform.WinPhone && !((Button)sender).IsEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
PrepareForNewGame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
|
@ -0,0 +1,191 @@
|
||||||
|
#define FIX_WINDOWS_DOUBLE_TAPS // Double-taps don't work well on Windows Runtime as of 2.3.0
|
||||||
|
#define FIX_WINDOWS_PHONE_NULL_CONTENT // Set Content of Frame to null doesn't work in Windows as of 2.3.0
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace BugSweeper
|
||||||
|
{
|
||||||
|
enum TileStatus
|
||||||
|
{
|
||||||
|
Hidden,
|
||||||
|
Flagged,
|
||||||
|
Exposed
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tile : Frame
|
||||||
|
{
|
||||||
|
TileStatus tileStatus = TileStatus.Hidden;
|
||||||
|
Label label;
|
||||||
|
Image flagImage, bugImage;
|
||||||
|
static ImageSource flagImageSource;
|
||||||
|
static ImageSource bugImageSource;
|
||||||
|
bool doNotFireEvent;
|
||||||
|
|
||||||
|
public event EventHandler<TileStatus> TileStatusChanged;
|
||||||
|
|
||||||
|
static Tile()
|
||||||
|
{
|
||||||
|
flagImageSource = ImageSource.FromResource("Samples.BugSweeper.Images.Xamarin120.png", System.Reflection.Assembly.GetCallingAssembly());
|
||||||
|
bugImageSource = ImageSource.FromResource("Samples.BugSweeper.Images.RedBug.png", System.Reflection.Assembly.GetCallingAssembly ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tile(int row, int col)
|
||||||
|
{
|
||||||
|
this.Row = row;
|
||||||
|
this.Col = col;
|
||||||
|
|
||||||
|
this.BackgroundColor = Color.Yellow;
|
||||||
|
this.OutlineColor = Color.Blue;
|
||||||
|
this.Padding = 2;
|
||||||
|
|
||||||
|
label = new Label {
|
||||||
|
Text = " ",
|
||||||
|
TextColor = Color.Yellow,
|
||||||
|
BackgroundColor = Color.Blue,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
VerticalTextAlignment = TextAlignment.Center,
|
||||||
|
};
|
||||||
|
|
||||||
|
flagImage = new Image {
|
||||||
|
Source = flagImageSource,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
bugImage = new Image {
|
||||||
|
Source = bugImageSource
|
||||||
|
};
|
||||||
|
|
||||||
|
TapGestureRecognizer singleTap = new TapGestureRecognizer {
|
||||||
|
NumberOfTapsRequired = 1
|
||||||
|
};
|
||||||
|
singleTap.Tapped += OnSingleTap;
|
||||||
|
this.GestureRecognizers.Add(singleTap);
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_DOUBLE_TAPS
|
||||||
|
|
||||||
|
if (Device.OS != TargetPlatform.Windows && Device.OS != TargetPlatform.WinPhone) {
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TapGestureRecognizer doubleTap = new TapGestureRecognizer {
|
||||||
|
NumberOfTapsRequired = 2
|
||||||
|
};
|
||||||
|
doubleTap.Tapped += OnDoubleTap;
|
||||||
|
this.GestureRecognizers.Add(doubleTap);
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_DOUBLE_TAPS
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Row { private set; get; }
|
||||||
|
|
||||||
|
public int Col { private set; get; }
|
||||||
|
|
||||||
|
public bool IsBug { set; get; }
|
||||||
|
|
||||||
|
public int SurroundingBugCount { set; get; }
|
||||||
|
|
||||||
|
public TileStatus Status {
|
||||||
|
set {
|
||||||
|
if (tileStatus != value) {
|
||||||
|
tileStatus = value;
|
||||||
|
|
||||||
|
switch (tileStatus) {
|
||||||
|
case TileStatus.Hidden:
|
||||||
|
this.Content = null;
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_PHONE_NULL_CONTENT
|
||||||
|
|
||||||
|
if (Device.OS == TargetPlatform.WinPhone || Device.OS == TargetPlatform.Windows) {
|
||||||
|
this.Content = new Label { Text = " " };
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TileStatus.Flagged:
|
||||||
|
this.Content = flagImage;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TileStatus.Exposed:
|
||||||
|
if (this.IsBug) {
|
||||||
|
this.Content = bugImage;
|
||||||
|
} else {
|
||||||
|
this.Content = label;
|
||||||
|
label.Text =
|
||||||
|
(this.SurroundingBugCount > 0) ?
|
||||||
|
this.SurroundingBugCount.ToString() : " ";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doNotFireEvent && TileStatusChanged != null) {
|
||||||
|
TileStatusChanged(this, tileStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
return tileStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not fire TileStatusChanged events.
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
doNotFireEvent = true;
|
||||||
|
this.Status = TileStatus.Hidden;
|
||||||
|
this.IsBug = false;
|
||||||
|
this.SurroundingBugCount = 0;
|
||||||
|
doNotFireEvent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_DOUBLE_TAPS
|
||||||
|
|
||||||
|
bool lastTapSingle;
|
||||||
|
DateTime lastTapTime;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void OnSingleTap(object sender, object args)
|
||||||
|
{
|
||||||
|
|
||||||
|
#if FIX_WINDOWS_DOUBLE_TAPS
|
||||||
|
|
||||||
|
if (Device.OS == TargetPlatform.Windows || Device.OS == TargetPlatform.WinPhone) {
|
||||||
|
if (lastTapSingle && DateTime.Now - lastTapTime < TimeSpan.FromMilliseconds (500)) {
|
||||||
|
OnDoubleTap (sender, args);
|
||||||
|
lastTapSingle = false;
|
||||||
|
} else {
|
||||||
|
lastTapTime = DateTime.Now;
|
||||||
|
lastTapSingle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
switch (this.Status) {
|
||||||
|
case TileStatus.Hidden:
|
||||||
|
this.Status = TileStatus.Flagged;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TileStatus.Flagged:
|
||||||
|
this.Status = TileStatus.Hidden;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TileStatus.Exposed:
|
||||||
|
// Do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDoubleTap (object sender, object args)
|
||||||
|
{
|
||||||
|
this.Status = TileStatus.Exposed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using Ooui;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Samples
|
||||||
|
{
|
||||||
|
public class BugSweeperSample : ISample
|
||||||
|
{
|
||||||
|
public string Title => "Xamarin.Forms BugSweeper";
|
||||||
|
|
||||||
|
public Ooui.Element CreateElement ()
|
||||||
|
{
|
||||||
|
var page = new BugSweeper.BugSweeperPage ();
|
||||||
|
return page.GetOouiElement ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ namespace Samples
|
||||||
};
|
};
|
||||||
button.Style.MarginTop = "2em";
|
button.Style.MarginTop = "2em";
|
||||||
var count = 0;
|
var count = 0;
|
||||||
button.Clicked += (s, e) => {
|
button.Click += (s, e) => {
|
||||||
count++;
|
count++;
|
||||||
button.Text = $"Clicked {count} times";
|
button.Text = $"Clicked {count} times";
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,10 +4,15 @@
|
||||||
x:Class="Samples.DisplayAlertPage">
|
x:Class="Samples.DisplayAlertPage">
|
||||||
<ContentPage.Content>
|
<ContentPage.Content>
|
||||||
<StackLayout>
|
<StackLayout>
|
||||||
<Label Text="Welcome to DisplayAlert Sample!" FontSize="32" FontAttributes="Bold" Margin="10,10,10,50" />
|
<Label Text="Welcome to DisplayAlert Sample!" FontSize="32" FontAttributes="Bold" Margin="10,10,10,50"
|
||||||
|
HorizontalOptions="Center"/>
|
||||||
<ActivityIndicator x:Name="activity" />
|
<ActivityIndicator x:Name="activity" />
|
||||||
<ProgressBar x:Name="progress" />
|
<ProgressBar x:Name="progress" />
|
||||||
<DatePicker x:Name="datePicker" />
|
<DatePicker x:Name="datePicker" />
|
||||||
|
<StackLayout Orientation="Horizontal">
|
||||||
|
<Switch x:Name="switch1" IsToggled="true" />
|
||||||
|
<Switch x:Name="switch2" IsToggled="false" />
|
||||||
|
</StackLayout>
|
||||||
<Button Text="Tap to Display Alert"
|
<Button Text="Tap to Display Alert"
|
||||||
Clicked="OnButtonClicked" />
|
Clicked="OnButtonClicked" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|
|
@ -18,15 +18,24 @@ namespace Samples
|
||||||
{
|
{
|
||||||
var heading = new Heading ("Draw");
|
var heading = new Heading ("Draw");
|
||||||
var subtitle = new Paragraph ("Click to draw a masterpiece");
|
var subtitle = new Paragraph ("Click to draw a masterpiece");
|
||||||
|
var toolSel = new Select ();
|
||||||
|
toolSel.AppendChild (new Option { Label = "Boxes", Value = "box" });
|
||||||
|
toolSel.AddOption ("Circles", "circle");
|
||||||
var canvas = new Canvas {
|
var canvas = new Canvas {
|
||||||
Width = 320,
|
Width = 320,
|
||||||
Height = 240,
|
Height = 240,
|
||||||
};
|
};
|
||||||
var context = canvas.GetContext2D ();
|
var context = canvas.GetContext2D ();
|
||||||
|
|
||||||
canvas.Clicked += (s, e) => {
|
canvas.Click += (s, e) => {
|
||||||
|
var radius = 10;
|
||||||
context.BeginPath ();
|
context.BeginPath ();
|
||||||
context.Rect (e.OffsetX - 5, e.OffsetY - 5, 10, 10);
|
if (toolSel.Value == "box") {
|
||||||
|
context.Rect (e.OffsetX - radius, e.OffsetY - radius, 2*radius, 2*radius);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.Arc (e.OffsetX, e.OffsetY, radius, 0, 2 * Math.PI, true);
|
||||||
|
}
|
||||||
context.Fill ();
|
context.Fill ();
|
||||||
};
|
};
|
||||||
canvas.Style.Cursor = "pointer";
|
canvas.Style.Cursor = "pointer";
|
||||||
|
@ -38,7 +47,7 @@ namespace Samples
|
||||||
Type = ButtonType.Submit,
|
Type = ButtonType.Submit,
|
||||||
ClassName = "btn btn-danger",
|
ClassName = "btn btn-danger",
|
||||||
};
|
};
|
||||||
clearbtn.Clicked += (s, e) => {
|
clearbtn.Click += (s, e) => {
|
||||||
context.ClearRect (0, 0, canvas.Width, canvas.Height);
|
context.ClearRect (0, 0, canvas.Width, canvas.Height);
|
||||||
};
|
};
|
||||||
clearbtn.Style.Display = "block";
|
clearbtn.Style.Display = "block";
|
||||||
|
@ -46,6 +55,7 @@ namespace Samples
|
||||||
var app = new Div ();
|
var app = new Div ();
|
||||||
app.AppendChild (heading);
|
app.AppendChild (heading);
|
||||||
app.AppendChild (subtitle);
|
app.AppendChild (subtitle);
|
||||||
|
app.AppendChild (new Div (toolSel));
|
||||||
app.AppendChild (canvas);
|
app.AppendChild (canvas);
|
||||||
app.AppendChild (clearbtn);
|
app.AppendChild (clearbtn);
|
||||||
return app;
|
return app;
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace Samples
|
||||||
{
|
{
|
||||||
public class EditorSample : ISample
|
public class EditorSample : ISample
|
||||||
{
|
{
|
||||||
public string Title => "Editor Sample";
|
public string Title => "Xamarin.Forms Editor Sample";
|
||||||
|
|
||||||
public Ooui.Element CreateElement()
|
public Ooui.Element CreateElement()
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,9 @@ namespace Samples
|
||||||
|
|
||||||
var titleLabel = new Xamarin.Forms.Label
|
var titleLabel = new Xamarin.Forms.Label
|
||||||
{
|
{
|
||||||
Text = "Editor"
|
Text = "Editor",
|
||||||
|
FontSize = 24,
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
};
|
};
|
||||||
panel.Children.Add(titleLabel);
|
panel.Children.Add(titleLabel);
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="**/*.xaml" />
|
<EmbeddedResource Include="**/*.xaml" />
|
||||||
|
<EmbeddedResource Include="BugSweeper\Images\RedBug.png" />
|
||||||
|
<EmbeddedResource Include="BugSweeper\Images\Xamarin120.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -41,6 +43,10 @@
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="BugSweeper\Images\RedBug.png" />
|
||||||
|
<None Remove="BugSweeper\Images\Xamarin120.png" />
|
||||||
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
|
|
|
@ -66,19 +66,19 @@ namespace Samples
|
||||||
if (string.IsNullOrWhiteSpace (input.Value))
|
if (string.IsNullOrWhiteSpace (input.Value))
|
||||||
return;
|
return;
|
||||||
var item = new Item (input.Value);
|
var item = new Item (input.Value);
|
||||||
item.Clicked += (s, e) => {
|
item.Click += (s, e) => {
|
||||||
item.IsDone = !item.IsDone;
|
item.IsDone = !item.IsDone;
|
||||||
};
|
};
|
||||||
items.InsertBefore (item, items.FirstChild);
|
items.InsertBefore (item, items.FirstChild);
|
||||||
input.Value = "";
|
input.Value = "";
|
||||||
}
|
}
|
||||||
addbtn.Clicked += (s, e) => {
|
addbtn.Click += (s, e) => {
|
||||||
AddItem ();
|
AddItem ();
|
||||||
};
|
};
|
||||||
inputForm.Submitted += (s, e) => {
|
inputForm.Submit += (s, e) => {
|
||||||
AddItem ();
|
AddItem ();
|
||||||
};
|
};
|
||||||
clearbtn.Clicked += (s, e) => {
|
clearbtn.Click += (s, e) => {
|
||||||
var toremove = new List<Node> ();
|
var toremove = new List<Node> ();
|
||||||
foreach (Item i in items.Children) {
|
foreach (Item i in items.Children) {
|
||||||
if (i.IsDone) toremove.Add (i);
|
if (i.IsDone) toremove.Add (i);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<ContentPage.Content>
|
<ContentPage.Content>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
@ -10,8 +11,12 @@
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<Editor x:Name="editor" FontFamily="monospace" Grid.Row="0" Grid.Column="0" />
|
<StackLayout Orientation="Vertical" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
|
||||||
<ContentView x:Name="results" Grid.Row="0" Grid.Column="1" BackgroundColor="White" />
|
<Label Text="Xamarin.Forms XAML Editor" FontSize="24" FontAttributes="Bold" Margin="8,8,8,0" />
|
||||||
|
<Label Text="Edit the XAML below to see a live preview on the right" Margin="8,0,8,8" />
|
||||||
|
</StackLayout>
|
||||||
|
<Editor x:Name="editor" FontFamily="monospace" FontSize="12" Grid.Row="1" Grid.Column="0" />
|
||||||
|
<ContentView x:Name="results" Grid.Row="1" Grid.Column="1" BackgroundColor="White" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</ContentPage.Content>
|
</ContentPage.Content>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Samples
|
namespace Samples
|
||||||
|
@ -23,24 +23,50 @@ namespace Samples
|
||||||
<ColumnDefinition Width=""*"" />
|
<ColumnDefinition Width=""*"" />
|
||||||
<ColumnDefinition Width=""*"" />
|
<ColumnDefinition Width=""*"" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Label Text=""Top Left"" Grid.Row=""0"" Grid.Column=""0"" />
|
<StackLayout Grid.Row=""0"" Grid.Column=""0"">
|
||||||
<Label Text=""Top Right"" Grid.Row=""0"" Grid.Column=""1"" />
|
<Label Text=""Top Left"" />
|
||||||
<Label Text=""Bottom Left"" Grid.Row=""1"" Grid.Column=""0"" />
|
<Entry Placeholder=""I'm ready for some text"" />
|
||||||
<Label Text=""Bottom Right"" Grid.Row=""1"" Grid.Column=""1"" />
|
<Button Text=""I'm a button, but I don't do anything"" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label Text=""Top Right"" Grid.Row=""0"" Grid.Column=""1"" TextColor=""White"" BackgroundColor=""#c5000b"" />
|
||||||
|
<Label Text=""Bottom Left"" Grid.Row=""1"" Grid.Column=""0"" TextColor=""Black"" BackgroundColor=""#ffd320"" />
|
||||||
|
<Label Text=""Bottom Right"" Grid.Row=""1"" Grid.Column=""1"" TextColor=""White"" BackgroundColor=""#008000"" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</ContentView>";
|
</ContentView>";
|
||||||
|
editor.TextChanged += (sender, e) => DisplayXaml ();
|
||||||
DisplayXaml ();
|
DisplayXaml ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CancellationTokenSource lastCts = null;
|
||||||
|
|
||||||
public void DisplayXaml ()
|
public void DisplayXaml ()
|
||||||
{
|
{
|
||||||
var asm = typeof (Xamarin.Forms.Xaml.Internals.XamlTypeResolver).Assembly;
|
try {
|
||||||
var xamlLoaderType = asm.GetType ("Xamarin.Forms.Xaml.XamlLoader");
|
var cts = new CancellationTokenSource ();
|
||||||
var loadArgTypes = new[] { typeof (object), typeof (string) };
|
var token = cts.Token;
|
||||||
var loadMethod = xamlLoaderType.GetMethod ("Load", System.Reflection.BindingFlags.Static|System.Reflection.BindingFlags.Public, null, System.Reflection.CallingConventions.Any, loadArgTypes, null);
|
lastCts?.Cancel ();
|
||||||
var contentView = new ContentView ();
|
lastCts = cts;
|
||||||
loadMethod.Invoke (null, new object[] { contentView, editor.Text });
|
|
||||||
results.Content = contentView;
|
var asm = typeof (Xamarin.Forms.Xaml.Internals.XamlTypeResolver).Assembly;
|
||||||
|
var xamlLoaderType = asm.GetType ("Xamarin.Forms.Xaml.XamlLoader");
|
||||||
|
var loadArgTypes = new[] { typeof (object), typeof (string) };
|
||||||
|
var loadMethod = xamlLoaderType.GetMethod ("Load", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public, null, System.Reflection.CallingConventions.Any, loadArgTypes, null);
|
||||||
|
var contentView = new ContentView ();
|
||||||
|
loadMethod.Invoke (null, new object[] { contentView, editor.Text });
|
||||||
|
|
||||||
|
if (!token.IsCancellationRequested) {
|
||||||
|
results.Content = contentView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) {
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
results.Content = new Label {
|
||||||
|
TextColor = Color.DarkRed,
|
||||||
|
FontSize = 12,
|
||||||
|
Text = ex.ToString (),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace Tests
|
||||||
listened = listened || (m.MessageType == MessageType.Listen);
|
listened = listened || (m.MessageType == MessageType.Listen);
|
||||||
};
|
};
|
||||||
Assert.IsFalse (listened);
|
Assert.IsFalse (listened);
|
||||||
b.Clicked += (s, e) => {
|
b.Click += (s, e) => {
|
||||||
clicked = true;
|
clicked = true;
|
||||||
};
|
};
|
||||||
Assert.IsTrue (listened);
|
Assert.IsTrue (listened);
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace Tests
|
||||||
var b = new Button ();
|
var b = new Button ();
|
||||||
p.AppendChild (b);
|
p.AppendChild (b);
|
||||||
var clicked = false;
|
var clicked = false;
|
||||||
b.Clicked += (s, e) => {
|
b.Click += (s, e) => {
|
||||||
clicked = true;
|
clicked = true;
|
||||||
};
|
};
|
||||||
p.Receive (Message.Event (b.Id, "click"));
|
p.Receive (Message.Event (b.Id, "click"));
|
||||||
|
|
|
@ -75,7 +75,7 @@ namespace Tests
|
||||||
{
|
{
|
||||||
var s = new Style ();
|
var s = new Style ();
|
||||||
s.BorderLeftWidth = 3.142;
|
s.BorderLeftWidth = 3.142;
|
||||||
Assert.AreEqual ("border-left-width:3.142", s.ToString ());
|
Assert.AreEqual ("border-left-width:3.142px", s.ToString ());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
|
@ -7,7 +7,7 @@ Type=simple
|
||||||
Environment=HOME=/home/ubuntu
|
Environment=HOME=/home/ubuntu
|
||||||
WorkingDirectory=/home/ubuntu/Ooui/PlatformSamples/AspNetCoreMvc
|
WorkingDirectory=/home/ubuntu/Ooui/PlatformSamples/AspNetCoreMvc
|
||||||
ExecStart=/usr/bin/dotnet run --no-build --server.urls=http://0.0.0.0:80/
|
ExecStart=/usr/bin/dotnet run --no-build --server.urls=http://0.0.0.0:80/
|
||||||
Restart=on-abort
|
Restart=always
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
Loading…
Reference in New Issue